Browse Source

wip, unitcell ref

Alexander Rose 5 years ago
parent
commit
14b900791f

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

@@ -19,6 +19,8 @@ import { Download, ParsePsf } from '../transforms/data';
 import { CoordinatesFromDcd, CustomModelProperties, CustomStructureProperties, TopologyFromPsf, TrajectoryFromModelAndCoordinates } from '../transforms/model';
 import { DataFormatProvider, guessCifVariant } from './data-format';
 
+// TODO make unitcell creation part of preset
+
 export const MmcifProvider: DataFormatProvider<PluginStateObject.Data.String | PluginStateObject.Data.Binary> = {
     label: 'mmCIF',
     description: 'mmCIF',
@@ -32,9 +34,10 @@ export const MmcifProvider: DataFormatProvider<PluginStateObject.Data.String | P
     },
     getDefaultBuilder: (ctx: PluginContext, data, options) => {
         return Task.create('mmCIF default builder', async taskCtx => {
-            const { structure } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'mmcif' });
+            const { structure, model } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'mmcif' });
             if (options.visuals) {
                 await ctx.builders.structure.representation.applyPreset(structure, 'auto');
+                await ctx.builders.structure.createUnitcell(model, undefined, { isHidden: true })
             }
         })
     }
@@ -50,9 +53,10 @@ export const PdbProvider: DataFormatProvider<any> = {
     },
     getDefaultBuilder: (ctx: PluginContext, data, options) => {
         return Task.create('PDB default builder', async () => {
-            const { structure } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'pdb' });
+            const { structure, model } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'pdb' });
             if (options.visuals) {
                 await ctx.builders.structure.representation.applyPreset(structure, 'auto');
+                await ctx.builders.structure.createUnitcell(model, undefined, { isHidden: true })
             }
         })
     }
@@ -68,9 +72,10 @@ export const GroProvider: DataFormatProvider<any> = {
     },
     getDefaultBuilder: (ctx: PluginContext, data, options) => {
         return Task.create('GRO default builder', async () => {
-            const { structure } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'gro' });
+            const { structure, model } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'gro' });
             if (options.visuals) {
                 await ctx.builders.structure.representation.applyPreset(structure, 'auto');
+                await ctx.builders.structure.createUnitcell(model, undefined, { isHidden: true })
             }
         })
     }
@@ -86,9 +91,10 @@ export const Provider3dg: DataFormatProvider<any> = {
     },
     getDefaultBuilder: (ctx: PluginContext, data, options) => {
         return Task.create('3DG default builder', async () => {
-            const { structure } = await ctx.builders.structure.parseStructure({ data, dataFormat: '3dg' });
+            const { structure, model } = await ctx.builders.structure.parseStructure({ data, dataFormat: '3dg' });
             if (options.visuals) {
                 await ctx.builders.structure.representation.applyPreset(structure, 'auto');
+                await ctx.builders.structure.createUnitcell(model, undefined, { isHidden: true })
             }
         })
     }
@@ -238,8 +244,8 @@ const DownloadStructure = StateAction.build({
             const blob = await plugin.builders.data.downloadBlob({
                 sources: downloadParams.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })),
                 maxConcurrency: 6
-            }, { state: { isGhost: true } });        
-            const { structure } = await plugin.builders.structure.parseStructure({
+            }, { state: { isGhost: true } });
+            const { structure, model } = await plugin.builders.structure.parseStructure({
                 blob,
                 blobParams:  { formats: downloadParams.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' })) },
                 modelProperties: supportProps,
@@ -247,11 +253,12 @@ const DownloadStructure = StateAction.build({
             });
             if (createRepr) {
                 await plugin.builders.structure.representation.applyPreset(structure, 'auto');
+                await plugin.builders.structure.createUnitcell(model, undefined, { isHidden: true })
             }
         } else {
             for (const download of downloadParams) {
                 const data = await plugin.builders.data.download(download, { state: { isGhost: true } });
-                const { structure } = await plugin.builders.structure.parseStructure({
+                const { structure, model } = await plugin.builders.structure.parseStructure({
                     data,
                     dataFormat: format,
                     modelProperties: supportProps,
@@ -259,6 +266,7 @@ const DownloadStructure = StateAction.build({
                 });
                 if (createRepr) {
                     await plugin.builders.structure.representation.applyPreset(structure, 'auto');
+                    await plugin.builders.structure.createUnitcell(model, undefined, { isHidden: true })
                 }
             }
         }

+ 1 - 1
src/mol-plugin-state/builder/data.ts

@@ -26,7 +26,7 @@ export class DataBuilder {
         return data.selector;
     }
 
-    async downloadBlob(params: StateTransformer.Params<DownloadBlob>, options?: Partial<StateTransform.Options>) {        
+    async downloadBlob(params: StateTransformer.Params<DownloadBlob>, options?: Partial<StateTransform.Options>) {
         const data = this.dataState.build().toRoot().apply(DownloadBlob, params, options);
         await this.plugin.updateDataState(data, { revertOnError: true });
         return data.selector;

+ 10 - 0
src/mol-plugin-state/builder/structure.ts

@@ -21,6 +21,7 @@ export type TrajectoryFormat = 'pdb' | 'cif' | 'gro' | '3dg'
 export enum StructureBuilderTags {
     Trajectory = 'trajectory',
     Model = 'model',
+    ModelUnitcell = 'model-unitcell',
     ModelProperties = 'model-properties',
     ModelGenericRepresentation = 'model-generic-representation',
     Structure = 'structure',
@@ -114,6 +115,15 @@ export class StructureBuilder {
         return props.selector;
     }
 
+    async createUnitcell(model: StateObjectRef<SO.Molecule.Model>, params?: StateTransformer.Params<StateTransforms['Representation']['ModelUnitcell3D']>, initialState?: Partial<StateTransform.State>) {
+        const state = this.plugin.state.data;
+        const unitcell = state.build().to(model)
+            .apply(StateTransforms.Representation.ModelUnitcell3D, params, { tags: StructureBuilderTags.ModelUnitcell, state: initialState });
+
+        await this.plugin.updateDataState(unitcell, { revertOnError: true });
+        return unitcell.selector;
+    }
+
     async createStructure(model: StateObjectRef<SO.Molecule.Model>, params?: RootStructureDefinition.Params, initialState?: Partial<StateTransform.State>) {
         const state = this.dataState;
         const structure = state.build().to(model)

+ 14 - 1
src/mol-plugin-state/manager/structure/hierarchy-state.ts

@@ -40,7 +40,7 @@ interface RefBase<K extends string = string, O extends StateObject = StateObject
 
 export type HierarchyRef =
     | TrajectoryRef
-    | ModelRef | ModelPropertiesRef
+    | ModelRef | ModelPropertiesRef | ModelUnitcellRef
     | StructureRef | StructurePropertiesRef | StructureVolumeStreamingRef | StructureComponentRef | StructureRepresentationRef
     | GenericRepresentationRef
 
@@ -57,6 +57,7 @@ export interface ModelRef extends RefBase<'model', SO.Molecule.Model> {
     properties?: ModelPropertiesRef,
     structures: StructureRef[],
     genericRepresentations?: GenericRepresentationRef[],
+    unitcell?: ModelUnitcellRef,
     /** to support decorators */
     childRoot: StateObjectCell<SO.Molecule.Model>
 }
@@ -73,6 +74,14 @@ function ModelPropertiesRef(cell: StateObjectCell<SO.Molecule.Model>, model: Mod
     return { kind: 'model-properties', cell, version: cell.transform.version, model };
 }
 
+export interface ModelUnitcellRef extends RefBase<'model-unitcell', SO.Shape.Representation3D, StateTransforms['Representation']['ModelUnitcell3D']> {
+    model: ModelRef
+}
+
+function ModelUnitcellRef(cell: StateObjectCell<SO.Shape.Representation3D>, model: ModelRef): ModelUnitcellRef {
+    return { kind: 'model-unitcell', cell, version: cell.transform.version, model };
+}
+
 export interface StructureRef extends RefBase<'structure', SO.Molecule.Structure> {
     model?: ModelRef,
     properties?: StructurePropertiesRef,
@@ -200,6 +209,10 @@ 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.ModelUnitcell, (state, cell) => {
+        if (!state.currentModel) return false;
+        state.currentModel.unitcell = createOrUpdateRef(state, cell, ModelUnitcellRef, cell, state.currentModel);
+    }, state => { }],
     [StructureBuilderTags.ModelGenericRepresentation, (state, cell) => {
         if (!state.currentModel) return false;
         if (!state.currentModel.genericRepresentations) state.currentModel.genericRepresentations = []

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

@@ -22,7 +22,6 @@ 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: '' }
@@ -275,7 +274,6 @@ export class DefaultStructureTools extends PluginUIComponent {
             <StructureComponentControls />
             <StructureMeasurementsControls />
             <VolumeStreamingControls />
-            <ObjectControls />
         </>;
     }
 }

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

@@ -1,204 +0,0 @@
-/**
- * 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, PurePluginUIComponent } from '../base';
-import { StateTransformer, StateTransform, StateObjectCell } from '../../mol-state';
-import { ModelRef, GenericRepresentationRef } 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';
-import { UpdateTransformControl } from '../state/update-transform';
-import { PluginStateObject } from '../../mol-plugin-state/objects';
-import { PluginCommands } from '../../mol-plugin/commands';
-
-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 })
-    }
-
-    renderUnitcell(unitcell: GenericRepresentationRef) {
-        const label = 'Unitcell'
-        const isVisible = unitcell.cell.state.isHidden
-
-        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>
-            {this.state.showOptions && unitcell && <>
-                <div className='msp-control-offset'>
-                    <UpdateTransformControl state={unitcell.cell.parent} transform={unitcell.cell.transform} customHeader='none' />
-                </div>
-            </>}
-        </>;
-    }
-
-    renderControls() {
-        const objects: JSX.Element[] = [];
-        for (const model of this.plugin.managers.structure.hierarchy.current.models) {
-            const cell = this.getUnitcell(model)?.cell
-            if (cell && cell.obj) objects.push(<UnitcellEntry key={cell.obj.id} cell={cell} />)
-        }
-
-        return <>
-            <>{objects}</>
-            <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>
-        </>;
-    }
-}
-
-export type UnitcellCell = StateObjectCell<PluginStateObject.Shape.Representation3D, StateTransform<StateTransformer<PluginStateObject.Molecule.Structure.Selections, PluginStateObject.Shape.Representation3D, any>>>
-
-export class UnitcellEntry extends PurePluginUIComponent<{ cell: UnitcellCell }, { showOptions: boolean }> {
-    state = { showOptions: false }
-
-    componentDidMount() {
-        this.subscribe(this.plugin.events.state.cell.stateUpdated, e => {
-            this.forceUpdate();
-        });
-    }
-
-
-    toggleVisibility = (e: React.MouseEvent<HTMLElement>) => {
-        e.preventDefault();
-        PluginCommands.State.ToggleVisibility(this.plugin, { state: this.props.cell.parent, ref: this.props.cell.transform.ref });
-        e.currentTarget.blur();
-    }
-
-    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 = () => this.setState({ showOptions: !this.state.showOptions })
-
-    render() {
-        const { cell } = this.props;
-        const { obj } = cell;
-        if (!obj) return null;
-
-        return <>
-            <div className='msp-control-row'>
-                <button className='msp-control-button-label' title={`Unitcell. Click to focus.`} onClick={this.focus} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ textAlign: 'left' }}>
-                    Unitcell
-                </button>
-                <div className='msp-select-row'>
-                    <IconButton onClick={this.toggleVisibility} icon='visual-visibility' toggleState={!cell.state.isHidden} title={`${cell.state.isHidden ? 'Show' : 'Hide'}`} style={{ flex: '0 0 40px' }} />
-                    <IconButton onClick={this.toggleOptions} icon='cog' title='Options' toggleState={this.state.showOptions} style={{ flex: '0 0 40px' }} />
-                </div>
-            </div>
-            {this.state.showOptions && <>
-                <div className='msp-control-offset'>
-                    <UpdateTransformControl state={cell.parent} transform={cell.transform} customHeader='none' />
-                </div>
-            </>}
-        </>;
-    }
-}

+ 21 - 1
src/mol-plugin-ui/structure/source.tsx

@@ -14,6 +14,7 @@ import { PluginCommands } from '../../mol-plugin/commands';
 import { StateTransforms } from '../../mol-plugin-state/transforms';
 import { memoize1 } from '../../mol-util/memoize';
 import { StructureHierarchyManager } from '../../mol-plugin-state/manager/structure/hierarchy';
+import { UnitcellEntry } from './unitcell';
 
 interface StructureSourceControlState extends CollapsableState {
     isBusy: boolean,
@@ -197,7 +198,7 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
         if (selection.trajectories.some(t => t.models.length > 1)) {
             ret.push(ActionMenu.Item('Load single model', () => this.plugin.managers.structure.hierarchy.createModels(selection.trajectories, 'single')));
         }
-        
+
         // TODO: remove actions?
         return ret;
     }
@@ -245,6 +246,24 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
         return <ParameterControls params={params} values={s.cell.params?.values} onChangeValues={this.updateStructure} isDisabled={this.state.isBusy} />
     }
 
+    get unitcell() {
+        const { selection } = this.plugin.managers.structure.hierarchy;
+        if (selection.structures.length !== 1) return null;
+
+        const model = selection.structures[0].model
+        if (!model) return null
+
+        const unitcell = model.unitcell
+        if (!unitcell) {
+            // this.plugin.builders.structure.createUnitcell(model.cell, undefined, { isHidden: true })
+            return null
+        } else if (!unitcell.cell.obj) {
+            return null;
+        }
+
+        return <UnitcellEntry key={unitcell.cell.obj.id} cell={unitcell.cell} />
+    }
+
     renderControls() {
         const disabled = this.state.isBusy || this.isEmpty;
         const actions = this.actions(this.plugin.managers.structure.hierarchy.selection);
@@ -260,6 +279,7 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
             {this.state.show === 'actions' && <ActionMenu items={actions} onSelect={this.selectAction} />}
             {this.modelIndex}
             {this.structureType}
+            {this.unitcell}
         </>;
     }
 }

+ 74 - 0
src/mol-plugin-ui/structure/unitcell.tsx

@@ -0,0 +1,74 @@
+/**
+ * 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 { PurePluginUIComponent } from '../base';
+import { StateTransformer, StateTransform, StateObjectCell } from '../../mol-state';
+import { IconButton } from '../controls/common';
+import { UpdateTransformControl } from '../state/update-transform';
+import { PluginStateObject } from '../../mol-plugin-state/objects';
+import { PluginCommands } from '../../mol-plugin/commands';
+
+export type UnitcellCell = StateObjectCell<PluginStateObject.Shape.Representation3D, StateTransform<StateTransformer<PluginStateObject.Molecule.Model, PluginStateObject.Shape.Representation3D, any>>>
+
+export class UnitcellEntry extends PurePluginUIComponent<{ cell: UnitcellCell }, { showOptions: boolean }> {
+    state = { showOptions: false }
+
+    componentDidMount() {
+        this.subscribe(this.plugin.events.state.cell.stateUpdated, e => {
+            this.forceUpdate();
+        });
+    }
+
+    toggleVisibility = (e: React.MouseEvent<HTMLElement>) => {
+        e.preventDefault();
+        PluginCommands.State.ToggleVisibility(this.plugin, { state: this.props.cell.parent, ref: this.props.cell.transform.ref });
+        e.currentTarget.blur();
+    }
+
+    highlight = (e: React.MouseEvent<HTMLElement>) => {
+        e.preventDefault();
+        if (!this.props.cell.state.isHidden) {
+            PluginCommands.Interactivity.Object.Highlight(this.plugin, { state: this.props.cell.parent, ref: this.props.cell.transform.ref });
+        }
+    }
+
+    clearHighlight = (e: React.MouseEvent<HTMLElement>) => {
+        e.preventDefault();
+        PluginCommands.Interactivity.ClearHighlights(this.plugin);
+    }
+
+    focus = (e: React.MouseEvent<HTMLElement>) => {
+        e.preventDefault();
+        if (!this.props.cell.state.isHidden) {
+            const loci = this.props.cell.obj?.data.repr.getLoci()
+            if (loci) this.plugin.managers.camera.focusLoci(loci);
+        }
+    }
+
+    toggleOptions = () => this.setState({ showOptions: !this.state.showOptions })
+
+    render() {
+        const { cell } = this.props;
+        const { obj } = cell;
+        if (!obj) return null;
+
+        return <>
+            <div className='msp-btn-row-group' style={{ marginTop: '3px' }}>
+                <button className='msp-form-control msp-control-button-label' title={`Unitcell. Click to focus.`} onClick={this.focus} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ textAlign: 'left' }}>
+                    Unitcell
+                </button>
+                <IconButton customClass='msp-form-control' onClick={this.toggleVisibility} icon='visual-visibility' toggleState={!cell.state.isHidden} title={`${cell.state.isHidden ? 'Show' : 'Hide'}`} small style={{ flex: '0 0 32px' }} />
+                <IconButton customClass='msp-form-control' onClick={this.toggleOptions} icon='dot-3' title='Options' toggleState={this.state.showOptions} style={{ flex: '0 0 32px', padding: '0px' }} />
+            </div>
+            {this.state.showOptions && <>
+                <div className='msp-control-offset'>
+                    <UpdateTransformControl state={cell.parent} transform={cell.transform} customHeader='none' autoHideApply />
+                </div>
+            </>}
+        </>;
+    }
+}