diff --git a/src/apps/basic-wrapper/helpers.ts b/src/apps/basic-wrapper/helpers.ts index 239beae075d79aece63bc65515e3bc844d460d16..aefcb26e4fd6b4f2fdd7fe211407f549ce82e208 100644 --- a/src/apps/basic-wrapper/helpers.ts +++ b/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) { diff --git a/src/apps/viewer/extensions/jolecule.ts b/src/apps/viewer/extensions/jolecule.ts index a546c3056053685d10d9c656ab63ed1f8282ea52..6d0c8d8e33d7129824280c5b73087bd29931da8c 100644 --- a/src/apps/viewer/extensions/jolecule.ts +++ b/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 }; } diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 66d1fcd98af72f4423b8d5e6ba114110f8e9ee98..da137a2130ed48d524880bec18c63270caecaa50 100644 --- a/src/mol-plugin/index.ts +++ b/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), diff --git a/src/mol-plugin/state/representation/model.ts b/src/mol-plugin/state/representation/model.ts new file mode 100644 index 0000000000000000000000000000000000000000..6edb77f75cef0351d42ec220eca4d0e2d5cf4fa0 --- /dev/null +++ b/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}`); + } +} \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index 1c0019136fbb04f50cff8aa530abf2ce4ed14a7d..940d2b1a2ca6acefcb076574eb5742a05f3fb716 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/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) => { diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index 790aff91678ff360e753846b0f5c272c95f770e4..ba6ee46b5b2f173c87d605c551025460e447c396 100644 --- a/src/mol-util/param-definition.ts +++ b/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); }