From 53c8ba549fe1af07a33e5524ba68efc93564f5d9 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Thu, 28 Feb 2019 09:47:40 +0100 Subject: [PATCH] wip selection model --- src/examples/proteopedia-wrapper/helpers.ts | 2 - .../structure/query/queries/combinators.ts | 2 +- src/mol-model/structure/query/selection.ts | 2 +- .../structure/query/utils/builders.ts | 2 +- src/mol-model/structure/structure/element.ts | 7 + .../structure/structure/structure.ts | 2 +- src/mol-plugin/util/canvas3d-identify.ts | 4 +- .../util/selection/structure-element.ts | 115 -------------- .../util/selection/structure-loci.ts | 147 ++++++++++++++++++ src/mol-util/object.ts | 8 + 10 files changed, 168 insertions(+), 123 deletions(-) delete mode 100644 src/mol-plugin/util/selection/structure-element.ts create mode 100644 src/mol-plugin/util/selection/structure-loci.ts diff --git a/src/examples/proteopedia-wrapper/helpers.ts b/src/examples/proteopedia-wrapper/helpers.ts index d3154f7c1..427fb057e 100644 --- a/src/examples/proteopedia-wrapper/helpers.ts +++ b/src/examples/proteopedia-wrapper/helpers.ts @@ -25,8 +25,6 @@ export namespace ModelInfo { const json = JSON.parse(src); const data = json && json[id]; - console.log(data); - const assemblies = data[0] && data[0].assemblies; if (!assemblies || !assemblies.length) return void 0; diff --git a/src/mol-model/structure/query/queries/combinators.ts b/src/mol-model/structure/query/queries/combinators.ts index 7bd531bd3..a3b060ae6 100644 --- a/src/mol-model/structure/query/queries/combinators.ts +++ b/src/mol-model/structure/query/queries/combinators.ts @@ -47,7 +47,7 @@ export function intersect(queries: ArrayLike<StructureQuery>): StructureQuery { } ctx.throwIfTimedOut(); - const pivotSet = HashSet<Structure>(s => s.hashCode, Structure.areEqual); + const pivotSet = HashSet<Structure>(s => s.hashCode, Structure.areUnitAndIndicesEqual); StructureSelection.forEach(selections[pivotIndex], s => pivotSet.add(s)); const ret = StructureSelection.UniqueBuilder(ctx.inputStructure); diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts index b25717071..901452573 100644 --- a/src/mol-model/structure/query/selection.ts +++ b/src/mol-model/structure/query/selection.ts @@ -84,7 +84,7 @@ namespace StructureSelection { class HashBuilderImpl implements Builder { private structures: Structure[] = []; private allSingletons = true; - private uniqueSets = HashSet(Structure.hashCode, Structure.areEqual); + private uniqueSets = HashSet(Structure.hashCode, Structure.areUnitAndIndicesEqual); add(structure: Structure) { const atomCount = structure.elementCount; diff --git a/src/mol-model/structure/query/utils/builders.ts b/src/mol-model/structure/query/utils/builders.ts index 9da400cba..a47a5354b 100644 --- a/src/mol-model/structure/query/utils/builders.ts +++ b/src/mol-model/structure/query/utils/builders.ts @@ -12,7 +12,7 @@ import { StructureSubsetBuilder } from '../../structure/util/subset-builder'; import { ElementIndex } from '../../model'; export class UniqueStructuresBuilder { - private set = HashSet(Structure.hashCode, Structure.areEqual); + private set = HashSet(Structure.hashCode, Structure.areUnitAndIndicesEqual); private structures: Structure[] = []; private allSingletons = true; diff --git a/src/mol-model/structure/structure/element.ts b/src/mol-model/structure/structure/element.ts index e9287c338..1e4c7ae2e 100644 --- a/src/mol-model/structure/structure/element.ts +++ b/src/mol-model/structure/structure/element.ts @@ -117,6 +117,13 @@ namespace StructureElement { }))); } + export function remap(loci: Loci, structure: Structure): Loci { + return Loci(structure, loci.elements.map(e => ({ + unit: structure.unitMap.get(e.unit.id)!, + indices: e.indices + }))); + } + export function union(xs: Loci, ys: Loci): Loci { if (xs.structure !== ys.structure) throw new Error(`Can't union Loci of different structures.`); if (xs.elements.length > ys.elements.length) return union(ys, xs); diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index b86668743..8d45b5146 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -424,7 +424,7 @@ namespace Structure { return hashString(s.units.map(u => Unit.conformationId(u)).join('|')) } - export function areEqual(a: Structure, b: Structure) { + export function areUnitAndIndicesEqual(a: Structure, b: Structure) { if (a.elementCount !== b.elementCount) return false; const len = a.units.length; if (len !== b.units.length) return false; diff --git a/src/mol-plugin/util/canvas3d-identify.ts b/src/mol-plugin/util/canvas3d-identify.ts index a9fd5e13c..c7e0e64ed 100644 --- a/src/mol-plugin/util/canvas3d-identify.ts +++ b/src/mol-plugin/util/canvas3d-identify.ts @@ -6,7 +6,7 @@ import { PluginContext } from '../context'; import { PickingId } from 'mol-geo/geometry/picking'; -import { EmptyLoci, Loci } from 'mol-model/loci'; +import { EmptyLoci } from 'mol-model/loci'; import { Representation } from 'mol-repr/representation'; import { ModifiersKeys, ButtonsType } from 'mol-util/input/input-observer'; @@ -49,7 +49,7 @@ export class Canvas3dIdentifyHelper { } const loci = this.ctx.canvas3d.getLoci(this.id); - if (loci.repr !== this.prevLoci.repr || !Loci.areEqual(loci.loci, this.prevLoci.loci)) { + if (!Representation.Loci.areEqual(this.prevLoci, loci)) { this.ctx.events.canvas3d.highlight.next({ loci, modifiers: this.modifiers }); this.prevLoci = loci; } diff --git a/src/mol-plugin/util/selection/structure-element.ts b/src/mol-plugin/util/selection/structure-element.ts deleted file mode 100644 index 8fda7de0d..000000000 --- a/src/mol-plugin/util/selection/structure-element.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { StructureElement, Structure } from 'mol-model/structure'; -import { PluginContext } from '../../context'; -import { Loci, EmptyLoci } from 'mol-model/loci'; -import { PluginStateObject } from 'mol-plugin/state/objects'; -import { State } from 'mol-state'; - -export { StructureElementSelectionManager } - -class StructureElementSelectionManager { - private entries = new Map<string, Entry>(); - - // maps structure to a parent StateObjectCell - private structures = { - root: new Map<Structure, string>(), - tracked: new Map<string, Structure>() - } - - private getEntry(s: Structure) { - if (!this.structures.root.has(s)) return; - const key = this.structures.root.get(s)!; - if (!this.entries.has(key)) { - const entry: Entry = { - selection: StructureElement.Loci(s, []), - highlight: StructureElement.Loci(s, []), - }; - this.entries.set(key, entry); - return entry; - } - - return this.entries.get(key)!; - } - - add(loci: StructureElement.Loci | Structure.Loci, type: 'selection' | 'highlight'): Loci { - const entry = this.getEntry(loci.structure); - if (!entry) return EmptyLoci; - entry[type] = Structure.isLoci(loci) ? StructureElement.Loci.all(loci.structure) : StructureElement.Loci.union(entry[type], loci); - return entry[type]; - } - - remove(loci: StructureElement.Loci | Structure.Loci, type: 'selection' | 'highlight'): Loci { - const entry = this.getEntry(loci.structure); - if (!entry) return EmptyLoci; - entry[type] = Structure.isLoci(loci) ? StructureElement.Loci(loci.structure, []) : StructureElement.Loci.subtract(entry[type], loci); - return entry[type].elements.length === 0 ? EmptyLoci : entry[type]; - } - - set(loci: StructureElement.Loci | Structure.Loci, type: 'selection' | 'highlight'): Loci { - const entry = this.getEntry(loci.structure); - if (!entry) return EmptyLoci; - entry[type] = Structure.isLoci(loci) ? StructureElement.Loci.all(loci.structure) : loci; - return entry[type].elements.length === 0 ? EmptyLoci : entry[type]; - } - - private prevHighlight: StructureElement.Loci | undefined = void 0; - - accumulateInteractiveHighlight(loci: StructureElement.Loci) { - if (this.prevHighlight) { - this.prevHighlight = StructureElement.Loci.union(this.prevHighlight, loci); - } else { - this.prevHighlight = loci; - } - return this.prevHighlight; - } - - clearInteractiveHighlight() { - const ret = this.prevHighlight; - this.prevHighlight = void 0; - return ret || EmptyLoci; - } - - private trackCell(state: State, ref: string) { - const cell = state.cells.get(ref); - if (!cell || !cell.obj || !PluginStateObject.Molecule.Structure.is(cell.obj)) return; - const parent = state.selectQ(q => q.byRef(cell.transform.ref).rootOfType([PluginStateObject.Molecule.Structure]))[0]; - this.structures.tracked.set(cell.transform.ref, cell.obj.data); - if (!parent) { - this.structures.root.set(cell.obj.data, cell.transform.ref); - } else { - this.structures.root.set(cell.obj.data, parent.transform.ref); - } - } - - private untrackCell(state: State, ref: string) { - if (!this.structures.tracked.has(ref)) return; - const s = this.structures.tracked.get(ref)!; - this.structures.tracked.delete(ref); - this.structures.root.delete(s); - } - - constructor(plugin: PluginContext) { - plugin.state.dataState.events.object.created.subscribe(e => { - this.trackCell(e.state, e.ref); - }); - - plugin.state.dataState.events.object.removed.subscribe(e => { - this.untrackCell(e.state, e.ref); - }); - - plugin.state.dataState.events.object.updated.subscribe(e => { - this.untrackCell(e.state, e.ref); - this.trackCell(e.state, e.ref); - }); - } -} - -interface Entry { - selection: StructureElement.Loci, - highlight: StructureElement.Loci -} diff --git a/src/mol-plugin/util/selection/structure-loci.ts b/src/mol-plugin/util/selection/structure-loci.ts new file mode 100644 index 000000000..b7613c10b --- /dev/null +++ b/src/mol-plugin/util/selection/structure-loci.ts @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { StructureElement, Structure } from 'mol-model/structure'; +import { PluginContext } from '../../context'; +import { Loci, EmptyLoci } from 'mol-model/loci'; +import { PluginStateObject } from 'mol-plugin/state/objects'; +import { State, StateSelection, StateObject } from 'mol-state'; +import { mapObjectMap } from 'mol-util/object'; + +export { StructureLociManager } + +class StructureLociManager { + private entries = new Map<string, SelectionEntry>(); + + // maps structure to a parent StateObjectCell + private mapping = { + root: new Map<Structure, string>(), + tracked: new Map<string, Structure>() + }; + + private getEntry(s: Structure) { + if (!this.mapping.root.has(s)) return; + const key = this.mapping.root.get(s)!; + if (!this.entries.has(key)) { + const entry = SelectionEntry(s); + this.entries.set(key, entry); + return entry; + } + + return this.entries.get(key)!; + } + + add(loci: StructureElement.Loci | Structure.Loci, type: 'selection' | 'highlight'): Loci { + const entry = this.getEntry(loci.structure); + if (!entry) return EmptyLoci; + const xs = entry.elements; + xs[type] = Structure.isLoci(loci) ? StructureElement.Loci.all(loci.structure) : StructureElement.Loci.union(xs[type], loci); + return xs[type]; + } + + remove(loci: StructureElement.Loci | Structure.Loci, type: 'selection' | 'highlight'): Loci { + const entry = this.getEntry(loci.structure); + if (!entry) return EmptyLoci; + const xs = entry.elements; + xs[type] = Structure.isLoci(loci) ? StructureElement.Loci(loci.structure, []) : StructureElement.Loci.subtract(xs[type], loci); + return xs[type].elements.length === 0 ? EmptyLoci : xs[type]; + } + + set(loci: StructureElement.Loci | Structure.Loci, type: 'selection' | 'highlight'): Loci { + const entry = this.getEntry(loci.structure); + if (!entry) return EmptyLoci; + const xs = entry.elements; + xs[type] = Structure.isLoci(loci) ? StructureElement.Loci.all(loci.structure) : loci; + return xs[type].elements.length === 0 ? EmptyLoci : xs[type]; + } + + private prevHighlight: StructureElement.Loci | undefined = void 0; + + accumulateInteractiveHighlight(loci: StructureElement.Loci) { + if (this.prevHighlight) { + this.prevHighlight = StructureElement.Loci.union(this.prevHighlight, loci); + } else { + this.prevHighlight = loci; + } + return this.prevHighlight; + } + + clearInteractiveHighlight() { + const ret = this.prevHighlight; + this.prevHighlight = void 0; + return ret || EmptyLoci; + } + + private addMapping(state: State, ref: string, obj: StateObject) { + if (!PluginStateObject.Molecule.Structure.is(obj)) return; + const parent = state.select(StateSelection.Generators.byRef(ref).rootOfType([PluginStateObject.Molecule.Structure]))[0]; + this.mapping.tracked.set(ref, obj.data); + if (!parent) { + this.mapping.root.set(obj.data, ref); + } else { + this.mapping.root.set(obj.data, parent.transform.ref); + } + } + + private removeMapping(ref: string) { + if (!this.mapping.tracked.has(ref)) return; + const s = this.mapping.tracked.get(ref)!; + this.mapping.tracked.delete(ref); + const root = this.mapping.root.get(s); + this.mapping.root.delete(s); + if (root === ref) this.entries.delete(ref); + } + + private updateMapping(state: State, ref: string, oldObj: StateObject | undefined, obj: StateObject) { + if (!PluginStateObject.Molecule.Structure.is(obj)) return; + + if (this.entries.has(ref)) { + if (!PluginStateObject.Molecule.Structure.is(oldObj) || oldObj === obj || oldObj.data === obj.data) return; + + // remap the old selection to be related to the new object if possible. + if (Structure.areUnitAndIndicesEqual(oldObj.data, obj.data)) { + this.entries.set(ref, remapSelectionEntry(this.entries.get(ref)!, obj.data)); + return; + } + + // clear the selection + this.entries.set(ref, SelectionEntry(obj.data)); + } else { + this.removeMapping(ref); + this.addMapping(state, ref, obj); + } + } + + constructor(plugin: PluginContext) { + plugin.state.dataState.events.object.created.subscribe(e => { + this.addMapping(e.state, e.ref, e.obj); + }); + + plugin.state.dataState.events.object.removed.subscribe(e => { + this.removeMapping(e.ref); + }); + + plugin.state.dataState.events.object.updated.subscribe(e => { + this.updateMapping(e.state, e.ref, e.oldObj, e.obj); + }); + } +} + +interface SelectionEntry { + elements: { [category: string]: StructureElement.Loci } +} + +function SelectionEntry(s: Structure): SelectionEntry { + return { + elements: { } + }; +} + +function remapSelectionEntry(e: SelectionEntry, s: Structure): SelectionEntry { + return { + elements: mapObjectMap(e.elements, (l: StructureElement.Loci) => StructureElement.Loci.remap(l, s)) + }; +} \ No newline at end of file diff --git a/src/mol-util/object.ts b/src/mol-util/object.ts index a88d70325..bfb4824f5 100644 --- a/src/mol-util/object.ts +++ b/src/mol-util/object.ts @@ -82,4 +82,12 @@ export function deepClone<T>(source: T): T { } throw new Error(`Can't clone, type "${typeof source}" unsupported`); +} + +export function mapObjectMap<O extends { [k: string]: T }, T, S>(o: O, f: (v: T) => S): { [k: string]: S } { + const ret: any = { }; + for (const k of Object.keys(o)) { + ret[k] = f((o as any)[k]); + } + return ret; } \ No newline at end of file -- GitLab