diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts index aaa87f3d9a2788661fd03c5371bb4af8124c4634..90c0f76f469e2507da86a68dc2aef8088261773d 100644 --- a/src/apps/basic-wrapper/index.ts +++ b/src/apps/basic-wrapper/index.ts @@ -50,7 +50,7 @@ class BasicWrapper { return parsed .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }) - .apply(StateTransforms.Model.CustomModelProperties, { properties: [StripedResidues.Descriptor.name] }, { ref: 'props', props: { isGhost: false } }) + .apply(StateTransforms.Model.CustomModelProperties, { properties: [StripedResidues.Descriptor.name] }, { ref: 'props', state: { isGhost: false } }) .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' }); } diff --git a/src/apps/model-server-query/index.tsx b/src/apps/model-server-query/index.tsx index a34f8617c8c3d70dfaf792023a9bd3a0f3a3b11d..0c143ceba2f121b03f34d0dd8151fe83e5f3ff0e 100644 --- a/src/apps/model-server-query/index.tsx +++ b/src/apps/model-server-query/index.tsx @@ -105,7 +105,7 @@ const state: State = { query: new Rx.BehaviorSubject(QueryList[1].definition), id: new Rx.BehaviorSubject('1cbs'), params: new Rx.BehaviorSubject({ }), - isBinary: new Rx.BehaviorSubject(false), + isBinary: new Rx.BehaviorSubject<boolean>(false), models: new Rx.BehaviorSubject<number[]>([]), url: new Rx.Subject() } diff --git a/src/apps/viewer/extensions/jolecule.ts b/src/apps/viewer/extensions/jolecule.ts index cdf0026b39a4201f802fe01f18bc736c2ca0c71e..1ce9c969a9a05c522b3f14044ce81b56be030abf 100644 --- a/src/apps/viewer/extensions/jolecule.ts +++ b/src/apps/viewer/extensions/jolecule.ts @@ -57,7 +57,7 @@ interface JoleculeSnapshot { 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` }, { props: { isGhost: true }}); + 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 }); diff --git a/src/examples/proteopedia-wrapper/changelog.md b/src/examples/proteopedia-wrapper/changelog.md index 041ecacd3454949a8be9e03ad2a6284c5b9f8381..9680f701e7da996b4d28c9627f87833d9571539d 100644 --- a/src/examples/proteopedia-wrapper/changelog.md +++ b/src/examples/proteopedia-wrapper/changelog.md @@ -1,3 +1,11 @@ +== v3.0 == + +* Fixed initial camera zoom. +* Custom chain coloring. +* Customize visualizations. +* Show ligand list. +* Show 3D-SNFG. + == v2.0 == * Changed how state saving works. diff --git a/src/examples/proteopedia-wrapper/coloring.ts b/src/examples/proteopedia-wrapper/coloring.ts new file mode 100644 index 0000000000000000000000000000000000000000..a2199b6edbc72f94989016b1e97de943d467e6aa --- /dev/null +++ b/src/examples/proteopedia-wrapper/coloring.ts @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author David Sehnal <david.sehnal@gmail.com> + */ + + +import { Unit, StructureProperties, StructureElement, Link } from 'mol-model/structure'; + +import { Color } from 'mol-util/color'; +import { Location } from 'mol-model/location'; +import { ColorTheme, LocationColor } from 'mol-theme/color'; +import { ParamDefinition as PD } from 'mol-util/param-definition' +import { ThemeDataContext } from 'mol-theme/theme'; +import { Column } from 'mol-data/db'; + +const Description = 'Gives every chain a color from a list based on its `asym_id` value.' + +export function createProteopediaCustomTheme(colors: number[]) { + const ProteopediaCustomColorThemeParams = { + colors: PD.ObjectList({ color: PD.Color(Color(0xffffff)) }, ({ color }) => Color.toHexString(color), + { defaultValue: colors.map(c => ({ color: Color(c) })) }) + } + type ProteopediaCustomColorThemeParams = typeof ProteopediaCustomColorThemeParams + function getChainIdColorThemeParams(ctx: ThemeDataContext) { + return ProteopediaCustomColorThemeParams // TODO return copy + } + + function getAsymId(unit: Unit): StructureElement.Property<string> { + switch (unit.kind) { + case Unit.Kind.Atomic: + return StructureProperties.chain.label_asym_id + case Unit.Kind.Spheres: + case Unit.Kind.Gaussians: + return StructureProperties.coarse.asym_id + } + } + + function addAsymIds(map: Map<string, number>, data: Column<string>) { + let j = map.size + for (let o = 0, ol = data.rowCount; o < ol; ++o) { + const k = data.value(o) + if (!map.has(k)) { + map.set(k, j) + j += 1 + } + } + } + + function ProteopediaCustomColorTheme(ctx: ThemeDataContext, props: PD.Values<ProteopediaCustomColorThemeParams>): ColorTheme<ProteopediaCustomColorThemeParams> { + let color: LocationColor + + const colors = props.colors, colorCount = colors.length, defaultColor = colors[0].color; + + if (ctx.structure) { + const l = StructureElement.create() + const { models } = ctx.structure + const asymIdSerialMap = new Map<string, number>() + for (let i = 0, il = models.length; i < il; ++i) { + const m = models[i] + addAsymIds(asymIdSerialMap, m.atomicHierarchy.chains.label_asym_id) + if (m.coarseHierarchy.isDefined) { + addAsymIds(asymIdSerialMap, m.coarseHierarchy.spheres.asym_id) + addAsymIds(asymIdSerialMap, m.coarseHierarchy.gaussians.asym_id) + } + } + + color = (location: Location): Color => { + if (StructureElement.isLocation(location)) { + const asym_id = getAsymId(location.unit); + const o = asymIdSerialMap.get(asym_id(location)) || 0; + return colors[o % colorCount].color; + } else if (Link.isLocation(location)) { + const asym_id = getAsymId(location.aUnit) + l.unit = location.aUnit + l.element = location.aUnit.elements[location.aIndex] + const o = asymIdSerialMap.get(asym_id(l)) || 0; + return colors[o % colorCount].color; + } + return defaultColor + } + } else { + color = () => defaultColor + } + + return { + factory: ProteopediaCustomColorTheme, + granularity: 'group', + color, + props, + description: Description, + legend: undefined + } + } + + const ProteopediaCustomColorThemeProvider: ColorTheme.Provider<ProteopediaCustomColorThemeParams> = { + label: 'Proteopedia Custom', + factory: ProteopediaCustomColorTheme, + getParams: getChainIdColorThemeParams, + defaultValues: PD.getDefaultValues(ProteopediaCustomColorThemeParams), + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure + } + + return ProteopediaCustomColorThemeProvider; +} \ No newline at end of file diff --git a/src/examples/proteopedia-wrapper/helpers.ts b/src/examples/proteopedia-wrapper/helpers.ts index 427fb057e0b423573a13bb1ea934a977e75a8845..17082c93c22762304fcf3c7357e623bd6e3524c3 100644 --- a/src/examples/proteopedia-wrapper/helpers.ts +++ b/src/examples/proteopedia-wrapper/helpers.ts @@ -92,9 +92,26 @@ export interface LoadParams { export interface RepresentationStyle { sequence?: RepresentationStyle.Entry, hetGroups?: RepresentationStyle.Entry, + snfg3d?: { hide?: boolean }, water?: RepresentationStyle.Entry } export namespace RepresentationStyle { - export type Entry = { kind?: BuiltInStructureRepresentationsName, coloring?: BuiltInColorThemeName } + export type Entry = { hide?: boolean, kind?: BuiltInStructureRepresentationsName, coloring?: BuiltInColorThemeName } +} + +export enum StateElements { + Model = 'model', + ModelProps = 'model-props', + Assembly = 'assembly', + + Sequence = 'sequence', + SequenceVisual = 'sequence-visual', + Het = 'het', + HetVisual = 'het-visual', + Het3DSNFG = 'het-3dsnfg', + Water = 'water', + WaterVisual = 'water-visual', + + HetGroupFocus = 'het-group-focus' } \ No newline at end of file diff --git a/src/examples/proteopedia-wrapper/index.html b/src/examples/proteopedia-wrapper/index.html index 009eb5bf8193a534a6a2c21e342e1c47cfb29e6d..288db037a5d4f2406c2a3b486fd2417dde62e592 100644 --- a/src/examples/proteopedia-wrapper/index.html +++ b/src/examples/proteopedia-wrapper/index.html @@ -55,11 +55,14 @@ </select> </div> <div id="app"></div> - <script> + <script> + // it might be a good idea to define these colors in a separate script file + var CustomColors = [0x00ff00, 0x0000ff]; + // create an instance of the plugin var PluginWrapper = new MolStarProteopediaWrapper(); - console.log('Wrapper version', MolStarProteopediaWrapper.VERSION_MAJOR); + console.log('Wrapper version', MolStarProteopediaWrapper.VERSION_MAJOR, MolStarProteopediaWrapper.VERSION_MINOR); function $(id) { return document.getElementById(id); } @@ -78,13 +81,23 @@ // var format = 'pdb'; // var assemblyId = 'deposited'; - PluginWrapper.init('app' /** or document.getElementById('app') */); + var representationStyle = { + sequence: { coloring: 'proteopedia-custom' }, // or just { } + hetGroups: { kind: 'ball-and-stick' }, // or 'spacefill + water: { hide: true }, + snfg3d: { hide: false } + }; + + PluginWrapper.init('app' /** or document.getElementById('app') */, { + customColorList: CustomColors + }); PluginWrapper.setBackground(0xffffff); - PluginWrapper.load({ url: url, format: format, assemblyId: assemblyId }); + PluginWrapper.load({ url: url, format: format, assemblyId: assemblyId, representationStyle: representationStyle }); PluginWrapper.toggleSpin(); PluginWrapper.events.modelInfo.subscribe(function (info) { console.log('Model Info', info); + listHetGroups(info); }); addControl('Load Asym Unit', () => PluginWrapper.load({ url: url, format: format })); @@ -92,6 +105,22 @@ addSeparator(); + addHeader('Representation'); + + addControl('Custom Chain Colors', () => PluginWrapper.updateStyle({ sequence: { coloring: 'proteopedia-custom' } }, true)); + addControl('Default Chain Colors', () => PluginWrapper.updateStyle({ sequence: { } }, true)); + + addControl('HET Spacefill', () => PluginWrapper.updateStyle({ hetGroups: { kind: 'spacefill' } }, true)); + addControl('HET Ball-and-stick', () => PluginWrapper.updateStyle({ hetGroups: { kind: 'ball-and-stick' } }, true)); + + addControl('Hide 3DSNFG', () => PluginWrapper.updateStyle({ snfg3d: { hide: true } }, true)); + addControl('Show 3DSNFG', () => PluginWrapper.updateStyle({ snfg3d: { hide: false } }, true)); + + addControl('Hide Water', () => PluginWrapper.updateStyle({ water: { hide: true } }, true)); + addControl('Show Water', () => PluginWrapper.updateStyle({ water: { hide: false } }, true)); + + addSeparator(); + addHeader('Camera'); addControl('Toggle Spin', () => PluginWrapper.toggleSpin()); @@ -115,6 +144,12 @@ addControl('Apply Evo Cons', () => PluginWrapper.coloring.evolutionaryConservation()); addControl('Default Visuals', () => PluginWrapper.updateStyle()); + addSeparator(); + addHeader('HET Groups'); + + addControl('Reset', () => PluginWrapper.hetGroups.reset()); + addHetGroupsContainer(); + addSeparator(); addHeader('State'); @@ -133,6 +168,12 @@ //////////////////////////////////////////////////////// + function addHetGroupsContainer() { + var div = document.createElement('div'); + div.id = 'het-groups'; + $('controls').appendChild(div); + } + function addControl(label, action) { var btn = document.createElement('button'); btn.onclick = action; @@ -150,6 +191,19 @@ h.innerText = header; $('controls').appendChild(h); } + + function listHetGroups(info) { + var div = $('het-groups'); + div.innerHTML = ''; + info.hetResidues.forEach(function (r) { + var l = document.createElement('button'); + l.innerText = r.name; + l.onclick = function () { + PluginWrapper.hetGroups.focusFirst(r.name); + }; + div.appendChild(l); + }); + } </script> </body> </html> \ No newline at end of file diff --git a/src/examples/proteopedia-wrapper/index.ts b/src/examples/proteopedia-wrapper/index.ts index 91677335b5ee870e06931e007c0ca3b7857db417..ee540eef34939d060f3d3813a0d4826be364ad27 100644 --- a/src/examples/proteopedia-wrapper/index.ts +++ b/src/examples/proteopedia-wrapper/index.ts @@ -15,15 +15,25 @@ import { PluginStateObject as PSO, PluginStateObject } from 'mol-plugin/state/ob import { AnimateModelIndex } from 'mol-plugin/state/animation/built-in'; import { StateBuilder, StateObject } from 'mol-state'; import { EvolutionaryConservation } from './annotation'; -import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo } from './helpers'; +import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo, StateElements } from './helpers'; import { RxEventHelper } from 'mol-util/rx-event-helper'; import { ControlsWrapper } from './ui/controls'; import { PluginState } from 'mol-plugin/state'; +import { Scheduler } from 'mol-task'; +import { createProteopediaCustomTheme } from './coloring'; +import { MolScriptBuilder as MS } from 'mol-script/language/builder'; +import { BuiltInStructureRepresentations } from 'mol-repr/structure/registry'; +import { BuiltInColorThemes } from 'mol-theme/color'; +import { BuiltInSizeThemes } from 'mol-theme/size'; +import { ColorNames } from 'mol-util/color/tables'; +// import { Vec3 } from 'mol-math/linear-algebra'; +// import { ParamDefinition } from 'mol-util/param-definition'; +// import { Text } from 'mol-geo/geometry/text/text'; require('mol-plugin/skin/light.scss') class MolStarProteopediaWrapper { - static VERSION_MAJOR = 2; - static VERSION_MINOR = 0; + static VERSION_MAJOR = 3; + static VERSION_MINOR = 1; private _ev = RxEventHelper.create(); @@ -33,9 +43,14 @@ class MolStarProteopediaWrapper { plugin: PluginContext; - init(target: string | HTMLElement) { + init(target: string | HTMLElement, options?: { + customColorList?: number[] + }) { this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, { ...DefaultPluginSpec, + animations: [ + AnimateModelIndex + ], layout: { initial: { isExpanded: false, @@ -47,6 +62,9 @@ class MolStarProteopediaWrapper { } }); + const customColoring = createProteopediaCustomTheme((options && options.customColorList) || []); + + this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add('proteopedia-custom', customColoring); this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(EvolutionaryConservation.Descriptor.name, EvolutionaryConservation.colorTheme!); this.plugin.lociLabels.addProvider(EvolutionaryConservation.labelProvider); this.plugin.customModelProperties.register(EvolutionaryConservation.propertyProvider); @@ -66,43 +84,86 @@ class MolStarProteopediaWrapper { : b.apply(StateTransforms.Model.TrajectoryFromPDB); return parsed - .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }, { ref: 'model' }); + .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }, { ref: StateElements.Model }); } private structure(assemblyId: string) { - const model = this.state.build().to('model'); + const model = this.state.build().to(StateElements.Model); + + const s = model + .apply(StateTransforms.Model.CustomModelProperties, { properties: [EvolutionaryConservation.Descriptor.name] }, { ref: StateElements.ModelProps, state: { isGhost: false } }) + .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: StateElements.Assembly }); - return model - .apply(StateTransforms.Model.CustomModelProperties, { properties: [EvolutionaryConservation.Descriptor.name] }, { ref: 'props', props: { isGhost: false } }) - .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' }); + s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: StateElements.Sequence }); + s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }, { ref: StateElements.Het }); + s.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' }, { ref: StateElements.Water }); + + return s; } - private visual(ref: string, style?: RepresentationStyle) { - const structure = this.getObj<PluginStateObject.Molecule.Structure>(ref); + private visual(_style?: RepresentationStyle, partial?: boolean) { + const structure = this.getObj<PluginStateObject.Molecule.Structure>(StateElements.Assembly); if (!structure) return; - const root = this.state.build().to(ref); - - root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: 'sequence' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, - (style && style.sequence && style.sequence.kind) || 'cartoon', - (style && style.sequence && style.sequence.coloring) || 'unit-index', structure), - { ref: 'sequence-visual' }); - root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }, { ref: 'het' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, - (style && style.hetGroups && style.hetGroups.kind) || 'ball-and-stick', - (style && style.hetGroups && style.hetGroups.coloring), structure), - { ref: 'het-visual' }); - root.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' }, { ref: 'water' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, - (style && style.water && style.water.kind) || 'ball-and-stick', - (style && style.water && style.water.coloring), structure, { alpha: 0.51 }), - { ref: 'water-visual' }); - - return root; + const style = _style || { }; + + const update = this.state.build(); + + if (!partial || (partial && style.sequence)) { + const root = update.to(StateElements.Sequence); + if (style.sequence && style.sequence.hide) { + root.delete(StateElements.SequenceVisual); + } else { + root.applyOrUpdate(StateElements.SequenceVisual, StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, + (style.sequence && style.sequence.kind) || 'cartoon', + (style.sequence && style.sequence.coloring) || 'unit-index', structure)); + } + } + + if (!partial || (partial && style.hetGroups)) { + const root = update.to(StateElements.Het); + if (style.hetGroups && style.hetGroups.hide) { + root.delete(StateElements.HetVisual); + } else { + if (style.hetGroups && style.hetGroups.hide) { + root.delete(StateElements.HetVisual); + } else { + root.applyOrUpdate(StateElements.HetVisual, StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, + (style.hetGroups && style.hetGroups.kind) || 'ball-and-stick', + (style.hetGroups && style.hetGroups.coloring), structure)); + } + } + } + + if (!partial || (partial && style.snfg3d)) { + const root = update.to(StateElements.Het); + if (style.hetGroups && style.hetGroups.hide) { + root.delete(StateElements.HetVisual); + } else { + if (style.snfg3d && style.snfg3d.hide) { + root.delete(StateElements.Het3DSNFG); + } else { + root.applyOrUpdate(StateElements.Het3DSNFG, StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, 'carbohydrate', void 0, structure)); + } + } + } + + if (!partial || (partial && style.water)) { + const root = update.to(StateElements.Water); + if (style.water && style.water.hide) { + root.delete(StateElements.WaterVisual); + } else { + root.applyOrUpdate(StateElements.WaterVisual, StateTransforms.Representation.StructureRepresentation3D, + StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, + (style.water && style.water.kind) || 'ball-and-stick', + (style.water && style.water.coloring), structure, { alpha: 0.51 })); + } + } + + return update; } private getObj<T extends StateObject>(ref: string): T['data'] { @@ -134,7 +195,7 @@ class MolStarProteopediaWrapper { if (this.loadedParams.url !== url || this.loadedParams.format !== format) { loadType = 'full'; } else if (this.loadedParams.url === url) { - if (state.select('asm').length > 0) loadType = 'update'; + if (state.select(StateElements.Assembly).length > 0) loadType = 'update'; } if (loadType === 'full') { @@ -146,18 +207,18 @@ class MolStarProteopediaWrapper { await this.applyState(structureTree); } else { const tree = state.build(); - tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' })); + tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' })); await this.applyState(tree); } await this.updateStyle(representationStyle); this.loadedParams = { url, format, assemblyId }; - PluginCommands.Camera.Reset.dispatch(this.plugin, { }); + Scheduler.setImmediate(() => PluginCommands.Camera.Reset.dispatch(this.plugin, { })); } - async updateStyle(style?: RepresentationStyle) { - const tree = this.visual('asm', style); + async updateStyle(style?: RepresentationStyle, partial?: boolean) { + const tree = this.visual(style, partial); if (!tree) return; await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree }); } @@ -186,7 +247,7 @@ class MolStarProteopediaWrapper { coloring = { evolutionaryConservation: async () => { - await this.updateStyle({ sequence: { kind: 'spacefill' } }); + await this.updateStyle({ sequence: { kind: 'spacefill' } }, true); const state = this.state; @@ -194,7 +255,7 @@ class MolStarProteopediaWrapper { const tree = state.build(); const colorTheme = { name: EvolutionaryConservation.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.Descriptor.name).defaultValues }; - tree.to('sequence-visual').update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme })); + tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme })); // for (const v of visuals) { // } @@ -202,6 +263,69 @@ class MolStarProteopediaWrapper { } } + hetGroups = { + reset: () => { + const update = this.state.build().delete(StateElements.HetGroupFocus); + PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update }); + PluginCommands.Camera.Reset.dispatch(this.plugin, { }); + }, + focusFirst: async (resn: string) => { + if (!this.state.transforms.has(StateElements.Assembly)) return; + + // const asm = (this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure).data; + + const update = this.state.build(); + + update.delete(StateElements.HetGroupFocus); + + const surroundings = MS.struct.modifier.includeSurroundings({ + 0: MS.struct.filter.first([ + MS.struct.generator.atomGroups({ + 'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), resn]), + 'group-by': MS.struct.atomProperty.macromolecular.residueKey() + }) + ]), + radius: 5, + 'as-whole-residues': true + }); + + const sel = update.to(StateElements.Assembly) + .apply(StateTransforms.Model.StructureSelection, { label: resn, query: surroundings }, { ref: StateElements.HetGroupFocus }); + + sel.apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams()); + // sel.apply(StateTransforms.Representation.StructureLabels3D, { + // target: { name: 'residues', params: { } }, + // options: { + // ...ParamDefinition.getDefaultValues(Text.Params), + // background: true, + // backgroundMargin: 0.2, + // backgroundColor: ColorNames.snow, + // backgroundOpacity: 0.9, + // } + // }); + + await PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update }); + + const focus = (this.state.select(StateElements.HetGroupFocus)[0].obj as PluginStateObject.Molecule.Structure).data; + const sphere = focus.boundary.sphere; + // const asmCenter = asm.boundary.sphere.center; + // const position = Vec3.sub(Vec3.zero(), sphere.center, asmCenter); + // Vec3.normalize(position, position); + // Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius); + const snapshot = this.plugin.canvas3d.camera.getFocus(sphere.center, 0.75 * sphere.radius); + PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { snapshot, durationMs: 250 }); + } + } + + private createSurVisualParams() { + const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure; + return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, { + repr: BuiltInStructureRepresentations['ball-and-stick'], + color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })], + size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )] + }); + } + snapshot = { get: () => { return this.plugin.state.getSnapshot(); diff --git a/src/mol-canvas3d/camera.ts b/src/mol-canvas3d/camera.ts index d61da319e3d6452fddfd17ed06c439af45324766..731dc74e67c6ab1674e39d86924465ea154a5f8d 100644 --- a/src/mol-canvas3d/camera.ts +++ b/src/mol-canvas3d/camera.ts @@ -84,7 +84,7 @@ class Camera implements Object3D { return ret; } - focus(target: Vec3, radius: number) { + getFocus(target: Vec3, radius: number): Partial<Camera.Snapshot> { const fov = this.state.fov const { width, height } = this.viewport const aspect = width / height @@ -98,7 +98,11 @@ class Camera implements Object3D { if (currentDistance < targetDistance) Vec3.negate(this.deltaDirection, this.deltaDirection) Vec3.add(this.newPosition, this.state.position, this.deltaDirection) - this.setState({ target, position: this.newPosition }) + return { target, position: Vec3.clone(this.newPosition) }; + } + + focus(target: Vec3, radius: number) { + this.setState(this.getFocus(target, radius)); } // lookAt(target: Vec3) { diff --git a/src/mol-math/geometry/symmetry-operator.ts b/src/mol-math/geometry/symmetry-operator.ts index 44ba2975516385dcf35260e7eddc971dd48c80b6..6f3a71fe52e0802fe89cc25d1014ed6d2e707567 100644 --- a/src/mol-math/geometry/symmetry-operator.ts +++ b/src/mol-math/geometry/symmetry-operator.ts @@ -5,6 +5,7 @@ */ import { Vec3, Mat4, Mat3, Quat } from '../linear-algebra/3d' +import { lerp as scalar_lerp } from 'mol-math/interpolate'; interface SymmetryOperator { readonly name: string, @@ -64,7 +65,7 @@ namespace SymmetryOperator { return create(name, t, { id: '', operList: [] }, ncsId); } - const _q1 = Quat.identity(), _q2 = Quat.zero(), _axis = Vec3.zero(); + const _q1 = Quat.identity(), _q2 = Quat.zero(), _q3 = Quat.zero(), _axis = Vec3.zero(); export function lerpFromIdentity(out: Mat4, op: SymmetryOperator, t: number): Mat4 { const m = op.inverse; if (op.isIdentity) return Mat4.copy(out, m); @@ -84,6 +85,25 @@ namespace SymmetryOperator { return out; } + export function slerp(out: Mat4, src: Mat4, tar: Mat4, t: number): Mat4 { + if (Math.abs(t) <= 0.00001) return Mat4.copy(out, src); + if (Math.abs(t - 1) <= 0.00001) return Mat4.copy(out, tar); + + // interpolate rotation + Mat4.getRotation(_q2, src); + Mat4.getRotation(_q3, tar); + Quat.slerp(_q3, _q2, _q3, t); + const angle = Quat.getAxisAngle(_axis, _q3); + Mat4.fromRotation(out, angle, _axis); + + // interpolate translation + Mat4.setValue(out, 0, 3, scalar_lerp(Mat4.getValue(src, 0, 3), Mat4.getValue(tar, 0, 3), t)); + Mat4.setValue(out, 1, 3, scalar_lerp(Mat4.getValue(src, 1, 3), Mat4.getValue(tar, 1, 3), t)); + Mat4.setValue(out, 2, 3, scalar_lerp(Mat4.getValue(src, 2, 3), Mat4.getValue(tar, 2, 3), t)); + + return out; + } + /** * Apply the 1st and then 2nd operator. ( = second.matrix * first.matrix). * Keep `name`, `assembly`, `ncsId` and `hkl` properties from second. diff --git a/src/mol-model-formats/structure/mmcif/bonds/comp.ts b/src/mol-model-formats/structure/mmcif/bonds/comp.ts index eceb64760607d51d544a8a9a587a46471bd9334d..9ba0b69eb645f03e1fc9f10946eba6bcb09eeff4 100644 --- a/src/mol-model-formats/structure/mmcif/bonds/comp.ts +++ b/src/mol-model-formats/structure/mmcif/bonds/comp.ts @@ -7,7 +7,7 @@ import { Model } from 'mol-model/structure/model/model' import { LinkType } from 'mol-model/structure/model/types' -import { ModelPropertyDescriptor } from 'mol-model/structure/model/properties/custom'; +import { CustomPropertyDescriptor } from 'mol-model/structure'; import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'; import { Structure, Unit, StructureProperties, StructureElement } from 'mol-model/structure'; import { Segmentation } from 'mol-data/int'; @@ -18,7 +18,7 @@ export interface ComponentBond { } export namespace ComponentBond { - export const Descriptor: ModelPropertyDescriptor = { + export const Descriptor: CustomPropertyDescriptor = { isStatic: true, name: 'chem_comp_bond', cifExport: { diff --git a/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts b/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts index 91c819e5a3b918e854029231df48cea4e3619fad..69689c51dbae68518e2d4ad14edc55303e6b5ade 100644 --- a/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts +++ b/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts @@ -10,7 +10,7 @@ import { Structure } from 'mol-model/structure' import { LinkType } from 'mol-model/structure/model/types' import { findEntityIdByAsymId, findAtomIndexByLabelName } from '../util' import { Column } from 'mol-data/db' -import { ModelPropertyDescriptor } from 'mol-model/structure/model/properties/custom'; +import { CustomPropertyDescriptor } from 'mol-model/structure'; import { mmCIF_Database, mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'; import { SortedArray } from 'mol-data/int'; import { CifWriter } from 'mol-io/writer/cif' @@ -23,7 +23,7 @@ export interface StructConn { } export namespace StructConn { - export const Descriptor: ModelPropertyDescriptor = { + export const Descriptor: CustomPropertyDescriptor = { isStatic: true, name: 'struct_conn', cifExport: { diff --git a/src/mol-model-formats/structure/mmcif/parser.ts b/src/mol-model-formats/structure/mmcif/parser.ts index d31b63d1a8a6d0eaa4368ee6660178a237f0f59b..71063f94d82d6df46bdcafa3bc5bb73da9102ba7 100644 --- a/src/mol-model-formats/structure/mmcif/parser.ts +++ b/src/mol-model-formats/structure/mmcif/parser.ts @@ -13,7 +13,7 @@ import { RuntimeContext } from 'mol-task'; import UUID from 'mol-util/uuid'; import { Model } from 'mol-model/structure/model/model'; import { Entities } from 'mol-model/structure/model/properties/common'; -import { CustomProperties } from 'mol-model/structure/model/properties/custom'; +import { CustomProperties } from 'mol-model/structure'; import { ModelSymmetry } from 'mol-model/structure/model/properties/symmetry'; import { createAssemblies } from './assembly'; import { getAtomicHierarchyAndConformation } from './atomic'; diff --git a/src/mol-model-props/common/custom-element-property.ts b/src/mol-model-props/common/custom-element-property.ts index 85f9306753784f838dbc00dce60b168394c5b6be..f5e2a6ff7e86a9a6a1845acb5d186cc851fe4529 100644 --- a/src/mol-model-props/common/custom-element-property.ts +++ b/src/mol-model-props/common/custom-element-property.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { ElementIndex, Model, ModelPropertyDescriptor } from 'mol-model/structure'; +import { ElementIndex, Model, CustomPropertyDescriptor } from 'mol-model/structure'; import { StructureElement } from 'mol-model/structure/structure'; import { Location } from 'mol-model/location'; import { CustomPropertyRegistry } from './custom-property-registry'; @@ -36,7 +36,7 @@ namespace CustomElementProperty { export function create<T>(params: CreateParams<T>) { const name = params.name; - const Descriptor = ModelPropertyDescriptor({ + const Descriptor = CustomPropertyDescriptor({ isStatic: params.isStatic, name: params.name, }); diff --git a/src/mol-model-props/common/custom-property-registry.ts b/src/mol-model-props/common/custom-property-registry.ts index b537d0df3752caf9a4679d8deae0b0909c568bb6..977cade54990a71dd9b3f821cc89bd02a91c78c6 100644 --- a/src/mol-model-props/common/custom-property-registry.ts +++ b/src/mol-model-props/common/custom-property-registry.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { ModelPropertyDescriptor, Model } from 'mol-model/structure'; +import { CustomPropertyDescriptor, Model } from 'mol-model/structure'; import { OrderedMap } from 'immutable'; import { ParamDefinition } from 'mol-util/param-definition'; import { Task } from 'mol-task'; @@ -58,7 +58,7 @@ namespace CustomPropertyRegistry { export interface Provider { option: [string, string], defaultSelected: boolean, - descriptor: ModelPropertyDescriptor<any, any>, + descriptor: CustomPropertyDescriptor<any, any>, attachableTo: (model: Model) => boolean, attach: (model: Model) => Task<boolean> } diff --git a/src/mol-model-props/pdbe/preferred-assembly.ts b/src/mol-model-props/pdbe/preferred-assembly.ts index fb5f5dc69d30b0ee3f12611aa9890086bed3fd8c..a5c540566b2cbd850c36a5f91ad90ce7447cfad8 100644 --- a/src/mol-model-props/pdbe/preferred-assembly.ts +++ b/src/mol-model-props/pdbe/preferred-assembly.ts @@ -7,7 +7,7 @@ import { Column, Table } from 'mol-data/db'; import { toTable } from 'mol-io/reader/cif/schema'; import { CifWriter } from 'mol-io/writer/cif'; -import { Model, ModelPropertyDescriptor } from 'mol-model/structure'; +import { Model, CustomPropertyDescriptor } from 'mol-model/structure'; export namespace PDBePreferredAssembly { export type Property = string @@ -31,7 +31,7 @@ export namespace PDBePreferredAssembly { }; export type Schema = typeof Schema - export const Descriptor = ModelPropertyDescriptor({ + export const Descriptor = CustomPropertyDescriptor({ isStatic: true, name: 'pdbe_preferred_assembly', cifExport: { diff --git a/src/mol-model-props/pdbe/struct-ref-domain.ts b/src/mol-model-props/pdbe/struct-ref-domain.ts index 3872d5a4d470fe0056024f961385846925f79bfe..8c3a8a7f34ef9762832395db1c6819a7627ad361 100644 --- a/src/mol-model-props/pdbe/struct-ref-domain.ts +++ b/src/mol-model-props/pdbe/struct-ref-domain.ts @@ -7,7 +7,7 @@ import { Column, Table } from 'mol-data/db'; import { toTable } from 'mol-io/reader/cif/schema'; import { CifWriter } from 'mol-io/writer/cif'; -import { Model, ModelPropertyDescriptor } from 'mol-model/structure'; +import { Model, CustomPropertyDescriptor } from 'mol-model/structure'; import { PropertyWrapper } from '../common/wrapper'; export namespace PDBeStructRefDomain { @@ -39,7 +39,7 @@ export namespace PDBeStructRefDomain { }; export type Schema = typeof Schema - export const Descriptor = ModelPropertyDescriptor({ + export const Descriptor = CustomPropertyDescriptor({ isStatic: true, name: 'pdbe_struct_ref_domain', cifExport: { diff --git a/src/mol-model-props/pdbe/structure-quality-report.ts b/src/mol-model-props/pdbe/structure-quality-report.ts index b534c465ff624e5793f4de87e7bc437c099073cd..483b957047120c0118d58b966805bc51ef92ac22 100644 --- a/src/mol-model-props/pdbe/structure-quality-report.ts +++ b/src/mol-model-props/pdbe/structure-quality-report.ts @@ -8,7 +8,7 @@ import { Column, Table } from 'mol-data/db'; import { toTable } from 'mol-io/reader/cif/schema'; import { mmCIF_residueId_schema } from 'mol-io/reader/cif/schema/mmcif-extras'; import { CifWriter } from 'mol-io/writer/cif'; -import { Model, ModelPropertyDescriptor, ResidueIndex, Unit, IndexedCustomProperty } from 'mol-model/structure'; +import { Model, CustomPropertyDescriptor, ResidueIndex, Unit, IndexedCustomProperty } from 'mol-model/structure'; import { residueIdFields } from 'mol-model/structure/export/categories/atom_site'; import { StructureElement, CifExportContext } from 'mol-model/structure/structure'; import { CustomPropSymbol } from 'mol-script/language/symbol'; @@ -43,7 +43,7 @@ export namespace StructureQualityReport { }; export type Schema = typeof Schema - export const Descriptor = ModelPropertyDescriptor({ + export const Descriptor = CustomPropertyDescriptor({ isStatic: false, name: 'pdbe_structure_quality_report', cifExport: { diff --git a/src/mol-model-props/rcsb/assembly-symmetry.ts b/src/mol-model-props/rcsb/assembly-symmetry.ts index c67ed0aca0b78727b2d073b60563f7b1f128d76b..4eab862518f6da97b1adf445a6d24ec16ab4d2ad 100644 --- a/src/mol-model-props/rcsb/assembly-symmetry.ts +++ b/src/mol-model-props/rcsb/assembly-symmetry.ts @@ -7,7 +7,7 @@ import { AssemblySymmetry as AssemblySymmetryGraphQL } from './graphql/types'; import query from './graphql/symmetry.gql'; -import { Model, ModelPropertyDescriptor } from 'mol-model/structure'; +import { Model, CustomPropertyDescriptor } from 'mol-model/structure'; import { CifWriter } from 'mol-io/writer/cif'; import { Database as _Database, Column, Table } from 'mol-data/db' import { Category } from 'mol-io/writer/cif/encoder'; @@ -140,7 +140,7 @@ function createDatabaseFromCif(model: Model): AssemblySymmetry.Database { }) } -const _Descriptor: ModelPropertyDescriptor = { +const _Descriptor: CustomPropertyDescriptor = { isStatic: true, name: 'rcsb_assembly_symmetry', cifExport: { diff --git a/src/mol-model/structure.ts b/src/mol-model/structure.ts index 38d5890d5f0fada2ea72a32c23e6e9d3f2e0261b..3dff04a4684f0c1ad2062fb255eb10755ffa7ea8 100644 --- a/src/mol-model/structure.ts +++ b/src/mol-model/structure.ts @@ -6,4 +6,5 @@ export * from './structure/model' export * from './structure/structure' -export * from './structure/query' \ No newline at end of file +export * from './structure/query' +export * from './structure/common/custom-property' \ No newline at end of file diff --git a/src/mol-model/structure/common/custom-property.ts b/src/mol-model/structure/common/custom-property.ts new file mode 100644 index 0000000000000000000000000000000000000000..12f6f557c4aa5da2e91bd2cb6600b74dd2a08376 --- /dev/null +++ b/src/mol-model/structure/common/custom-property.ts @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { CifWriter } from 'mol-io/writer/cif' +import { CifExportContext } from '../export/mmcif'; +import { QuerySymbolRuntime } from 'mol-script/runtime/query/compiler'; +import { UUID } from 'mol-util'; + +export { CustomPropertyDescriptor, CustomProperties } + +interface CustomPropertyDescriptor<ExportCtx = CifExportContext, Symbols extends { [name: string]: QuerySymbolRuntime } = { }> { + readonly isStatic: boolean, + readonly name: string, + + cifExport?: { + // Prefix enforced during export. + prefix: string, + context?: (ctx: CifExportContext) => ExportCtx | undefined, + categories: CifWriter.Category<ExportCtx>[] + }, + + // TODO: add aliases when lisp-like mol-script is done + symbols?: Symbols +} + +function CustomPropertyDescriptor<Ctx, Desc extends CustomPropertyDescriptor<Ctx>>(desc: Desc) { + return desc; +} + +namespace CustomPropertyDescriptor { + export function getUUID(prop: CustomPropertyDescriptor): UUID { + if (!(prop as any).__key) { + (prop as any).__key = UUID.create22(); + } + return (prop as any).__key; + } +} + +class CustomProperties { + private _list: CustomPropertyDescriptor[] = []; + private _set = new Set<CustomPropertyDescriptor>(); + + get all(): ReadonlyArray<CustomPropertyDescriptor> { + return this._list; + } + + add(desc: CustomPropertyDescriptor<any>) { + if (this._set.has(desc)) return; + + this._list.push(desc); + this._set.add(desc); + } + + has(desc: CustomPropertyDescriptor<any>): boolean { + return this._set.has(desc); + } +} \ No newline at end of file diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts index e0c189ab4afa7088656e3f0312e2539f7335e4de..b94604b104dae5a250080db2bfc3c94c0c9c036a 100644 --- a/src/mol-model/structure/export/mmcif.ts +++ b/src/mol-model/structure/export/mmcif.ts @@ -16,7 +16,7 @@ import { _chem_comp, _pdbx_chem_comp_identifier, _pdbx_nonpoly_scheme } from './ import { Model } from '../model'; import { getUniqueEntityIndicesFromStructures, copy_mmCif_category } from './categories/utils'; import { _struct_asym, _entity_poly, _entity_poly_seq } from './categories/sequence'; -import { ModelPropertyDescriptor } from '../model/properties/custom'; +import { CustomPropertyDescriptor } from '../common/custom-property'; export interface CifExportContext { structures: Structure[], @@ -100,8 +100,32 @@ export const mmCIF_Export_Filters = { } } +function encodeCustomProp(customProp: CustomPropertyDescriptor, ctx: CifExportContext, encoder: CifWriter.Encoder, params: encode_mmCIF_categories_Params) { + if (!customProp.cifExport || customProp.cifExport.categories.length === 0) return; + + const prefix = customProp.cifExport.prefix; + const cats = customProp.cifExport.categories; + + let propCtx = ctx; + if (customProp.cifExport.context) { + const propId = CustomPropertyDescriptor.getUUID(customProp); + if (ctx.cache[propId + '__ctx']) propCtx = ctx.cache[propId + '__ctx']; + else { + propCtx = customProp.cifExport.context(ctx) || ctx; + ctx.cache[propId + '__ctx'] = propCtx; + } + } + for (const cat of cats) { + if (params.skipCategoryNames && params.skipCategoryNames.has(cat.name)) continue; + if (cat.name.indexOf(prefix) !== 0) throw new Error(`Custom category '${cat.name}' name must start with prefix '${prefix}.'`); + encoder.writeCategory(cat, propCtx); + } +} + +type encode_mmCIF_categories_Params = { skipCategoryNames?: Set<string>, exportCtx?: CifExportContext } + /** Doesn't start a data block */ -export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures: Structure | Structure[], params?: { skipCategoryNames?: Set<string>, exportCtx?: CifExportContext }) { +export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures: Structure | Structure[], params?: encode_mmCIF_categories_Params) { const first = Array.isArray(structures) ? structures[0] : (structures as Structure); const models = first.models; if (models.length !== 1) throw 'Can\'t export stucture composed from multiple models.'; @@ -115,26 +139,15 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures: } for (const customProp of models[0].customProperties.all) { - if (!customProp.cifExport || customProp.cifExport.categories.length === 0) continue; - - const prefix = customProp.cifExport.prefix; - const cats = customProp.cifExport.categories; - - let propCtx = ctx; - if (customProp.cifExport.context) { - const propId = ModelPropertyDescriptor.getUUID(customProp); - if (ctx.cache[propId + '__ctx']) propCtx = ctx.cache[propId + '__ctx']; - else { - propCtx = customProp.cifExport.context(ctx) || ctx; - ctx.cache[propId + '__ctx'] = propCtx; - } - } - for (const cat of cats) { - if (_params.skipCategoryNames && _params.skipCategoryNames.has(cat.name)) continue; - if (cat.name.indexOf(prefix) !== 0) throw new Error(`Custom category '${cat.name}' name must start with prefix '${prefix}.'`); - encoder.writeCategory(cat, propCtx); - } + encodeCustomProp(customProp, ctx, encoder, _params); + } + + const structureCustomProps = new Set<CustomPropertyDescriptor>(); + for (const s of ctx.structures) { + if (!s.hasCustomProperties) continue; + for (const p of s.customPropertyDescriptors.all) structureCustomProps.add(p); } + structureCustomProps.forEach(customProp => encodeCustomProp(customProp, ctx, encoder, _params)); } function to_mmCIF(name: string, structure: Structure, asBinary = false) { diff --git a/src/mol-model/structure/model.ts b/src/mol-model/structure/model.ts index df9e98d18fa289780ace42f1326250fd232ce92e..8a1dccfdebe0bda698574a261203ab9741156036 100644 --- a/src/mol-model/structure/model.ts +++ b/src/mol-model/structure/model.ts @@ -9,6 +9,6 @@ import * as Types from './model/types' import { ModelSymmetry } from './model/properties/symmetry' import StructureSequence from './model/properties/sequence' -export * from './model/properties/custom' +export * from './model/properties/custom/indexed' export * from './model/indexing' export { Model, Types, ModelSymmetry, StructureSequence } \ No newline at end of file diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index c777d8d55fe61d75afc5b24f07e58dbfe0b4748e..303048fdde6740ec47502a9c3baee05a0d927dc7 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -10,7 +10,7 @@ import { AtomicHierarchy, AtomicConformation } from './properties/atomic'; import { ModelSymmetry } from './properties/symmetry'; import { CoarseHierarchy, CoarseConformation } from './properties/coarse'; import { Entities } from './properties/common'; -import { CustomProperties } from './properties/custom'; +import { CustomProperties } from '../common/custom-property'; import { SecondaryStructure } from './properties/seconday-structure'; import { SaccharideComponentMap } from '../structure/carbohydrates/constants'; import { ModelFormat } from 'mol-model-formats/structure/format'; diff --git a/src/mol-model/structure/model/properties/custom.ts b/src/mol-model/structure/model/properties/custom.ts deleted file mode 100644 index 9b9ab7c3dc7430388fe7b5bf4a6977c6936454a4..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/model/properties/custom.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -export * from './custom/descriptor' -export * from './custom/collection' -export * from './custom/indexed' \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/custom/chain.ts b/src/mol-model/structure/model/properties/custom/chain.ts deleted file mode 100644 index 4e711c32e7ad450c3d409de7b38169f79b7dbab8..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/model/properties/custom/chain.ts +++ /dev/null @@ -1,91 +0,0 @@ -// /** -// * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. -// * -// * @author David Sehnal <david.sehnal@gmail.com> -// */ - -// import { ChainIndex } from '../../indexing'; -// import { Unit, Structure, StructureElement } from '../../../structure'; -// import { Segmentation } from 'mol-data/int'; -// import { UUID } from 'mol-util'; -// import { CifWriter } from 'mol-io/writer/cif'; - -// export interface ChainCustomProperty<T = any> { -// readonly id: UUID, -// readonly kind: Unit.Kind, -// has(idx: ChainIndex): boolean -// get(idx: ChainIndex): T | undefined -// } - -// export namespace ChainCustomProperty { -// export interface ExportCtx<T> { -// elements: StructureElement[], -// property(index: number): T -// }; - -// function getExportCtx<T>(prop: ChainCustomProperty<T>, structure: Structure): ExportCtx<T> { -// const chainIndex = structure.model.atomicHierarchy.chainAtomSegments.index; -// const elements = getStructureElements(structure, prop); -// return { elements, property: i => prop.get(chainIndex[elements[i].element])! }; -// } - -// export function getCifDataSource<T>(structure: Structure, prop: ChainCustomProperty<T> | undefined, cache: any): CifWriter.Category.Instance['source'][0] { -// if (!prop) return { rowCount: 0 }; -// if (cache && cache[prop.id]) return cache[prop.id]; -// const data = getExportCtx(prop, structure); -// const ret = { data, rowCount: data.elements.length }; -// if (cache) cache[prop.id] = ret; -// return ret; -// } - -// class FromMap<T> implements ChainCustomProperty<T> { -// readonly id = UUID.create(); - -// has(idx: ChainIndex): boolean { -// return this.map.has(idx); -// } - -// get(idx: ChainIndex) { -// return this.map.get(idx); -// } - -// constructor(private map: Map<ChainIndex, T>, public kind: Unit.Kind) { -// } -// } - -// export function fromMap<T>(map: Map<ChainIndex, T>, kind: Unit.Kind) { -// return new FromMap(map, kind); -// } - -// /** -// * Gets all StructureElements that correspond to 1st atoms of residues that have an property assigned. -// * Only works correctly for structures with a single model. -// */ -// export function getStructureElements(structure: Structure, property: ChainCustomProperty) { -// const models = structure.models; -// if (models.length !== 1) throw new Error(`Only works on structures with a single model.`); - -// const seenChains = new Set<ChainIndex>(); -// const unitGroups = structure.unitSymmetryGroups; -// const loci: StructureElement[] = []; - -// for (const unitGroup of unitGroups) { -// const unit = unitGroup.units[0]; -// if (unit.kind !== property.kind) { -// continue; -// } - -// const chains = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, unit.elements); -// while (chains.hasNext) { -// const seg = chains.move(); -// if (!property.has(seg.index) || seenChains.has(seg.index)) continue; - -// seenChains.add(seg.index); -// loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]); -// } -// } - -// loci.sort((x, y) => x.element - y.element); -// return loci; -// } -// } \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/custom/collection.ts b/src/mol-model/structure/model/properties/custom/collection.ts deleted file mode 100644 index e6602fe192108b654abfaaf90667d51a4e8a8482..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/model/properties/custom/collection.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { ModelPropertyDescriptor } from './descriptor' - -export class CustomProperties { - private _list: ModelPropertyDescriptor[] = []; - private _set = new Set<ModelPropertyDescriptor>(); - - get all(): ReadonlyArray<ModelPropertyDescriptor> { - return this._list; - } - - add(desc: ModelPropertyDescriptor<any>) { - if (this._set.has(desc)) return; - - this._list.push(desc); - this._set.add(desc); - } - - has(desc: ModelPropertyDescriptor<any>): boolean { - return this._set.has(desc); - } -} \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/custom/descriptor.ts b/src/mol-model/structure/model/properties/custom/descriptor.ts deleted file mode 100644 index 1d2abb21c712dc17dcfd8129e3cf6faf7abc31c7..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/model/properties/custom/descriptor.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { CifWriter } from 'mol-io/writer/cif' -import { CifExportContext } from '../../../export/mmcif'; -import { QuerySymbolRuntime } from 'mol-script/runtime/query/compiler'; -import { UUID } from 'mol-util'; - -interface ModelPropertyDescriptor<ExportCtx = CifExportContext, Symbols extends { [name: string]: QuerySymbolRuntime } = { }> { - readonly isStatic: boolean, - readonly name: string, - - cifExport?: { - // Prefix enforced during export. - prefix: string, - context?: (ctx: CifExportContext) => ExportCtx | undefined, - categories: CifWriter.Category<ExportCtx>[] - }, - - // TODO: add aliases when lisp-like mol-script is done - symbols?: Symbols -} - -function ModelPropertyDescriptor<Ctx, Desc extends ModelPropertyDescriptor<Ctx>>(desc: Desc) { - return desc; -} - -namespace ModelPropertyDescriptor { - export function getUUID(prop: ModelPropertyDescriptor): UUID { - if (!(prop as any).__key) { - (prop as any).__key = UUID.create22(); - } - return (prop as any).__key; - } -} - -export { ModelPropertyDescriptor } \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/custom/residue.ts b/src/mol-model/structure/model/properties/custom/residue.ts deleted file mode 100644 index 4dd7cb130e9e06d94e2337d1928405faec51422a..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/model/properties/custom/residue.ts +++ /dev/null @@ -1,91 +0,0 @@ -// /** -// * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. -// * -// * @author David Sehnal <david.sehnal@gmail.com> -// */ - -// import { ResidueIndex } from '../../indexing'; -// import { Unit, Structure, StructureElement } from '../../../structure'; -// import { Segmentation } from 'mol-data/int'; -// import { UUID } from 'mol-util'; -// import { CifWriter } from 'mol-io/writer/cif'; - -// export interface ResidueCustomProperty<T = any> { -// readonly id: UUID, -// readonly kind: Unit.Kind, -// has(idx: ResidueIndex): boolean -// get(idx: ResidueIndex): T | undefined -// } - -// export namespace ResidueCustomProperty { -// export interface ExportCtx<T> { -// elements: StructureElement[], -// property(index: number): T -// }; - -// function getExportCtx<T>(prop: ResidueCustomProperty<T>, structure: Structure): ExportCtx<T> { -// const residueIndex = structure.model.atomicHierarchy.residueAtomSegments.index; -// const elements = getStructureElements(structure, prop); -// return { elements, property: i => prop.get(residueIndex[elements[i].element])! }; -// } - -// export function getCifDataSource<T>(structure: Structure, prop: ResidueCustomProperty<T> | undefined, cache: any): CifWriter.Category.Instance['source'][0] { -// if (!prop) return { rowCount: 0 }; -// if (cache && cache[prop.id]) return cache[prop.id]; -// const data = getExportCtx(prop, structure); -// const ret = { data, rowCount: data.elements.length }; -// if (cache) cache[prop.id] = ret; -// return ret; -// } - -// class FromMap<T> implements ResidueCustomProperty<T> { -// readonly id = UUID.create(); - -// has(idx: ResidueIndex): boolean { -// return this.map.has(idx); -// } - -// get(idx: ResidueIndex) { -// return this.map.get(idx); -// } - -// constructor(private map: Map<ResidueIndex, T>, public kind: Unit.Kind) { -// } -// } - -// export function fromMap<T>(map: Map<ResidueIndex, T>, kind: Unit.Kind) { -// return new FromMap(map, kind); -// } - -// /** -// * Gets all StructureElements that correspond to 1st atoms of residues that have an property assigned. -// * Only works correctly for structures with a single model. -// */ -// export function getStructureElements(structure: Structure, property: ResidueCustomProperty) { -// const models = structure.models; -// if (models.length !== 1) throw new Error(`Only works on structures with a single model.`); - -// const seenResidues = new Set<ResidueIndex>(); -// const unitGroups = structure.unitSymmetryGroups; -// const loci: StructureElement[] = []; - -// for (const unitGroup of unitGroups) { -// const unit = unitGroup.units[0]; -// if (unit.kind !== property.kind) { -// continue; -// } - -// const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements); -// while (residues.hasNext) { -// const seg = residues.move(); -// if (!property.has(seg.index) || seenResidues.has(seg.index)) continue; - -// seenResidues.add(seg.index); -// loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]); -// } -// } - -// loci.sort((x, y) => x.element - y.element); -// return loci; -// } -// } \ No newline at end of file diff --git a/src/mol-model/structure/query.ts b/src/mol-model/structure/query.ts index df09d47cb60acb44696a3ebe3ea7d7bb26480c79..8538f0bc795a9043518a5109bf5df6f86609586e 100644 --- a/src/mol-model/structure/query.ts +++ b/src/mol-model/structure/query.ts @@ -9,12 +9,14 @@ import { StructureQuery } from './query/query' export * from './query/context' import * as generators from './query/queries/generators' import * as modifiers from './query/queries/modifiers' +import * as filters from './query/queries/filters' import * as combinators from './query/queries/combinators' import * as internal from './query/queries/internal' import pred from './query/predicates' export const Queries = { generators, + filters, modifiers, combinators, pred, diff --git a/src/mol-model/structure/query/queries/filters.ts b/src/mol-model/structure/query/queries/filters.ts index a64cffc38bf15eae0a037b80153a2ac03b8d6fd4..4b08350f68967e22fa5002cc6babafb37121756a 100644 --- a/src/mol-model/structure/query/queries/filters.ts +++ b/src/mol-model/structure/query/queries/filters.ts @@ -31,6 +31,25 @@ export function pick(query: StructureQuery, pred: QueryPredicate): StructureQuer }; } +export function first(query: StructureQuery): StructureQuery { + return ctx => { + const sel = query(ctx); + const ret = StructureSelection.LinearBuilder(ctx.inputStructure); + if (sel.kind === 'singletons') { + if (sel.structure.elementCount > 0) { + const u = sel.structure.units[0]; + const s = Structure.create([u.getChild(SortedArray.ofSingleton(u.elements[0]))], ctx.inputStructure); + ret.add(s); + } + } else { + if (sel.structures.length > 0) { + ret.add(sel.structures[0]); + } + } + return ret.getSelection(); + }; +} + export interface UnitTypeProperties { atomic?: QueryFn, coarse?: QueryFn } export function getCurrentStructureProperties(ctx: QueryContext, props: UnitTypeProperties, set: Set<any>) { diff --git a/src/mol-model/structure/query/queries/generators.ts b/src/mol-model/structure/query/queries/generators.ts index 34ce3fd620119edf4ce852b80bd19e146f205489..24f49db0f080c80869b55d31efdfa8c481b46ef2 100644 --- a/src/mol-model/structure/query/queries/generators.ts +++ b/src/mol-model/structure/query/queries/generators.ts @@ -168,10 +168,10 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group }; } -function getRingStructure(unit: Unit.Atomic, ring: UnitRing) { +function getRingStructure(unit: Unit.Atomic, ring: UnitRing, inputStructure: Structure) { const elements = new Int32Array(ring.length) as any as ElementIndex[]; for (let i = 0, _i = ring.length; i < _i; i++) elements[i] = unit.elements[ring[i]]; - return Structure.create([unit.getChild(SortedArray.ofSortedArray(elements))]) + return Structure.create([unit.getChild(SortedArray.ofSortedArray(elements))], inputStructure); } export function rings(fingerprints?: ArrayLike<UnitRing.Fingerprint>): StructureQuery { @@ -184,7 +184,7 @@ export function rings(fingerprints?: ArrayLike<UnitRing.Fingerprint>): Structure if (!Unit.isAtomic(u)) continue; for (const r of u.rings.all) { - ret.add(getRingStructure(u, r)); + ret.add(getRingStructure(u, r, ctx.inputStructure)); } } } else { @@ -198,7 +198,7 @@ export function rings(fingerprints?: ArrayLike<UnitRing.Fingerprint>): Structure for (const fp of uniqueFps.array) { if (!rings.byFingerprint.has(fp)) continue; for (const r of rings.byFingerprint.get(fp)!) { - ret.add(getRingStructure(u, rings.all[r])); + ret.add(getRingStructure(u, rings.all[r], ctx.inputStructure)); } } } diff --git a/src/mol-model/structure/query/queries/internal.ts b/src/mol-model/structure/query/queries/internal.ts index 8cf1078110a6c3c929f56bada9d17a1e2ea8d8bf..d451ca422df1647be96601af99d03351f986556c 100644 --- a/src/mol-model/structure/query/queries/internal.ts +++ b/src/mol-model/structure/query/queries/internal.ts @@ -35,7 +35,7 @@ export function atomicSequence(): StructureQuery { units.push(unit); } - return StructureSelection.Singletons(inputStructure, new Structure(units)); + return StructureSelection.Singletons(inputStructure, new Structure(units, inputStructure, )); }; } @@ -54,7 +54,7 @@ export function water(): StructureQuery { if (P.entity.type(l) !== 'water') continue; units.push(unit); } - return StructureSelection.Singletons(inputStructure, new Structure(units)); + return StructureSelection.Singletons(inputStructure, new Structure(units, inputStructure)); }; } @@ -84,7 +84,7 @@ export function atomicHet(): StructureQuery { units.push(unit); } - return StructureSelection.Singletons(inputStructure, new Structure(units)); + return StructureSelection.Singletons(inputStructure, new Structure(units, inputStructure)); }; } @@ -97,6 +97,6 @@ export function spheres(): StructureQuery { if (unit.kind !== Unit.Kind.Spheres) continue; units.push(unit); } - return StructureSelection.Singletons(inputStructure, new Structure(units)); + return StructureSelection.Singletons(inputStructure, new Structure(units, inputStructure)); }; } diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts index c13c370daaa5ea8783845a10d5303611cefd2c99..9fe6201be03c59e39f096f9650b9c08127988e35 100644 --- a/src/mol-model/structure/query/selection.ts +++ b/src/mol-model/structure/query/selection.ts @@ -135,7 +135,7 @@ namespace StructureSelection { const { elements } = unit; for (let i = 0, _i = elements.length; i < _i; i++) { // TODO: optimize this somehow??? - const s = Structure.create([unit.getChild(SortedArray.ofSingleton(elements[i]))]); + const s = Structure.create([unit.getChild(SortedArray.ofSingleton(elements[i]))], sel.source); fn(s, idx++); } } diff --git a/src/mol-model/structure/query/utils/structure-set.ts b/src/mol-model/structure/query/utils/structure-set.ts index 0ab6a9bbfafe296799320aa3f5d22e7f5d6b28b8..4f61dbc78f7223a01597dce742942cf657044e57 100644 --- a/src/mol-model/structure/query/utils/structure-set.ts +++ b/src/mol-model/structure/query/utils/structure-set.ts @@ -80,7 +80,7 @@ export function structureIntersect(sA: Structure, sB: Structure): Structure { } } - return Structure.create(units); + return Structure.create(units, sA.parent || sB.parent); } export function structureSubtract(a: Structure, b: Structure): Structure { @@ -100,5 +100,5 @@ export function structureSubtract(a: Structure, b: Structure): Structure { } } - return Structure.create(units); + return Structure.create(units, a.parent || b.parent); } \ No newline at end of file diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index 8d45b51467ac1f67d2d734e7fd12c70efddbd444..5243b670ff63ffe4c5d13052525010479372ef96 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -26,6 +26,7 @@ import { Vec3, Mat4 } from 'mol-math/linear-algebra'; import { idFactory } from 'mol-util/id-factory'; import { GridLookup3D } from 'mol-math/geometry'; import { UUID } from 'mol-util'; +import { CustomProperties } from '../common/custom-property'; class Structure { /** Maps unit.id to unit */ @@ -34,6 +35,7 @@ class Structure { readonly units: ReadonlyArray<Unit>; private _props: { + parent?: Structure, lookup3d?: StructureLookup3D, links?: InterUnitBonds, crossLinkRestraints?: PairRestraints<CrossLinkRestraint>, @@ -49,7 +51,16 @@ class Structure { transformHash: number, elementCount: number, polymerResidueCount: number, - } = { hashCode: -1, transformHash: -1, elementCount: 0, polymerResidueCount: 0 }; + coordinateSystem: SymmetryOperator, + propertyData?: any, + customProps?: CustomProperties + } = { + hashCode: -1, + transformHash: -1, + elementCount: 0, + polymerResidueCount: 0, + coordinateSystem: SymmetryOperator.Default + }; subsetBuilder(isSorted: boolean) { return new StructureSubsetBuilder(this, isSorted); @@ -60,6 +71,30 @@ class Structure { return this._props.elementCount; } + get hasCustomProperties() { + return !!this._props.customProps && this._props.customProps.all.length > 0; + } + + get customPropertyDescriptors() { + if (!this._props.customProps) this._props.customProps = new CustomProperties(); + return this._props.customProps; + } + + /** + * Property data unique to this instance of the structure. + */ + get currentPropertyData() { + if (!this._props.propertyData) this._props.propertyData = Object.create(null); + return this._props.propertyData; + } + + /** + * Property data of the parent structure if it exists, currentPropertyData otherwise. + */ + get inheritedPropertyData() { + return this.parent ? this.parent.currentPropertyData : this.currentPropertyData; + } + /** Count of all polymer residues in the structure */ get polymerResidueCount() { return this._props.polymerResidueCount; @@ -106,6 +141,14 @@ class Structure { return new Structure.ElementLocationIterator(this); } + get parent() { + return this._props.parent; + } + + get coordinateSystem() { + return this._props.coordinateSystem; + } + get boundary() { return this.lookup3d.boundary; } @@ -174,7 +217,7 @@ class Structure { return SortedArray.has(this.unitMap.get(e.unit.id).elements, e.element); } - constructor(units: ArrayLike<Unit>) { + private initUnits(units: ArrayLike<Unit>) { const map = IntMap.Mutable<Unit>(); let elementCount = 0; let polymerResidueCount = 0; @@ -188,11 +231,18 @@ class Structure { if (u.id < lastId) isSorted = false; lastId = u.id; } - if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap) - this.unitMap = map; - this.units = units as ReadonlyArray<Unit>; + if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap); this._props.elementCount = elementCount; this._props.polymerResidueCount = polymerResidueCount; + return map; + } + + constructor(units: ArrayLike<Unit>, parent: Structure | undefined, coordinateSystem?: SymmetryOperator) { + this.unitMap = this.initUnits(units); + this.units = units as ReadonlyArray<Unit>; + if (parent) this._props.parent = parent; + if (coordinateSystem) this._props.coordinateSystem = coordinateSystem; + else if (parent) this._props.coordinateSystem = parent.coordinateSystem; } } @@ -283,7 +333,7 @@ function getUniqueAtomicResidueIndices(structure: Structure): ReadonlyMap<UUID, } namespace Structure { - export const Empty = new Structure([]); + export const Empty = new Structure([], void 0, void 0); /** Represents a single structure */ export interface Loci { @@ -302,7 +352,9 @@ namespace Structure { return a.structure === b.structure } - export function create(units: ReadonlyArray<Unit>): Structure { return new Structure(units); } + export function create(units: ReadonlyArray<Unit>, parent: Structure | undefined, coordinateSystem?: SymmetryOperator): Structure { + return new Structure(units, parent, coordinateSystem); + } /** * Construct a Structure from a model. @@ -312,7 +364,7 @@ namespace Structure { */ export function ofModel(model: Model): Structure { const chains = model.atomicHierarchy.chainAtomSegments; - const builder = new StructureBuilder(); + const builder = new StructureBuilder(void 0, void 0); for (let c = 0; c < chains.count; c++) { const start = chains.offsets[c]; @@ -381,11 +433,13 @@ namespace Structure { const units: Unit[] = []; for (const u of s.units) { const old = u.conformation.operator; - const op = SymmetryOperator.create(old.name, transform, { id: '', operList: [] }, old.ncsId, old.hkl); + const op = SymmetryOperator.create(old.name, transform, old.assembly, old.ncsId, old.hkl); units.push(u.applyOperator(u.id, op)); } - return new Structure(units); + const cs = s.coordinateSystem; + const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs.assembly, cs.ncsId, cs.hkl), cs); + return new Structure(units, s, newCS); } export class StructureBuilder { @@ -405,15 +459,21 @@ namespace Structure { } getStructure(): Structure { - return create(this.units); + return create(this.units, this.parent, this.coordinateSystem); } get isEmpty() { return this.units.length === 0; } + + constructor(private parent: Structure | undefined, private coordinateSystem: SymmetryOperator | undefined) { + + } } - export function Builder() { return new StructureBuilder(); } + export function Builder(parent: Structure | undefined, coordinateSystem: SymmetryOperator | undefined) { + return new StructureBuilder(parent, coordinateSystem); + } export function hashCode(s: Structure) { return s.hashCode; diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts index 1b917041444704a539124a1d2800354e8fdebb6e..eee13f1b61ee70727fc562a6cc38b9490b674ad3 100644 --- a/src/mol-model/structure/structure/symmetry.ts +++ b/src/mol-model/structure/structure/symmetry.ts @@ -24,7 +24,7 @@ namespace StructureSymmetry { const assembly = ModelSymmetry.findAssembly(models[0], asmName); if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`); - const assembler = Structure.Builder(); + const assembler = Structure.Builder(void 0, SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] })); const queryCtx = new QueryContext(structure); @@ -83,7 +83,12 @@ namespace StructureSymmetry { export function areTransformGroupsEquivalent(a: ReadonlyArray<Unit.SymmetryGroup>, b: ReadonlyArray<Unit.SymmetryGroup>) { if (a.length !== b.length) return false for (let i = 0, il = a.length; i < il; ++i) { + const au = a[i].units, bu = b[i].units; + if (au.length !== bu.length) return false; if (a[i].hashCode !== b[i].hashCode) return false + for (let j = 0, _j = au.length; j < _j; j++) { + if (au[j].conformation !== bu[j].conformation) return false; + } } return true } @@ -132,7 +137,7 @@ function getOperatorsCached333(symmetry: ModelSymmetry) { } function assembleOperators(structure: Structure, operators: ReadonlyArray<SymmetryOperator>) { - const assembler = Structure.Builder(); + const assembler = Structure.Builder(void 0, void 0); const { units } = structure; for (const oper of operators) { for (const unit of units) { @@ -174,7 +179,7 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius const operators = getOperatorsCached333(symmetry); const lookup = structure.lookup3d; - const assembler = Structure.Builder(); + const assembler = Structure.Builder(void 0, void 0); const { units } = structure; const center = Vec3.zero(); diff --git a/src/mol-model/structure/structure/util/subset-builder.ts b/src/mol-model/structure/structure/util/subset-builder.ts index 77c228902fc14c27a75e070df1868498ca68057a..dbdf0cf774c647e4380a1c3a81f0b4ebff1fc8d0 100644 --- a/src/mol-model/structure/structure/util/subset-builder.ts +++ b/src/mol-model/structure/structure/util/subset-builder.ts @@ -90,7 +90,7 @@ export class StructureSubsetBuilder { newUnits[newUnits.length] = child; } - return Structure.create(newUnits); + return Structure.create(newUnits, this.parent); } getStructure() { diff --git a/src/mol-model/structure/structure/util/unique-subset-builder.ts b/src/mol-model/structure/structure/util/unique-subset-builder.ts index bc18e6519217570f2bb6e6b8097e5baa3e2d6f96..8b808e2466b9b768288de4fc22e6ba783f99f0ab 100644 --- a/src/mol-model/structure/structure/util/unique-subset-builder.ts +++ b/src/mol-model/structure/structure/util/unique-subset-builder.ts @@ -85,7 +85,7 @@ export class StructureUniqueSubsetBuilder { newUnits[newUnits.length] = child; } - return Structure.create(newUnits); + return Structure.create(newUnits, this.parent, this.parent.coordinateSystem); } get isEmpty() { diff --git a/src/mol-plugin/behavior/dynamic/labels.ts b/src/mol-plugin/behavior/dynamic/labels.ts index 16b43e187b7f1b41e48ea825119088240df1e457..b3597932ce0e3f68dbfcb74d00c6fc9c8e27a1bf 100644 --- a/src/mol-plugin/behavior/dynamic/labels.ts +++ b/src/mol-plugin/behavior/dynamic/labels.ts @@ -118,7 +118,7 @@ export const SceneLabels = PluginBehavior.create<SceneLabelsProps>({ for (const s of structures) { const rootStructure = getRootStructure(s, state) if (!rootStructure || !SO.Molecule.Structure.is(rootStructure.obj)) continue - if (!state.cellStates.get(s.transform.ref).isHidden) { + if (!s.state.isHidden) { rootStructures.add(rootStructure.obj) } } diff --git a/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts b/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts index 497d1420536422a93cff6f13aded50981b145507..7fc4487aa03f3610b41120f4ce75eb9bbf4d3609 100644 --- a/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts +++ b/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts @@ -56,30 +56,30 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W if (!refs['structure-interaction-group']) { refs['structure-interaction-group'] = builder.to(cell).group(StateTransforms.Misc.CreateGroup, - { label: 'Current Interaction' }, { props: { tag: Tags.Group } }).ref; + { label: 'Current Interaction' }, { tags: Tags.Group }).ref; } // Selections if (!refs[Tags.ResidueSel]) { refs[Tags.ResidueSel] = builder.to(refs['structure-interaction-group']).apply(StateTransforms.Model.StructureSelection, - { query: { } as any, label: 'Residue' }, { props: { tag: Tags.ResidueSel } }).ref; + { query: { } as any, label: 'Residue' }, { tags: Tags.ResidueSel }).ref; } if (!refs[Tags.SurrSel]) { refs[Tags.SurrSel] = builder.to(refs['structure-interaction-group']).apply(StateTransforms.Model.StructureSelection, - { query: { } as any, label: 'Surroundings' }, { props: { tag: Tags.SurrSel } }).ref; + { query: { } as any, label: 'Surroundings' }, { tags: Tags.SurrSel }).ref; } // Representations // TODO: ability to customize how it looks in the behavior params if (!refs[Tags.ResidueRepr]) { refs[Tags.ResidueRepr] = builder.to(refs['structure-interaction-residue-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D, - this.createResVisualParams(cell.obj!.data), { props: { tag: Tags.ResidueRepr } }).ref; + this.createResVisualParams(cell.obj!.data), { tags: Tags.ResidueRepr }).ref; } if (!refs[Tags.SurrRepr]) { refs[Tags.SurrRepr] = builder.to(refs['structure-interaction-surr-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D, - this.createSurVisualParams(cell.obj!.data), { props: { tag: Tags.SurrRepr } }).ref; + this.createSurVisualParams(cell.obj!.data), { tags: Tags.SurrRepr }).ref; } return { state, builder, refs }; @@ -87,7 +87,7 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W private clear(root: StateTransform.Ref) { const state = this.plugin.state.dataState; - const groups = state.select(StateSelection.Generators.byRef(root).subtree().filter(o => o.transform.props.tag === Tags.Group)); + const groups = state.select(StateSelection.Generators.byRef(root).subtree().withTag(Tags.Group)); if (groups.length === 0) return; const update = state.build(); diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts index 42e01f59695ca66d0bcf036babc2364823cd0036..aa98aeff147ac90f77973a1dab5f16b2bbb7d56a 100644 --- a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts +++ b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts @@ -59,11 +59,11 @@ export const InitVolumeStreaming = StateAction.build({ PD.getDefaultValues(VolumeStreaming.createParams(infoObj.data))); if (params.method === 'em') { - behTree.apply(VolumeStreamingVisual, { channel: 'em' }, { props: { isGhost: true } }); + behTree.apply(VolumeStreamingVisual, { channel: 'em' }, { state: { isGhost: true } }); } else { - behTree.apply(VolumeStreamingVisual, { channel: '2fo-fc' }, { props: { isGhost: true } }); - behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(+ve)' }, { props: { isGhost: true } }); - behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(-ve)' }, { props: { isGhost: true } }); + behTree.apply(VolumeStreamingVisual, { channel: '2fo-fc' }, { state: { isGhost: true } }); + behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(+ve)' }, { state: { isGhost: true } }); + behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(-ve)' }, { state: { isGhost: true } }); } await state.updateTree(behTree).runInContext(taskCtx); })); diff --git a/src/mol-plugin/behavior/static/representation.ts b/src/mol-plugin/behavior/static/representation.ts index b84697c9d7237d9ec824fba879929dfdc19f7eca..df0310e777b3f0af68d50952f13a2f4c1279a616 100644 --- a/src/mol-plugin/behavior/static/representation.ts +++ b/src/mol-plugin/behavior/static/representation.ts @@ -7,7 +7,7 @@ import { PluginStateObject as SO } from '../../state/objects'; import { PluginContext } from 'mol-plugin/context'; import { Representation } from 'mol-repr/representation'; -import { State } from 'mol-state'; +import { StateObjectCell } from 'mol-state'; export function registerDefault(ctx: PluginContext) { SyncRepresentationToCanvas(ctx); @@ -21,7 +21,7 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) { const events = ctx.state.dataState.events; events.object.created.subscribe(e => { if (!SO.isRepresentation3D(e.obj)) return; - updateVisibility(e, e.obj.data.repr); + updateVisibility(e.state.cells.get(e.ref)!, e.obj.data.repr); e.obj.data.repr.setState({ syncManually: true }); ctx.canvas3d.add(e.obj.data.repr); @@ -39,7 +39,7 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) { return; } - updateVisibility(e, e.obj.data.repr); + updateVisibility(e.state.cells.get(e.ref)!, e.obj.data.repr); if (e.action === 'recreate') { e.obj.data.repr.setState({ syncManually: true }); } @@ -86,11 +86,11 @@ export function UpdateRepresentationVisibility(ctx: PluginContext) { ctx.state.dataState.events.cell.stateUpdated.subscribe(e => { const cell = e.state.cells.get(e.ref)!; if (!SO.isRepresentation3D(cell.obj)) return; - updateVisibility(e, cell.obj.data.repr); + updateVisibility(cell, cell.obj.data.repr); ctx.canvas3d.requestDraw(true); }) } -function updateVisibility(e: State.ObjectEvent, r: Representation<any>) { - r.setState({ visible: !e.state.cellStates.get(e.ref).isHidden }); +function updateVisibility(cell: StateObjectCell, r: Representation<any>) { + r.setState({ visible: !cell.state.isHidden }); } \ No newline at end of file diff --git a/src/mol-plugin/behavior/static/state.ts b/src/mol-plugin/behavior/static/state.ts index 818a14252d4b8f0a553c8c73ed13f30b17bd71c1..c4111b37104383e19db7b1f1d602892404d82b3d 100644 --- a/src/mol-plugin/behavior/static/state.ts +++ b/src/mol-plugin/behavior/static/state.ts @@ -72,7 +72,8 @@ export function RemoveObject(ctx: PluginContext) { const children = tree.children.get(curr.parent); if (curr.parent === curr.ref || children.size > 1) return remove(state, curr.ref); const parent = tree.transforms.get(curr.parent); - if (!parent.props || !parent.props.isGhost) return remove(state, curr.ref); + // TODO: should this use "cell state" instead? + if (!parent.state.isGhost) return remove(state, curr.ref); curr = parent; } } else { @@ -86,7 +87,7 @@ export function ToggleExpanded(ctx: PluginContext) { } export function ToggleVisibility(ctx: PluginContext) { - PluginCommands.State.ToggleVisibility.subscribe(ctx, ({ state, ref }) => setVisibility(state, ref, !state.cellStates.get(ref).isHidden)); + PluginCommands.State.ToggleVisibility.subscribe(ctx, ({ state, ref }) => setVisibility(state, ref, !state.cells.get(ref)!.state.isHidden)); } function setVisibility(state: State, root: StateTransform.Ref, value: boolean) { diff --git a/src/mol-plugin/command.ts b/src/mol-plugin/command.ts index 05a967dccdd398e0e8815993742a515da3a2a221..01e3375f9552d19827ba5c8c1efeb610f5b9e2b6 100644 --- a/src/mol-plugin/command.ts +++ b/src/mol-plugin/command.ts @@ -53,7 +53,7 @@ export const PluginCommands = { }, Camera: { Reset: PluginCommand<{}>(), - SetSnapshot: PluginCommand<{ snapshot: Camera.Snapshot, durationMs?: number }>(), + SetSnapshot: PluginCommand<{ snapshot: Partial<Camera.Snapshot>, durationMs?: number }>(), Snapshots: { Add: PluginCommand<{ name?: string, description?: string }>(), Remove: PluginCommand<{ id: string }>(), diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 6b2e8e6e44db466294b3d80b08593d3ff86472b9..8826344ea6969826e0145138a8fe9c3a4cfe481d 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -180,7 +180,7 @@ export class PluginContext { const tree = this.state.behaviorState.build(); for (const cat of Object.keys(PluginBehavior.Categories)) { - tree.toRoot().apply(PluginBehavior.CreateCategory, { label: (PluginBehavior.Categories as any)[cat] }, { ref: cat, props: { isLocked: true } }); + tree.toRoot().apply(PluginBehavior.CreateCategory, { label: (PluginBehavior.Categories as any)[cat] }, { ref: cat, state: { isLocked: true } }); } for (const b of this.spec.behaviors) { diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 7650a7447a7f29aa2a4330aa3107b440fc019772..8b9610bba7b1695ba377016a29bc5a396fc25eec 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -12,10 +12,11 @@ import * as ReactDOM from 'react-dom'; import { PluginSpec } from './spec'; import { StateTransforms } from './state/transforms'; import { PluginBehaviors } from './behavior'; -import { AnimateModelIndex, AnimateAssemblyUnwind, AnimateUnitsExplode } from './state/animation/built-in'; +import { AnimateModelIndex, AnimateAssemblyUnwind, AnimateUnitsExplode, AnimateStateInterpolation } from './state/animation/built-in'; import { StateActions } from './state/actions'; import { InitVolumeStreaming, BoxifyVolumeStreaming, CreateVolumeStreamingBehavior } from './behavior/dynamic/volume-streaming/transformers'; import { StructureRepresentationInteraction } from './behavior/dynamic/selection/structure-representation-interaction'; +import { TransformStructureConformation } from './state/actions/structure'; export const DefaultPluginSpec: PluginSpec = { actions: [ @@ -38,6 +39,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Action(StateTransforms.Model.TrajectoryFromPDB), PluginSpec.Action(StateTransforms.Model.StructureAssemblyFromModel), PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel), + PluginSpec.Action(TransformStructureConformation), PluginSpec.Action(StateTransforms.Model.StructureFromModel), PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory), PluginSpec.Action(StateTransforms.Model.UserStructureSelection), @@ -66,6 +68,7 @@ export const DefaultPluginSpec: PluginSpec = { AnimateModelIndex, AnimateAssemblyUnwind, AnimateUnitsExplode, + AnimateStateInterpolation ] } diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts index 0e2a82007cb13fd299de8f7e56cf8fdd302ee28d..25f6c1035fe071c91ca4c1f06bc0577e2717a863 100644 --- a/src/mol-plugin/state.ts +++ b/src/mol-plugin/state.ts @@ -99,7 +99,7 @@ class PluginState { constructor(private plugin: import('./context').PluginContext) { this.snapshots = new PluginStateSnapshotManager(plugin); this.dataState = State.create(new SO.Root({ }), { globalContext: plugin }); - this.behaviorState = State.create(new PluginBehavior.Root({ }), { globalContext: plugin, rootProps: { isLocked: true } }); + this.behaviorState = State.create(new PluginBehavior.Root({ }), { globalContext: plugin, rootState: { isLocked: true } }); this.dataState.behaviors.currentObject.subscribe(o => { if (this.behavior.kind.value === 'data') this.behavior.currentObject.next(o); diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index b9b623d33ce48996797da7efe367930b6235cc22..c32e25c47e360c1e12bb71c36e849c9b50458df4 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/src/mol-plugin/state/actions/structure.ts @@ -146,7 +146,7 @@ const DownloadStructure = StateAction.build({ createStructureTree(ctx, traj, supportProps); } else { for (const download of downloadParams) { - const data = b.toRoot().apply(StateTransforms.Data.Download, download, { props: { isGhost: true } }); + const data = b.toRoot().apply(StateTransforms.Data.Download, download, { state: { isGhost: true } }); const traj = createModelTree(data, src.name === 'url' ? src.params.format : 'cif'); createStructureTree(ctx, traj, supportProps) } @@ -179,14 +179,14 @@ export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary let parsed: StateBuilder.To<PluginStateObject.Molecule.Trajectory> switch (format) { case 'cif': - parsed = b.apply(StateTransforms.Data.ParseCif, void 0, { props: { isGhost: true } }) - .apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { props: { isGhost: true } }) + parsed = b.apply(StateTransforms.Data.ParseCif, void 0, { state: { isGhost: true } }) + .apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { state: { isGhost: true } }) break case 'pdb': - parsed = b.apply(StateTransforms.Model.TrajectoryFromPDB, void 0, { props: { isGhost: true } }); + parsed = b.apply(StateTransforms.Model.TrajectoryFromPDB, void 0, { state: { isGhost: true } }); break case 'gro': - parsed = b.apply(StateTransforms.Model.TrajectoryFromGRO, void 0, { props: { isGhost: true } }); + parsed = b.apply(StateTransforms.Model.TrajectoryFromGRO, void 0, { state: { isGhost: true } }); break default: throw new Error('unsupported format') @@ -287,6 +287,15 @@ export const EnableModelCustomProps = StateAction.build({ return state.updateTree(root); }); +export const TransformStructureConformation = StateAction.build({ + display: { name: 'Transform Conformation' }, + from: PluginStateObject.Molecule.Structure, + params: StateTransforms.Model.TransformStructureConformation.definition.params, +})(({ ref, params, state }) => { + const root = state.build().to(ref).insert(StateTransforms.Model.TransformStructureConformation, params as any); + return state.updateTree(root); +}); + export const StructureFromSelection = StateAction.build({ display: { name: 'Selection Structure', description: 'Create a new Structure from the current selection.' }, from: PluginStateObject.Molecule.Structure, diff --git a/src/mol-plugin/state/animation/built-in.ts b/src/mol-plugin/state/animation/built-in.ts index 308bc1bd6d07f909c3977287cf1a825719cc4b2f..4bbab98fe88601f239b20f6a5b45812123ce7aec 100644 --- a/src/mol-plugin/state/animation/built-in.ts +++ b/src/mol-plugin/state/animation/built-in.ts @@ -121,7 +121,7 @@ export const AnimateAssemblyUnwind = PluginStateAnimation.create({ changed = true; update.to(r) - .apply(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D, { t: 0 }, { props: { tag: 'animate-assembly-unwind' } }); + .apply(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D, { t: 0 }, { tags: 'animate-assembly-unwind' }); } if (!changed) return; @@ -131,7 +131,7 @@ export const AnimateAssemblyUnwind = PluginStateAnimation.create({ async teardown(_, plugin) { const state = plugin.state.dataState; const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3DState) - .filter(c => c.transform.props.tag === 'animate-assembly-unwind')); + .withTag('animate-assembly-unwind')); if (reprs.length === 0) return; const update = state.build(); @@ -191,7 +191,7 @@ export const AnimateUnitsExplode = PluginStateAnimation.create({ changed = true; update.to(r.transform.ref) - .apply(StateTransforms.Representation.ExplodeStructureRepresentation3D, { t: 0 }, { props: { tag: 'animate-units-explode' } }); + .apply(StateTransforms.Representation.ExplodeStructureRepresentation3D, { t: 0 }, { tags: 'animate-units-explode' }); } if (!changed) return; @@ -201,7 +201,7 @@ export const AnimateUnitsExplode = PluginStateAnimation.create({ async teardown(_, plugin) { const state = plugin.state.dataState; const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3DState) - .filter(c => c.transform.props.tag === 'animate-units-explode')); + .withTag('animate-units-explode')); if (reprs.length === 0) return; const update = state.build(); @@ -229,4 +229,54 @@ export const AnimateUnitsExplode = PluginStateAnimation.create({ return { kind: 'next', state: { t: newTime } }; } +}) + +export const AnimateStateInterpolation = PluginStateAnimation.create({ + name: 'built-in.animate-state-interpolation', + display: { name: 'Animate State Interpolation' }, + params: () => ({ + transtionDurationInMs: PD.Numeric(2000, { min: 100, max: 30000, step: 10 }) + }), + initialState: () => ({ }), + async apply(animState, t, ctx) { + + const snapshots = ctx.plugin.state.snapshots.state.entries; + if (snapshots.size < 2) return { kind: 'finished' }; + + // const totalTime = (snapshots.size - 1) * ctx.params.transtionDurationInMs; + const currentT = (t.current % ctx.params.transtionDurationInMs) / ctx.params.transtionDurationInMs; + + let srcIndex = Math.floor(t.current / ctx.params.transtionDurationInMs) % snapshots.size; + let tarIndex = Math.ceil(t.current / ctx.params.transtionDurationInMs); + if (tarIndex === srcIndex) tarIndex++; + tarIndex = tarIndex % snapshots.size; + + const _src = snapshots.get(srcIndex)!.snapshot, _tar = snapshots.get(tarIndex)!.snapshot; + + if (!_src.data || !_tar.data) return { kind: 'skip' }; + + const src = _src.data.tree.transforms, tar = _tar.data.tree.transforms; + + const state = ctx.plugin.state.dataState; + const update = state.build(); + + for (const s of src) { + for (const t of tar) { + if (t.ref !== s.ref) continue; + if (t.version === s.version) continue; + + const e = StateTransform.fromJSON(s), f = StateTransform.fromJSON(t); + + if (!e.transformer.definition.interpolate) { + update.to(s.ref).update(currentT <= 0.5 ? e.params : f.params); + } else { + update.to(s.ref).update(e.transformer.definition.interpolate(e.params, f.params, currentT, ctx.plugin)); + } + } + } + + await PluginCommands.State.Update.dispatch(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); + + return { kind: 'next', state: { } }; + } }) \ 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 5f890882adf2887c5739111615d8b8632638c2c7..01ef8d7ddeb870f743cf4a733fe4efccc1ab3a63 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -6,7 +6,7 @@ */ import { parsePDB } from 'mol-io/reader/pdb/parser'; -import { Vec3 } from 'mol-math/linear-algebra'; +import { Vec3, Mat4, Quat } from 'mol-math/linear-algebra'; import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif'; import { trajectoryFromPDB } from 'mol-model-formats/structure/pdb'; import { Model, ModelSymmetry, Queries, QueryContext, Structure, StructureQuery, StructureSelection as Sel, StructureSymmetry, QueryFn } from 'mol-model/structure'; @@ -25,6 +25,7 @@ import { parseGRO } from 'mol-io/reader/gro/parser'; import { parseMolScript } from 'mol-script/language/parser'; import { transpileMolScript } from 'mol-script/script/mol-script/symbols'; import { shapeFromPly } from 'mol-model-formats/shape/ply'; +import { SymmetryOperator } from 'mol-math/geometry'; export { TrajectoryFromBlob }; export { TrajectoryFromMmCif }; @@ -34,6 +35,7 @@ export { ModelFromTrajectory }; export { StructureFromModel }; export { StructureAssemblyFromModel }; export { StructureSymmetryFromModel }; +export { TransformStructureConformation } export { StructureSelection }; export { UserStructureSelection }; export { StructureComplexElement }; @@ -251,6 +253,52 @@ const StructureSymmetryFromModel = PluginStateTransform.BuiltIn({ } }); +const _translation = Vec3.zero(), _m = Mat4.zero(), _n = Mat4.zero(); +type TransformStructureConformation = typeof TransformStructureConformation +const TransformStructureConformation = PluginStateTransform.BuiltIn({ + name: 'transform-structure-conformation', + display: { name: 'Transform Conformation' }, + from: SO.Molecule.Structure, + to: SO.Molecule.Structure, + params: { + axis: PD.Vec3(Vec3.create(1, 0, 0)), + angle: PD.Numeric(0, { min: -180, max: 180, step: 0.1 }), + translation: PD.Vec3(Vec3.create(0, 0, 0)), + } +})({ + canAutoUpdate() { + return true; + }, + apply({ a, params }) { + // TODO: optimze + + const center = a.data.boundary.sphere.center; + Mat4.fromTranslation(_m, Vec3.negate(_translation, center)); + Mat4.fromTranslation(_n, Vec3.add(_translation, center, params.translation)); + const rot = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * params.angle, Vec3.normalize(Vec3.zero(), params.axis)); + + const m = Mat4.zero(); + Mat4.mul3(m, _n, rot, _m); + + const s = Structure.transform(a.data, m); + const props = { label: `${a.label}`, description: `Transformed` }; + return new SO.Molecule.Structure(s, props); + }, + interpolate(src, tar, t) { + // TODO: optimize + const u = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * src.angle, Vec3.normalize(Vec3.zero(), src.axis)); + Mat4.setTranslation(u, src.translation); + const v = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * tar.angle, Vec3.normalize(Vec3.zero(), tar.axis)); + Mat4.setTranslation(v, tar.translation); + const m = SymmetryOperator.slerp(Mat4.zero(), u, v, t); + const rot = Mat4.getRotation(Quat.zero(), m); + const axis = Vec3.zero(); + const angle = Quat.getAxisAngle(axis, rot); + const translation = Mat4.getTranslation(Vec3.zero(), m); + return { axis, angle, translation }; + } +}); + type StructureSelection = typeof StructureSelection const StructureSelection = PluginStateTransform.BuiltIn({ name: 'structure-selection', diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts index 2b71e273fbb64a2aa7db09922100f7aa4f4fd06c..f0873a69e5a3589938802a013ea1faaf89d58cba 100644 --- a/src/mol-plugin/state/transforms/representation.ts +++ b/src/mol-plugin/state/transforms/representation.ts @@ -15,7 +15,7 @@ import { BuiltInVolumeRepresentationsName } from 'mol-repr/volume/registry'; import { VolumeParams } from 'mol-repr/volume/representation'; import { StateTransformer } from 'mol-state'; import { Task } from 'mol-task'; -import { BuiltInColorThemeName, ColorTheme } from 'mol-theme/color'; +import { BuiltInColorThemeName, ColorTheme, BuiltInColorThemes } from 'mol-theme/color'; import { BuiltInSizeThemeName, SizeTheme } from 'mol-theme/size'; import { createTheme, ThemeRegistryContext } from 'mol-theme/theme'; import { ParamDefinition as PD } from 'mol-util/param-definition'; @@ -191,6 +191,19 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({ await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx); return StateTransformer.UpdateResult.Updated; }); + }, + interpolate(src, tar, t) { + if (src.colorTheme.name !== 'uniform' || tar.colorTheme.name !== 'uniform') { + return t <= 0.5 ? src : tar; + } + BuiltInColorThemes + const from = src.colorTheme.params.value as Color, to = tar.colorTheme.params.value as Color; + const value = Color.interpolate(from, to, t); + return { + type: t <= 0.5 ? src.type : tar.type, + colorTheme: { name: 'uniform', params: { value } }, + sizeTheme: t <= 0.5 ? src.sizeTheme : tar.sizeTheme, + }; } }); diff --git a/src/mol-plugin/ui/base.tsx b/src/mol-plugin/ui/base.tsx index d4ae0171d84882a5f0c9fb6e2152b4fbfd5e2d83..afa47c47d7cf31e9b27ba82b054fbbcb94ecebcb 100644 --- a/src/mol-plugin/ui/base.tsx +++ b/src/mol-plugin/ui/base.tsx @@ -58,4 +58,7 @@ export abstract class PurePluginUIComponent<P = {}, S = {}, SS = {}> extends Rea this.plugin = context; if (this.init) this.init(); } -} \ No newline at end of file +} + +export type _Props<C extends React.Component> = C extends React.Component<infer P> ? P : never +export type _State<C extends React.Component> = C extends React.Component<any, infer S> ? S : never \ No newline at end of file diff --git a/src/mol-plugin/ui/controls/parameters.tsx b/src/mol-plugin/ui/controls/parameters.tsx index 72135f24d2a4cca1b01a47a69b98830547f9e292..e179f8cfabfef097d13bee5106eaad2819ee8e15 100644 --- a/src/mol-plugin/ui/controls/parameters.tsx +++ b/src/mol-plugin/ui/controls/parameters.tsx @@ -16,6 +16,7 @@ import * as React from 'react'; import LineGraphComponent from './line-graph/line-graph-component'; import { Slider, Slider2 } from './slider'; import { NumericInput, IconButton } from './common'; +import { _Props, _State } from '../base'; export interface ParameterControlsProps<P extends PD.Params = PD.Params> { params: P, @@ -513,9 +514,6 @@ export class MappedControl extends React.PureComponent<ParamProps<PD.Mapped<any> } } -type _Props<C extends React.Component> = C extends React.Component<infer P> ? P : never -type _State<C extends React.Component> = C extends React.Component<any, infer S> ? S : never - class ObjectListEditor extends React.PureComponent<{ params: PD.Params, value: object, isUpdate?: boolean, apply: (value: any) => void, isDisabled?: boolean }, { params: PD.Params, value: object, current: object }> { state = { params: {}, value: void 0 as any, current: void 0 as any }; diff --git a/src/mol-plugin/ui/state/tree.tsx b/src/mol-plugin/ui/state/tree.tsx index f2c4ac4f805505441a4f14a1b9e387b64cdf1a5a..16293756d3cb72c941337869271fc52a0a629e0c 100644 --- a/src/mol-plugin/ui/state/tree.tsx +++ b/src/mol-plugin/ui/state/tree.tsx @@ -6,9 +6,9 @@ import * as React from 'react'; import { PluginStateObject } from 'mol-plugin/state/objects'; -import { State, StateObject, StateTransform } from 'mol-state' +import { State, StateObject, StateTransform, StateObjectCell } from 'mol-state' import { PluginCommands } from 'mol-plugin/command'; -import { PluginUIComponent } from '../base'; +import { PluginUIComponent, _Props, _State } from '../base'; import { StateObjectActions } from './actions'; export class StateTree extends PluginUIComponent<{ state: State }, { showActions: boolean }> { @@ -37,74 +37,75 @@ export class StateTree extends PluginUIComponent<{ state: State }, { showActions if (this.state.showActions) { return <StateObjectActions state={this.props.state} nodeRef={ref} hideHeader={true} /> } - return <StateTreeNode state={this.props.state} nodeRef={ref} depth={0} />; + return <StateTreeNode cell={this.props.state.cells.get(ref)!} depth={0} />; } } -class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State, depth: number }, { state: State, isCollapsed: boolean }> { +class StateTreeNode extends PluginUIComponent<{ cell: StateObjectCell, depth: number }, { isCollapsed: boolean }> { is(e: State.ObjectEvent) { - return e.ref === this.props.nodeRef && e.state === this.props.state; + return e.ref === this.ref && e.state === this.props.cell.parent; } - get cellState() { - return this.props.state.cellStates.get(this.props.nodeRef); + get ref() { + return this.props.cell.transform.ref; } componentDidMount() { this.subscribe(this.plugin.events.state.cell.stateUpdated, e => { - if (this.is(e) && e.state.transforms.has(this.props.nodeRef)) { - this.setState({ isCollapsed: e.cellState.isCollapsed }); + if (this.props.cell === e.cell && this.is(e) && e.state.cells.has(this.ref)) { + this.forceUpdate(); + // if (!!this.props.cell.state.isCollapsed !== this.state.isCollapsed) { + // this.setState({ isCollapsed: !!e.cell.state.isCollapsed }); + // } } }); this.subscribe(this.plugin.events.state.cell.created, e => { - if (this.props.state === e.state && this.props.nodeRef === e.cell.transform.parent) { + if (this.props.cell.parent === e.state && this.ref === e.cell.transform.parent) { this.forceUpdate(); } }); this.subscribe(this.plugin.events.state.cell.removed, e => { - if (this.props.state === e.state && this.props.nodeRef === e.parent) { + if (this.props.cell.parent === e.state && this.ref === e.parent) { this.forceUpdate(); } }); } state = { - isCollapsed: this.props.state.cellStates.get(this.props.nodeRef).isCollapsed, - state: this.props.state + isCollapsed: !!this.props.cell.state.isCollapsed } - static getDerivedStateFromProps(props: { nodeRef: string, state: State }, state: { state: State, isCollapsed: boolean }) { - if (props.state === state.state) return null; - return { - isCollapsed: props.state.cellStates.get(props.nodeRef).isCollapsed, - state: props.state - }; + static getDerivedStateFromProps(props: _Props<StateTreeNode>, state: _State<StateTreeNode>): _State<StateTreeNode> | null { + if (!!props.cell.state.isCollapsed === state.isCollapsed) return null; + return { isCollapsed: !!props.cell.state.isCollapsed }; } render() { - const cell = this.props.state.cells.get(this.props.nodeRef); - if (!cell || cell.obj === StateObject.Null) return null; + const cell = this.props.cell; + if (!cell || cell.obj === StateObject.Null || !cell.parent.tree.transforms.has(cell.transform.ref)) { + return null; + } - const cellState = this.cellState; - const showLabel = cell.status !== 'ok' || !cell.transform.props || !cell.transform.props.isGhost; - const children = this.props.state.tree.children.get(this.props.nodeRef); + const cellState = cell.state; + const showLabel = cell.status !== 'ok' || !cell.state.isGhost; + const children = cell.parent.tree.children.get(this.ref); const newDepth = showLabel ? this.props.depth + 1 : this.props.depth; if (!showLabel) { if (children.size === 0) return null; return <div style={{ display: cellState.isCollapsed ? 'none' : 'block' }}> - {children.map(c => <StateTreeNode state={this.props.state} nodeRef={c!} key={c} depth={newDepth} />)} + {children.map(c => <StateTreeNode cell={cell.parent.cells.get(c!)!} key={c} depth={newDepth} />)} </div>; } return <> - <StateTreeNodeLabel nodeRef={this.props.nodeRef} state={this.props.state} depth={this.props.depth} /> + <StateTreeNodeLabel cell={cell} depth={this.props.depth} /> {children.size === 0 ? void 0 : <div style={{ display: cellState.isCollapsed ? 'none' : 'block' }}> - {children.map(c => <StateTreeNode state={this.props.state} nodeRef={c!} key={c} depth={newDepth} />)} + {children.map(c => <StateTreeNode cell={cell.parent.cells.get(c!)!} key={c} depth={newDepth} />)} </div> } </>; @@ -112,11 +113,15 @@ class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State, d } class StateTreeNodeLabel extends PluginUIComponent< - { nodeRef: string, state: State, depth: number }, - { state: State, isCurrent: boolean, isCollapsed: boolean /*, updaterCollapsed: boolean */ }> { + { cell: StateObjectCell, depth: number }, + { isCurrent: boolean, isCollapsed: boolean }> { is(e: State.ObjectEvent) { - return e.ref === this.props.nodeRef && e.state === this.props.state; + return e.ref === this.ref && e.state === this.props.cell.parent; + } + + get ref() { + return this.props.cell.transform.ref; } componentDidMount() { @@ -126,70 +131,66 @@ class StateTreeNodeLabel extends PluginUIComponent< this.subscribe(this.plugin.state.behavior.currentObject, e => { if (!this.is(e)) { - if (this.state.isCurrent && e.state.transforms.has(this.props.nodeRef)) { - this.setState({ isCurrent: this.props.state.current === this.props.nodeRef }); + if (this.state.isCurrent && e.state.transforms.has(this.ref)) { + this.setState({ isCurrent: this.props.cell.parent.current === this.ref }); } return; } - if (e.state.transforms.has(this.props.nodeRef)) { + if (e.state.transforms.has(this.ref)) { this.setState({ - isCurrent: this.props.state.current === this.props.nodeRef, - isCollapsed: this.props.state.cellStates.get(this.props.nodeRef).isCollapsed + isCurrent: this.props.cell.parent.current === this.ref, + isCollapsed: !!this.props.cell.state.isCollapsed }); } }); } state = { - isCurrent: this.props.state.current === this.props.nodeRef, - isCollapsed: this.props.state.cellStates.get(this.props.nodeRef).isCollapsed, - state: this.props.state, - // updaterCollapsed: true + isCurrent: this.props.cell.parent.current === this.ref, + isCollapsed: !!this.props.cell.state.isCollapsed } - static getDerivedStateFromProps(props: { nodeRef: string, state: State }, state: { state: State, isCurrent: boolean, isCollapsed: boolean }) { - if (props.state === state.state) return null; - return { - isCurrent: props.state.current === props.nodeRef, - isCollapsed: props.state.cellStates.get(props.nodeRef).isCollapsed, - state: props.state, - updaterCollapsed: true - }; + static getDerivedStateFromProps(props: _Props<StateTreeNodeLabel>, state: _State<StateTreeNodeLabel>): _State<StateTreeNodeLabel> | null { + const isCurrent = props.cell.parent.current === props.cell.transform.ref; + const isCollapsed = !!props.cell.state.isCollapsed; + + if (state.isCollapsed === isCollapsed && state.isCurrent === isCurrent) return null; + return { isCurrent, isCollapsed }; } setCurrent = (e: React.MouseEvent<HTMLElement>) => { e.preventDefault(); e.currentTarget.blur(); - PluginCommands.State.SetCurrentObject.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef }); + PluginCommands.State.SetCurrentObject.dispatch(this.plugin, { state: this.props.cell.parent, ref: this.ref }); } remove = (e: React.MouseEvent<HTMLElement>) => { e.preventDefault(); - PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef, removeParentGhosts: true }); + PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.props.cell.parent, ref: this.ref, removeParentGhosts: true }); } toggleVisible = (e: React.MouseEvent<HTMLElement>) => { e.preventDefault(); - PluginCommands.State.ToggleVisibility.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef }); + PluginCommands.State.ToggleVisibility.dispatch(this.plugin, { state: this.props.cell.parent, ref: this.ref }); e.currentTarget.blur(); } toggleExpanded = (e: React.MouseEvent<HTMLElement>) => { e.preventDefault(); - PluginCommands.State.ToggleExpanded.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef }); + PluginCommands.State.ToggleExpanded.dispatch(this.plugin, { state: this.props.cell.parent, ref: this.ref }); e.currentTarget.blur(); } highlight = (e: React.MouseEvent<HTMLElement>) => { e.preventDefault(); - PluginCommands.State.Highlight.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef }); + PluginCommands.State.Highlight.dispatch(this.plugin, { state: this.props.cell.parent, ref: this.ref }); e.currentTarget.blur(); } clearHighlight = (e: React.MouseEvent<HTMLElement>) => { e.preventDefault(); - PluginCommands.State.ClearHighlight.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef }); + PluginCommands.State.ClearHighlight.dispatch(this.plugin, { state: this.props.cell.parent, ref: this.ref }); e.currentTarget.blur(); } @@ -201,12 +202,11 @@ class StateTreeNodeLabel extends PluginUIComponent< // } render() { - const n = this.props.state.transforms.get(this.props.nodeRef)!; - const cell = this.props.state.cells.get(this.props.nodeRef); + const cell = this.props.cell; + const n = cell.transform; if (!cell) return null; - const isCurrent = this.is(this.props.state.behaviors.currentObject.value); - + const isCurrent = this.state.isCurrent; // this.is(cell.parent.behaviors.currentObject.value); let label: any; if (cell.status === 'pending' || cell.status === 'processing') { @@ -226,8 +226,8 @@ class StateTreeNodeLabel extends PluginUIComponent< } } - const children = this.props.state.tree.children.get(this.props.nodeRef); - const cellState = this.props.state.cellStates.get(this.props.nodeRef); + const children = cell.parent.tree.children.get(this.ref); + const cellState = cell.state; const visibility = <button onClick={this.toggleVisible} className={`msp-btn msp-btn-link msp-tree-visibility${cellState.isHidden ? ' msp-tree-visibility-hidden' : ''}`}> <span className='msp-icon msp-icon-visual-visibility' /> @@ -244,7 +244,7 @@ class StateTreeNodeLabel extends PluginUIComponent< {children.size > 0 && <button onClick={this.toggleExpanded} className='msp-btn msp-btn-link msp-tree-toggle-exp-button'> <span className={`msp-icon msp-icon-${cellState.isCollapsed ? 'expand' : 'collapse'}`} /> </button>} - {!cell.transform.props.isLocked && <button onClick={this.remove} className='msp-btn msp-btn-link msp-tree-remove-button'> + {!cell.state.isLocked && <button onClick={this.remove} className='msp-btn msp-btn-link msp-tree-remove-button'> <span className='msp-icon msp-icon-remove' /> </button>}{visibility} </div>; diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index b54a84c252131a13e1af0c16612e026a309a731b..89e7adc7b8f1fecb5075cc92a5e578e3957c5ee4 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/src/mol-repr/structure/units-representation.ts @@ -65,7 +65,6 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length }) } } else if (structure && !Structure.areEquivalent(structure, _structure)) { - // console.log(label, 'structure not equivalent') // Tries to re-use existing visuals for the groups of the new structure. // Creates additional visuals if needed, destroys left-over visuals. _groups = structure.unitSymmetryGroups; diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index e0b0f76af4b985392a93fba8a829e02c17c20cb5..bad9e645c3e32c8137c6ef0438b9bdc2219b06ae 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/src/mol-repr/structure/units-visual.ts @@ -121,8 +121,12 @@ export function UnitsVisual<G extends Geometry, P extends UnitsParams & Geometry } // check if the conformation of unit.model has changed - if (Unit.conformationId(newStructureGroup.group.units[0]) !== Unit.conformationId(currentStructureGroup.group.units[0])) { + // if (Unit.conformationId(newStructureGroup.group.units[0]) !== Unit.conformationId(currentStructureGroup.group.units[0])) { + if (Unit.conformationId(newStructureGroup.group.units[0]) !== Unit.conformationId(currentStructureGroup.group.units[0]) + // TODO: this needs more attention + || newStructureGroup.group.units[0].conformation !== currentStructureGroup.group.units[0].conformation) { // console.log('new conformation') + updateState.updateTransform = true; updateState.createGeometry = true } diff --git a/src/mol-script/language/symbol-table/structure-query.ts b/src/mol-script/language/symbol-table/structure-query.ts index 4f9f7377efb220f0dbb65717677675540095a9d1..f7d81aa44419493271ac210e149e5b3fb28f2e0e 100644 --- a/src/mol-script/language/symbol-table/structure-query.ts +++ b/src/mol-script/language/symbol-table/structure-query.ts @@ -167,6 +167,10 @@ const filter = { test: Argument(Type.Bool) }), Types.ElementSelectionQuery, 'Pick all atom sets that satisfy the test.'), + first: symbol(Arguments.Dictionary({ + 0: Argument(Types.ElementSelectionQuery) + }), Types.ElementSelectionQuery, 'Take the 1st atom set in the sequence.'), + withSameAtomProperties: symbol(Arguments.Dictionary({ 0: Argument(Types.ElementSelectionQuery), source: Argument(Types.ElementSelectionQuery), diff --git a/src/mol-script/runtime/query/compiler.ts b/src/mol-script/runtime/query/compiler.ts index ce49a29c3a640db42c317b06f302f52d339dbd34..28b96cb5275dda8bb12fa12f8b03eaf49d09af5e 100644 --- a/src/mol-script/runtime/query/compiler.ts +++ b/src/mol-script/runtime/query/compiler.ts @@ -5,7 +5,7 @@ */ import Expression from '../../language/expression'; -import { QueryContext, QueryFn, Structure, ModelPropertyDescriptor } from 'mol-model/structure'; +import { QueryContext, QueryFn, Structure, CustomPropertyDescriptor } from 'mol-model/structure'; import { MSymbol } from '../../language/symbol'; export class QueryRuntimeTable { @@ -18,7 +18,7 @@ export class QueryRuntimeTable { this.map.set(runtime.symbol.id, runtime); } - addCustomProp(desc: ModelPropertyDescriptor<any>) { + addCustomProp(desc: CustomPropertyDescriptor<any>) { if (!desc.symbols) return; for (const k of Object.keys(desc.symbols)) { diff --git a/src/mol-script/runtime/query/table.ts b/src/mol-script/runtime/query/table.ts index ba1072a587beb9c0c8ed2bf3b83dac130894b7f8..4ddbdb8b1945619c723c187001858a952a489ad8 100644 --- a/src/mol-script/runtime/query/table.ts +++ b/src/mol-script/runtime/query/table.ts @@ -186,6 +186,9 @@ const symbols = [ C(MolScript.structureQuery.slot.element, (ctx, _) => ctx.element), // C(MolScript.structureQuery.slot.elementSetReduce, (ctx, _) => ctx.element), + // ============= FILTERS ================ + D(MolScript.structureQuery.filter.first, (ctx, xs) => Queries.filters.first(xs[0] as any)(ctx)), + // ============= GENERATORS ================ D(MolScript.structureQuery.generator.atomGroups, (ctx, xs) => Queries.generators.atoms({ entityTest: xs['entity-test'], diff --git a/src/mol-script/script/mol-script/symbols.ts b/src/mol-script/script/mol-script/symbols.ts index 0c4826f7ffc685e85a84b9617b83188def6a6e06..9bd1119533c764200a1ab49b9e0f2e13faed0738 100644 --- a/src/mol-script/script/mol-script/symbols.ts +++ b/src/mol-script/script/mol-script/symbols.ts @@ -148,6 +148,7 @@ export const SymbolTable = [ [ 'Filters', Alias(MolScript.structureQuery.filter.pick, 'sel.atom.pick'), + Alias(MolScript.structureQuery.filter.first, 'sel.atom.first'), Alias(MolScript.structureQuery.filter.withSameAtomProperties, 'sel.atom.with-same-atom-properties'), Alias(MolScript.structureQuery.filter.intersectedBy, 'sel.atom.intersected-by'), Alias(MolScript.structureQuery.filter.within, 'sel.atom.within'), diff --git a/src/mol-state/manager.ts b/src/mol-state/manager.ts deleted file mode 100644 index 0042b15a93f4184628d575672d2570f63ac64dc6..0000000000000000000000000000000000000000 --- a/src/mol-state/manager.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -// TODO manage snapshots etc \ No newline at end of file diff --git a/src/mol-state/object.ts b/src/mol-state/object.ts index b74efcf5ee78f98572acf08c549929d32282932d..dd4c5c1d34261e53c36614f9ffca1c8c86543c44 100644 --- a/src/mol-state/object.ts +++ b/src/mol-state/object.ts @@ -19,7 +19,7 @@ interface StateObject<D = any, T extends StateObject.Type = StateObject.Type<any readonly label: string, readonly description?: string, // assigned by reconciler to be StateTransform.props.tag - readonly tag?: string + readonly tags?: string[] } namespace StateObject { @@ -56,12 +56,15 @@ namespace StateObject { } interface StateObjectCell<T extends StateObject = StateObject, F extends StateTransform<StateTransformer<any, T, any>> = StateTransform<StateTransformer<any, T, any>>> { + parent: State, + transform: F, // Which object was used as a parent to create data in this cell sourceRef: StateTransform.Ref | undefined, status: StateObjectCell.Status, + state: StateTransform.State, params: { definition: ParamDefinition.Params, @@ -79,24 +82,6 @@ namespace StateObjectCell { export type Obj<C extends StateObjectCell> = C extends StateObjectCell<infer T> ? T : never export type Transform<C extends StateObjectCell> = C extends StateObjectCell<any, infer T> ? T : never - - export interface State { - isHidden: boolean, - isCollapsed: boolean - } - - export const DefaultState: State = { isHidden: false, isCollapsed: false }; - - export function areStatesEqual(a: State, b: State) { - return a.isHidden !== b.isHidden || a.isCollapsed !== b.isCollapsed; - } - - export function isStateChange(a: State, b?: Partial<State>) { - if (!b) return false; - if (typeof b.isCollapsed !== 'undefined' && a.isCollapsed !== b.isCollapsed) return true; - if (typeof b.isHidden !== 'undefined' && a.isHidden !== b.isHidden) return true; - return false; - } } // TODO: improve the API? diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 66563660ade1dd87bd95a428fd867f2d20618baa..6ffb8ff6480353dec56a2d1a6692e573a3314f71 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -33,7 +33,7 @@ class State { readonly globalContext: unknown = void 0; readonly events = { cell: { - stateUpdated: this.ev<State.ObjectEvent & { cellState: StateObjectCell.State }>(), + stateUpdated: this.ev<State.ObjectEvent & { cell: StateObjectCell }>(), created: this.ev<State.ObjectEvent & { cell: StateObjectCell }>(), removed: this.ev<State.ObjectEvent & { parent: StateTransform.Ref }>(), }, @@ -55,7 +55,6 @@ class State { get tree(): StateTree { return this._tree; } get transforms() { return (this._tree as StateTree).transforms; } - get cellStates() { return (this._tree as StateTree).cellStates; } get current() { return this.behaviors.currentObject.value.ref; } build() { return new StateBuilder.Root(this.tree, this); } @@ -76,13 +75,15 @@ class State { this.behaviors.currentObject.next({ state: this, ref }); } - updateCellState(ref: StateTransform.Ref, stateOrProvider: ((old: StateObjectCell.State) => Partial<StateObjectCell.State>) | Partial<StateObjectCell.State>) { - const update = typeof stateOrProvider === 'function' - ? stateOrProvider(this.tree.cellStates.get(ref)) - : stateOrProvider; + updateCellState(ref: StateTransform.Ref, stateOrProvider: ((old: StateTransform.State) => Partial<StateTransform.State>) | Partial<StateTransform.State>) { + const cell = this.cells.get(ref); + if (!cell) return; - if (this._tree.updateCellState(ref, update)) { - this.events.cell.stateUpdated.next({ state: this, ref, cellState: this.tree.cellStates.get(ref) }); + const update = typeof stateOrProvider === 'function' ? stateOrProvider(cell.state) : stateOrProvider; + + if (StateTransform.assignState(cell.state, update)) { + cell.transform = this._tree.assignState(cell.transform.ref, update); + this.events.cell.stateUpdated.next({ state: this, ref, cell }); } } @@ -165,10 +166,6 @@ class State { if (updated) this.events.changed.next(); this.events.isUpdating.next(false); - - for (const ref of ctx.stateChanges) { - this.events.cell.stateUpdated.next({ state: this, ref, cellState: this.tree.cellStates.get(ref) }); - } } } @@ -189,7 +186,6 @@ class State { spine: this.spine, results: [], - stateChanges: [], options: { ...StateUpdateDefaultOptions, ...options }, @@ -203,16 +199,18 @@ class State { return ctx; } - constructor(rootObject: StateObject, params?: { globalContext?: unknown, rootProps?: StateTransform.Props }) { - this._tree = StateTree.createEmpty(StateTransform.createRoot(params && params.rootProps)).asTransient(); + constructor(rootObject: StateObject, params?: { globalContext?: unknown, rootState?: StateTransform.State }) { + this._tree = StateTree.createEmpty(StateTransform.createRoot(params && params.rootState)).asTransient(); const tree = this._tree; const root = tree.root; (this.cells as Map<StateTransform.Ref, StateObjectCell>).set(root.ref, { + parent: this, transform: root, sourceRef: void 0, obj: rootObject, status: 'ok', + state: { ...root.state }, errorText: void 0, params: { definition: {}, @@ -245,7 +243,7 @@ namespace State { doNotUpdateCurrent: boolean } - export function create(rootObject: StateObject, params?: { globalContext?: unknown, rootProps?: StateTransform.Props }) { + export function create(rootObject: StateObject, params?: { globalContext?: unknown, rootState?: StateTransform.State }) { return new State(rootObject, params); } } @@ -271,7 +269,6 @@ interface UpdateContext { spine: StateTreeSpine.Impl, results: UpdateNodeResult[], - stateChanges: StateTransform.Ref[], // suppress timing messages options: State.UpdateOptions, @@ -319,12 +316,6 @@ async function update(ctx: UpdateContext) { roots = findUpdateRoots(ctx.cells, ctx.tree); } - let newCellStates: StateTree.CellStates; - if (!ctx.editInfo) { - newCellStates = ctx.tree.cellStatesSnapshot(); - syncOldStates(ctx); - } - // Init empty cells where not present // this is done in "pre order", meaning that "parents" will be created 1st. const addedCells = initCells(ctx, roots); @@ -353,7 +344,7 @@ async function update(ctx: UpdateContext) { // Sync cell states if (!ctx.editInfo) { - syncNewStates(ctx, newCellStates!); + syncNewStates(ctx); } let newCurrent: StateTransform.Ref | undefined = ctx.newCurrent; @@ -363,7 +354,7 @@ async function update(ctx: UpdateContext) { ctx.parent.events.object.created.next({ state: ctx.parent, ref: update.ref, obj: update.obj! }); if (!ctx.newCurrent) { const transform = ctx.tree.transforms.get(update.ref); - if (!(transform.props && transform.props.isGhost) && update.obj !== StateObject.Null) newCurrent = update.ref; + if (!transform.state.isGhost && update.obj !== StateObject.Null) newCurrent = update.ref; } } else if (update.action === 'updated') { ctx.parent.events.object.updated.next({ state: ctx.parent, ref: update.ref, action: 'in-place', obj: update.obj }); @@ -415,25 +406,14 @@ function findDeletes(ctx: UpdateContext): Ref[] { return deleteCtx.deletes; } -function syncOldStatesVisitor(n: StateTransform, tree: StateTree, oldState: StateTree.CellStates) { - if (oldState.has(n.ref)) { - (tree as TransientTree).updateCellState(n.ref, oldState.get(n.ref)); - } -} -function syncOldStates(ctx: UpdateContext) { - StateTree.doPreOrder(ctx.tree, ctx.tree.root, ctx.oldTree.cellStates, syncOldStatesVisitor); +function syncNewStatesVisitor(n: StateTransform, tree: StateTree, ctx: UpdateContext) { + const cell = ctx.cells.get(n.ref); + if (!cell || !StateTransform.syncState(cell.state, n.state)) return; + ctx.parent.events.cell.stateUpdated.next({ state: ctx.parent, ref: n.ref, cell }); } -function syncNewStatesVisitor(n: StateTransform, tree: StateTree, ctx: { newState: StateTree.CellStates, changes: StateTransform.Ref[] }) { - if (ctx.newState.has(n.ref)) { - const changed = (tree as TransientTree).updateCellState(n.ref, ctx.newState.get(n.ref)); - if (changed) { - ctx.changes.push(n.ref); - } - } -} -function syncNewStates(ctx: UpdateContext, newState: StateTree.CellStates) { - StateTree.doPreOrder(ctx.tree, ctx.tree.root, { newState, changes: ctx.stateChanges }, syncNewStatesVisitor); +function syncNewStates(ctx: UpdateContext) { + StateTree.doPreOrder(ctx.tree, ctx.tree.root, ctx, syncNewStatesVisitor); } function setCellStatus(ctx: UpdateContext, ref: Ref, status: StateObjectCell.Status, errorText?: string) { @@ -441,7 +421,7 @@ function setCellStatus(ctx: UpdateContext, ref: Ref, status: StateObjectCell.Sta const changed = cell.status !== status; cell.status = status; cell.errorText = errorText; - if (changed) ctx.parent.events.cell.stateUpdated.next({ state: ctx.parent, ref, cellState: ctx.tree.cellStates.get(ref) }); + if (changed) ctx.parent.events.cell.stateUpdated.next({ state: ctx.parent, ref, cell }); } function initCellStatusVisitor(t: StateTransform, _: any, ctx: UpdateContext) { @@ -462,9 +442,11 @@ function initCellsVisitor(transform: StateTransform, _: any, { ctx, added }: Ini } const cell: StateObjectCell = { + parent: ctx.parent, transform, sourceRef: void 0, status: 'pending', + state: { ...transform.state }, errorText: void 0, params: void 0, cache: void 0 @@ -505,7 +487,7 @@ function _findNewCurrent(tree: StateTree, ref: Ref, deletes: Set<Ref>, cells: Ma } const t = tree.transforms.get(s.value); - if (t.props && t.props.isGhost) continue; + if (t.state.isGhost) continue; if (s.value === ref) { seenRef = true; if (!deletes.has(ref)) prevCandidate = ref; @@ -671,7 +653,7 @@ async function updateNode(ctx: UpdateContext, currentRef: Ref): Promise<UpdateNo function updateTag(obj: StateObject | undefined, transform: StateTransform) { if (!obj || obj === StateObject.Null) return; - (obj.tag as string | undefined) = transform.props.tag; + (obj.tags as string[] | undefined) = transform.tags; } function runTask<T>(t: T | Task<T>, ctx: RuntimeContext) { diff --git a/src/mol-state/state/builder.ts b/src/mol-state/state/builder.ts index 6e9e48a10a680457d27f4efa86e602a0450f032f..ec9fd4d9217a875edaeb6fc9c2cfab05a998152e 100644 --- a/src/mol-state/state/builder.ts +++ b/src/mol-state/state/builder.ts @@ -36,7 +36,7 @@ namespace StateBuilder { | { kind: 'add', transform: StateTransform } | { kind: 'update', ref: string, params: any } | { kind: 'delete', ref: string } - | { kind: 'insert', ref: string, transform: StateTransform, initialCellState?: Partial<StateObjectCell.State> } + | { kind: 'insert', ref: string, transform: StateTransform } function buildTree(state: BuildState) { if (!state.state || state.state.tree === state.editInfo.sourceTree) { @@ -52,7 +52,7 @@ namespace StateBuilder { case 'delete': tree.remove(a.ref); break; case 'insert': { const children = tree.children.get(a.ref).toArray(); - tree.add(a.transform, a.initialCellState); + tree.add(a.transform); for (const c of children) { tree.changeParent(c, a.transform.ref); } @@ -84,12 +84,13 @@ namespace StateBuilder { } toRoot<A extends StateObject>() { return new To<A>(this.state, this.state.tree.root.ref, this); } delete(ref: StateTransform.Ref) { + if (!this.state.tree.transforms.has(ref)) return this; this.editInfo.count++; this.state.tree.remove(ref); this.state.actions.push({ kind: 'delete', ref }); return this; } - getTree(): StateTree { return buildTree(this.state); } //this.state.tree.asImmutable(); } + getTree(): StateTree { return buildTree(this.state); } constructor(tree: StateTree, state?: State) { this.state = { state, tree: tree.asTransient(), actions: [], editInfo: { sourceTree: tree, count: 0, lastUpdate: void 0 } } } } @@ -102,9 +103,9 @@ namespace StateBuilder { * Apply the transformed to the parent node * If no params are specified (params <- undefined), default params are lazily resolved. */ - apply<T extends StateTransformer<A, any, any>>(tr: T, params?: StateTransformer.Params<T>, options?: Partial<StateTransform.Options>, initialCellState?: Partial<StateObjectCell.State>): To<StateTransformer.To<T>> { + apply<T extends StateTransformer<A, any, any>>(tr: T, params?: StateTransformer.Params<T>, options?: Partial<StateTransform.Options>): To<StateTransformer.To<T>> { const t = tr.apply(this.ref, params, options); - this.state.tree.add(t, initialCellState); + this.state.tree.add(t); this.editInfo.count++; this.editInfo.lastUpdate = t.ref; @@ -113,23 +114,36 @@ namespace StateBuilder { return new To(this.state, t.ref, this.root); } + /** + * If the ref is present, the transform is applied. + * Otherwise a transform with the specifed ref is created. + */ + applyOrUpdate<T extends StateTransformer<A, any, any>>(ref: StateTransform.Ref, tr: T, params?: StateTransformer.Params<T>, options?: Partial<StateTransform.Options>): To<StateTransformer.To<T>> { + if (this.state.tree.transforms.has(ref)) { + this.to(ref).update(params); + return this.to(ref) as To<StateTransformer.To<T>>; + } else { + return this.apply(tr, params, { ...options, ref }); + } + } + /** * A helper to greate a group-like state object and keep the current type. */ - group<T extends StateTransformer<A, any, any>>(tr: T, params?: StateTransformer.Params<T>, options?: Partial<StateTransform.Options>, initialCellState?: Partial<StateObjectCell.State>): To<A> { - return this.apply(tr, params, options, initialCellState) as To<A>; + group<T extends StateTransformer<A, any, any>>(tr: T, params?: StateTransformer.Params<T>, options?: Partial<StateTransform.Options>): To<A> { + return this.apply(tr, params, options) as To<A>; } /** * Inserts a new transform that does not change the object type and move the original children to it. */ - insert<T extends StateTransformer<A, A, any>>(tr: T, params?: StateTransformer.Params<T>, options?: Partial<StateTransform.Options>, initialCellState?: Partial<StateObjectCell.State>): To<StateTransformer.To<T>> { + insert<T extends StateTransformer<A, A, any>>(tr: T, params?: StateTransformer.Params<T>, options?: Partial<StateTransform.Options>): To<StateTransformer.To<T>> { // cache the children const children = this.state.tree.children.get(this.ref).toArray(); // add the new node const t = tr.apply(this.ref, params, options); - this.state.tree.add(t, initialCellState); + this.state.tree.add(t); // move the original children to the new node for (const c of children) { @@ -139,7 +153,7 @@ namespace StateBuilder { this.editInfo.count++; this.editInfo.lastUpdate = t.ref; - this.state.actions.push({ kind: 'insert', ref: this.ref, transform: t, initialCellState }); + this.state.actions.push({ kind: 'insert', ref: this.ref, transform: t }); return new To(this.state, t.ref, this.root); } @@ -193,7 +207,7 @@ namespace StateBuilder { toRoot<A extends StateObject>() { return this.root.toRoot<A>(); } delete(ref: StateTransform.Ref) { return this.root.delete(ref); } - getTree(): StateTree { return buildTree(this.state); } //this.state.tree.asImmutable(); } + getTree(): StateTree { return buildTree(this.state); } constructor(private state: BuildState, ref: StateTransform.Ref, private root: Root) { this.ref = ref; diff --git a/src/mol-state/state/selection.ts b/src/mol-state/state/selection.ts index 12a31907342c71fbfcdb7dc27ac750e255034e7f..2a47745f5c84e51b9ec4116a91a10b88e6033461 100644 --- a/src/mol-state/state/selection.ts +++ b/src/mol-state/state/selection.ts @@ -49,6 +49,7 @@ namespace StateSelection { parent(): Builder<C>; first(): Builder<C>; filter(p: (n: C) => boolean): Builder<C>; + withTag(tag: string): Builder<C>; withTransformer<T extends StateTransformer<any, StateObjectCell.Obj<C>, any>>(t: T): Builder<StateObjectCell<StateObjectCell.Obj<C>, StateTransform<T>>>; withStatus(s: StateObjectCell.Status): Builder<C>; subtree(): Builder; @@ -200,6 +201,9 @@ namespace StateSelection { registerModifier('withStatus', withStatus); export function withStatus(b: Selector, s: StateObjectCell.Status) { return filter(b, n => n.status === s); } + registerModifier('withTag', withTag); + export function withTag(b: Selector, tag: string) { return filter(b, n => !!n.transform.tags && n.transform.tags.indexOf(tag) >= 0); } + registerModifier('subtree', subtree); export function subtree(b: Selector) { return flatMap(b, (n, s) => { @@ -268,8 +272,12 @@ namespace StateSelection { } function _findUniqueTagsInSubtree(n: StateTransform, _: any, s: { refs: { [name: string]: StateTransform.Ref }, tags: Set<string> }) { - if (n.props.tag && s.tags.has(n.props.tag)) { - s.refs[n.props.tag] = n.ref; + if (n.tags) { + for (const t of n.tags) { + if (!s.tags.has(t)) continue; + s.refs[t] = n.ref; + break; + } } return true; } @@ -279,7 +287,7 @@ namespace StateSelection { } function _findTagInSubtree(n: StateTransform, _: any, s: { ref: string | undefined, tag: string }) { - if (n.props.tag === s.tag) { + if (n.tags && n.tags.indexOf(s.tag) >= 0) { s.ref = n.ref; return false; } diff --git a/src/mol-state/transform.ts b/src/mol-state/transform.ts index 9703a2f53254c6dbfbcf2e5449b8a0775679cc05..fe4b251bf76b60ec6e6cea23d6b903f99b6586cf 100644 --- a/src/mol-state/transform.ts +++ b/src/mol-state/transform.ts @@ -12,7 +12,8 @@ export { Transform as StateTransform } interface Transform<T extends StateTransformer = StateTransformer> { readonly parent: Transform.Ref, readonly transformer: T, - readonly props: Transform.Props, + readonly state: Transform.State, + readonly tags?: string[], readonly ref: Transform.Ref, readonly params?: StateTransformer.Params<T>, readonly version: string @@ -24,24 +25,80 @@ namespace Transform { export const RootRef = '-=root=-' as Ref; - export interface Props { - tag?: string + export interface State { + // is the cell shown in the UI isGhost?: boolean, - // determine if the corresponding cell can be deleted by the user. - isLocked?: boolean + // can the corresponding be deleted by the user. + isLocked?: boolean, + // is the representation associated with the cell hidden + isHidden?: boolean, + // is the tree node collapsed? + isCollapsed?: boolean + } + + export function areStatesEqual(a: State, b: State) { + return !!a.isHidden !== !!b.isHidden || !!a.isCollapsed !== !!b.isCollapsed + || !!a.isGhost !== !!b.isGhost || !!a.isLocked !== !!b.isLocked; + } + + export function isStateChange(a: State, b?: Partial<State>) { + if (!b) return false; + if (typeof b.isCollapsed !== 'undefined' && a.isCollapsed !== b.isCollapsed) return true; + if (typeof b.isHidden !== 'undefined' && a.isHidden !== b.isHidden) return true; + if (typeof b.isGhost !== 'undefined' && a.isGhost !== b.isGhost) return true; + if (typeof b.isLocked !== 'undefined' && a.isLocked !== b.isLocked) return true; + return false; + } + + export function assignState(a: State, b?: Partial<State>): boolean { + if (!b) return false; + + let changed = false; + for (const k of Object.keys(b)) { + const s = (b as any)[k], t = (a as any)[k]; + if (!!s === !!t) continue; + changed = true; + (a as any)[k] = s; + } + return changed; + } + + export function syncState(a: State, b?: Partial<State>): boolean { + if (!b) return false; + + let changed = false; + for (const k of Object.keys(b)) { + const s = (b as any)[k], t = (a as any)[k]; + if (!!s === !!t) continue; + changed = true; + (a as any)[k] = s; + } + for (const k of Object.keys(a)) { + const s = (b as any)[k], t = (a as any)[k]; + if (!!s === !!t) continue; + changed = true; + (a as any)[k] = s; + } + return changed; } export interface Options { ref?: string, - props?: Props + tags?: string | string[], + state?: State } export function create<T extends StateTransformer>(parent: Ref, transformer: T, params?: StateTransformer.Params<T>, options?: Options): Transform<T> { const ref = options && options.ref ? options.ref : UUID.create22() as string as Ref; + let tags: string[] | undefined = void 0; + if (options && options.tags) { + tags = typeof options.tags === 'string' ? [options.tags] : options.tags; + } return { parent, transformer, - props: (options && options.props) || { }, + state: (options && options.state) || { }, + tags, ref, params, version: UUID.create22() @@ -52,23 +109,30 @@ namespace Transform { return { ...t, params, version: UUID.create22() }; } + export function withState(t: Transform, state?: Partial<State>): Transform { + if (!state) return t; + return { ...t, state: { ...t.state, ...state } }; + } + export function withParent(t: Transform, parent: Ref): Transform { return { ...t, parent, version: UUID.create22() }; } - export function withNewVersion(t: Transform): Transform { - return { ...t, version: UUID.create22() }; + export function createRoot(state?: State): Transform { + return create(RootRef, StateTransformer.ROOT, {}, { ref: RootRef, state }); } - export function createRoot(props?: Props): Transform { - return create(RootRef, StateTransformer.ROOT, {}, { ref: RootRef, props }); + export function hasTag(t: Transform, tag: string) { + if (!t.tags) return false; + return t.tags.indexOf(tag) >= 0; } export interface Serialized { parent: string, transformer: string, params: any, - props: Props, + state?: State, + tags?: string[], ref: string, version: string } @@ -78,11 +142,19 @@ namespace Transform { const pToJson = t.transformer.definition.customSerialization ? t.transformer.definition.customSerialization.toJSON : _id; + let state: any = void 0; + for (const k of Object.keys(t.state)) { + const s = (t.state as any)[k]; + if (!s) continue; + if (!state) state = { }; + state[k] = true; + } return { parent: t.parent, transformer: t.transformer.id, params: t.params ? pToJson(t.params) : void 0, - props: t.props, + state, + tags: t.tags, ref: t.ref, version: t.version }; @@ -97,7 +169,8 @@ namespace Transform { parent: t.parent as Ref, transformer, params: t.params ? pFromJson(t.params) : void 0, - props: t.props, + state: t.state || { }, + tags: t.tags, ref: t.ref as Ref, version: t.version }; diff --git a/src/mol-state/transformer.ts b/src/mol-state/transformer.ts index 01759e7382efe88f25f3311f1509300c06290ced..4e60189b745c7bbd21e844f0a28f230c736cd1cc 100644 --- a/src/mol-state/transformer.ts +++ b/src/mol-state/transformer.ts @@ -86,6 +86,9 @@ namespace Transformer { /** By default, returns true */ isSerializable?(params: P): { isSerializable: true } | { isSerializable: false; reason: string }, + /** Parameter interpolation */ + interpolate?(src: P, target: P, t: number, globalCtx: unknown): P + /** Custom conversion to and from JSON */ readonly customSerialization?: { toJSON(params: P, obj?: B): any, fromJSON(data: any): P } } @@ -95,7 +98,7 @@ namespace Transformer { readonly from: StateObject.Ctor[], readonly to: StateObject.Ctor[], readonly display: { readonly name: string, readonly description?: string }, - params?(a: A | undefined, globalCtx: unknown): { [K in keyof P]: PD.Any }, + params?(a: A | undefined, globalCtx: unknown): { [K in keyof P]: PD.Any } } const registry = new Map<Id, Transformer<any, any>>(); diff --git a/src/mol-state/tree/immutable.ts b/src/mol-state/tree/immutable.ts index ab3d8f60301414dc4deb67d41348a393eea6e2f5..fba1811e7e3108decbb0b36023e791ec8de11a51 100644 --- a/src/mol-state/tree/immutable.ts +++ b/src/mol-state/tree/immutable.ts @@ -7,7 +7,6 @@ import { Map as ImmutableMap, OrderedSet } from 'immutable'; import { StateTransform } from '../transform'; import { TransientTree } from './transient'; -import { StateObjectCell } from 'mol-state/object'; export { StateTree } @@ -19,7 +18,6 @@ interface StateTree { readonly root: StateTransform, readonly transforms: StateTree.Transforms, readonly children: StateTree.Children, - readonly cellStates: StateTree.CellStates, asTransient(): TransientTree } @@ -43,7 +41,6 @@ namespace StateTree { export interface Transforms extends _Map<StateTransform> {} export interface Children extends _Map<ChildSet> { } - export interface CellStates extends _Map<StateObjectCell.State> { } class Impl implements StateTree { get root() { return this.transforms.get(StateTransform.RootRef)! } @@ -52,7 +49,7 @@ namespace StateTree { return new TransientTree(this); } - constructor(public transforms: StateTree.Transforms, public children: Children, public cellStates: CellStates) { + constructor(public transforms: StateTree.Transforms, public children: Children) { } } @@ -61,11 +58,11 @@ namespace StateTree { */ export function createEmpty(customRoot?: StateTransform): StateTree { const root = customRoot || StateTransform.createRoot(); - return create(ImmutableMap([[root.ref, root]]), ImmutableMap([[root.ref, OrderedSet()]]), ImmutableMap([[root.ref, StateObjectCell.DefaultState]])); + return create(ImmutableMap([[root.ref, root]]), ImmutableMap([[root.ref, OrderedSet()]])); } - export function create(nodes: Transforms, children: Children, cellStates: CellStates): StateTree { - return new Impl(nodes, children, cellStates); + export function create(nodes: Transforms, children: Children): StateTree { + return new Impl(nodes, children); } type VisitorCtx = { tree: StateTree, state: any, f: (node: StateTransform, tree: StateTree, state: any) => boolean | undefined | void }; @@ -116,19 +113,19 @@ namespace StateTree { return doPostOrder<StateTransform[]>(tree, root, [], _subtree); } - function _visitNodeToJson(node: StateTransform, tree: StateTree, ctx: [StateTransform.Serialized, StateObjectCell.State][]) { + function _visitNodeToJson(node: StateTransform, tree: StateTree, ctx: StateTransform.Serialized[]) { // const children: Ref[] = []; // tree.children.get(node.ref).forEach(_visitChildToJson as any, children); - ctx.push([StateTransform.toJSON(node), tree.cellStates.get(node.ref)]); + ctx.push(StateTransform.toJSON(node)); } export interface Serialized { /** Transforms serialized in pre-order */ - transforms: [StateTransform.Serialized, StateObjectCell.State][] + transforms: StateTransform.Serialized[] } export function toJSON(tree: StateTree): Serialized { - const transforms: [StateTransform.Serialized, StateObjectCell.State][] = []; + const transforms: StateTransform.Serialized[] = []; doPreOrder(tree, tree.root, transforms, _visitNodeToJson); return { transforms }; } @@ -136,12 +133,10 @@ namespace StateTree { export function fromJSON(data: Serialized): StateTree { const nodes = ImmutableMap<Ref, StateTransform>().asMutable(); const children = ImmutableMap<Ref, OrderedSet<Ref>>().asMutable(); - const cellStates = ImmutableMap<Ref, StateObjectCell.State>().asMutable(); for (const t of data.transforms) { - const transform = StateTransform.fromJSON(t[0]); + const transform = StateTransform.fromJSON(t); nodes.set(transform.ref, transform); - cellStates.set(transform.ref, t[1]); if (!children.has(transform.ref)) { children.set(transform.ref, OrderedSet<Ref>().asMutable()); @@ -151,19 +146,18 @@ namespace StateTree { } for (const t of data.transforms) { - const ref = t[0].ref; + const ref = t.ref; children.set(ref, children.get(ref).asImmutable()); } - return create(nodes.asImmutable(), children.asImmutable(), cellStates.asImmutable()); + return create(nodes.asImmutable(), children.asImmutable()); } export function dump(tree: StateTree) { console.log({ tr: (tree.transforms as ImmutableMap<any, any>).keySeq().toArray(), tr1: (tree.transforms as ImmutableMap<any, any>).valueSeq().toArray().map(t => t.ref), - ch: (tree.children as ImmutableMap<any, any>).keySeq().toArray(), - cs: (tree.cellStates as ImmutableMap<any, any>).keySeq().toArray() + ch: (tree.children as ImmutableMap<any, any>).keySeq().toArray() }); } } \ No newline at end of file diff --git a/src/mol-state/tree/transient.ts b/src/mol-state/tree/transient.ts index a48e8e6635778e3037298890dedf69f5f892f1b8..c9a5e8d8402a08a0c2115b2caca26af4bb424105 100644 --- a/src/mol-state/tree/transient.ts +++ b/src/mol-state/tree/transient.ts @@ -7,7 +7,6 @@ import { Map as ImmutableMap, OrderedSet } from 'immutable'; import { StateTransform } from '../transform'; import { StateTree } from './immutable'; -import { StateObjectCell } from 'mol-state/object'; import { shallowEqual } from 'mol-util/object'; export { TransientTree } @@ -15,13 +14,12 @@ export { TransientTree } class TransientTree implements StateTree { transforms = this.tree.transforms as ImmutableMap<StateTransform.Ref, StateTransform>; children = this.tree.children as ImmutableMap<StateTransform.Ref, OrderedSet<StateTransform.Ref>>; - cellStates = this.tree.cellStates as ImmutableMap<StateTransform.Ref, StateObjectCell.State>; private changedNodes = false; private changedChildren = false; - private changedStates = false; private _childMutations: Map<StateTransform.Ref, OrderedSet<StateTransform.Ref>> | undefined = void 0; + private _stateUpdates: Set<StateTransform.Ref> | undefined = void 0; private get childMutations() { if (this._childMutations) return this._childMutations; @@ -29,12 +27,6 @@ class TransientTree implements StateTree { return this._childMutations; } - private changeStates() { - if (this.changedStates) return; - this.changedStates = true; - this.cellStates = this.cellStates.asMutable(); - } - private changeNodes() { if (this.changedNodes) return; this.changedNodes = true; @@ -49,10 +41,6 @@ class TransientTree implements StateTree { get root() { return this.transforms.get(StateTransform.RootRef)! } - cellStatesSnapshot() { - return this.cellStates.asImmutable(); - } - asTransient() { return this.asImmutable().asTransient(); } @@ -104,15 +92,7 @@ class TransientTree implements StateTree { this.transforms.set(ref, StateTransform.withParent(old, newParent)); } - updateVersion(ref: StateTransform.Ref) { - ensurePresent(this.transforms, ref); - - const t = this.transforms.get(ref); - this.changeNodes(); - this.transforms.set(ref, StateTransform.withNewVersion(t)); - } - - add(transform: StateTransform, initialState?: Partial<StateObjectCell.State>) { + add(transform: StateTransform) { const ref = transform.ref; if (this.transforms.has(transform.ref)) { @@ -138,15 +118,6 @@ class TransientTree implements StateTree { this.changeNodes(); this.transforms.set(ref, transform); - if (!this.cellStates.has(ref)) { - this.changeStates(); - if (StateObjectCell.isStateChange(StateObjectCell.DefaultState, initialState)) { - this.cellStates.set(ref, { ...StateObjectCell.DefaultState, ...initialState }); - } else { - this.cellStates.set(ref, StateObjectCell.DefaultState); - } - } - return this; } @@ -169,16 +140,21 @@ class TransientTree implements StateTree { return true; } - updateCellState(ref: StateTransform.Ref, state: Partial<StateObjectCell.State>) { + assignState(ref: StateTransform.Ref, state?: Partial<StateTransform.State>) { ensurePresent(this.transforms, ref); - const old = this.cellStates.get(ref); - if (!StateObjectCell.isStateChange(old, state)) return false; - - this.changeStates(); - this.cellStates.set(ref, { ...old, ...state }); - - return true; + const old = this.transforms.get(ref); + if (this._stateUpdates && this._stateUpdates.has(ref)) { + StateTransform.assignState(old.state, state); + return old; + } else { + if (!this._stateUpdates) this._stateUpdates = new Set(); + this._stateUpdates.add(old.ref); + this.changeNodes(); + const updated = StateTransform.withState(old, state); + this.transforms.set(ref, updated); + return updated; + } } remove(ref: StateTransform.Ref): StateTransform[] { @@ -197,12 +173,10 @@ class TransientTree implements StateTree { this.changeNodes(); this.changeChildren(); - this.changeStates(); for (const n of st) { this.transforms.delete(n.ref); this.children.delete(n.ref); - this.cellStates.delete(n.ref); if (this._childMutations) this._childMutations.delete(n.ref); } @@ -210,12 +184,11 @@ class TransientTree implements StateTree { } asImmutable() { - if (!this.changedNodes && !this.changedChildren && !this.changedStates && !this._childMutations) return this.tree; + if (!this.changedNodes && !this.changedChildren && !this._childMutations) return this.tree; if (this._childMutations) this._childMutations.forEach(fixChildMutations, this.children); return StateTree.create( this.changedNodes ? this.transforms.asImmutable() : this.transforms, - this.changedChildren ? this.children.asImmutable() : this.children, - this.changedStates ? this.cellStates.asImmutable() : this.cellStates); + this.changedChildren ? this.children.asImmutable() : this.children); } constructor(private tree: StateTree) { diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index bfb9311dcdfa65bf5febb10796124bd71d631643..67c6b8d06879da6e2dbd7dbe67e52ce27bc89e12 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -168,7 +168,7 @@ export namespace ParamDefinition { } export interface NamedParams<T = any, K = string> { name: K, params: T } - export type NamedParamUnion<P extends Params, K = keyof P> = K extends any ? NamedParams<P[K]['defaultValue'], K> : never + export type NamedParamUnion<P extends Params, K extends keyof P = keyof P> = K extends any ? NamedParams<P[K]['defaultValue'], K> : never export interface Mapped<T extends NamedParams<any, any>> extends Base<T> { type: 'mapped', select: Select<string>, diff --git a/src/perf-tests/mol-script.ts b/src/perf-tests/mol-script.ts index 3895e0f8269a018c9bd9dae8ceba58744bb842ce..f459e3a203a5c48a0df98d490066240b8c9dcab3 100644 --- a/src/perf-tests/mol-script.ts +++ b/src/perf-tests/mol-script.ts @@ -1,6 +1,6 @@ import { MolScriptBuilder } from 'mol-script/language/builder'; import { compile, QuerySymbolRuntime, DefaultQueryRuntimeTable } from 'mol-script/runtime/query/compiler'; -import { QueryContext, Structure, StructureQuery, ModelPropertyDescriptor } from 'mol-model/structure'; +import { QueryContext, Structure, StructureQuery, CustomPropertyDescriptor } from 'mol-model/structure'; import { readCifFile, getModelsAndStructure } from '../apps/structure-info/model'; import { CustomPropSymbol } from 'mol-script/language/symbol'; import Type from 'mol-script/language/type'; @@ -46,7 +46,7 @@ const compiled = compile<number>(expr); const result = compiled(new QueryContext(Structure.Empty)); console.log(result); -const CustomProp = ModelPropertyDescriptor({ +const CustomProp = CustomPropertyDescriptor({ name: 'test_prop', isStatic: true, cifExport: { prefix: '', categories: [ ]},