mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 22:31:26 +08:00
Compare commits
29 Commits
v3.20.0
...
clamp-cube
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3667092327 | ||
|
|
842824057b | ||
|
|
6a32f85e60 | ||
|
|
f29c62ec33 | ||
|
|
d77981230b | ||
|
|
e2b92c15f0 | ||
|
|
14614f4803 | ||
|
|
37d3489b07 | ||
|
|
f81225cc0d | ||
|
|
eb47f43940 | ||
|
|
7618a5e2c9 | ||
|
|
ab3ff842b2 | ||
|
|
82f0f92c15 | ||
|
|
545d9434d8 | ||
|
|
bbc43d5113 | ||
|
|
a6709acf65 | ||
|
|
509a027742 | ||
|
|
7244023233 | ||
|
|
c5f987d8b2 | ||
|
|
793696d4c0 | ||
|
|
305ca05f04 | ||
|
|
f4d7d1920a | ||
|
|
458aad0161 | ||
|
|
9e3132461f | ||
|
|
8301291215 | ||
|
|
daed14e228 | ||
|
|
7db82c5ba5 | ||
|
|
9ea6f51126 | ||
|
|
87c708e3c1 |
10
.github/pull_request_template.md
vendored
Normal file
10
.github/pull_request_template.md
vendored
Normal 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`
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -6,6 +6,24 @@ 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`
|
||||
|
||||
## [v3.21.0] - 2022-10-17
|
||||
|
||||
- Add `VolumeIsosurfaceParams.pickingGranularity` param
|
||||
- Prevent component controls collapsing when option is selected
|
||||
|
||||
## [v3.20.0] - 2022-10-16
|
||||
|
||||
- [Breaking] Rename the ``model-index`` color theme to ``trajectory-index``
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.20.0",
|
||||
"version": "3.23.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "molstar",
|
||||
"version": "3.20.0",
|
||||
"version": "3.23.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.10",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.20.0",
|
||||
"version": "3.23.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -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: {} } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,9 @@ namespace Loci {
|
||||
? Structure.toStructureElementLoci(loci.structure)
|
||||
: ShapeGroup.isLoci(loci)
|
||||
? Shape.Loci(loci.shape)
|
||||
: loci;
|
||||
: Volume.Cell.isLoci(loci)
|
||||
? Volume.Loci(loci.volume)
|
||||
: loci;
|
||||
},
|
||||
'elementInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
|
||||
@@ -219,4 +219,14 @@ export namespace Volume {
|
||||
return Sphere3D.expand(bs, bs, Mat4.getMaxScaleOnAxis(transform) * 10);
|
||||
}
|
||||
}
|
||||
|
||||
export type PickingGranularity = 'volume' | 'object' | 'voxel';
|
||||
export const PickingGranularity = {
|
||||
set(volume: Volume, granularity: PickingGranularity) {
|
||||
volume._propertyData['__picking_granularity__'] = granularity;
|
||||
},
|
||||
get(volume: Volume): PickingGranularity {
|
||||
return volume._propertyData['__picking_granularity__'] ?? 'voxel';
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -280,7 +280,6 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
|
||||
|
||||
selectAction: ActionMenu.OnSelect = item => {
|
||||
if (!item) return;
|
||||
this.setState({ action: void 0 });
|
||||
(item?.value as any)();
|
||||
};
|
||||
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +66,16 @@ function getLoci(volume: Volume, props: VolumeIsosurfaceProps) {
|
||||
|
||||
function getIsosurfaceLoci(pickingId: PickingId, volume: Volume, props: VolumeIsosurfaceProps, id: number) {
|
||||
const { objectId, groupId } = pickingId;
|
||||
|
||||
if (id === objectId) {
|
||||
return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
|
||||
const granularity = Volume.PickingGranularity.get(volume);
|
||||
if (granularity === 'volume') {
|
||||
return Volume.Loci(volume);
|
||||
} else if (granularity === 'object') {
|
||||
return Volume.Isosurface.Loci(volume, props.isoValue);
|
||||
} else {
|
||||
return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
|
||||
}
|
||||
}
|
||||
return EmptyLoci;
|
||||
}
|
||||
|
||||
@@ -150,7 +150,14 @@ function getLoci(volume: Volume, props: PD.Values<SliceParams>) {
|
||||
function getSliceLoci(pickingId: PickingId, volume: Volume, props: PD.Values<SliceParams>, id: number) {
|
||||
const { objectId, groupId } = pickingId;
|
||||
if (id === objectId) {
|
||||
return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
|
||||
const granularity = Volume.PickingGranularity.get(volume);
|
||||
if (granularity === 'volume') {
|
||||
return Volume.Loci(volume);
|
||||
} if (granularity === 'object') {
|
||||
return getLoci(volume, props);
|
||||
} else {
|
||||
return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
|
||||
}
|
||||
}
|
||||
return EmptyLoci;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user