Browse Source

wip plugin

David Sehnal 6 years ago
parent
commit
9c348e0510

+ 18 - 3
src/mol-plugin/context.ts

@@ -10,12 +10,13 @@ import { StateTransforms } from './state/transforms';
 import { PluginStateObjects as SO } from './state/objects';
 import { RxEventHelper } from 'mol-util/rx-event-helper';
 import { PluginState } from './state';
+import { MolScriptBuilder } from 'mol-script/language/builder';
 
 export class PluginContext {
     private disposed = false;
     private ev = RxEventHelper.create();
 
-    readonly state = new PluginState();
+    readonly state = new PluginState(this);
 
     readonly events = {
         stateUpdated: this.ev<undefined>()
@@ -54,11 +55,25 @@ export class PluginContext {
 
     _test_createState(url: string) {
         const b = StateTree.build(this.state.data.tree);
+
+        const query = MolScriptBuilder.struct.generator.atomGroups({
+            // 'atom-test': MolScriptBuilder.core.rel.eq([
+            //     MolScriptBuilder.struct.atomProperty.macromolecular.label_comp_id(),
+            //     MolScriptBuilder.es('C')
+            // ]),
+            'residue-test': MolScriptBuilder.core.rel.eq([
+                MolScriptBuilder.struct.atomProperty.macromolecular.label_comp_id(),
+                'ALA'
+            ])
+        });
+
         const newTree = b.toRoot()
             .apply(StateTransforms.Data.Download, { url })
             .apply(StateTransforms.Data.ParseCif)
-            .apply(StateTransforms.Model.CreateModelsFromMmCif, {}, { ref: 'models' })
+            .apply(StateTransforms.Model.ParseModelsFromMmCif, {}, { ref: 'models' })
             .apply(StateTransforms.Model.CreateStructureFromModel, { modelIndex: 0 }, { ref: 'structure' })
+            .apply(StateTransforms.Model.CreateStructureAssembly)
+            .apply(StateTransforms.Model.CreateStructureSelection, { query, label: 'ALA residues' })
             .apply(StateTransforms.Visuals.CreateStructureRepresentation)
             .getTree();
 
@@ -81,7 +96,7 @@ export class PluginContext {
         this.state.data.context.events.object.updated.subscribe(o => {
             const oo = o.obj;
             if (!SO.StructureRepresentation3D.is(oo)) return;
-            console.log('adding repr', oo.data.repr);
+            console.log('updating repr', oo.data.repr);
             this.canvas3d.add(oo.data.repr);
             this.canvas3d.requestDraw(true);
         });

+ 5 - 1
src/mol-plugin/state.ts

@@ -10,7 +10,7 @@ import { PluginStateObjects as SO } from './state/objects';
 export { PluginState }
 
 class PluginState {
-    readonly data = State.create(new SO.Root({ label: 'Root' }, { }));
+    readonly data: State;
 
     getSnapshot(): PluginState.Snapshot {
         throw 'nyi';
@@ -27,6 +27,10 @@ class PluginState {
     dispose() {
         this.data.dispose();
     }
+
+    constructor(globalContext: unknown) {
+        this.data = State.create(new SO.Root({ label: 'Root' }, { }), { globalContext });
+    }
 }
 
 namespace PluginState {

+ 2 - 2
src/mol-plugin/state/base.ts

@@ -11,11 +11,11 @@ export type TypeClass = 'root' | 'data' | 'prop'
 export namespace PluginStateObject {
     export type TypeClass = 'Root' | 'Group' | 'Data' | 'Object' | 'Representation' | 'Behaviour'
     export interface TypeInfo { name: string, shortName: string, description: string, typeClass: TypeClass }
-    export interface Props { label: string, desctiption?: string }
+    export interface Props { label: string, description?: string }
 
     export const Create = StateObject.factory<TypeInfo, Props>();
 }
 
 export namespace PluginStateTransform {
     export const Create = Transformer.factory('ms-plugin');
-}
+}

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

@@ -9,6 +9,7 @@ import { PluginStateObjects as SO } from '../objects';
 import { Task } from 'mol-task';
 import CIF from 'mol-io/reader/cif'
 import { PluginContext } from 'mol-plugin/context';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
 
 export { Download }
 namespace Download { export interface Params { url: string, isBinary?: boolean, label?: string } }
@@ -20,6 +21,12 @@ const Download = PluginStateTransform.Create<SO.Root, SO.Data.String | SO.Data.B
     },
     from: [SO.Root],
     to: [SO.Data.String, SO.Data.Binary],
+    params: {
+        controls: () => ({
+            url: PD.Text('URL', 'Resource URL. Must be the same domain or support CORS.', ''),
+            isBinary: PD.Boolean('Binary', 'If true, download data as binary (string otherwise)', false)
+        })
+    },
     apply({ params: p }, globalCtx: PluginContext) {
         return Task.create('Download', async ctx => {
             // TODO: track progress

+ 78 - 11
src/mol-plugin/state/transforms/model.ts

@@ -7,19 +7,31 @@
 import { PluginStateTransform } from '../base';
 import { PluginStateObjects as SO } from '../objects';
 import { Task } from 'mol-task';
-import { Model, Format, Structure } from 'mol-model/structure';
+import { Model, Format, Structure, ModelSymmetry, StructureSymmetry, QueryContext, StructureSelection } from 'mol-model/structure';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import Expression from 'mol-script/language/expression';
+import { compile } from 'mol-script/runtime/query/compiler';
 
-export { CreateModelsFromMmCif }
-namespace CreateModelsFromMmCif { export interface Params { blockHeader?: string } }
-const CreateModelsFromMmCif = PluginStateTransform.Create<SO.Data.Cif, SO.Models, CreateModelsFromMmCif.Params>({
-    name: 'create-models-from-mmcif',
+export { ParseModelsFromMmCif }
+namespace ParseModelsFromMmCif { export interface Params { blockHeader?: string } }
+const ParseModelsFromMmCif = PluginStateTransform.Create<SO.Data.Cif, SO.Models, ParseModelsFromMmCif.Params>({
+    name: 'parse-models-from-mmcif',
     display: {
         name: 'Models from mmCIF',
         description: 'Identify and create all separate models in the specified CIF data block'
     },
     from: [SO.Data.Cif],
     to: [SO.Models],
-    params: { default: a => ({ blockHeader: a.data.blocks[0].header }) },
+    params: {
+        default: a => ({ blockHeader: a.data.blocks[0].header }),
+        controls(a) {
+            const { blocks } = a.data;
+            if (blocks.length === 0) return {};
+            return {
+                blockHeader: PD.Select('Header', 'Header of the block to parse', blocks[0].header, blocks.map(b => [b.header, b.header] as [string, string]))
+            };
+        }
+    },
     apply({ a, params }) {
         return Task.create('Parse mmCIF', async ctx => {
             const header = params.blockHeader || a.data.blocks[0].header;
@@ -36,18 +48,73 @@ const CreateModelsFromMmCif = PluginStateTransform.Create<SO.Data.Cif, SO.Models
 export { CreateStructureFromModel }
 namespace CreateStructureFromModel { export interface Params { modelIndex: number } }
 const CreateStructureFromModel = PluginStateTransform.Create<SO.Models, SO.Structure, CreateStructureFromModel.Params>({
-    name: 'structure-from-model',
+    name: 'create-structure-from-model',
     display: {
         name: 'Structure from Model',
         description: 'Create a molecular structure from the specified model.'
     },
     from: [SO.Models],
     to: [SO.Structure],
-    params: { default: () => ({ modelIndex: 0 }) },
+    params: {
+        default: () => ({ modelIndex: 0 }),
+        controls: a => ({ modelIndex: PD.Range('Model Index', 'Model Index', 0, 0, Math.max(0, a.data.length - 1), 1) })
+    },
     apply({ a, params }) {
         if (params.modelIndex < 0 || params.modelIndex >= a.data.length) throw new Error(`Invalid modelIndex ${params.modelIndex}`);
-        // TODO: make Structure.ofModel async?
         const s = Structure.ofModel(a.data[params.modelIndex]);
-        return new SO.Structure({ label: `${a.data[params.modelIndex].label} (model ${s.models[0].modelNum})`, desctiption: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` }, s);
+        return new SO.Structure({ label: `Model ${s.models[0].modelNum}`, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` }, s);
     }
-});
+});
+
+
+export { CreateStructureAssembly }
+namespace CreateStructureAssembly { export interface Params { /** if not specified, use the 1st */ id?: string } }
+const CreateStructureAssembly = PluginStateTransform.Create<SO.Structure, SO.Structure, CreateStructureAssembly.Params>({
+    name: 'create-structure-assembly',
+    display: {
+        name: 'Structure Assembly',
+        description: 'Create a molecular structure assembly.'
+    },
+    from: [SO.Structure],
+    to: [SO.Structure],
+    params: {
+        default: () => ({ id: void 0 }),
+        controls(a) {
+            const { model } = a.data;
+            const ids = model.symmetry.assemblies.map(a => [a.id, a.id] as [string, string]);
+            return { id: PD.Select('Asm Id', 'Assembly Id', ids.length ? ids[0][0] : '', ids) };
+        }
+    },
+    isApplicable: a => a.data.models.length === 1 && a.data.model.symmetry.assemblies.length > 0,
+    apply({ a, params }) {
+        return Task.create('Build Assembly', async ctx => {
+            let id = params.id;
+            const model = a.data.model;
+            if (!id && model.symmetry.assemblies.length) id = model.symmetry.assemblies[0].id;
+            const asm = ModelSymmetry.findAssembly(a.data.model, id || '');
+            if (!asm) throw new Error(`Assembly '${id}' not found`);
+
+            const s = await StructureSymmetry.buildAssembly(a.data, id!).runInContext(ctx);
+            return new SO.Structure({ label: `Assembly ${id}`, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` }, s);
+        })
+    }
+});
+
+export { CreateStructureSelection }
+namespace CreateStructureSelection { export interface Params { query: Expression, label?: string } }
+const CreateStructureSelection = PluginStateTransform.Create<SO.Structure, SO.Structure, CreateStructureSelection.Params>({
+    name: 'create-structure-selection',
+    display: {
+        name: 'Structure Selection',
+        description: 'Create a molecular structure from the specified model.'
+    },
+    from: [SO.Structure],
+    to: [SO.Structure],
+    apply({ a, params }) {
+        // TODO: use cache, add "update"
+        const compiled = compile<StructureSelection>(params.query);
+        const result = compiled(new QueryContext(a.data));
+        const s = StructureSelection.unionStructure(result);
+        return new SO.Structure({ label: `${params.label || 'Selection'}`, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` }, s);
+    }
+});

+ 4 - 3
src/mol-plugin/state/transforms/visuals.ts

@@ -8,7 +8,8 @@ import { Transformer } from 'mol-state';
 import { Task } from 'mol-task';
 import { PluginStateTransform } from '../base';
 import { PluginStateObjects as SO } from '../objects';
-import { CartoonRepresentation, DefaultCartoonProps } from 'mol-repr/structure/representation/cartoon';
+//import { CartoonRepresentation, DefaultCartoonProps } from 'mol-repr/structure/representation/cartoon';
+import { BallAndStickRepresentation, DefaultBallAndStickProps } from 'mol-repr/structure/representation/ball-and-stick';
 
 export { CreateStructureRepresentation }
 namespace CreateStructureRepresentation { export interface Params { } }
@@ -18,8 +19,8 @@ const CreateStructureRepresentation = PluginStateTransform.Create<SO.Structure,
     to: [SO.StructureRepresentation3D],
     apply({ a, params }) {
         return Task.create('Structure Representation', async ctx => {
-            const repr = CartoonRepresentation();
-            await repr.createOrUpdate({ /* TODO add `webgl: WebGLContext` */ }, { ...DefaultCartoonProps }, a.data).runInContext(ctx);
+            const repr = BallAndStickRepresentation(); // CartoonRepresentation();
+            await repr.createOrUpdate({ /* TODO add `webgl: WebGLContext` */ }, DefaultBallAndStickProps, a.data).runInContext(ctx);
             return new SO.StructureRepresentation3D({ label: 'Cartoon' }, { repr });
         });
     },

+ 8 - 1
src/mol-plugin/ui/tree.tsx

@@ -7,6 +7,7 @@
 import * as React from 'react';
 import { PluginContext } from '../context';
 import { PluginStateObject } from 'mol-plugin/state/base';
+import { StateObject } from 'mol-state'
 
 export class Tree extends React.Component<{ plugin: PluginContext }, { }> {
 
@@ -25,8 +26,14 @@ export class TreeNode extends React.Component<{ plugin: PluginContext, nodeRef:
     render() {
         const n = this.props.plugin.state.data.tree.nodes.get(this.props.nodeRef)!;
         const obj = this.props.plugin.state.data.objects.get(this.props.nodeRef)!;
+        if (!obj.obj) {
+            return <div style={{ borderLeft: '1px solid black', paddingLeft: '5px' }}>
+                {StateObject.StateType[obj.state]} {obj.errorText}
+            </div>;
+        }
+        const props = obj.obj!.props as PluginStateObject.Props;
         return <div style={{ borderLeft: '1px solid black', paddingLeft: '5px' }}>
-            {(obj.obj!.props as PluginStateObject.Props).label}
+            {props.label} {props.description ? <small>{props.description}</small> : void 0}
             {n.children.size === 0
                 ? void 0
                 : <div style={{ marginLeft: '10px' }}>{n.children.map(c => <TreeNode plugin={this.props.plugin} nodeRef={c!} key={c} />)}</div>

+ 2 - 2
src/mol-state/state.ts

@@ -54,7 +54,7 @@ class State {
         });
     }
 
-    constructor(rootObject: StateObject, params?: { globalContext?: unknown, defaultObjectProps: unknown }) {
+    constructor(rootObject: StateObject, params?: { globalContext?: unknown, defaultObjectProps?: unknown }) {
         const tree = this._tree;
         const root = tree.getValue(tree.rootRef)!;
         const defaultObjectProps = (params && params.defaultObjectProps) || { }
@@ -82,7 +82,7 @@ namespace State {
         readonly props: { [key: string]: unknown }
     }
 
-    export function create(rootObject: StateObject, params?: { globalContext?: unknown, defaultObjectProps: unknown }) {
+    export function create(rootObject: StateObject, params?: { globalContext?: unknown, defaultObjectProps?: unknown }) {
         return new State(rootObject, params);
     }
 }

+ 1 - 1
src/mol-state/transformer.ts

@@ -20,7 +20,7 @@ export namespace Transformer {
     export type Id = string & { '@type': 'transformer-id' }
     export type Params<T extends Transformer<any, any, any>> = T extends Transformer<any, any, infer P> ? P : unknown;
     export type To<T extends Transformer<any, any, any>> = T extends Transformer<any, infer B, any> ? B : unknown;
-    export type ControlsFor<A extends StateObject, Props> = { [P in keyof Props]?: ((a: A, globalCtx: unknown) => PD.Any) }
+    export type ControlsFor<A extends StateObject, Props> = { [P in keyof Props]?: PD.Any }
 
     export interface ApplyParams<A extends StateObject = StateObject, P = unknown> {
         a: A,