Browse Source

add Component: option to check if an equivalent component already exists

David Sehnal 5 years ago
parent
commit
c4e43228a2

+ 16 - 0
src/mol-model/structure/query/utils/structure-set.ts

@@ -40,6 +40,22 @@ function buildUnion(this: StructureSubsetBuilder, elements: StructureElement.Set
     this.setUnit(id, elements);
 }
 
+export function structureAreEqual(sA: Structure, sB: Structure): boolean {
+    if (sA === sB) return true;
+
+    if (sA.units.length !== sB.units.length) return false;
+
+    const aU = sA.units, bU = sB.unitMap;
+    for (let i = 0, _i = aU.length; i < _i; i++) {
+        const u = aU[i];
+        if (!bU.has(u.id)) return false;
+        const v = bU.get(u.id);
+        if (!SortedArray.areEqual(u.elements, v.elements)) return false;
+    }
+
+    return true;
+}
+
 export function structureAreIntersecting(sA: Structure, sB: Structure): boolean {
     if (sA === sB) return true;
 

+ 40 - 9
src/mol-plugin-state/manager/structure/component.ts

@@ -6,11 +6,11 @@
 
 import { VisualQualityOptions } from '../../../mol-geo/geometry/base';
 import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
-import { Structure, StructureElement } from '../../../mol-model/structure';
-import { structureAreIntersecting, structureIntersect, structureSubtract, structureUnion } from '../../../mol-model/structure/query/utils/structure-set';
+import { Structure, StructureElement, StructureSelection } from '../../../mol-model/structure';
+import { structureAreEqual, structureAreIntersecting, structureIntersect, structureSubtract, structureUnion } from '../../../mol-model/structure/query/utils/structure-set';
 import { setSubtreeVisibility } from '../../../mol-plugin/behavior/static/state';
 import { PluginContext } from '../../../mol-plugin/context';
-import { StateBuilder, StateTransformer } from '../../../mol-state';
+import { StateBuilder, StateObjectRef, StateTransformer } from '../../../mol-state';
 import { Task } from '../../../mol-task';
 import { ColorTheme } from '../../../mol-theme/color';
 import { SizeTheme } from '../../../mol-theme/size';
@@ -308,6 +308,24 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
         }, { canUndo: 'Add Representation' });
     }
 
+    private tryFindComponent(structure: StructureRef, selection: StructureSelectionQuery) {
+        if (structure.components.length === 0) return;
+
+        return this.plugin.runTask(Task.create('Find Component', async taskCtx => {
+
+            const data = structure.cell.obj?.data;
+            if (!data) return;
+            const sel = StructureSelection.unionStructure(await selection.getSelection(this.plugin, taskCtx, data));
+
+            for (const c of structure.components) {
+                const comp = c.cell.obj?.data;
+                if (!comp || !c.cell.parent) continue;
+
+                if (structureAreEqual(sel, comp)) return c.cell;
+            }
+        }));
+    }
+
     async add(params: StructureComponentManager.AddParams, structures?: ReadonlyArray<StructureRef>) {
         return this.plugin.dataTransaction(async () => {
             const xs = structures || this.currentStructures;
@@ -319,9 +337,18 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
 
             const componentKey = UUID.create22();
             for (const s of xs) {
-                const component = await this.plugin.builders.structure.tryCreateComponentFromSelection(s.cell, params.selection, componentKey, {
-                    label: params.label || (params.selection === StructureSelectionQueries.current ? 'Custom Selection' : ''),
-                });
+                let component: StateObjectRef | undefined = void 0;
+
+                if (params.options.checkExisting) {
+                    component = await this.tryFindComponent(s, params.selection);
+                }
+
+                if (!component) {
+                    component = await this.plugin.builders.structure.tryCreateComponentFromSelection(s.cell, params.selection, componentKey, {
+                        label: params.options.label || (params.selection === StructureSelectionQueries.current ? 'Custom Selection' : ''),
+                    });
+                }
+
                 if (params.representation === 'none' || !component) continue;
                 await this.plugin.builders.structure.representation.addRepresentation(component, {
                     type: this.plugin.representation.structure.registry.get(params.representation),
@@ -400,22 +427,26 @@ namespace StructureComponentManager {
     };
     export type Options = PD.Values<typeof OptionsParams>
 
-    export function getAddParams(plugin: PluginContext, params?: { pivot?: StructureRef, allowNone: boolean, hideSelection?: boolean, defaultSelection?: StructureSelectionQuery }) {
+    export function getAddParams(plugin: PluginContext, params?: { pivot?: StructureRef, allowNone: boolean, hideSelection?: boolean, checkExisting?: boolean, defaultSelection?: StructureSelectionQuery }) {
         const { options } = plugin.query.structure.registry;
         params = {
             pivot: plugin.managers.structure.component.pivotStructure,
             allowNone: true,
             hideSelection: false,
+            checkExisting: false,
             defaultSelection: StructureSelectionQueries.current,
             ...params
         };
         return {
             selection: PD.Select(options[1][0], options, { isHidden: params?.hideSelection }),
             representation: getRepresentationTypesSelect(plugin, params?.pivot, params?.allowNone ? [['none', '< Create Later >']] : []),
-            label: PD.Text('')
+            options: PD.Group({
+                label: PD.Text(''),
+                checkExisting: PD.Boolean(!!params?.checkExisting, { help: () => ({ description: 'Checks if a selection with the specifield elements already exists to avoid creating duplicate components.' }) }),
+            })
         };
     }
-    export type AddParams = { selection: StructureSelectionQuery, label: string, representation: string }
+    export type AddParams = { selection: StructureSelectionQuery, options: { checkExisting: boolean, label: string }, representation: string }
 
     export function getColorParams(plugin: PluginContext, pivot: StructureRef | StructureComponentRef | undefined) {
         return {

+ 3 - 9
src/mol-plugin-ui/structure/components.tsx

@@ -149,7 +149,7 @@ interface AddComponentControlsProps {
 export class AddComponentControls extends PurePluginUIComponent<AddComponentControlsProps, AddComponentControlsState> {
     createState(): AddComponentControlsState {
         const params = StructureComponentManager.getAddParams(this.plugin, this.props.forSelection
-            ? { allowNone: false, hideSelection: true }
+            ? { allowNone: false, hideSelection: true, checkExisting: this.props.forSelection }
             : void 0);
         return { params, values: ParamDefinition.getDefaultValues(params) };
     }
@@ -161,19 +161,13 @@ export class AddComponentControls extends PurePluginUIComponent<AddComponentCont
     }
 
     apply = () => {
-        const target = this.props.forSelection ? this.plugin.managers.structure.hierarchy.getStructuresWithSelection() : this.selectedStructures;
+        const structures = this.props.forSelection ? this.plugin.managers.structure.hierarchy.getStructuresWithSelection() : this.selectedStructures;
         this.props.onApply();
-        this.plugin.managers.structure.component.add(this.state.values, target);
+        this.plugin.managers.structure.component.add(this.state.values, structures);
     }
 
     paramsChanged = (values: any) => this.setState({ values })
 
-    // componentDidUpdate(prevProps: AddComponentControlsProps) {
-    //     if (this.props.structures !== prevProps.structures) {
-    //         this.setState(this.createState());
-    //     }
-    // }
-
     render() {
         return <>
             <ParameterControls params={this.state.params} values={this.state.values} onChangeValues={this.paramsChanged} />