Compare commits

...

15 Commits

Author SHA1 Message Date
Alexander Rose
3667092327 tweak cube clamp param 2022-11-08 22:28:47 -08:00
Alexander Rose
842824057b add param to clamp cube values 2022-11-08 22:17:23 -08:00
Alexander Rose
6a32f85e60 Merge pull request #603 from giagitom/transparent-bg-fixes 2022-11-06 16:42:27 -08:00
giagitom
f29c62ec33 Transparent bg fix 2022-10-30 12:06:32 +01:00
Alexander Rose
d77981230b mmcif schema update 2022-10-29 12:15:29 -07:00
dsehnal
e2b92c15f0 PluginContext.initContainer options 2022-10-27 08:41:01 +02:00
dsehnal
14614f4803 3.23.0 2022-10-19 13:11:47 +02:00
dsehnal
37d3489b07 changelog 2022-10-19 13:09:02 +02:00
David Sehnal
f81225cc0d Merge pull request #592 from molstar/volume/defaults-for-em
Change EM Volume Streaming default Auto
2022-10-19 13:08:17 +02:00
dsehnal
eb47f43940 Change EM Volume Streaming default Auto 2022-10-19 13:05:16 +02:00
dsehnal
7618a5e2c9 pr template 2022-10-19 12:41:24 +02:00
David Sehnal
ab3ff842b2 Merge pull request #590 from molstar/reusable-canvas
Built-in support for mouting/unmounting PluginContext
2022-10-19 09:55:31 +02:00
dsehnal
82f0f92c15 remove unused code 2022-10-18 15:20:48 +02:00
dsehnal
545d9434d8 Add PluginContext.initContainer/canvas3dInitialized and their usage 2022-10-18 09:16:08 +02:00
dsehnal
bbc43d5113 Add PluginContext.mount/unmount 2022-10-18 08:41:29 +02:00
21 changed files with 173 additions and 73 deletions

10
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,10 @@
<!-- Thank you for contributing to Mol* -->
# Description
## Actions
- [ ] Added description of changes to the `[Unreleased]` section of `CHANGELOG.md`
- [ ] Updated headers of modified files
- [ ] Added my name to `package.json`'s `contributors`

View File

@@ -6,6 +6,15 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
- Make `PluginContext.initContainer` checkered canvas background optional
## [v3.23.0] - 2022-10-19
- Add `PluginContext.initContainer/mount/unmount` methods; these should make it easier to reuse a plugin context with both custom and built-in UI
- Add `PluginContext.canvas3dInitialized`
- `createPluginUI` now resolves after the 3d canvas has been initialized
- Change EM Volume Streaming default from `Whote Structure` to `Auto`
## [v3.22.0] - 2022-10-17
- Replace `VolumeIsosurfaceParams.pickingGranularity` param with `Volume.PickingGranuality`

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "molstar",
"version": "3.22.0",
"version": "3.23.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "molstar",
"version": "3.22.0",
"version": "3.23.0",
"license": "MIT",
"dependencies": {
"@types/argparse": "^2.0.10",

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.22.0",
"version": "3.23.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {

View File

@@ -58,20 +58,22 @@ class Viewer {
}
static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
const o = { ...DefaultViewerOptions, ...{
layoutIsExpanded: false,
layoutShowControls: false,
layoutShowRemoteState: false,
layoutShowSequence: true,
layoutShowLog: false,
layoutShowLeftPanel: true,
const o = {
...DefaultViewerOptions, ...{
layoutIsExpanded: false,
layoutShowControls: false,
layoutShowRemoteState: false,
layoutShowSequence: true,
layoutShowLog: false,
layoutShowLeftPanel: true,
viewportShowExpand: true,
viewportShowControls: false,
viewportShowSettings: false,
viewportShowSelectionMode: false,
viewportShowAnimation: false,
} };
viewportShowExpand: true,
viewportShowControls: false,
viewportShowSettings: false,
viewportShowSelectionMode: false,
viewportShowAnimation: false,
}
};
const defaultSpec = DefaultPluginUISpec();
const spec: PluginUISpec = {
@@ -135,18 +137,16 @@ class Viewer {
}
};
plugin.behaviors.canvas3d.initialized.subscribe(v => {
if (v) {
PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
renderer: {
...plugin.canvas3d!.props.renderer,
backgroundColor: ColorNames.white,
},
camera: {
...plugin.canvas3d!.props.camera,
helper: { axes: { name: 'off', params: {} } }
}
} });
PluginCommands.Canvas3D.SetSettings(plugin, {
settings: {
renderer: {
...plugin.canvas3d!.props.renderer,
backgroundColor: ColorNames.white,
},
camera: {
...plugin.canvas3d!.props.camera,
helper: { axes: { name: 'off', params: {} } }
}
}
});

View File

@@ -71,6 +71,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'ec-type':
case 'ucode-alphanum-csv':
case 'id_list':
case 'entity_id_list':
return ListCol('str', ',', description);
case 'id_list_spc':
return ListCol('str', ' ', description);

View File

@@ -4,16 +4,16 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { AlphaOrbitalsExample } from '.';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { PluginContextContainer } from '../../mol-plugin-ui/plugin';
export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) {
ReactDOM.render(<PluginContextContainer plugin={orbitals.plugin}>
createRoot(parent).render(<PluginContextContainer plugin={orbitals.plugin}>
<Controls orbitals={orbitals} />
</PluginContextContainer>, parent);
</PluginContextContainer>);
}
function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) {

View File

@@ -82,24 +82,20 @@ export class AlphaOrbitalsExample {
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
this.plugin.behaviors.canvas3d.initialized.subscribe(init => {
if (!init) return;
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
PluginCommands.Toast.Show(this.plugin, {
title: 'Error',
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
});
return;
}
this.load({
moleculeSdf: DemoMoleculeSDF,
...DemoOrbitals
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
PluginCommands.Toast.Show(this.plugin, {
title: 'Error',
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
});
return;
}
mountControls(this, document.getElementById('controls')!);
this.load({
moleculeSdf: DemoMoleculeSDF,
...DemoOrbitals
});
mountControls(this, document.getElementById('controls')!);
}
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);

View File

@@ -57,7 +57,7 @@ export const apply_light_color = `
RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
outgoingLight = clamp(outgoingLight, 0.0, 1.0); // prevents black artifacts on specular highlight with transparent background
outgoingLight = clamp(outgoingLight, 0.01, 0.99); // prevents black artifacts on specular highlight with transparent background
gl_FragColor = vec4(outgoingLight, color.a);
#endif

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.360, IHM 1.17, MA 1.4.2.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.362, IHM 1.17, MA 1.4.3.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.360, IHM 1.17, MA 1.4.2.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.362, IHM 1.17, MA 1.4.3.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.360, IHM 1.17, MA 1.4.2.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.362, IHM 1.17, MA 1.4.3.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { CubeFile } from '../../mol-io/reader/cube/parser';
@@ -11,8 +12,9 @@ import { Task } from '../../mol-task';
import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
import { ModelFormat } from '../format';
import { CustomProperties } from '../../mol-model/custom-property';
import { clamp } from '../../mol-math/interpolate';
export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number, label?: string, entryId?: string }): Task<Volume> {
export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number, label?: string, entryId?: string, clamp?: { min: number, max: number } }): Task<Volume> {
return Task.create<Volume>('Create Volume', async () => {
const { header, values: sourceValues } = source;
const space = Tensor.Space(header.dim, [0, 1, 2], Float64Array);
@@ -24,6 +26,7 @@ export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number,
// get every nth value from the source values
const [h, k, l] = header.dim;
const nth = (params?.dataIndex || 0) + 1;
const { min, max } = params?.clamp || { min: -Infinity, max: Infinity };
let o = 0, s = 0;
@@ -31,7 +34,7 @@ export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number,
for (let u = 0; u < h; u++) {
for (let v = 0; v < k; v++) {
for (let w = 0; w < l; w++) {
values[o++] = sourceValues[s];
values[o++] = clamp(sourceValues[s], min, max);
s += nth;
}
}

View File

@@ -88,12 +88,24 @@ const VolumeFromCube = PluginStateTransform.BuiltIn({
return {
dataIndex,
entryId: PD.Text(''),
clamp: PD.MappedStatic('off', {
'off': PD.EmptyGroup(),
'on': PD.Group({
min: PD.Numeric(-1024),
max: PD.Numeric(1024),
})
}, { cycle: true })
};
}
})({
apply({ a, params }) {
return Task.create('Create volume from Cube', async ctx => {
const volume = await volumeFromCube(a.data, { ...params, label: a.data.name || a.label }).runInContext(ctx);
const volume = await volumeFromCube(a.data, {
dataIndex: params.dataIndex,
label: a.data.name || a.label,
entryId: params.entryId,
clamp: params.clamp.name === 'on' ? params.clamp.params : undefined,
}).runInContext(ctx);
const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.dim[0]}\u00D7${a.data.header.dim[1]}\u00D7${a.data.header.dim[2]}` };
return new SO.Volume.Data(volume, props);
});

View File

@@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o
await options.onBeforeUIRender(ctx);
}
ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target);
try {
await ctx.canvas3dInitialized;
} catch {
// Error reported in UI/console elsewhere.
}
return ctx;
}

View File

@@ -24,14 +24,6 @@ import { BehaviorSubject } from 'rxjs';
import { useBehavior } from './hooks/use-behavior';
export class Plugin extends React.Component<{ plugin: PluginUIContext, children?: any }, {}> {
region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) {
return <div className={`msp-layout-region msp-layout-${kind}`}>
<div className='msp-layout-static'>
{element}
</div>
</div>;
}
render() {
return <PluginReactContext.Provider value={this.props.plugin}>
<Layout />

View File

@@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o
await options.onBeforeUIRender(ctx);
}
createRoot(target).render(createElement(Plugin, { plugin: ctx }));
try {
await ctx.canvas3dInitialized;
} catch {
// Error reported in UI/console elsewhere.
}
return ctx;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -19,13 +19,14 @@ export interface ViewportCanvasParams {
parentClassName?: string,
parentStyle?: React.CSSProperties,
// NOTE: hostClassName/hostStyle no longer in use
// TODO: remove in 4.0
hostClassName?: string,
hostStyle?: React.CSSProperties,
}
export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, ViewportCanvasState> {
private container = React.createRef<HTMLDivElement>();
private canvas = React.createRef<HTMLCanvasElement>();
state: ViewportCanvasState = {
noWebGl: false,
@@ -37,7 +38,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
};
componentDidMount() {
if (!this.canvas.current || !this.container.current || !this.plugin.initViewer(this.canvas.current!, this.container.current!)) {
if (!this.container.current || !this.plugin.mount(this.container.current!, { checkeredCanvasBackground: true })) {
this.setState({ noWebGl: true });
return;
}
@@ -47,7 +48,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
componentWillUnmount() {
super.componentWillUnmount();
// TODO viewer cleanup
this.plugin.unmount();
}
renderMissing() {
@@ -70,10 +71,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
const Logo = this.props.logo;
return <div className={this.props.parentClassName || 'msp-viewport'} style={this.props.parentStyle}>
<div className={this.props.hostClassName || 'msp-viewport-host3d'} style={this.props.hostStyle} ref={this.container}>
<canvas ref={this.canvas} />
</div>
return <div className={this.props.parentClassName || 'msp-viewport'} style={this.props.parentStyle} ref={this.container}>
{(this.state.showLogo && Logo) && <Logo />}
</div>;
}

View File

@@ -80,7 +80,7 @@ export namespace VolumeStreaming {
const box = (structure && structure.boundary.box) || Box3D();
return {
view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'cell' : 'selection-box'), {
view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'auto' : 'selection-box'), {
'off': PD.Group<{}>({}),
'box': PD.Group({
bottomLeft: PD.Vec3(box.min),

View File

@@ -43,7 +43,7 @@ export const InitVolumeStreaming = StateAction.build({
return {
method: PD.Select<VolumeServerInfo.Kind>(method, [['em', 'EM'], ['x-ray', 'X-Ray']]),
entries: PD.ObjectList({ id: PD.Text(ids[0] || '') }, ({ id }) => id, { defaultValue: ids.map(id => ({ id })) }),
defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box', VolumeStreaming.ViewTypeOptions as any),
defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'auto' : 'selection-box', VolumeStreaming.ViewTypeOptions as any),
options: PD.Group({
serverUrl: PD.Text(plugin.config.get(PluginConfig.VolumeStreaming.DefaultServer) || 'https://ds.litemol.org'),
behaviorRef: PD.Text('', { isHidden: true }),

View File

@@ -35,7 +35,7 @@ import { Representation } from '../mol-repr/representation';
import { StructureRepresentationRegistry } from '../mol-repr/structure/registry';
import { VolumeRepresentationRegistry } from '../mol-repr/volume/registry';
import { StateTransform } from '../mol-state';
import { RuntimeContext, Task } from '../mol-task';
import { RuntimeContext, Scheduler, Task } from '../mol-task';
import { ColorTheme } from '../mol-theme/color';
import { SizeTheme } from '../mol-theme/size';
import { ThemeRegistryContext } from '../mol-theme/theme';
@@ -71,8 +71,10 @@ export class PluginContext {
};
protected subs: Subscription[] = [];
private initCanvas3dPromiseCallbacks: [res: () => void, rej: (err: any) => void] = [() => {}, () => {}];
private disposed = false;
private canvasContainer: HTMLDivElement | undefined = void 0;
private ev = RxEventHelper.create();
readonly config = new PluginConfigManager(this.spec.config); // needed to init state
@@ -102,10 +104,15 @@ export class PluginContext {
leftPanelTabName: this.ev.behavior<LeftPanelTabName>('root')
},
canvas3d: {
// TODO: remove in 4.0?
initialized: this.canvas3dInit.pipe(filter(v => !!v), take(1))
}
} as const;
readonly canvas3dInitialized = new Promise<void>((res, rej) => {
this.initCanvas3dPromiseCallbacks = [res, rej];
});
readonly canvas3dContext: Canvas3DContext | undefined;
readonly canvas3d: Canvas3D | undefined;
readonly layout = new PluginLayout(this);
@@ -186,6 +193,63 @@ export class PluginContext {
*/
readonly customState: unknown = Object.create(null);
initContainer(options?: { canvas3dContext?: Canvas3DContext, checkeredCanvasBackground?: boolean }) {
if (this.canvasContainer) return true;
const container = document.createElement('div');
Object.assign(container.style, {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
'-webkit-user-select': 'none',
'user-select': 'none',
'-webkit-tap-highlight-color': 'rgba(0,0,0,0)',
'-webkit-touch-callout': 'none',
'touch-action': 'manipulation',
});
let canvas = options?.canvas3dContext?.canvas;
if (!canvas) {
canvas = document.createElement('canvas');
if (options?.checkeredCanvasBackground) {
Object.assign(canvas.style, {
'background-image': 'linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey)',
'background-size': '60px 60px',
'background-position': '0 0, 30px 30px'
});
}
container.appendChild(canvas);
}
if (!this.initViewer(canvas, container, options?.canvas3dContext)) {
return false;
}
this.canvasContainer = container;
return true;
}
/**
* Mount the plugin into the target element (assumes the target has "relative"-like positioninig).
* If initContainer wasn't called separately before, initOptions will be passed to it.
*/
mount(target: HTMLElement, initOptions?: { canvas3dContext?: Canvas3DContext, checkeredCanvasBackground?: boolean }) {
if (this.disposed) throw new Error('Cannot mount a disposed context');
if (!this.initContainer(initOptions)) return false;
if (this.canvasContainer!.parentElement !== target) {
this.canvasContainer!.parentElement?.removeChild(this.canvasContainer!);
}
target.appendChild(this.canvasContainer!);
this.handleResize();
return true;
}
unmount() {
this.canvasContainer?.parentElement?.removeChild(this.canvasContainer);
}
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement, canvas3dContext?: Canvas3DContext) {
try {
this.layout.setRoot(container);
@@ -232,10 +296,12 @@ export class PluginContext {
this.handleResize();
Scheduler.setImmediate(() => this.initCanvas3dPromiseCallbacks[0]());
return true;
} catch (e) {
this.log.error('' + e);
console.error(e);
Scheduler.setImmediate(() => this.initCanvas3dPromiseCallbacks[1](e));
return false;
}
}
@@ -306,6 +372,9 @@ export class PluginContext {
objectForEach(this.managers, m => (m as any)?.dispose?.());
objectForEach(this.managers.structure, m => (m as any)?.dispose?.());
this.unmount();
this.canvasContainer = undefined;
this.disposed = true;
}