diff --git a/src/apps/viewer/extensions/jolecule.ts b/src/apps/viewer/extensions/jolecule.ts index 6d0c8d8e33d7129824280c5b73087bd29931da8c..c92a5ed47a705ee7ebe818b27eb2150997561f8e 100644 --- a/src/apps/viewer/extensions/jolecule.ts +++ b/src/apps/viewer/extensions/jolecule.ts @@ -6,7 +6,7 @@ import { StateTree, StateBuilder, StateAction, State } from '../../../mol-state'; import { StateTransforms } from '../../../mol-plugin/state/transforms'; -import { createModelTree, complexRepresentation } from '../../../mol-plugin/state/actions/structure'; +import { createModelTree } from '../../../mol-plugin/state/actions/structure'; import { PluginContext } from '../../../mol-plugin/context'; import { PluginStateObject } from '../../../mol-plugin/state/objects'; import { ParamDefinition } from '../../../mol-util/param-definition'; @@ -19,6 +19,7 @@ import { UUID } from '../../../mol-util'; import { ColorNames } from '../../../mol-util/color/names'; import { Camera } from '../../../mol-canvas3d/camera'; import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation'; +import { createDefaultStructureComplex } from '../../../mol-plugin/util/structure-complex-helper'; export const CreateJoleculeState = StateAction.build({ display: { name: 'Jolecule State Import' }, @@ -60,7 +61,7 @@ function createTemplate(plugin: PluginContext, state: State, id: string) { 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); - complexRepresentation(plugin, structure, { hideWater: true }); + createDefaultStructureComplex(plugin, structure); return { tree: b.getTree(), structure: structure.ref }; } diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 29b41e63cfa3d8d6bb223b88daba28ae996ee763..e877ab4a27fd17070ef005605a88c3ba02ce9686 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -43,6 +43,7 @@ import { StructureOverpaintHelper } from './util/structure-overpaint-helper'; import { PluginToastManager } from './state/toast'; import { StructureMeasurementManager } from './util/structure-measurement'; import { ViewportScreenshotWrapper } from './util/viewport-screenshot'; +import { StructureRepresentationManager } from './state/representation/structure'; interface Log { entries: List<LogEntry> @@ -111,7 +112,8 @@ export class PluginContext { readonly structureRepresentation = { registry: new StructureRepresentationRegistry(), - themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext + themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext, + manager: void 0 as any as StructureRepresentationManager } as const readonly volumeRepresentation = { @@ -128,12 +130,12 @@ export class PluginContext { readonly customParamEditors = new Map<string, StateTransformParameters.Class>(); readonly helpers = { + measurement: new StructureMeasurementManager(this), structureSelectionManager: new StructureElementSelectionManager(this), structureSelection: new StructureSelectionHelper(this), structureRepresentation: new StructureRepresentationHelper(this), structureOverpaint: new StructureOverpaintHelper(this), substructureParent: new SubstructureParentHelper(this), - measurement: new StructureMeasurementManager(this), viewportScreenshot: void 0 as ViewportScreenshotWrapper | undefined } as const; @@ -271,8 +273,11 @@ export class PluginContext { this.interactivity = new Interactivity(this); this.lociLabels = new LociLabelManager(this); + (this.structureRepresentation.manager as StructureRepresentationManager)= new StructureRepresentationManager(this); + this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`); if (!isProductionMode) this.log.message(`Development mode enabled`); if (isDebugMode) this.log.message(`Debug mode enabled`); + } } \ No newline at end of file diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index da137a2130ed48d524880bec18c63270caecaa50..b25a509b9ee0e7fd79b0140b43f34b313846b1fa 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -24,7 +24,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Action(StateActions.Structure.DownloadStructure), PluginSpec.Action(StateActions.Volume.DownloadDensity), PluginSpec.Action(StateActions.DataFormat.OpenFile), - PluginSpec.Action(StateActions.Structure.CreateComplexRepresentation), + PluginSpec.Action(StateActions.Structure.Create3DRepresentationPreset), PluginSpec.Action(StateActions.Structure.EnableModelCustomProps), PluginSpec.Action(StateActions.Structure.EnableStructureCustomProps), diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index 1697e593b351cd05e19bbd85fc6bd0cee98e298a..bc9990253037523282cc34fe686f3ce284a5ad55 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/src/mol-plugin/state/actions/structure.ts @@ -11,7 +11,6 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { PluginStateObject } from '../objects'; import { StateTransforms } from '../transforms'; import { Download } from '../transforms/data'; -import { StructureRepresentation3DHelpers } from '../transforms/representation'; import { CustomModelProperties, StructureSelectionFromExpression, CustomStructureProperties } from '../transforms/model'; import { DataFormatProvider, guessCifVariant, DataFormatBuilderOptions } from './data-format'; import { FileInfo } from '../../../mol-util/file-info'; @@ -91,7 +90,7 @@ type StructureFormat = 'pdb' | 'cif' | 'gro' | '3dg' // -const DownloadModelRepresentationOptions = ModelStructureRepresentation.getParams(void 0, 'assembly').kind; +const DownloadModelRepresentationOptions = ModelStructureRepresentation.getParams(void 0, 'assembly').type; const DownloadStructurePdbIdSourceOptions = PD.Group({ structure: DownloadModelRepresentationOptions, @@ -257,45 +256,23 @@ function createStructureTree(ctx: PluginContext, b: StateBuilder.To<PluginStateO if (supportProps) { root = root.apply(StateTransforms.Model.CustomModelProperties); } - const structure = root.apply(StateTransforms.Model.StructureFromModel, { kind: params || { name: 'assembly', params: { } } }); + const structure = root.apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } }); createDefaultStructureComplex(ctx, structure); return root; } -export function complexRepresentation( - ctx: PluginContext, root: StateBuilder.To<PluginStateObject.Molecule.Structure>, - params?: { hideSequence?: boolean, hideHET?: boolean, hideWater?: boolean, hideCoarse?: boolean; } -) { - if (!params || !params.hideSequence) { - root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'cartoon')); - } - if (!params || !params.hideHET) { - root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick')); - } - if (!params || !params.hideWater) { - root.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', { alpha: 0.51 })); - } - if (!params || !params.hideCoarse) { - root.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'spacefill', {}, 'polymer-id')); +export const Create3DRepresentationPreset = StateAction.build({ + display: { name: '3D Representation Preset', description: 'Create one of preset 3D representations.' }, + from: PluginStateObject.Molecule.Structure, + isApplicable(a, _, plugin: PluginContext) { return plugin.structureRepresentation.manager.hasProvider(a.data); }, + params(a, plugin: PluginContext) { + return { + type: plugin.structureRepresentation.manager.getOptions(a.data) + }; } -} - -export const CreateComplexRepresentation = StateAction.build({ - display: { name: 'Create Complex', description: 'Split the structure into Sequence/Water/Ligands/... ' }, - from: PluginStateObject.Molecule.Structure -})(({ ref, state }, ctx: PluginContext) => { - const root = state.build().to(ref); - createDefaultStructureComplex(ctx, root); - return state.updateTree(root); +})(({ ref, params }, plugin: PluginContext) => { + plugin.structureRepresentation.manager.apply(ref, params.type.name, params.type.params); }); export const UpdateTrajectory = StateAction.build({ diff --git a/src/mol-plugin/state/representation/model.ts b/src/mol-plugin/state/representation/model.ts index 2f2d0718d75a838267cb36a175b1a64bbfb03b2e..4044135b369c3113960d619cb8f3260eaffda753 100644 --- a/src/mol-plugin/state/representation/model.ts +++ b/src/mol-plugin/state/representation/model.ts @@ -50,11 +50,11 @@ export namespace ModelStructureRepresentation { } return { - kind: PD.MappedStatic(defaultValue || 'deposited', modes, { options }) + type: PD.MappedStatic(defaultValue || 'deposited', modes, { options }) }; } - export type Params = PD.Values<ReturnType<typeof getParams>>['kind'] + export type Params = PD.Values<ReturnType<typeof getParams>>['type'] async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string) { let asm: Assembly | undefined = void 0; diff --git a/src/mol-plugin/state/representation/structure.ts b/src/mol-plugin/state/representation/structure.ts new file mode 100644 index 0000000000000000000000000000000000000000..def4c3406752b06ba4cb93178c40f128a270071e --- /dev/null +++ b/src/mol-plugin/state/representation/structure.ts @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { arrayFind } from '../../../mol-data/util'; +import { Structure } from '../../../mol-model/structure'; +import { StateTransform } from '../../../mol-state'; +import { Task } from '../../../mol-task'; +import { isProductionMode } from '../../../mol-util/debug'; +import { objectForEach } from '../../../mol-util/object'; +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; +import { PluginContext } from '../../context'; +import { PresetStructureReprentations } from './structure/preset'; +import { StructureRepresentationProvider } from './structure/providers'; + +export class StructureRepresentationManager { + private providers: StructureRepresentationProvider[] = []; + + hasProvider(s: Structure) { + for (const p of this.providers) { + if (!p.isApplicable || p.isApplicable(s, this.plugin)) return true; + } + return false; + } + + getOptions(s: Structure) { + const options: [string, string][] = []; + const map: { [K in string]: PD.Any } = Object.create(null); + for (const p of this.providers) { + if (p.isApplicable && !p.isApplicable(s, this.plugin)) continue; + + options.push([p.id, p.display.name]); + map[p.id] = p.params ? PD.Group(p.params(s, this.plugin)) : PD.EmptyGroup() + } + if (options.length === 0) return PD.MappedStatic('', { '': PD.EmptyGroup() }); + return PD.MappedStatic(options[0][0], map, { options }); + } + + register(provider: StructureRepresentationProvider) { + // TODO: sort by group + this.providers.push(provider); + } + + apply<P>(ref: StateTransform.Ref, providerOrId: StructureRepresentationProvider<P> | string, params?: P) { + const provider = typeof providerOrId === 'string' + ? arrayFind(this.providers, p => p.id === providerOrId) + : providerOrId; + if (!provider) return; + + const state = this.plugin.state.dataState; + const cell = state.cells.get(ref); + if (!cell || !cell.obj || cell.status !== 'ok') { + if (!isProductionMode) console.warn(`Applying structure repr. provider to bad cell.`); + return; + } + + const prms = params || (provider.params + ? PD.getDefaultValues(provider.params(cell.obj.data, this.plugin)) + : {}) + + const apply = provider.apply(state, cell, prms, this.plugin); + + if (Task.is(apply)) return this.plugin.runTask(apply); + return apply; + } + + // init() { + // objectForEach(PresetStructureReprentations, r => this.register(r)); + // } + + constructor(public plugin: PluginContext) { + objectForEach(PresetStructureReprentations, r => this.register(r)); + } +} \ No newline at end of file diff --git a/src/mol-plugin/state/representation/structure/preset.ts b/src/mol-plugin/state/representation/structure/preset.ts new file mode 100644 index 0000000000000000000000000000000000000000..e8505b88f7f17cd20d4ebf809d52d890f3c6437c --- /dev/null +++ b/src/mol-plugin/state/representation/structure/preset.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { StateTransforms } from '../../transforms'; +import { StructureComplexElementTypes } from '../../transforms/model'; +import { StructureRepresentation3DHelpers } from '../../transforms/representation'; +import { applyBuiltInSelection } from '../../../util/structure-selection-helper'; +import { BuiltInStructureRepresentations } from '../../../../mol-repr/structure/registry'; +import { StructureRepresentationProvider } from './providers'; + +export const PresetStructureReprentations = { + default: StructureRepresentationProvider({ + id: 'preset-structure-representation-default', + display: { name: 'Default', group: 'Preset' }, + apply(state, structureCell, _, plugin) { + const root = state.build().to(structureCell.transform.ref); + const structure = structureCell.obj!.data; + + const tags = this.id; + + root.apply(StateTransforms.Model.StructureComplexElement, { type: 'protein-or-nucleic' }, { tags: [this.id, StructureComplexElementTypes['protein-or-nucleic']] }) + .apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParams(plugin, 'cartoon', structure), { tags }); + + root.apply(StateTransforms.Model.StructureComplexElement, { type: 'ligand' }, { tags: [this.id, StructureComplexElementTypes.ligand] }) + .apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParams(plugin, 'ball-and-stick', structure), { tags }); + + root.apply(StateTransforms.Model.StructureComplexElement, { type: 'modified' }, { tags: [this.id, StructureComplexElementTypes.modified] }) + .apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsWithTheme(plugin, 'ball-and-stick', 'polymer-id', structure, void 0), { tags }); + + const branched = root.apply(StateTransforms.Model.StructureComplexElement, { type: 'branched' }, { tags: [this.id, StructureComplexElementTypes.branched] }) + + branched.apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParams(plugin, 'ball-and-stick', structure, { alpha: 0.15 }), { tags }); + branched.apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParams(plugin, 'carbohydrate', structure), { tags }); + + root.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' }, { tags: [this.id, StructureComplexElementTypes.water] }) + .apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParams(plugin, 'ball-and-stick', structure, { alpha: 0.51 }), { tags }); + + root.apply(StateTransforms.Model.StructureComplexElement, { type: 'coarse' }, { tags: [this.id, StructureComplexElementTypes.coarse] }) + .apply(StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsWithTheme(plugin, 'spacefill', 'polymer-id', structure, {}), { tags }); + + return state.updateTree(root, { revertIfAborted: true }); + } + }), + capsid: StructureRepresentationProvider({ + id: 'preset-structure-representation-capsid', + display: { name: 'Capsid', group: 'Preset' }, + apply(state, structureCell, _, plugin) { + const root = plugin.state.dataState.build().to(structureCell.transform.ref); + const structure = structureCell.obj!.data; + + const params = StructureRepresentation3DHelpers.createParams(plugin, structure, { + repr: [BuiltInStructureRepresentations['gaussian-surface'], () => ({ smoothness: 1 })] + }); + + applyBuiltInSelection(root, 'polymer', this.id) + .apply(StateTransforms.Representation.StructureRepresentation3D, params, { tags: this.id }); + + return state.updateTree(root, { revertIfAborted: true }); + } + }) +}; \ No newline at end of file diff --git a/src/mol-plugin/state/representation/structure/providers.ts b/src/mol-plugin/state/representation/structure/providers.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a2fa4c3d716ea579fccd4909ec3c59860f550ee --- /dev/null +++ b/src/mol-plugin/state/representation/structure/providers.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { PluginContext } from '../../../context'; +import { State, StateObjectCell } from '../../../../mol-state'; +import { Task } from '../../../../mol-task'; +import { Structure } from '../../../../mol-model/structure'; +import { ParamDefinition as PD } from '../../../../mol-util/param-definition'; +import { PluginStateObject } from '../../objects'; + +export interface StructureRepresentationProvider<P = any> { + id: string, + display: { name: string, group: string, description?: string }, + isApplicable?(structure: Structure, plugin: PluginContext): boolean, + params?(structure: Structure | undefined, plugin: PluginContext): PD.Def<P>, + // TODO: have create return a "representation structure object" that allows modifications + apply(state: State, structure: StateObjectCell<PluginStateObject.Molecule.Structure>, params: P, plugin: PluginContext): Task<any> | Promise<void> | void +} + +export function StructureRepresentationProvider<P>(repr: StructureRepresentationProvider<P>) { return repr; } \ 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 49815e97a75012ad167a9a3df99c40c1397111af..66086268c20bc5980f508aec61943612f3e41d60 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -208,7 +208,7 @@ const StructureFromModel = PluginStateTransform.BuiltIn({ })({ apply({ a, params }, plugin: PluginContext) { return Task.create('Build Structure', async ctx => { - return ModelStructureRepresentation.create(plugin, ctx, a.data, params && params.kind); + return ModelStructureRepresentation.create(plugin, ctx, a.data, params && params.type); }) } }); diff --git a/src/mol-plugin/util/structure-selection-helper.ts b/src/mol-plugin/util/structure-selection-helper.ts index 2ddad35682e4e4c5c73975cb5000804683caa7f6..386fff8b391544601fb3dd772154ba05f1692c00 100644 --- a/src/mol-plugin/util/structure-selection-helper.ts +++ b/src/mol-plugin/util/structure-selection-helper.ts @@ -6,7 +6,7 @@ */ import { MolScriptBuilder as MS } from '../../mol-script/language/builder'; -import { StateSelection } from '../../mol-state'; +import { StateSelection, StateBuilder } from '../../mol-state'; import { PluginStateObject } from '../state/objects'; import { QueryContext, StructureSelection, StructureQuery, StructureElement } from '../../mol-model/structure'; import { compile } from '../../mol-script/runtime/query/compiler'; @@ -14,6 +14,7 @@ import { Loci } from '../../mol-model/loci'; import { PluginContext } from '../context'; import Expression from '../../mol-script/language/expression'; import { LinkType } from '../../mol-model/structure/model/types'; +import { StateTransforms } from '../state/transforms'; export interface StructureSelectionQuery { label: string @@ -277,6 +278,12 @@ export const StructureSelectionQueries = { bonded, } +export function applyBuiltInSelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof StructureSelectionQueries, customTag?: string) { + return to.apply(StateTransforms.Model.StructureSelectionFromExpression, + { expression: StructureSelectionQueries[query].expression, label: StructureSelectionQueries[query].label }, + { tags: customTag ? [query, customTag] : [query] }); +} + // export type SelectionModifier = 'add' | 'remove' | 'only' diff --git a/src/mol-task/task.ts b/src/mol-task/task.ts index be42ea46c4e7ba8ea6ea6c5ea58ba69d17c14630..ef4c3040e069ab5f5d7beb894070f8a578abf74a 100644 --- a/src/mol-task/task.ts +++ b/src/mol-task/task.ts @@ -54,6 +54,11 @@ namespace Task { } } + export function is<T = any>(t: any): t is Task<T> { + const _t = t as Task<any>; + return !!t && typeof _t.id === 'number' && typeof _t.name === 'string' && !!_t.run; + } + export interface Aborted { isAborted: true, reason: string, toString(): string } export function isAbort(e: any): e is Aborted { return !!e && !!e.isAborted; } export function Aborted(reason: string): Aborted { return { isAborted: true, reason, toString() { return `Aborted${reason ? ': ' + reason : ''}`; } }; } diff --git a/src/mol-util/object.ts b/src/mol-util/object.ts index bfb4824f5202bec67115a979304f11ccc8f30716..38e194d55698eebbc39930a640678888b4d4e124 100644 --- a/src/mol-util/object.ts +++ b/src/mol-util/object.ts @@ -84,10 +84,16 @@ export function deepClone<T>(source: T): T { throw new Error(`Can't clone, type "${typeof source}" unsupported`); } -export function mapObjectMap<O extends { [k: string]: T }, T, S>(o: O, f: (v: T) => S): { [k: string]: S } { +export function mapObjectMap<T, S>(o: { [k: string]: T }, f: (v: T) => S): { [k: string]: S } { const ret: any = { }; for (const k of Object.keys(o)) { ret[k] = f((o as any)[k]); } return ret; +} + +export function objectForEach<T>(o: { [k: string]: T }, f: (v: T, k: string) => void) { + for (const k of Object.keys(o)) { + f((o as any)[k], k); + } } \ No newline at end of file diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index ba6ee46b5b2f173c87d605c551025460e447c396..da98a7c1f3004443a9f4e7f08d63e5aa90c10130 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -265,6 +265,7 @@ export namespace ParamDefinition { type NonOptionals<P> = { [K in keyof P]-?: undefined extends P[K] ? never: K }[keyof P] export type Normalize<P> = Pick<P, NonOptionals<P>> & Partial<Pick<P, Optionals<P>>> export type For<P> = { [K in keyof P]-?: Base<P[K]> } + export type Def<P> = { [K in keyof P]: Any } export function getDefaultValues<T extends Params>(params: T) { const d: { [k: string]: any } = {}