diff --git a/src/examples/proteopedia-wrapper/helpers.ts b/src/examples/proteopedia-wrapper/helpers.ts index b111c230900fad13e1f56813c2767d21225e0c59..17082c93c22762304fcf3c7357e623bd6e3524c3 100644 --- a/src/examples/proteopedia-wrapper/helpers.ts +++ b/src/examples/proteopedia-wrapper/helpers.ts @@ -111,5 +111,7 @@ export enum StateElements { HetVisual = 'het-visual', Het3DSNFG = 'het-3dsnfg', Water = 'water', - WaterVisual = 'water-visual' + 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 43b6dc6866cedc3beaea03e9ebd705cca38e6137..0bd3bb60714bbb09f32c05ed522103c05f93fef7 100644 --- a/src/examples/proteopedia-wrapper/index.html +++ b/src/examples/proteopedia-wrapper/index.html @@ -99,6 +99,7 @@ PluginWrapper.events.modelInfo.subscribe(function (info) { console.log('Model Info', info); + listHetGroups(info); }); addControl('Load Asym Unit', () => PluginWrapper.load({ url: url, format: format })); @@ -145,6 +146,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'); @@ -163,6 +170,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; @@ -180,6 +193,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 6d49de844f3e6a36f1b36bddafc64cd406103160..fdef0b0f3f375965e5c088521c90826d18fd4455 100644 --- a/src/examples/proteopedia-wrapper/index.ts +++ b/src/examples/proteopedia-wrapper/index.ts @@ -21,6 +21,14 @@ 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 { @@ -257,6 +265,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-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..17200a1bfd20a9cf615b2e3a2c2dba984f4fc491 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]))]); + 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-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-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/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'),