Procházet zdrojové kódy

refactored StructureSelectionManager

David Sehnal před 5 roky
rodič
revize
74f8a5053e

+ 13 - 7
src/mol-plugin-state/actions/structure.ts

@@ -11,13 +11,15 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { PluginStateObject } from '../objects';
 import { StateTransforms } from '../transforms';
 import { Download, ParsePsf } from '../transforms/data';
-import { CustomModelProperties, StructureSelectionFromExpression, CustomStructureProperties, CoordinatesFromDcd, TrajectoryFromModelAndCoordinates, TopologyFromPsf } from '../transforms/model';
+import { CustomModelProperties, CustomStructureProperties, CoordinatesFromDcd, TrajectoryFromModelAndCoordinates, TopologyFromPsf } from '../transforms/model';
 import { DataFormatProvider, guessCifVariant, DataFormatBuilderOptions } from './data-format';
 import { FileInfo } from '../../mol-util/file-info';
 import { Task } from '../../mol-task';
 import { StructureElement } from '../../mol-model/structure';
 import { createDefaultStructureComplex } from '../../mol-plugin/util/structure-complex-helper';
 import { RootStructureDefinition } from '../helpers/root-structure';
+import { UUID } from '../../mol-util';
+import { Loci } from '../../mol-model/loci';
 
 export const MmcifProvider: DataFormatProvider<any> = {
     label: 'mmCIF',
@@ -401,13 +403,17 @@ export const StructureFromSelection = StateAction.build({
     // isApplicable(a, t, ctx: PluginContext) {
     //     return t.transformer !== CustomModelProperties;
     // }
-})(({ a, ref, params, state }, plugin: PluginContext) => {
-    const sel = plugin.helpers.structureSelectionManager.get(a.data);
-    if (sel.kind === 'empty-loci') return Task.constant('', void 0);
+})(async ({ a, ref, params, state }, plugin: PluginContext) => {
+    const sel = plugin.managers.structure.selection.getLoci(a.data);
+    if (Loci.isEmpty(sel)) return;
 
-    const expression = StructureElement.Loci.toExpression(sel);
-    const root = state.build().to(ref).apply(StructureSelectionFromExpression, { expression, label: params.label });
-    return state.updateTree(root);
+    const bundle = StructureElement.Bundle.fromLoci(sel);
+
+    await plugin.builders.structure.tryCreateComponent(ref, {
+        type: { name: 'bundle', params: bundle },
+        nullIfEmpty: true,
+        label: params.label
+    }, UUID.create22());
 });
 
 export const AddTrajectory = StateAction.build({

+ 1 - 1
src/mol-plugin-state/builder/structure/preset.ts

@@ -7,7 +7,7 @@
 
 import { StateTransforms } from '../../transforms';
 import { StructureRepresentation3DHelpers } from '../../transforms/representation';
-import { StructureSelectionQueries as Q } from '../../../mol-plugin/util/structure-selection-helper';
+import { StructureSelectionQueries as Q } from '../../../mol-plugin/util/structure-selection-query';
 import { BuiltInStructureRepresentations } from '../../../mol-repr/structure/registry';
 import { StructureRepresentationProvider, RepresentationProviderTags } from './provider';
 import { StateObjectRef } from '../../../mol-state';

+ 2 - 2
src/mol-plugin-state/helpers/structure-component.ts

@@ -8,9 +8,9 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import Expression from '../../mol-script/language/expression';
 import { MolScriptBuilder } from '../../mol-script/language/builder';
 import { StructureElement, Structure, StructureSelection as Sel, StructureQuery, Queries, QueryContext } from '../../mol-model/structure';
-import { StructureQueryHelper } from '../../mol-plugin/util/structure-query';
+import { StructureQueryHelper } from './structure-query';
 import { PluginStateObject as SO } from '../objects';
-import { StructureSelectionQueries } from '../../mol-plugin/util/structure-selection-helper';
+import { StructureSelectionQueries } from '../../mol-plugin/util/structure-selection-query';
 import { StateTransformer, StateObject } from '../../mol-state';
 import { Script } from '../../mol-script/script';
 

+ 1 - 1
src/mol-plugin/util/structure-query.ts → src/mol-plugin-state/helpers/structure-query.ts

@@ -8,7 +8,7 @@ import Expression from '../../mol-script/language/expression';
 import { QueryFn, Structure, StructureSelection as Sel, QueryContext } from '../../mol-model/structure';
 import { Script } from '../../mol-script/script';
 import { compile } from '../../mol-script/runtime/query/compiler';
-import { PluginStateObject as SO } from '../../mol-plugin-state/objects';
+import { PluginStateObject as SO } from '../objects';
 
 export { StructureQueryHelper }
 namespace StructureQueryHelper {

+ 4 - 3
src/mol-plugin-state/manager/structure/selection.ts

@@ -18,7 +18,7 @@ import { arrayRemoveAtInPlace } from '../../../mol-util/array';
 import { EmptyLoci, Loci } from '../../../mol-model/loci';
 import { StateObject, StateSelection } from '../../../mol-state';
 import { PluginStateObject } from '../../objects';
-import { StructureSelectionQuery } from '../../../mol-plugin/util/structure-selection-helper';
+import { StructureSelectionQuery } from '../../../mol-plugin/util/structure-selection-query';
 import { Task } from '../../../mol-task';
 
 interface StructureSelectionManagerState {
@@ -203,7 +203,8 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
             s.selection = StructureElement.Loci(s.selection.structure, []);
         }
         this.referenceLoci = undefined
-        this.plugin.events.interactivity.selectionUpdated.next()
+        this.state.stats = void 0;
+        this.events.changed.next()
         return selections;
     }
 
@@ -325,7 +326,7 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
         return PrincipalAxes.ofPositions(positions)
     }
 
-    applyLoci(modifier: StructureSelectionModifier, loci: Loci) {
+    modify(modifier: StructureSelectionModifier, loci: Loci) {
         let changed = false;
         switch (modifier) {
             case 'add': changed = this.add(loci); break;

+ 2 - 2
src/mol-plugin-state/transforms/model.ts

@@ -24,8 +24,8 @@ import { SymmetryOperator } from '../../mol-math/geometry';
 import { Script } from '../../mol-script/script';
 import { parse3DG } from '../../mol-io/reader/3dg/parser';
 import { trajectoryFrom3DG } from '../../mol-model-formats/structure/3dg';
-import { StructureSelectionQueries } from '../../mol-plugin/util/structure-selection-helper';
-import { StructureQueryHelper } from '../../mol-plugin/util/structure-query';
+import { StructureSelectionQueries } from '../../mol-plugin/util/structure-selection-query';
+import { StructureQueryHelper } from '../helpers/structure-query';
 import { RootStructureDefinition } from '../helpers/root-structure';
 import { parseDcd } from '../../mol-io/reader/dcd/parser';
 import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';

+ 4 - 4
src/mol-plugin-ui/sequence.tsx

@@ -12,7 +12,6 @@ import { Sequence } from './sequence/sequence';
 import { Structure, StructureElement, StructureProperties as SP, Unit } from '../mol-model/structure';
 import { SequenceWrapper } from './sequence/wrapper';
 import { PolymerSequenceWrapper } from './sequence/polymer';
-import { StructureElementSelectionManager } from '../mol-plugin/util/structure-element-selection';
 import { MarkerAction } from '../mol-util/marker-action';
 import { PureSelectControl } from './controls/parameters';
 import { ParamDefinition as PD } from '../mol-util/param-definition';
@@ -22,6 +21,7 @@ import { ChainSequenceWrapper } from './sequence/chain';
 import { ElementSequenceWrapper } from './sequence/element';
 import { elementLabel } from '../mol-theme/label';
 import { Icon } from './controls/icons';
+import { StructureSelectionManager } from '../mol-plugin-state/manager/structure/selection';
 
 const MaxDisplaySequenceLength = 5000
 
@@ -38,7 +38,7 @@ function splitModelEntityId(modelEntityId: string) {
     return [ parseInt(modelIdx), entityId ]
 }
 
-function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureElementSelectionManager): SequenceWrapper.Any | string {
+function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureSelectionManager): SequenceWrapper.Any | string {
     const { structure, modelEntityId, chainGroupId, operatorKey } = state
     const l = StructureElement.Location.create(structure)
     const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId)
@@ -84,7 +84,7 @@ function getSequenceWrapper(state: SequenceViewState, structureSelection: Struct
             sw = new ChainSequenceWrapper(data)
         }
 
-        sw.markResidue(structureSelection.get(structure), MarkerAction.Select)
+        sw.markResidue(structureSelection.getLoci(structure), MarkerAction.Select)
         return sw
     } else {
         return 'No sequence available'
@@ -223,7 +223,7 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
     }
 
     private getSequenceWrapper() {
-        return getSequenceWrapper(this.state, this.plugin.helpers.structureSelectionManager)
+        return getSequenceWrapper(this.state, this.plugin.managers.structure.selection)
     }
 
     private getInitialState(): SequenceViewState {

+ 1 - 1
src/mol-plugin-ui/structure/representation.tsx

@@ -118,7 +118,7 @@ class EverythingStructureRepresentationControls extends BaseStructureRepresentat
 class SelectionStructureRepresentationControls extends BaseStructureRepresentationControls {
     label = 'Selection'
     lociGetter = (structure: Structure) => {
-        const loci = this.plugin.helpers.structureSelectionManager.get(structure)
+        const loci = this.plugin.managers.structure.selection.getLoci(structure)
         return isEmptyLoci(loci) ? StructureElement.Loci.none(structure) : loci
     }
 }

+ 23 - 22
src/mol-plugin-ui/structure/selection.tsx

@@ -7,7 +7,7 @@
 
 import * as React from 'react';
 import { CollapsableControls, CollapsableState } from '../base';
-import { StructureSelectionQuery, SelectionModifier, StructureSelectionQueryList } from '../../mol-plugin/util/structure-selection-helper';
+import { StructureSelectionQuery, StructureSelectionQueryList } from '../../mol-plugin/util/structure-selection-query';
 import { PluginCommands } from '../../mol-plugin/commands';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { Interactivity } from '../../mol-plugin/util/interactivity';
@@ -17,6 +17,7 @@ import { StructureElement } from '../../mol-model/structure';
 import { ActionMenu } from '../controls/action-menu';
 import { ToggleButton } from '../controls/common';
 import { Icon } from '../controls/icons';
+import { StructureSelectionModifier } from '../../mol-plugin-state/manager/structure/selection';
 
 export const DefaultQueries = ActionMenu.createItems(StructureSelectionQueryList, {
     label: q => q.label,
@@ -34,12 +35,12 @@ interface StructureSelectionControlsState extends CollapsableState {
 
     isDisabled: boolean,
 
-    queryAction?: SelectionModifier
+    queryAction?: StructureSelectionModifier
 }
 
 export class StructureSelectionControls<P, S extends StructureSelectionControlsState> extends CollapsableControls<P, S> {
     componentDidMount() {
-        this.subscribe(this.plugin.events.interactivity.selectionUpdated, () => {
+        this.subscribe(this.plugin.managers.structure.selection.events.changed, () => {
             this.forceUpdate()
         });
 
@@ -47,13 +48,13 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
             this.forceUpdate()
         });
 
-        this.subscribe(this.plugin.state.dataState.events.isUpdating, v => {
+        this.subscribe(this.plugin.behaviors.state.isBusy, v => {
             this.setState({ isDisabled: v, queryAction: void 0 })
         })
     }
 
     get stats() {
-        const stats = this.plugin.helpers.structureSelectionManager.stats
+        const stats = this.plugin.managers.structure.selection.stats
         if (stats.structureCount === 0 || stats.elementCount === 0) {
             return 'Selected nothing'
         } else {
@@ -63,10 +64,10 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
 
     focus = () => {
         const { extraRadius, minRadius, durationMs } = this.state
-        if (this.plugin.helpers.structureSelectionManager.stats.elementCount === 0) return
-        const principalAxes = this.plugin.helpers.structureSelectionManager.getPrincipalAxes();
+        if (this.plugin.managers.structure.selection.stats.elementCount === 0) return
+        const principalAxes = this.plugin.managers.structure.selection.getPrincipalAxes();
         const { origin, dirA, dirC } = principalAxes.boxAxes
-        const { sphere } = this.plugin.helpers.structureSelectionManager.getBoundary()
+        const { sphere } = this.plugin.managers.structure.selection.getBoundary()
         const radius = Math.max(sphere.radius + extraRadius, minRadius);
         this.plugin.canvas3d?.camera.focus(origin, radius, this.plugin.canvas3d.boundingSphere.radius, durationMs, dirA, dirC);
     }
@@ -74,7 +75,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
     focusLoci(loci: StructureElement.Loci) {
         return () => {
             const { extraRadius, minRadius, durationMs } = this.state
-            if (this.plugin.helpers.structureSelectionManager.stats.elementCount === 0) return
+            if (this.plugin.managers.structure.selection.stats.elementCount === 0) return
             const { sphere } = StructureElement.Loci.getBoundary(loci)
             const radius = Math.max(sphere.radius + extraRadius, minRadius);
             this.plugin.canvas3d?.camera.focus(sphere.center, radius, this.plugin.canvas3d.boundingSphere.radius, durationMs);
@@ -82,27 +83,27 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
     }
 
     measureDistance = () => {
-        const loci = this.plugin.helpers.structureSelectionManager.latestLoci;
+        const loci = this.plugin.managers.structure.selection.history;
         this.plugin.managers.structure.measurement.addDistance(loci[0].loci, loci[1].loci);
     }
 
     measureAngle = () => {
-        const loci = this.plugin.helpers.structureSelectionManager.latestLoci;
+        const loci = this.plugin.managers.structure.selection.history;
         this.plugin.managers.structure.measurement.addAngle(loci[0].loci, loci[1].loci, loci[2].loci);
     }
 
     measureDihedral = () => {
-        const loci = this.plugin.helpers.structureSelectionManager.latestLoci;
+        const loci = this.plugin.managers.structure.selection.history;
         this.plugin.managers.structure.measurement.addDihedral(loci[0].loci, loci[1].loci, loci[2].loci, loci[3].loci);
     }
 
     addLabel = () => {
-        const loci = this.plugin.helpers.structureSelectionManager.latestLoci;
+        const loci = this.plugin.managers.structure.selection.history;
         this.plugin.managers.structure.measurement.addLabel(loci[0].loci);
     }
 
     addOrientation = () => {
-        const loci = this.plugin.helpers.structureSelectionManager.latestLoci;
+        const loci = this.plugin.managers.structure.selection.history;
         this.plugin.managers.structure.measurement.addOrientation(loci[0].loci);
     }
 
@@ -118,8 +119,8 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
         }
     }
 
-    set = (modifier: SelectionModifier, selectionQuery: StructureSelectionQuery) => {
-        this.plugin.helpers.structureSelection.set(modifier, selectionQuery, false)
+    set = (modifier: StructureSelectionModifier, selectionQuery: StructureSelectionQuery) => {
+        this.plugin.managers.structure.selection.fromSelectionQuery(modifier, selectionQuery, false)
     }
 
     selectQuery: ActionMenu.OnSelect = item => {
@@ -135,20 +136,20 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
 
     queries = DefaultQueries
 
-    private showQueries(q: SelectionModifier) {
+    private showQueries(q: StructureSelectionModifier) {
         return () => this.setState({ queryAction: this.state.queryAction === q ? void 0 : q });
     }
 
     toggleAdd = this.showQueries('add')
     toggleRemove = this.showQueries('remove')
-    toggleOnly = this.showQueries('only')
+    toggleOnly = this.showQueries('set')
 
     get controls() {
         return <div>
             <div className='msp-control-row msp-button-row'>
                 <ToggleButton label='Select' toggle={this.toggleAdd} isSelected={this.state.queryAction === 'add'} disabled={this.state.isDisabled} />
                 <ToggleButton label='Deselect' toggle={this.toggleRemove} isSelected={this.state.queryAction === 'remove'} disabled={this.state.isDisabled} />
-                <ToggleButton label='Only' toggle={this.toggleOnly} isSelected={this.state.queryAction === 'only'} disabled={this.state.isDisabled} />
+                <ToggleButton label='Only' toggle={this.toggleOnly} isSelected={this.state.queryAction === 'set'} disabled={this.state.isDisabled} />
             </div>
             {this.state.queryAction && <ActionMenu items={this.queries} onSelect={this.selectQuery} />}
         </div>
@@ -172,12 +173,12 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
     renderControls() {
         const latest: JSX.Element[] = [];
 
-        const mng = this.plugin.helpers.structureSelectionManager;
+        const mng = this.plugin.managers.structure.selection;
 
         // TODO: fix the styles, move them to CSS
 
-        for (let i = 0, _i = Math.min(4, mng.latestLoci.length); i < _i; i++) {
-            const e = mng.latestLoci[i];
+        for (let i = 0, _i = Math.min(4, mng.history.length); i < _i; i++) {
+            const e = mng.history[i];
             latest.push(<li key={e!.label}>
                 <button className='msp-btn msp-btn-block msp-form-control' style={{ borderRight: '6px solid transparent', overflow: 'hidden' }}
                     title='Click to focus.' onClick={this.focusLoci(e.loci)}>

+ 1 - 1
src/mol-plugin/behavior/dynamic/representation.ts

@@ -105,7 +105,7 @@ export const SelectLoci = PluginBehavior.create({
                     if (clear) {
                         this.lociMarkProvider({ loci: Structure.Loci(so.data) }, MarkerAction.Deselect)
                     }
-                    const loci = this.ctx.helpers.structureSelectionManager.get(so.data)
+                    const loci = this.ctx.managers.structure.selection.getLoci(so.data)
                     this.lociMarkProvider({ loci }, MarkerAction.Select)
                 }
             }

+ 1 - 6
src/mol-plugin/context.ts

@@ -31,14 +31,12 @@ import { StateTransformParameters } from '../mol-plugin-ui/state/common';
 import { LociLabelEntry, LociLabelManager } from './util/loci-label-manager';
 import { TaskManager } from './util/task-manager';
 import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version';
-import { StructureElementSelectionManager } from './util/structure-element-selection';
 import { SubstructureParentHelper } from './util/substructure-parent-helper';
 import { ModifiersKeys } from '../mol-util/input/input-observer';
 import { isProductionMode, isDebugMode } from '../mol-util/debug';
 import { Model, Structure } from '../mol-model/structure';
 import { Interactivity } from './util/interactivity';
 import { StructureRepresentationHelper } from './util/structure-representation-helper';
-import { StructureSelectionHelper } from './util/structure-selection-helper';
 import { StructureOverpaintHelper } from './util/structure-overpaint-helper';
 import { PluginToastManager } from './util/toast';
 import { StructureMeasurementManager } from '../mol-plugin-state/manager/structure/measurement';
@@ -81,8 +79,7 @@ export class PluginContext {
             settingsUpdated: this.ev()
         },
         interactivity: {
-            propsUpdated: this.ev(),
-            selectionUpdated: this.ev()
+            propsUpdated: this.ev()
         }
     } as const
 
@@ -147,8 +144,6 @@ export class PluginContext {
     readonly customParamEditors = new Map<string, StateTransformParameters.Class>();
 
     readonly helpers = {
-        structureSelectionManager: new StructureElementSelectionManager(this),
-        structureSelection: new StructureSelectionHelper(this),
         structureRepresentation: new StructureRepresentationHelper(this),
         structureOverpaint: new StructureOverpaintHelper(this),
         substructureParent: new SubstructureParentHelper(this),

+ 9 - 9
src/mol-plugin/util/interactivity.ts

@@ -10,11 +10,11 @@ import { ModifiersKeys, ButtonsType } from '../../mol-util/input/input-observer'
 import { Representation } from '../../mol-repr/representation';
 import { StructureElement } from '../../mol-model/structure';
 import { MarkerAction } from '../../mol-util/marker-action';
-import { StructureElementSelectionManager } from './structure-element-selection';
 import { PluginContext } from '../context';
 import { Structure } from '../../mol-model/structure';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { PluginCommands } from '../commands';
+import { StructureSelectionManager } from '../../mol-plugin-state/manager/structure/selection';
 
 export { Interactivity }
 
@@ -64,7 +64,7 @@ namespace Interactivity {
 
     export abstract class LociMarkManager {
         protected providers: LociMarkProvider[] = [];
-        protected sel: StructureElementSelectionManager
+        protected sel: StructureSelectionManager
 
         readonly props: Readonly<Props> = PD.getDefaultValues(Params)
 
@@ -92,7 +92,7 @@ namespace Interactivity {
         }
 
         constructor(public readonly ctx: PluginContext, props: Partial<Props> = {}) {
-            this.sel = ctx.helpers.structureSelectionManager
+            this.sel = ctx.managers.structure.selection
             this.setProps(props)
         }
     }
@@ -178,7 +178,7 @@ namespace Interactivity {
         select(current: Loci<ModelLoci>, applyGranularity = true) {
             const normalized = this.normalizedLoci(current, applyGranularity)
             if (StructureElement.Loci.is(normalized.loci)) {
-                this.sel.add(normalized.loci);
+                this.sel.modify('add', normalized.loci);
             }
             this.mark(normalized, MarkerAction.Select);
         }
@@ -187,7 +187,7 @@ namespace Interactivity {
             this.deselectAll()
             const normalized = this.normalizedLoci(current, applyGranularity)
             if (StructureElement.Loci.is(normalized.loci)) {
-                this.sel.set(normalized.loci);
+                this.sel.modify('set', normalized.loci);
             }
             this.mark(normalized, MarkerAction.Select);
         }
@@ -195,7 +195,7 @@ namespace Interactivity {
         deselect(current: Loci<ModelLoci>, applyGranularity = true) {
             const normalized = this.normalizedLoci(current, applyGranularity)
             if (StructureElement.Loci.is(normalized.loci)) {
-                this.sel.remove(normalized.loci);
+                this.sel.modify('remove', normalized.loci);
             }
             this.mark(normalized, MarkerAction.Deselect);
         }
@@ -215,7 +215,7 @@ namespace Interactivity {
                 // do a full deselect/select for the current structure so visuals
                 // that are marked with granularity unequal to 'element' are handled properly
                 super.mark({ loci: Structure.Loci(loci.structure) }, MarkerAction.Deselect)
-                super.mark({ loci: this.sel.get(loci.structure) }, MarkerAction.Select)
+                super.mark({ loci: this.sel.getLoci(loci.structure) }, MarkerAction.Select)
             } else {
                 super.mark(current, action)
             }
@@ -223,10 +223,10 @@ namespace Interactivity {
 
         private toggleSel(current: Loci<ModelLoci>) {
             if (this.sel.has(current.loci)) {
-                this.sel.remove(current.loci);
+                this.sel.modify('remove', current.loci);
                 this.mark(current, MarkerAction.Deselect);
             } else {
-                this.sel.add(current.loci);
+                this.sel.modify('add', current.loci);
                 this.mark(current, MarkerAction.Select);
             }
         }

+ 0 - 350
src/mol-plugin/util/structure-element-selection.ts

@@ -1,350 +0,0 @@
-/**
- * Copyright (c) 2019 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 { OrderedSet } from '../../mol-data/int';
-import { EmptyLoci, Loci } from '../../mol-model/loci';
-import { Structure, StructureElement } from '../../mol-model/structure';
-import { StateObject } from '../../mol-state';
-import { PluginContext } from '../context';
-import { PluginStateObject } from '../../mol-plugin-state/objects';
-import { structureElementStatsLabel } from '../../mol-theme/label';
-import { Vec3 } from '../../mol-math/linear-algebra';
-import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
-import { Boundary } from '../../mol-model/structure/structure/util/boundary';
-import { PrincipalAxes } from '../../mol-math/linear-algebra/matrix/principal-axes';
-import { arrayRemoveAtInPlace } from '../../mol-util/array';
-
-const boundaryHelper = new BoundaryHelper();
-const LATEST_LOCI_CAPACITY = 8;
-
-export { StructureElementSelectionManager };
-class StructureElementSelectionManager {
-    private entries = new Map<string, SelectionEntry>();
-    private _latestLoci: LatestEntry[] = [];
-    private referenceLoci: Loci | undefined
-
-    private getEntry(s: Structure) {
-        const cell = this.plugin.helpers.substructureParent.get(s);
-        if (!cell) return;
-        const ref = cell.transform.ref;
-        if (!this.entries.has(ref)) {
-            const entry = SelectionEntry(s);
-            this.entries.set(ref, entry);
-            return entry;
-        }
-
-        return this.entries.get(ref)!;
-    }
-
-    get latestLoci(): ReadonlyArray<LatestEntry> {
-        return this._latestLoci;
-    }
-
-    /** Count of all selected elements */
-    size() {
-        let count = 0
-        this.entries.forEach(v => {
-            count += StructureElement.Loci.size(v.selection)
-        })
-        return count
-    }
-
-    getBoundary() {
-        const min = Vec3.create(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE)
-        const max = Vec3.create(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE)
-
-        boundaryHelper.reset(0);
-
-        const boundaries: Boundary[] = []
-        this.entries.forEach(v => {
-            const loci = v.selection
-            if (!StructureElement.Loci.isEmpty(loci)) {
-                boundaries.push(StructureElement.Loci.getBoundary(loci))
-            }
-        })
-
-        for (let i = 0, il = boundaries.length; i < il; ++i) {
-            const { box, sphere } = boundaries[i];
-            Vec3.min(min, min, box.min);
-            Vec3.max(max, max, box.max);
-            boundaryHelper.boundaryStep(sphere.center, sphere.radius)
-        }
-
-        boundaryHelper.finishBoundaryStep();
-
-        for (let i = 0, il = boundaries.length; i < il; ++i) {
-            const { sphere } = boundaries[i];
-            boundaryHelper.extendStep(sphere.center, sphere.radius);
-        }
-
-        return { box: { min, max }, sphere: boundaryHelper.getSphere() };
-    }
-
-    getPrincipalAxes(): PrincipalAxes {
-        const elementCount = this.size()
-        const positions = new Float32Array(3 * elementCount)
-        let offset = 0
-        this.entries.forEach(v => {
-            StructureElement.Loci.toPositionsArray(v.selection, positions, offset)
-            offset += StructureElement.Loci.size(v.selection) * 3
-        })
-        return PrincipalAxes.ofPositions(positions)
-    }
-
-    get stats() {
-        let structureCount = 0
-        let elementCount = 0
-        const stats = StructureElement.Stats.create()
-
-        this.entries.forEach(v => {
-            const { elements } = v.selection
-            if (elements.length) {
-                structureCount += 1
-                for (let i = 0, il = elements.length; i < il; ++i) {
-                    elementCount += OrderedSet.size(elements[i].indices)
-                }
-                StructureElement.Stats.add(stats, stats, StructureElement.Stats.ofLoci(v.selection))
-            }
-        })
-
-        const label = structureElementStatsLabel(stats, { countsOnly: true })
-
-        return { structureCount, elementCount, label }
-    }
-
-    add(loci: Loci): Loci {
-        if (StructureElement.Loci.is(loci)) {
-            const entry = this.getEntry(loci.structure);
-            if (entry) {
-                entry.selection = StructureElement.Loci.union(entry.selection, loci);
-                this.addLatest(loci);
-                this.referenceLoci = loci
-                this.plugin.events.interactivity.selectionUpdated.next();
-                return entry.selection;
-            }
-        }
-        return EmptyLoci
-    }
-
-    remove(loci: Loci): Loci {
-        if (StructureElement.Loci.is(loci)) {
-            const entry = this.getEntry(loci.structure);
-            if (entry) {
-                entry.selection = StructureElement.Loci.subtract(entry.selection, loci);
-                this.removeLatest(loci);
-                this.referenceLoci = loci
-                this.plugin.events.interactivity.selectionUpdated.next();
-                return StructureElement.Loci.isEmpty(entry.selection) ? EmptyLoci : entry.selection;
-            }
-        }
-        return EmptyLoci
-    }
-
-    set(loci: Loci): Loci {
-        if (StructureElement.Loci.is(loci)) {
-            const entry = this.getEntry(loci.structure);
-            if (entry) {
-                entry.selection = loci;
-                this.referenceLoci = undefined
-                this.plugin.events.interactivity.selectionUpdated.next()
-                return StructureElement.Loci.isEmpty(entry.selection) ? EmptyLoci : entry.selection;
-            }
-        }
-        return EmptyLoci;
-    }
-
-    /** Removes all selections and returns them */
-    clear() {
-        const keys = this.entries.keys();
-        const selections: StructureElement.Loci[] = [];
-        while (true) {
-            const k = keys.next();
-            if (k.done) break;
-            const s = this.entries.get(k.value)!;
-            if (!StructureElement.Loci.isEmpty(s.selection)) selections.push(s.selection);
-            s.selection = StructureElement.Loci(s.selection.structure, []);
-        }
-        this.referenceLoci = undefined
-        this.plugin.events.interactivity.selectionUpdated.next()
-        return selections;
-    }
-
-    get(structure: Structure) {
-        const entry = this.getEntry(structure);
-        if (!entry) return EmptyLoci;
-        return entry.selection;
-    }
-
-    has(loci: Loci) {
-        if (StructureElement.Loci.is(loci)) {
-            const entry = this.getEntry(loci.structure);
-            if (entry) {
-                return StructureElement.Loci.isSubset(entry.selection, loci);
-            }
-        }
-        return false;
-    }
-
-    tryGetRange(loci: Loci): StructureElement.Loci | undefined {
-        if (!StructureElement.Loci.is(loci)) return;
-        if (loci.elements.length !== 1) return;
-        const entry = this.getEntry(loci.structure);
-        if (!entry) return;
-
-        const xs = loci.elements[0];
-        if (!xs) return;
-
-        const ref = this.referenceLoci
-        if (!ref || !StructureElement.Loci.is(ref) || ref.structure.root !== loci.structure.root) return;
-
-        let e: StructureElement.Loci['elements'][0] | undefined;
-        for (const _e of ref.elements) {
-            if (xs.unit === _e.unit) {
-                e = _e;
-                break;
-            }
-        }
-        if (!e) return;
-
-        if (xs.unit !== e.unit) return;
-
-        return getElementRange(loci.structure.root, e, xs)
-    }
-
-    private prevHighlight: StructureElement.Loci | undefined = void 0;
-
-    accumulateInteractiveHighlight(loci: Loci) {
-        if (StructureElement.Loci.is(loci)) {
-            if (this.prevHighlight) {
-                this.prevHighlight = StructureElement.Loci.union(this.prevHighlight, loci);
-            } else {
-                this.prevHighlight = loci;
-            }
-        }
-        return this.prevHighlight;
-    }
-
-    clearInteractiveHighlight() {
-        const ret = this.prevHighlight;
-        this.prevHighlight = void 0;
-        return ret || EmptyLoci;
-    }
-
-    private onRemove(ref: string) {
-        if (this.entries.has(ref)) {
-            this.entries.delete(ref);
-            // TODO: property update the latest loci
-            this._latestLoci = [];
-            this.referenceLoci = undefined
-        }
-    }
-
-    private onUpdate(ref: string, oldObj: StateObject | undefined, obj: StateObject) {
-        if (!PluginStateObject.Molecule.Structure.is(obj)) return;
-
-        if (this.entries.has(ref)) {
-            if (!PluginStateObject.Molecule.Structure.is(oldObj) || oldObj === obj || oldObj.data === obj.data) return;
-
-            // TODO: property update the latest loci & reference loci
-            this._latestLoci = [];
-            this.referenceLoci = undefined
-
-            // remap the old selection to be related to the new object if possible.
-            if (Structure.areUnitAndIndicesEqual(oldObj.data, obj.data)) {
-                this.entries.set(ref, remapSelectionEntry(this.entries.get(ref)!, obj.data));
-                return;
-            }
-
-            // clear the selection
-            this.entries.set(ref, SelectionEntry(obj.data));
-        }
-    }
-
-    private addLatest(loci: StructureElement.Loci) {
-        if (Loci.isEmpty(loci)) return;
-
-        let idx = 0, entry: LatestEntry | undefined = void 0;
-        for (const l of this._latestLoci) {
-            if (Loci.areEqual(l.loci, loci)) {
-                entry = l;
-                break;
-            }
-            idx++;
-        }
-
-        if (entry) {
-            arrayRemoveAtInPlace(this._latestLoci, idx);
-            this._latestLoci.unshift(entry);
-            return;
-        }
-
-        const stats = StructureElement.Stats.ofLoci(loci);
-        const label = structureElementStatsLabel(stats)
-
-        this._latestLoci.unshift({ loci, label });
-        if (this._latestLoci.length > LATEST_LOCI_CAPACITY) this._latestLoci.pop();
-    }
-
-    private removeLatest(loci: Loci) {
-        if (Loci.isEmpty(loci)) return;
-
-        let idx = 0, found = false;
-        for (const l of this._latestLoci) {
-            if (Loci.areEqual(l.loci, loci)) {
-                found = true;
-                break;
-            }
-            idx++;
-        }
-
-        if (found) {
-            arrayRemoveAtInPlace(this._latestLoci, idx);
-        }
-    }
-
-    // private removeLatestOnChange
-
-    constructor(private plugin: PluginContext) {
-        plugin.state.dataState.events.object.removed.subscribe(e => this.onRemove(e.ref));
-        plugin.state.dataState.events.object.updated.subscribe(e => this.onUpdate(e.ref, e.oldObj, e.obj));
-    }
-}
-
-interface SelectionEntry {
-    selection: StructureElement.Loci
-}
-
-interface LatestEntry {
-    loci: StructureElement.Loci,
-    label: string
-}
-
-function SelectionEntry(s: Structure): SelectionEntry {
-    return {
-        selection: StructureElement.Loci(s, [])
-    };
-}
-
-/** remap `selection-entry` to be related to `structure` if possible */
-function remapSelectionEntry(e: SelectionEntry, s: Structure): SelectionEntry {
-    return {
-        selection: StructureElement.Loci.remap(e.selection, s)
-    };
-}
-
-/**
- * Assumes `ref` and `ext` belong to the same unit in the same structure
- */
-function getElementRange(structure: Structure, ref: StructureElement.Loci['elements'][0], ext: StructureElement.Loci['elements'][0]) {
-    const min = Math.min(OrderedSet.min(ref.indices), OrderedSet.min(ext.indices))
-    const max = Math.max(OrderedSet.max(ref.indices), OrderedSet.max(ext.indices))
-
-    return StructureElement.Loci(structure, [{
-        unit: ref.unit,
-        indices: OrderedSet.ofRange(min as StructureElement.UnitIndex, max as StructureElement.UnitIndex)
-    }]);
-}

+ 12 - 61
src/mol-plugin/util/structure-selection-helper.ts → src/mol-plugin/util/structure-selection-query.ts

@@ -5,22 +5,21 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
-import { StateSelection, StateBuilder } from '../../mol-state';
+import { CustomProperty } from '../../mol-model-props/common/custom-property';
+import { AccessibleSurfaceAreaProvider, AccessibleSurfaceAreaSymbols } from '../../mol-model-props/computed/accessible-surface-area';
+import { ValidationReport, ValidationReportProvider } from '../../mol-model-props/rcsb/validation-report';
+import { QueryContext, Structure, StructureQuery, StructureSelection } from '../../mol-model/structure';
+import { BondType, NucleicBackboneAtoms, ProteinBackboneAtoms, SecondaryStructureType } from '../../mol-model/structure/model/types';
 import { PluginStateObject } from '../../mol-plugin-state/objects';
-import { QueryContext, StructureSelection, StructureQuery, StructureElement, Structure } from '../../mol-model/structure';
-import { compile } from '../../mol-script/runtime/query/compiler';
-import { Loci } from '../../mol-model/loci';
-import { PluginContext } from '../context';
-import Expression from '../../mol-script/language/expression';
-import { BondType, ProteinBackboneAtoms, NucleicBackboneAtoms, SecondaryStructureType } from '../../mol-model/structure/model/types';
 import { StateTransforms } from '../../mol-plugin-state/transforms';
+import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
+import Expression from '../../mol-script/language/expression';
+import { compile } from '../../mol-script/runtime/query/compiler';
+import { StateBuilder } from '../../mol-state';
+import { RuntimeContext } from '../../mol-task';
 import { SetUtils } from '../../mol-util/set';
-import { ValidationReport, ValidationReportProvider } from '../../mol-model-props/rcsb/validation-report';
-import { CustomProperty } from '../../mol-model-props/common/custom-property';
-import { Task, RuntimeContext } from '../../mol-task';
-import { AccessibleSurfaceAreaSymbols, AccessibleSurfaceAreaProvider } from '../../mol-model-props/computed/accessible-surface-area';
 import { stringToWords } from '../../mol-util/string';
+import { PluginContext } from '../context';
 
 export enum StructureSelectionCategory {
     Type = 'Type',
@@ -36,7 +35,7 @@ export enum StructureSelectionCategory {
     Internal = 'Internal',
 }
 
-export { StructureSelectionQuery }
+export { StructureSelectionQuery };
 
 interface StructureSelectionQuery {
     readonly label: string
@@ -513,52 +512,4 @@ namespace StructureSelectionQuery {
         const result = selectionQuery.query(new QueryContext(structure, { currentSelection }))
         return StructureSelection.toLociWithSourceUnits(result)
     }
-}
-
-//
-
-export type SelectionModifier = 'add' | 'remove' | 'only'
-
-export class StructureSelectionHelper {
-    private get structures() {
-        return this.plugin.state.dataState.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure)).map(s => s.obj!.data)
-    }
-
-    private _set(modifier: SelectionModifier, loci: Loci, applyGranularity = true) {
-        switch (modifier) {
-            case 'add':
-                this.plugin.interactivity.lociSelects.select({ loci }, applyGranularity)
-                break
-            case 'remove':
-                this.plugin.interactivity.lociSelects.deselect({ loci }, applyGranularity)
-                break
-            case 'only':
-                this.plugin.interactivity.lociSelects.selectOnly({ loci }, applyGranularity)
-                break
-        }
-    }
-
-    set(modifier: SelectionModifier, selectionQuery: StructureSelectionQuery, applyGranularity = true) {
-        this.plugin.runTask(Task.create('Structure Selection', async runtime => {
-            const ctx = { fetch: this.plugin.fetch, runtime }
-            for (const s of this.structures) {
-                const current = this.plugin.helpers.structureSelectionManager.get(s)
-                const currentSelection = Loci.isEmpty(current)
-                    ? StructureSelection.Empty(s)
-                    : StructureSelection.Singletons(s, StructureElement.Loci.toStructure(current))
-
-                if (selectionQuery.ensureCustomProperties) {
-                    await selectionQuery.ensureCustomProperties(ctx, s)
-                }
-
-                const result = selectionQuery.query(new QueryContext(s, { currentSelection }))
-                const loci = StructureSelection.toLociWithSourceUnits(result)
-                this._set(modifier, loci, applyGranularity)
-            }
-        }))
-    }
-
-    constructor(private plugin: PluginContext) {
-
-    }
 }