Bladeren bron

mol-plugin: open structure from file

David Sehnal 6 jaren geleden
bovenliggende
commit
bc5b221946
3 gewijzigde bestanden met toevoegingen van 76 en 21 verwijderingen
  1. 2 1
      src/mol-plugin/index.ts
  2. 40 20
      src/mol-plugin/state/actions/basic.ts
  3. 34 0
      src/mol-plugin/state/transforms/data.ts

+ 2 - 1
src/mol-plugin/index.ts

@@ -10,7 +10,7 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { PluginCommands } from './command';
 import { PluginSpec } from './spec';
-import { DownloadAtomicStructure, CreateComplexRepresentation } from './state/actions/basic';
+import { DownloadAtomicStructure, CreateComplexRepresentation, OpenAtomicStructure } from './state/actions/basic';
 import { StateTransforms } from './state/transforms';
 import { PluginBehaviors } from './behavior';
 import { LogEntry } from 'mol-util/log-entry';
@@ -23,6 +23,7 @@ function getParam(name: string, regex: string): string {
 const DefaultSpec: PluginSpec = {
     actions: [
         PluginSpec.Action(DownloadAtomicStructure),
+        PluginSpec.Action(OpenAtomicStructure),
         PluginSpec.Action(CreateComplexRepresentation),
         PluginSpec.Action(StateTransforms.Data.Download),
         PluginSpec.Action(StateTransforms.Data.ParseCif),

+ 40 - 20
src/mol-plugin/state/actions/basic.ts

@@ -12,10 +12,14 @@ import { StateSelection } from 'mol-state/state/selection';
 import { CartoonParams } from 'mol-repr/structure/representation/cartoon';
 import { BallAndStickParams } from 'mol-repr/structure/representation/ball-and-stick';
 import { Download } from '../transforms/data';
+import { StateTree } from 'mol-state';
+import { StateTreeBuilder } from 'mol-state/tree/builder';
+
+// TODO: "structure parser provider"
 
 export { DownloadAtomicStructure }
 namespace DownloadAtomicStructure {
-    export type Sources = 'pdbe-updated' | 'rcsb' | 'bcif-static' | 'url' | 'file'
+    export type Sources = 'pdbe-updated' | 'rcsb' | 'bcif-static' | 'url'
     export type Source = ObtainStructureHelpers.MapParams<Sources, typeof ObtainStructureHelpers.ControlMap>
     export interface Params {
         source: Source
@@ -26,15 +30,13 @@ namespace ObtainStructureHelpers {
         ['pdbe-updated', 'PDBe Updated'],
         ['rcsb', 'RCSB'],
         ['bcif-static', 'BinaryCIF (static PDBe Updated)'],
-        ['url', 'URL'],
-        ['file', 'File']
+        ['url', 'URL']
     ];
     export const ControlMap = {
         'pdbe-updated': PD.Text('1cbs', { label: 'Id' }),
         'rcsb': PD.Text('1tqn', { label: 'Id' }),
         'bcif-static': PD.Text('1tqn', { label: 'Id' }),
-        'url': PD.Group({ url: PD.Text(''), isBinary: PD.Boolean(false) }, { isExpanded: true }),
-        'file': PD.File({ accept: '.cif,.bcif' })
+        'url': PD.Group({ url: PD.Text(''), isBinary: PD.Boolean(false) }, { isExpanded: true })
     }
     export function getControls(key: string) { return (ControlMap as any)[key]; }
 
@@ -46,7 +48,7 @@ namespace ObtainStructureHelpers {
             case 'pdbe-updated': return { url: `https://www.ebi.ac.uk/pdbe/static/entry/${src.params.toLowerCase()}_updated.cif`, isBinary: false, label: `PDBe: ${src.params}` };
             case 'rcsb': return { url: `https://files.rcsb.org/download/${src.params.toUpperCase()}.cif`, isBinary: false, label: `RCSB: ${src.params}` };
             case 'bcif-static': return { url: `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${src.params.toLowerCase()}`, isBinary: true, label: `BinaryCIF: ${src.params}` };
-            default: throw new Error(`${src.name} not supported.`);
+            default: throw new Error(`${(src as any).name} not supported.`);
         }
     }
 }
@@ -73,24 +75,42 @@ const DownloadAtomicStructure = StateAction.create<PluginStateObject.Root, void,
 
         const url = ObtainStructureHelpers.getUrl(params.source);
 
-        const root = b.toRoot()
-            .apply(StateTransforms.Data.Download, url)
-            .apply(StateTransforms.Data.ParseCif)
-            .apply(StateTransforms.Model.TrajectoryFromMmCif, {})
-            .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
-            .apply(StateTransforms.Model.StructureAssemblyFromModel);
-
-        root.apply(StateTransforms.Model.StructureComplexElement, { type: 'sequence' })
-            .apply(StateTransforms.Representation.StructureRepresentation3D, { type: { name: 'cartoon', params: PD.getDefaultValues(CartoonParams) } });
-        root.apply(StateTransforms.Model.StructureComplexElement, { type: 'ligands' })
-            .apply(StateTransforms.Representation.StructureRepresentation3D, { type: { name: 'ball-and-stick', params: PD.getDefaultValues(BallAndStickParams) } });
-        root.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
-            .apply(StateTransforms.Representation.StructureRepresentation3D, { type: { name: 'ball-and-stick', params: { ...PD.getDefaultValues(BallAndStickParams), alpha: 0.51 } } });
+        const data = b.toRoot().apply(StateTransforms.Data.Download, url);
+        return state.update(atomicStructureTree(data));
+    }
+});
 
-        return state.update(root.getTree());
+export const OpenAtomicStructure = StateAction.create<PluginStateObject.Root, void, { file: File }>({
+    from: [PluginStateObject.Root],
+    display: {
+        name: 'Open Structure',
+        description: 'Load a structure from file and create its default Assembly and visual'
+    },
+    params: () => ({ file: PD.File({ accept: '.cif,.bcif' }) }),
+    apply({ params, state }) {
+        const b = state.build();
+        const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) });
+        return state.update(atomicStructureTree(data));
     }
 });
 
+function atomicStructureTree(b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>): StateTree {
+    const root = b
+        .apply(StateTransforms.Data.ParseCif)
+        .apply(StateTransforms.Model.TrajectoryFromMmCif, {})
+        .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
+        .apply(StateTransforms.Model.StructureAssemblyFromModel);
+
+    root.apply(StateTransforms.Model.StructureComplexElement, { type: 'sequence' })
+        .apply(StateTransforms.Representation.StructureRepresentation3D, { type: { name: 'cartoon', params: PD.getDefaultValues(CartoonParams) } });
+    root.apply(StateTransforms.Model.StructureComplexElement, { type: 'ligands' })
+        .apply(StateTransforms.Representation.StructureRepresentation3D, { type: { name: 'ball-and-stick', params: PD.getDefaultValues(BallAndStickParams) } });
+    root.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
+        .apply(StateTransforms.Representation.StructureRepresentation3D, { type: { name: 'ball-and-stick', params: { ...PD.getDefaultValues(BallAndStickParams), alpha: 0.51 } } });
+
+    return root.getTree();
+}
+
 export const CreateComplexRepresentation = StateAction.create<PluginStateObject.Molecule.Structure, void, {}>({
     from: [PluginStateObject.Molecule.Structure],
     display: {

+ 34 - 0
src/mol-plugin/state/transforms/data.ts

@@ -11,6 +11,7 @@ import CIF from 'mol-io/reader/cif'
 import { PluginContext } from 'mol-plugin/context';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Transformer } from 'mol-state';
+import { readFromFile } from 'mol-util/data-source';
 
 export { Download }
 namespace Download { export interface Params { url: string, isBinary?: boolean, label?: string } }
@@ -45,6 +46,39 @@ const Download = PluginStateTransform.Create<SO.Root, SO.Data.String | SO.Data.B
     }
 });
 
+export { ReadFile }
+namespace ReadFile { export interface Params { file: File, isBinary?: boolean, label?: string } }
+const ReadFile = PluginStateTransform.Create<SO.Root, SO.Data.String | SO.Data.Binary, ReadFile.Params>({
+    name: 'read-file',
+    display: {
+        name: 'Read File',
+        description: 'Read string or binary data from the specified file'
+    },
+    from: [SO.Root],
+    to: [SO.Data.String, SO.Data.Binary],
+    params: () => ({
+        file: PD.File(),
+        label: PD.Text('', { isOptional: true }),
+        isBinary: PD.Boolean(false, { description: 'If true, open file as as binary (string otherwise)', isOptional: true })
+    }),
+    apply({ params: p }, globalCtx: PluginContext) {
+        return Task.create('Open File', async ctx => {
+            const data = await readFromFile(p.file, p.isBinary ? 'binary' : 'string').runInContext(ctx);
+            return p.isBinary
+                ? new SO.Data.Binary(data as Uint8Array, { label: p.label ? p.label : p.file.name })
+                : new SO.Data.String(data as string, { label: p.label ? p.label : p.file.name });
+        });
+    },
+    update({ oldParams, newParams, b }) {
+        if (oldParams.label !== newParams.label) {
+            (b.label as string) = newParams.label || oldParams.file.name;
+            return Transformer.UpdateResult.Updated;
+        }
+        return Transformer.UpdateResult.Unchanged;
+    },
+    isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user loaded files.' })
+});
+
 export { ParseCif }
 namespace ParseCif { export interface Params { } }
 const ParseCif = PluginStateTransform.Create<SO.Data.String | SO.Data.Binary, SO.Data.Cif, ParseCif.Params>({