Alexander Rose 5 years ago
parent
commit
9c8f1f3e11

+ 20 - 19
src/mol-plugin-state/builder/structure.ts

@@ -22,6 +22,7 @@ export enum StructureBuilderTags {
     Trajectory = 'trajectory',
     Model = 'model',
     ModelProperties = 'model-properties',
+    ModelGenericRepresentation = 'model-generic-representation',
     Structure = 'structure',
     StructureProperties = 'structure-properties',
     Component = 'structure-component'
@@ -43,7 +44,7 @@ export class StructureBuilder {
         const state = this.dataState;
         const trajectory = state.build().to(data)
             .apply(StateTransforms.Data.ParseBlob, params, { state: { isGhost: true } })
-            .apply(StateTransforms.Model.TrajectoryFromBlob, void 0, { tags: StructureBuilderTags.Trajectory });        
+            .apply(StateTransforms.Model.TrajectoryFromBlob, void 0, { tags: StructureBuilderTags.Trajectory });
         await this.plugin.updateDataState(trajectory, { revertOnError: true });
         return trajectory.selector;
     }
@@ -60,18 +61,18 @@ export class StructureBuilder {
         structure?: RootStructureDefinition.Params,
         structureProperties?: boolean | StateTransformer.Params<StateTransforms['Model']['CustomStructureProperties']>
     }) {
-        const trajectory = params.data 
+        const trajectory = params.data
             ? await this.parseTrajectory(params.data, params.dataFormat! || 'cif')
             : await this.parseTrajectoryBlob(params.blob!, params.blobParams!);
-        
+
         const model = await this.createModel(trajectory, params.model);
-        const modelProperties = !!params.modelProperties 
+        const modelProperties = !!params.modelProperties
             ? await this.insertModelProperties(model, typeof params?.modelProperties !== 'boolean' ? params?.modelProperties : void 0) : void 0;
-        
+
         const structure = await this.createStructure(modelProperties || model, params.structure);
-        const structureProperties = !!params.structureProperties 
+        const structureProperties = !!params.structureProperties
             ? await this.insertStructureProperties(structure, typeof params?.structureProperties !== 'boolean' ? params?.structureProperties : void 0) : void 0;
-    
+
         return {
             trajectory,
             model: modelProperties || model,
@@ -116,7 +117,7 @@ export class StructureBuilder {
     async createStructure(model: StateObjectRef<SO.Molecule.Model>, params?: RootStructureDefinition.Params, initialState?: Partial<StateTransform.State>) {
         const state = this.dataState;
         const structure = state.build().to(model)
-            .apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } }, { tags: StructureBuilderTags.Structure, state: initialState });        
+            .apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } }, { tags: StructureBuilderTags.Structure, state: initialState });
 
         await this.plugin.updateDataState(structure, { revertOnError: true });
         return structure.selector;
@@ -141,7 +142,7 @@ export class StructureBuilder {
         const root = state.build().to(structure);
 
         const keyTag = `structure-component-${key}`;
-        const component = root.applyOrUpdateTagged(keyTag, StateTransforms.Model.StructureComponent, params, { 
+        const component = root.applyOrUpdateTagged(keyTag, StateTransforms.Model.StructureComponent, params, {
             tags: tags ? [...tags, StructureBuilderTags.Component, keyTag] : [StructureBuilderTags.Component, keyTag]
         });
 
@@ -158,7 +159,7 @@ export class StructureBuilder {
         return selector;
     }
 
-    tryCreateStaticComponent(params: { structure: StateObjectRef<SO.Molecule.Structure>, type: StaticStructureComponentType, key: string, label?: string, tags?: string[] }) { 
+    tryCreateStaticComponent(params: { structure: StateObjectRef<SO.Molecule.Structure>, type: StaticStructureComponentType, key: string, label?: string, tags?: string[] }) {
         return this.tryCreateComponent(params.structure, {
             type: { name: 'static', params: params.type },
             nullIfEmpty: true,
@@ -168,13 +169,13 @@ export class StructureBuilder {
 
     tryCreateQueryComponent(params: { structure: StateObjectRef<SO.Molecule.Structure>, query: StructureSelectionQuery, key: string, label?: string, tags?: string[] }): Promise<StateObjectRef<SO.Molecule.Structure> | undefined> {
         return this.plugin.runTask(Task.create('Query Component', async taskCtx => {
-            let { structure, query, key, label, tags } = params;        
+            let { structure, query, key, label, tags } = params;
             label = (label || '').trim();
 
             const structureData = StateObjectRef.resolveAndCheck(this.dataState, structure)?.obj?.data;
 
             if (!structureData) return;
-    
+
             const transformParams: StructureComponentParams = query.referencesCurrent
                 ? {
                     type: {
@@ -191,25 +192,25 @@ export class StructureBuilder {
             if (query.ensureCustomProperties) {
                 await query.ensureCustomProperties({ fetch: this.plugin.fetch, runtime: taskCtx }, structureData);
             }
-            
+
             const state = this.dataState;
             const root = state.build().to(structure);
             const keyTag = `structure-component-${key}`;
-            const component = root.applyOrUpdateTagged(keyTag, StateTransforms.Model.StructureComponent, transformParams, { 
+            const component = root.applyOrUpdateTagged(keyTag, StateTransforms.Model.StructureComponent, transformParams, {
                 tags: tags ? [...tags, StructureBuilderTags.Component, keyTag] : [StructureBuilderTags.Component, keyTag]
             });
-    
+
             await this.dataState.updateTree(component).runInContext(taskCtx);
-    
+
             const selector = component.selector;
-    
+
             if (!selector.isOk || selector.cell?.obj?.data.elementCount === 0) {
                 const del = state.build().delete(selector.ref);
                 await this.plugin.updateDataState(del);
                 return;
             }
-    
-            return selector; 
+
+            return selector;
         }))
     }
 

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

@@ -86,7 +86,7 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
     private async updateInterationProps() {
         for (const s of this.currentStructures) {
             const interactionParams = InteractionsProvider.getParams(s.cell.obj?.data!);
-            
+
             if (s.properties) {
                 const params = s.properties.cell.transform.params;
                 if (PD.areEqual(interactionParams, params, this.state.options.interactions)) continue;
@@ -137,12 +137,12 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
 
     modifyByCurrentSelection(components: ReadonlyArray<StructureComponentRef>, action: StructureComponentManager.ModifyAction) {
         return this.plugin.runTask(Task.create('Modify Component', async taskCtx => {
-            const b = this.dataState.build();        
+            const b = this.dataState.build();
             for (const c of components) {
                 if (!this.canBeModified(c)) continue;
 
                 const selection = this.plugin.managers.structure.selection.getStructure(c.structure.cell.obj!.data);
-                if (!selection || selection.elementCount === 0) continue;                
+                if (!selection || selection.elementCount === 0) continue;
                 this.modifyComponent(b, c, selection, action);
             }
             await this.dataState.updateTree(b, { canUndo: 'Modify Selection' }).runInContext(taskCtx);
@@ -157,7 +157,7 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
             for (const c of components) {
                 setSubtreeVisibility(this.dataState, c.cell.transform.ref, isHidden);
             }
-        } else {            
+        } else {
             const index = components[0].representations.indexOf(reprPivot);
             const isHidden = !reprPivot.cell.state.isHidden;
 
@@ -192,14 +192,14 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
         return this.plugin.managers.structure.hierarchy.remove(toRemove, true);
     }
 
-    updateRepresentations(components: ReadonlyArray<StructureComponentRef>, pivot: StructureRepresentationRef, params: StateTransformer.Params<StructureRepresentation3D>) {        
+    updateRepresentations(components: ReadonlyArray<StructureComponentRef>, pivot: StructureRepresentationRef, params: StateTransformer.Params<StructureRepresentation3D>) {
         if (components.length === 0) return Promise.resolve();
 
         const index = components[0].representations.indexOf(pivot);
         if (index < 0) return Promise.resolve();
-        
+
         const update = this.dataState.build();
-        
+
         for (const c of components) {
             // TODO: is it ok to use just the index here? Could possible lead to ugly edge cases, but perhaps not worth the trouble to "fix".
             const repr = c.representations[index];
@@ -214,19 +214,19 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
 
     updateRepresentationsTheme<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(components: ReadonlyArray<StructureComponentRef>, params: StructureComponentManager.UpdateThemeParams<C, S>): Promise<any> {
         if (components.length === 0) return Promise.resolve();
-        
+
         const update = this.dataState.build();
 
         for (const c of components) {
             for (const repr of c.representations) {
                 const old = repr.cell.transform.params;
-                const colorTheme = params.color 
+                const colorTheme = params.color
                     ? createStructureColorThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.color, params.colorParams)
                     : void 0;
-                const sizeTheme = params.color 
+                const sizeTheme = params.color
                     ? createStructureSizeThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.size, params.sizeParams)
                     : void 0;
-                update.to(repr.cell).update(prev => { 
+                update.to(repr.cell).update(prev => {
                     if (colorTheme) prev.colorTheme = colorTheme;
                     if (sizeTheme) prev.sizeTheme = sizeTheme;
                 });
@@ -260,7 +260,7 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
 
             const componentKey = UUID.create22();
             for (const s of xs) {
-                const component = await this.plugin.builders.structure.tryCreateQueryComponent({ 
+                const component = await this.plugin.builders.structure.tryCreateQueryComponent({
                     structure: s.childRoot,
                     query: params.selection,
                     key: componentKey,
@@ -297,11 +297,11 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
         if ((action === 'subtract' || action === 'intersect') && !structureAreIntersecting(structure, by)) return;
 
         const parent = component.structure.cell.obj?.data!;
-        const modified = action === 'union' 
-            ? structureUnion(parent, [structure, by]) 
+        const modified = action === 'union'
+            ? structureUnion(parent, [structure, by])
             : action === 'intersect'
-            ? structureIntersect(structure, by)
-            : structureSubtract(structure, by);
+                ? structureIntersect(structure, by)
+                : structureSubtract(structure, by);
 
         if (modified.elementCount === 0) {
             builder.delete(component.cell.transform.ref);
@@ -346,9 +346,9 @@ namespace StructureComponentManager {
         interactions: PD.Group(InteractionsProvider.defaultParams, { label: 'Non-covalent Interactions' }),
     }
     export type Options = PD.Values<typeof OptionsParams>
-    
+
     const SelectionParam = PD.Select(StructureSelectionQueryOptions[1][0], StructureSelectionQueryOptions)
-    
+
     export function getAddParams(plugin: PluginContext) {
         return {
             selection: SelectionParam,
@@ -389,7 +389,7 @@ namespace StructureComponentManager {
     export type ModifyAction = 'union' | 'subtract' | 'intersect'
 
     export interface UpdateThemeParams<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn> {
-        /** 
+        /**
          * this works for any theme name (use 'name as any'), but code completion will break
          */
         color?: C,

+ 8 - 3
src/mol-plugin-state/manager/structure/hierarchy-state.ts

@@ -15,7 +15,7 @@ import { CreateVolumeStreamingBehavior } from '../../../mol-plugin/behavior/dyna
 
 export function buildStructureHierarchy(state: State, previous?: StructureHierarchy) {
     const build = BuildState(state, previous || StructureHierarchy());
-//    StateTree.doPreOrder(state.tree, state.tree.root, build, visitCell);
+    // StateTree.doPreOrder(state.tree, state.tree.root, build, visitCell);
     doPreOrder(state.tree, build);
     if (previous) previous.refs.forEach(isRemoved, build);
     return { hierarchy: build.hierarchy, added: build.added, updated: build.updated, removed: build.removed };
@@ -38,7 +38,7 @@ interface RefBase<K extends string = string, O extends StateObject = StateObject
     version: StateTransform['version']
 }
 
-export type HierarchyRef = 
+export type HierarchyRef =
     | TrajectoryRef
     | ModelRef | ModelPropertiesRef
     | StructureRef | StructurePropertiesRef | StructureVolumeStreamingRef | StructureComponentRef | StructureRepresentationRef
@@ -48,7 +48,7 @@ export interface TrajectoryRef extends RefBase<'trajectory', SO.Molecule.Traject
     models: ModelRef[]
 }
 
-function TrajectoryRef(cell: StateObjectCell<SO.Molecule.Trajectory>): TrajectoryRef { 
+function TrajectoryRef(cell: StateObjectCell<SO.Molecule.Trajectory>): TrajectoryRef {
     return { kind: 'trajectory', cell, version: cell.transform.version, models: [] };
 }
 
@@ -200,6 +200,11 @@ const tagMap: [string, (state: BuildState, cell: StateObjectCell) => boolean | v
         if (!state.currentModel) return false;
         state.currentModel.properties = createOrUpdateRef(state, cell, ModelPropertiesRef, cell, state.currentModel);
     }, state => { }],
+    [StructureBuilderTags.ModelGenericRepresentation, (state, cell) => {
+        if (!state.currentModel) return false;
+        if (!state.currentModel.genericRepresentations) state.currentModel.genericRepresentations = []
+        createOrUpdateRefList(state, cell, state.currentModel.genericRepresentations, GenericRepresentationRef, cell, state.currentModel);
+    }, state => { }],
     [StructureBuilderTags.Structure, (state, cell) => {
         if (state.currentModel) {
             state.currentStructure = createOrUpdateRefList(state, cell, state.currentModel.structures, StructureRef, cell, state.currentModel);

+ 2 - 0
src/mol-plugin-ui/controls.tsx

@@ -22,6 +22,7 @@ import { Icon } from './controls/icons';
 import { StructureComponentControls } from './structure/components';
 import { StructureSourceControls } from './structure/source';
 import { VolumeStreamingControls } from './structure/volume';
+import { ObjectControls } from './structure/objects';
 
 export class TrajectoryViewportControls extends PluginUIComponent<{}, { show: boolean, label: string }> {
     state = { show: false, label: '' }
@@ -274,6 +275,7 @@ export class DefaultStructureTools extends PluginUIComponent {
             <StructureComponentControls />
             <StructureMeasurementsControls />
             <VolumeStreamingControls />
+            <ObjectControls />
         </>;
     }
 }

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

@@ -246,9 +246,9 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
         const repr = this.pivot.representations[0];
         const name = repr.cell.transform.params?.colorTheme.name;
         const themes = getStructureThemeTypes(this.plugin, this.pivot.cell.obj?.data);
-        return ActionMenu.createItemsFromSelectOptions(themes, { 
+        return ActionMenu.createItemsFromSelectOptions(themes, {
             value: o => () => mng.updateRepresentationsTheme(this.props.group, { color: o[0] }),
-            selected: o => o[0] === name 
+            selected: o => o[0] === name
         }) as ActionMenu.Item[];
     }
 
@@ -282,7 +282,7 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
 
         return ret;
     }
-    
+
     selectAction: ActionMenu.OnSelect = item => {
         if (!item) return;
         this.setState({ action: void 0 });

+ 121 - 0
src/mol-plugin-ui/structure/objects.tsx

@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react';
+import { CollapsableControls, CollapsableState } from '../base';
+import { StateTransformer, StateTransform } from '../../mol-state';
+import { ModelRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
+import { StateTransforms } from '../../mol-plugin-state/transforms';
+import { StructureBuilderTags } from '../../mol-plugin-state/builder/structure';
+import { IconButton } from '../controls/common';
+
+interface ObjectControlState extends CollapsableState {
+    isBusy: boolean,
+    showOptions: boolean,
+}
+
+export class ObjectControls extends CollapsableControls<{}, ObjectControlState> {
+    protected defaultState(): ObjectControlState {
+        return {
+            header: 'Objects',
+            isCollapsed: false,
+            isBusy: false,
+            showOptions: false
+        };
+    }
+
+    componentDidMount() {
+        this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, () => this.forceUpdate());
+        this.subscribe(this.plugin.behaviors.state.isBusy, v => {
+            this.setState({ isBusy: v })
+        });
+    }
+
+    getUnitcell(model: ModelRef) {
+        return model.genericRepresentations?.filter(r => {
+            return r.cell.transform.transformer === StateTransforms.Representation.ModelUnitcell3D
+        })[0]
+    }
+
+    toggleVisible = (e: React.MouseEvent<HTMLElement>) => {
+        e.preventDefault();
+        e.currentTarget.blur();
+
+        for (const model of this.plugin.managers.structure.hierarchy.current.models) {
+            const unitcell = this.getUnitcell(model)
+            if (unitcell) {
+                this.plugin.state.data.updateCellState(unitcell.cell.transform.ref, { isHidden: !unitcell.cell.state.isHidden });
+            }
+        }
+    }
+
+    isVisible() {
+        for (const model of this.plugin.managers.structure.hierarchy.current.models) {
+            const unitcell = this.getUnitcell(model)
+            if (unitcell && !unitcell.cell.state.isHidden) return true
+        }
+        return false
+    }
+
+    async createUnitcell(model: ModelRef, params?: StateTransformer.Params<StateTransforms['Representation']['ModelUnitcell3D']>, initialState?: Partial<StateTransform.State>) {
+        const state = this.plugin.state.data;
+        const unitcell = state.build().to(model.cell)
+            .apply(StateTransforms.Representation.ModelUnitcell3D, params, { tags: StructureBuilderTags.ModelGenericRepresentation, state: initialState });
+
+        await this.plugin.updateDataState(unitcell, { revertOnError: true });
+        return unitcell.selector;
+    }
+
+    ensureUnitcell = async () => {
+        for (const model of this.plugin.managers.structure.hierarchy.current.models) {
+            if (!this.getUnitcell(model)) await this.createUnitcell(model)
+        }
+    }
+
+    highlight = (e: React.MouseEvent<HTMLElement>) => {
+        e.preventDefault();
+        // TODO
+        // PluginCommands.Interactivity.Object.Highlight(this.plugin, { state: this.props.group[0].cell.parent, ref: this.props.group.map(c => c.cell.transform.ref) });
+    }
+
+    clearHighlight = (e: React.MouseEvent<HTMLElement>) => {
+        e.preventDefault();
+        // TODO
+        // PluginCommands.Interactivity.ClearHighlights(this.plugin);
+    }
+
+    focus = () => {
+        // TODO
+        // const sphere = this.pivot.cell.obj?.data.boundary.sphere;
+        // if (sphere) this.plugin.managers.camera.focusSphere(sphere);
+    }
+
+    toggleOptions = () => {
+        // TODO
+        this.setState({ showOptions: !this.state.showOptions })
+    }
+
+    renderControls() {
+        const label = 'Unitcell'
+        const isVisible = this.isVisible()
+        return <>
+            <div className='msp-control-row'>
+                <button className='msp-control-button-label' title={`${label}. Click to focus.`} onClick={this.focus} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ textAlign: 'left' }}>
+                    {label}
+                </button>
+                <div className='msp-select-row'>
+                    <IconButton onClick={this.toggleVisible} icon='visual-visibility' toggleState={isVisible} title={`${isVisible ? 'Hide' : 'Show'} component`} disabled={this.state.isBusy} style={{ flex: '0 0 40px' }} />
+                    <IconButton onClick={this.toggleOptions} icon='cog' title='Options' toggleState={this.state.showOptions} disabled={this.state.isBusy} style={{ flex: '0 0 40px' }} />
+                </div>
+            </div>
+            <div className='msp-control-group-header msp-flex-row' style={{ marginTop: '1px' }}>
+                <button className='msp-btn msp-form-control msp-flex-item msp-no-overflow' onClick={this.ensureUnitcell}>
+                    Unitcell
+                </button>
+            </div>
+        </>;
+    }
+}