diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts index 51b37fede568bf532aa4a2844070943396305933..951ec3946342d207d28d3f0dd001d69adcac0485 100644 --- a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts +++ b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts @@ -22,7 +22,7 @@ import { PluginCommands } from '../../../command'; import { StateSelection } from '../../../../mol-state'; import { Representation } from '../../../../mol-repr/representation'; import { ButtonsType, ModifiersKeys } from '../../../../mol-util/input/input-observer'; -import { StructureElement } from '../../../../mol-model/structure'; +import { StructureElement, Link } from '../../../../mol-model/structure'; import { PluginContext } from '../../../context'; import { Binding } from '../../../../mol-util/binding'; @@ -72,7 +72,7 @@ export namespace VolumeStreaming { }, { description: 'Box around last-interacted element.', isFlat: true }), 'cell': PD.Group({}), // 'auto': PD.Group({ }), // TODO based on camera distance/active selection/whatever, show whole structure or slice. - }, { options: [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Surroundings'], ['cell', 'Whole Structure']] }), + }, { options: ViewTypeOptions as any }), detailLevel: PD.Select<number>(Math.min(3, info.header.availablePrecisions.length - 1), info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string])), channels: info.kind === 'em' @@ -88,6 +88,8 @@ export namespace VolumeStreaming { }; } + export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Surroundings'], ['cell', 'Whole Structure']]; + export type ViewTypes = 'off' | 'box' | 'selection-box' | 'cell' export type ParamDefinition = typeof createParams extends (...args: any[]) => (infer T) ? T : never @@ -113,6 +115,8 @@ export namespace VolumeStreaming { export class Behavior extends PluginBehavior.WithSubscribers<Params> { private cache = LRUCache.create<ChannelsData>(25); public params: Params = {} as any; + private lastLoci: Representation.Loci = Representation.Loci.Empty; + private ref: string = ''; channels: Channels = {} @@ -166,7 +170,7 @@ export namespace VolumeStreaming { return ret; } - private updateDynamicBox(ref: string, box: Box3D) { + private updateDynamicBox(box: Box3D) { if (this.params.view.name !== 'selection-box') return; const state = this.plugin.state.dataState; @@ -181,64 +185,87 @@ export namespace VolumeStreaming { } } }; - const update = state.build().to(ref).update(newParams); + const update = state.build().to(this.ref).update(newParams); PluginCommands.State.Update.dispatch(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } }); } - private getStructureRoot(ref: string) { - return this.plugin.state.dataState.select(StateSelection.Generators.byRef(ref).rootOfType([PluginStateObject.Molecule.Structure]))[0]; + private getStructureRoot() { + return this.plugin.state.dataState.select(StateSelection.Generators.byRef(this.ref).rootOfType([PluginStateObject.Molecule.Structure]))[0]; } register(ref: string): void { - let lastLoci: Representation.Loci = Representation.Loci.Empty; + this.ref = ref; this.subscribeObservable(this.plugin.events.state.object.removed, o => { - if (!PluginStateObject.Molecule.Structure.is(o.obj) || lastLoci.loci.kind !== 'element-loci') return; - if (lastLoci.loci.structure === o.obj.data) { - lastLoci = Representation.Loci.Empty; + if (!PluginStateObject.Molecule.Structure.is(o.obj) || this.lastLoci.loci.kind !== 'element-loci') return; + if (this.lastLoci.loci.structure === o.obj.data) { + this.lastLoci = Representation.Loci.Empty; } }); this.subscribeObservable(this.plugin.events.state.object.updated, o => { - if (!PluginStateObject.Molecule.Structure.is(o.oldObj) || lastLoci.loci.kind !== 'element-loci') return; - if (lastLoci.loci.structure === o.oldObj.data) { - lastLoci = Representation.Loci.Empty; + if (!PluginStateObject.Molecule.Structure.is(o.oldObj) || this.lastLoci.loci.kind !== 'element-loci') return; + if (this.lastLoci.loci.structure === o.oldObj.data) { + this.lastLoci = Representation.Loci.Empty; } }); this.subscribeObservable(this.plugin.behaviors.interaction.click, ({ current, buttons, modifiers }) => { - if (this.params.view.name !== 'selection-box') return; if (!Binding.match(this.params.bindings.clickVolumeAroundOnly, buttons, modifiers)) return; - - if (current.loci.kind === 'empty-loci') { - this.updateDynamicBox(ref, Box3D.empty()); - lastLoci = current; - return; + if (this.params.view.name !== 'selection-box') { + this.lastLoci = current; + } else { + this.updateInteraction(current); } + }); + } - // TODO: support link and structure loci as well? - if (!StructureElement.Loci.is(current.loci)) return; + private getBoxFromLoci(current: Representation.Loci) { + if (current.loci.kind === 'empty-loci') { + return; + } - const parent = this.plugin.helpers.substructureParent.get(current.loci.structure); - if (!parent) return; - const root = this.getStructureRoot(ref); - if (!root || !root.obj || root.obj !== parent.obj) return; + let loci: StructureElement.Loci; - if (Representation.Loci.areEqual(lastLoci, current)) { - lastLoci = Representation.Loci.Empty; - this.updateDynamicBox(ref, Box3D.empty()); - return; - } - lastLoci = current; + // TODO: support structure loci as well? + if (StructureElement.Loci.is(current.loci)) { + loci = current.loci; + } else if (Link.isLoci(current.loci) && current.loci.links.length !== 0) { + loci = Link.toStructureElementLoci(current.loci); + } else { + return; + } - const loci = StructureElement.Loci.extendToWholeResidues(current.loci); - const box = StructureElement.Loci.getBoundary(loci).box; - this.updateDynamicBox(ref, box); - }); + const parent = this.plugin.helpers.substructureParent.get(loci.structure); + if (!parent) return; + const root = this.getStructureRoot(); + if (!root || !root.obj || root.obj !== parent.obj) return; + + return StructureElement.Loci.getBoundary(StructureElement.Loci.extendToWholeResidues(loci)).box; + } + + private updateInteraction(current: Representation.Loci) { + if (Representation.Loci.areEqual(this.lastLoci, current)) { + this.lastLoci = Representation.Loci.Empty; + this.updateDynamicBox(Box3D.empty()); + return; + } + + if (current.loci.kind === 'empty-loci') { + this.updateDynamicBox(Box3D.empty()); + this.lastLoci = current; + return; + } + + const box = this.getBoxFromLoci(current); + if (!box) return; + this.updateDynamicBox(box); } async update(params: Params) { + const switchedToSelection = params.view.name === 'selection-box' && this.params && this.params.view && this.params.view.name !== 'selection-box'; + this.params = params; let box: Box3D | undefined = void 0, emptyData = false; @@ -252,7 +279,11 @@ export namespace VolumeStreaming { emptyData = Box3D.volume(box) < 0.0001; break; case 'selection-box': { - box = Box3D.create(Vec3.clone(params.view.params.bottomLeft), Vec3.clone(params.view.params.topRight)); + if (switchedToSelection) { + box = this.getBoxFromLoci(this.lastLoci) || Box3D.empty(); + } else { + box = Box3D.create(Vec3.clone(params.view.params.bottomLeft), Vec3.clone(params.view.params.topRight)); + } const r = params.view.params.radius; emptyData = Box3D.volume(box) < 0.0001; Box3D.expand(box, box, Vec3.create(r, r, r)); diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts index 27c9cd543282d1d76a194cbf8f6ff793e9599af3..492ef447fe102d20cd7ffe934ced22e533ccdb55 100644 --- a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts +++ b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts @@ -32,7 +32,7 @@ export const InitVolumeStreaming = StateAction.build({ method: PD.Select<VolumeServerInfo.Kind>(method, [['em', 'EM'], ['x-ray', 'X-Ray']]), id: PD.Text(id), serverUrl: PD.Text('https://ds.litemol.org'), - defaultView: PD.Text<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box'), + defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box', VolumeStreaming.ViewTypeOptions as any), behaviorRef: PD.Text('', { isHidden: true }), emContourProvider: PD.Select<'wwpdb' | 'pdbe'>('wwpdb', [['wwpdb', 'wwPDB'], ['pdbe', 'PDBe']], { isHidden: true }), };