ソースを参照

mol-plugin: refactor StructureFromModel

David Sehnal 5 年 前
コミット
65ed5c98bf

+ 1 - 1
src/apps/basic-wrapper/helpers.ts

@@ -29,7 +29,7 @@ export namespace StateHelper {
     }
 
     export function structure(b: StateBuilder.To<PSO.Molecule.Model>) {
-        return b.apply(StateTransforms.Model.StructureFromModel, { tags: 'structure' })
+        return b.apply(StateTransforms.Model.StructureFromModel, void 0, { tags: 'structure' })
     };
 
     export function selectChain(b: StateBuilder.To<PSO.Molecule.Structure>, auth_asym_id: string) {

+ 1 - 1
src/apps/viewer/extensions/jolecule.ts

@@ -59,7 +59,7 @@ function createTemplate(plugin: PluginContext, state: State, id: string) {
     const b = new StateBuilder.Root(state.tree);
     const data = b.toRoot().apply(StateTransforms.Data.Download, { url: `https://www.ebi.ac.uk/pdbe/static/entry/${id}_updated.cif` }, { state: { isGhost: true }});
     const model = createModelTree(data, 'cif');
-    const structure = model.apply(StateTransforms.Model.StructureFromModel, {});
+    const structure = model.apply(StateTransforms.Model.StructureFromModel);
     complexRepresentation(plugin, structure, { hideWater: true });
     return { tree: b.getTree(), structure: structure.ref };
 }

+ 0 - 3
src/mol-plugin/index.ts

@@ -40,9 +40,6 @@ export const DefaultPluginSpec: PluginSpec = {
 
         PluginSpec.Action(StateTransforms.Model.TrajectoryFromMmCif),
         PluginSpec.Action(StateTransforms.Model.TrajectoryFromPDB),
-        PluginSpec.Action(StateTransforms.Model.StructureAssemblyFromModel),
-        PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel),
-        PluginSpec.Action(StateTransforms.Model.StructureSymmetryMatesFromModel),
         PluginSpec.Action(TransformStructureConformation),
         PluginSpec.Action(StateTransforms.Model.StructureFromModel),
         PluginSpec.Action(StateTransforms.Model.StructureFromTrajectory),

+ 126 - 0
src/mol-plugin/state/representation/model.ts

@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Model, Structure, StructureSymmetry } from '../../../mol-model/structure';
+import { stringToWords } from '../../../mol-util/string';
+import { SpacegroupCell } from '../../../mol-math/geometry';
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+import { Vec3 } from '../../../mol-math/linear-algebra';
+import { ensureSecondaryStructure } from '../transforms/helpers';
+import { RuntimeContext } from '../../../mol-task';
+import { PluginContext } from '../../context';
+import { Assembly, ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry';
+import { PluginStateObject as SO } from '../objects';
+
+export namespace ModelStructureRepresentation {
+    export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates') {
+        const assemblyIds = model ? model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]) : [];
+        const showSymm = !model ? true : !SpacegroupCell.isZero(model.symmetry.spacegroup.cell);
+
+        const modes = {
+            deposited: PD.EmptyGroup(),
+            assembly: PD.Group({
+                id: PD.Optional(model
+                    ? PD.Select(assemblyIds[0][0], assemblyIds, { label: 'Asm Id', description: 'Assembly Id' })
+                    : PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' }))
+            }, { isFlat: true }),
+            'symmetry-mates': PD.Group({
+                radius: PD.Numeric(5)
+            }, { isFlat: true }),
+            'symmetry': PD.Group({
+                ijkMin: PD.Vec3(Vec3.create(-1, -1, -1), { label: 'Min IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }),
+                ijkMax: PD.Vec3(Vec3.create(1, 1, 1), { label: 'Max IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } })
+            }, { isFlat: true })
+        };
+
+        const options: [keyof typeof modes, string][] = [
+            ['deposited', 'Deposited']
+        ];
+
+        if (assemblyIds) {
+            options.push(['assembly', 'Assembly']);
+        }
+
+        if (showSymm) {
+            options.push(['symmetry-mates', 'Symmetry Mates']);
+            options.push(['symmetry', 'Symmetry (indices)']);
+        }
+
+        return {
+            kind: PD.MappedStatic(defaultValue || 'deposited', modes, { options })
+        };
+    }
+
+    export type Params = PD.Values<ReturnType<typeof getParams>>
+
+    async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string) {
+        let asm: Assembly | undefined = void 0;
+
+        // if no id is specified, use the 1st assembly.
+        if (!id && model.symmetry.assemblies.length !== 0) {
+            id = model.symmetry.assemblies[0].id;
+        }
+
+        if (model.symmetry.assemblies.length === 0) {
+            if (id !== 'deposited') {
+                plugin.log.warn(`Model '${model.entryId}' has no assembly, returning deposited structure.`);
+            }
+        } else {
+            asm = ModelSymmetry.findAssembly(model, id || '');
+            if (!asm) {
+                plugin.log.warn(`Model '${model.entryId}' has no assembly called '${id}', returning deposited structure.`);
+            }
+        }
+
+        const base = Structure.ofModel(model);
+        if (!asm) {
+            await ensureSecondaryStructure(base)
+            const label = { label: 'Deposited', description: Structure.elementDescription(base) };
+            return new SO.Molecule.Structure(base, label);
+        }
+
+        id = asm.id;
+        const s = await StructureSymmetry.buildAssembly(base, id!).runInContext(ctx);
+        await ensureSecondaryStructure(s)
+        const props = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
+        return new SO.Molecule.Structure(s, props);
+    }
+
+    async function buildSymmetry(ctx: RuntimeContext, model: Model, ijkMin: Vec3, ijkMax: Vec3) {
+        const base = Structure.ofModel(model);
+        const s = await StructureSymmetry.buildSymmetryRange(base, ijkMin, ijkMax).runInContext(ctx);
+        await ensureSecondaryStructure(s)
+        const props = { label: `Symmetry [${ijkMin}] to [${ijkMax}]`, description: Structure.elementDescription(s) };
+        return new SO.Molecule.Structure(s, props);
+    }
+
+    async function buildSymmetryMates(ctx: RuntimeContext, model: Model, radius: number) {
+        const base = Structure.ofModel(model);
+        const s = await StructureSymmetry.builderSymmetryMates(base, radius).runInContext(ctx);
+        await ensureSecondaryStructure(s)
+        const props = { label: `Symmetry Mates`, description: Structure.elementDescription(s) };
+        return new SO.Molecule.Structure(s, props);
+    }
+
+    export async function create(plugin: PluginContext, ctx: RuntimeContext, model: Model, params?: Params): Promise<SO.Molecule.Structure> {
+        if (!params || !params.kind || params.kind.name === 'deposited') {
+            const s = Structure.ofModel(model);
+            await ensureSecondaryStructure(s);
+            return new SO.Molecule.Structure(s, { label: 'Deposited', description: Structure.elementDescription(s) });
+        }
+        if (params.kind.name === 'assembly') {
+            return buildAssembly(plugin, ctx, model, params.kind.params.id)
+        }
+        if (params.kind.name === 'symmetry') {
+            return buildSymmetry(ctx, model, params.kind.params.ijkMin, params.kind.params.ijkMax)
+        }
+        if (params.kind.name === 'symmetry-mates') {
+            return buildSymmetryMates(ctx, model, params.kind.params.radius)
+        }
+
+        throw new Error(`Unknown represetation type: ${(params.kind as any).name}`);
+    }
+}

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

@@ -23,12 +23,12 @@ import { trajectoryFromGRO } from '../../../mol-model-formats/structure/gro';
 import { parseGRO } from '../../../mol-io/reader/gro/parser';
 import { shapeFromPly } from '../../../mol-model-formats/shape/ply';
 import { SymmetryOperator } from '../../../mol-math/geometry';
-import { ensureSecondaryStructure } from './helpers';
 import { Script } from '../../../mol-script/script';
 import { parse3DG } from '../../../mol-io/reader/3dg/parser';
 import { trajectoryFrom3DG } from '../../../mol-model-formats/structure/3dg';
 import { StructureSelectionQueries } from '../../util/structure-selection-helper';
 import { StructureQueryHelper } from '../../util/structure-query';
+import { ModelStructureRepresentation } from '../representation/model';
 
 export { TrajectoryFromBlob };
 export { TrajectoryFromMmCif };
@@ -39,8 +39,6 @@ export { ModelFromTrajectory };
 export { StructureFromTrajectory };
 export { StructureFromModel };
 export { StructureAssemblyFromModel };
-export { StructureSymmetryFromModel };
-export { StructureSymmetryMatesFromModel };
 export { TransformStructureConformation };
 export { TransformStructureConformationByMatrix };
 export { StructureSelectionFromExpression };
@@ -204,20 +202,19 @@ const StructureFromTrajectory = PluginStateTransform.BuiltIn({
 type StructureFromModel = typeof StructureFromModel
 const StructureFromModel = PluginStateTransform.BuiltIn({
     name: 'structure-from-model',
-    display: { name: 'Structure from Model', description: 'Create a molecular structure from the specified model.' },
+    display: { name: 'Structure', description: 'Create a molecular structure (deposited, assembly, or symmetry) from the specified model.' },
     from: SO.Molecule.Model,
-    to: SO.Molecule.Structure
+    to: SO.Molecule.Structure,
+    params(a) { return ModelStructureRepresentation.getParams(a && a.data); }
 })({
-    apply({ a }) {
+    apply({ a, params }, plugin: PluginContext) {
         return Task.create('Build Structure', async ctx => {
-            const s = Structure.ofModel(a.data);
-            await ensureSecondaryStructure(s)
-            const props = { label: 'Deposited', description: Structure.elementDescription(s) };
-            return new SO.Molecule.Structure(s, props);
+            return ModelStructureRepresentation.create(plugin, ctx, a.data, params);
         })
     }
 });
 
+// TODO: deprecate this in favor of StructureFromModel
 type StructureAssemblyFromModel = typeof StructureAssemblyFromModel
 const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({
     name: 'structure-assembly-from-model',
@@ -232,94 +229,13 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({
         const ids = model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]);
         ids.push(['deposited', 'Deposited']);
         return {
-            id: PD.Optional(PD.Select(ids[0][0], ids, { label: 'Asm Id', description: 'Assembly Id' })) };
+            id: PD.Optional(PD.Select(ids[0][0], ids, { label: 'Asm Id', description: 'Assembly Id' }))
+        };
     }
 })({
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Build Assembly', async ctx => {
-            const model = a.data;
-            let id = params.id;
-            let asm: Assembly | undefined = void 0;
-
-            // if no id is specified, use the 1st assembly.
-            if (!id && model.symmetry.assemblies.length !== 0) {
-                id = model.symmetry.assemblies[0].id;
-            }
-
-            if (model.symmetry.assemblies.length === 0) {
-                if (id !== 'deposited') {
-                    plugin.log.warn(`Model '${a.data.entryId}' has no assembly, returning deposited structure.`);
-                }
-            } else {
-                asm = ModelSymmetry.findAssembly(model, id || '');
-                if (!asm) {
-                    plugin.log.warn(`Model '${a.data.entryId}' has no assembly called '${id}', returning deposited structure.`);
-                }
-            }
-
-            const base = Structure.ofModel(model);
-            if (!asm) {
-                await ensureSecondaryStructure(base)
-                const label = { label: 'Deposited', description: Structure.elementDescription(base) };
-                return new SO.Molecule.Structure(base, label);
-            }
-
-            id = asm.id;
-            const s = await StructureSymmetry.buildAssembly(base, id!).runInContext(ctx);
-            await ensureSecondaryStructure(s)
-            const props = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
-            return new SO.Molecule.Structure(s, props);
-        })
-    }
-});
-
-type StructureSymmetryFromModel = typeof StructureSymmetryFromModel
-const StructureSymmetryFromModel = PluginStateTransform.BuiltIn({
-    name: 'structure-symmetry-from-model',
-    display: { name: 'Structure Symmetry', description: 'Create a molecular structure symmetry.' },
-    from: SO.Molecule.Model,
-    to: SO.Molecule.Structure,
-    params(a) {
-        return {
-            ijkMin: PD.Vec3(Vec3.create(-1, -1, -1), { label: 'Min IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }),
-            ijkMax: PD.Vec3(Vec3.create(1, 1, 1), { label: 'Max IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } })
-        }
-    }
-})({
-    apply({ a, params }, plugin: PluginContext) {
-        return Task.create('Build Symmetry', async ctx => {
-            const { ijkMin, ijkMax } = params
-            const model = a.data;
-            const base = Structure.ofModel(model);
-            const s = await StructureSymmetry.buildSymmetryRange(base, ijkMin, ijkMax).runInContext(ctx);
-            await ensureSecondaryStructure(s)
-            const props = { label: `Symmetry [${ijkMin}] to [${ijkMax}]`, description: Structure.elementDescription(s) };
-            return new SO.Molecule.Structure(s, props);
-        })
-    }
-});
-
-type StructureSymmetryMatesFromModel = typeof StructureSymmetryMatesFromModel
-const StructureSymmetryMatesFromModel = PluginStateTransform.BuiltIn({
-    name: 'structure-symmetry-mates-from-model',
-    display: { name: 'Structure Symmetry Mates', description: 'Create molecular structure symmetry mates.' },
-    from: SO.Molecule.Model,
-    to: SO.Molecule.Structure,
-    params(a) {
-        return {
-            radius: PD.Numeric(5),
-        }
-    }
-})({
-    apply({ a, params }, plugin: PluginContext) {
-        return Task.create('Build Symmetry Mates', async ctx => {
-            const { radius } = params
-            const model = a.data;
-            const base = Structure.ofModel(model);
-            const s = await StructureSymmetry.builderSymmetryMates(base, radius).runInContext(ctx);
-            await ensureSecondaryStructure(s)
-            const props = { label: `Symmetry Mates`, description: Structure.elementDescription(s) };
-            return new SO.Molecule.Structure(s, props);
+            return ModelStructureRepresentation.create(plugin, ctx, a.data, { kind: { name: 'assembly', params } });
         })
     }
 });
@@ -703,7 +619,7 @@ const StructureComplexElement = PluginStateTransform.BuiltIn({
 type CustomModelProperties = typeof CustomModelProperties
 const CustomModelProperties = PluginStateTransform.BuiltIn({
     name: 'custom-model-properties',
-    display: { name: 'Custom Model Properties' },
+    display: { name: 'Custom Properties' },
     from: SO.Molecule.Model,
     to: SO.Molecule.Model,
     params: (a, ctx: PluginContext) => {

+ 6 - 2
src/mol-util/param-definition.ts

@@ -179,6 +179,9 @@ export namespace ParamDefinition {
         if (info && info.isFlat) ret.isFlat = info.isFlat;
         return ret;
     }
+    export function EmptyGroup(info?: Info) {
+        return Group({}, info);
+    }
 
     export interface NamedParams<T = any, K = string> { name: K, params: T }
     export type NamedParamUnion<P extends Params, K extends keyof P = keyof P> = K extends any ? NamedParams<P[K]['defaultValue'], K> : never
@@ -200,10 +203,11 @@ export namespace ParamDefinition {
         const options: [string, string][] = info && info.options
             ? info.options as [string, string][]
             : Object.keys(map).map(k => [k, k]) as [string, string][];
+        const name = checkDefaultKey(defaultKey, options);
         return setInfo<Mapped<NamedParamUnion<C>>>({
             type: 'mapped',
-            defaultValue: { name: defaultKey, params: map[defaultKey].defaultValue } as any,
-            select: Select<string>(defaultKey as string, options, info),
+            defaultValue: { name, params: map[name].defaultValue } as any,
+            select: Select<string>(name as string, options, info),
             map: key => map[key]
         }, info);
     }