123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
- import { StructureElement } from '../../../mol-model/structure';
- import { PluginContext } from '../../../mol-plugin/context';
- import { StateSelection, StateTransform, StateTransformer, StateObject, StateObjectCell } from '../../../mol-state';
- import { StateTransforms } from '../../transforms';
- import { PluginCommands } from '../../../mol-plugin/commands';
- import { arraySetAdd } from '../../../mol-util/array';
- import { PluginStateObject } from '../../objects';
- import { StatefulPluginComponent } from '../../component';
- import { ParamDefinition as PD } from '../../../mol-util/param-definition';
- import { MeasurementRepresentationCommonTextParams, LociLabelTextParams } from '../../../mol-repr/shape/loci/common';
- import { LineParams } from '../../../mol-repr/structure/representation/line';
- import { Expression } from '../../../mol-script/language/expression';
- import { Color } from '../../../mol-util/color';
- export { StructureMeasurementManager };
- export const MeasurementGroupTag = 'measurement-group';
- export const MeasurementOrderLabelTag = 'measurement-order-label';
- export type StructureMeasurementCell = StateObjectCell<PluginStateObject.Shape.Representation3D, StateTransform<StateTransformer<PluginStateObject.Molecule.Structure.Selections, PluginStateObject.Shape.Representation3D, any>>>
- export const StructureMeasurementParams = {
- distanceUnitLabel: PD.Text('\u212B', { isEssential: true }),
- textColor: MeasurementRepresentationCommonTextParams.textColor
- };
- const DefaultStructureMeasurementOptions = PD.getDefaultValues(StructureMeasurementParams);
- export type StructureMeasurementOptions = PD.ValuesFor<typeof StructureMeasurementParams>
- export interface StructureMeasurementManagerState {
- labels: StructureMeasurementCell[],
- distances: StructureMeasurementCell[],
- angles: StructureMeasurementCell[],
- dihedrals: StructureMeasurementCell[],
- orientations: StructureMeasurementCell[],
- planes: StructureMeasurementCell[],
- options: StructureMeasurementOptions
- }
- type StructureMeasurementManagerAddOptions = {
- customText?: string,
- selectionTags?: string | string[],
- reprTags?: string | string[],
- lineParams?: Partial<PD.Values<LineParams>>,
- labelParams?: Partial<PD.Values<LociLabelTextParams>>
- }
- class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasurementManagerState> {
- readonly behaviors = {
- state: this.ev.behavior(this.state)
- };
- private stateUpdated() {
- this.behaviors.state.next(this.state);
- }
- private getGroup() {
- const state = this.plugin.state.data;
- const groupRef = StateSelection.findTagInSubtree(state.tree, StateTransform.RootRef, MeasurementGroupTag);
- const builder = this.plugin.state.data.build();
- if (groupRef) return builder.to(groupRef);
- return builder.toRoot().group(StateTransforms.Misc.CreateGroup, { label: `Measurements` }, { tags: MeasurementGroupTag });
- }
- async setOptions(options: StructureMeasurementOptions) {
- if (this.updateState({ options })) this.stateUpdated();
- const update = this.plugin.state.data.build();
- for (const cell of this.state.distances) {
- update.to(cell).update((old: any) => {
- old.unitLabel = options.distanceUnitLabel;
- old.textColor = options.textColor;
- });
- }
- for (const cell of this.state.labels) {
- update.to(cell).update((old: any) => { old.textColor = options.textColor; });
- }
- for (const cell of this.state.angles) {
- update.to(cell).update((old: any) => { old.textColor = options.textColor; });
- }
- for (const cell of this.state.dihedrals) {
- update.to(cell).update((old: any) => { old.textColor = options.textColor; });
- }
- if (update.editInfo.count === 0) return;
- await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree: update, options: { doNotLogTiming: true } });
- }
- async addDistance(a: StructureElement.Loci, b: StructureElement.Loci,
- options?: StructureMeasurementManagerAddOptions & { visualParams?: Partial<StateTransformer.Params<typeof StateTransforms.Representation.StructureSelectionsDistance3D>> }) {
- const cellA = this.plugin.helpers.substructureParent.get(a.structure);
- const cellB = this.plugin.helpers.substructureParent.get(b.structure);
- if (!cellA || !cellB) return;
- const dependsOn = [cellA.transform.ref];
- arraySetAdd(dependsOn, cellB.transform.ref);
- const update = this.getGroup();
- update
- .apply(StateTransforms.Model.MultiStructureSelectionFromExpression, {
- selections: [
- { key: 'a', groupId: 'a', ref: cellA.transform.ref, expression: StructureElement.Loci.toExpression(a) },
- { key: 'b', groupId: 'b', ref: cellB.transform.ref, expression: StructureElement.Loci.toExpression(b) }
- ],
- isTransitive: true,
- label: 'Distance'
- }, { dependsOn, tags: options?.selectionTags })
- .apply(StateTransforms.Representation.StructureSelectionsDistance3D, {
- customText: options?.customText || '',
- unitLabel: this.state.options.distanceUnitLabel,
- textColor: this.state.options.textColor,
- ...(options?.lineParams as any),
- ...options?.labelParams,
- ...options?.visualParams
- }, { tags: options?.reprTags });
- const state = this.plugin.state.data;
- await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
- }
- async addAngle(a: StructureElement.Loci, b: StructureElement.Loci, c: StructureElement.Loci,
- options?: StructureMeasurementManagerAddOptions & { visualParams?: Partial<StateTransformer.Params<typeof StateTransforms.Representation.StructureSelectionsAngle3D>> }) {
- const cellA = this.plugin.helpers.substructureParent.get(a.structure);
- const cellB = this.plugin.helpers.substructureParent.get(b.structure);
- const cellC = this.plugin.helpers.substructureParent.get(c.structure);
- if (!cellA || !cellB || !cellC) return;
- const dependsOn = [cellA.transform.ref];
- arraySetAdd(dependsOn, cellB.transform.ref);
- arraySetAdd(dependsOn, cellC.transform.ref);
- const update = this.getGroup();
- update
- .apply(StateTransforms.Model.MultiStructureSelectionFromExpression, {
- selections: [
- { key: 'a', ref: cellA.transform.ref, expression: StructureElement.Loci.toExpression(a) },
- { key: 'b', ref: cellB.transform.ref, expression: StructureElement.Loci.toExpression(b) },
- { key: 'c', ref: cellC.transform.ref, expression: StructureElement.Loci.toExpression(c) }
- ],
- isTransitive: true,
- label: 'Angle'
- }, { dependsOn, tags: options?.selectionTags })
- .apply(StateTransforms.Representation.StructureSelectionsAngle3D, {
- customText: options?.customText || '',
- textColor: this.state.options.textColor,
- ...(options?.lineParams as any),
- ...options?.labelParams,
- ...options?.visualParams
- }, { tags: options?.reprTags });
- const state = this.plugin.state.data;
- await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
- }
- async addDihedral(a: StructureElement.Loci, b: StructureElement.Loci, c: StructureElement.Loci, d: StructureElement.Loci,
- options?: StructureMeasurementManagerAddOptions & { visualParams?: Partial<StateTransformer.Params<typeof StateTransforms.Representation.StructureSelectionsDihedral3D>> }) {
- const cellA = this.plugin.helpers.substructureParent.get(a.structure);
- const cellB = this.plugin.helpers.substructureParent.get(b.structure);
- const cellC = this.plugin.helpers.substructureParent.get(c.structure);
- const cellD = this.plugin.helpers.substructureParent.get(d.structure);
- if (!cellA || !cellB || !cellC || !cellD) return;
- const dependsOn = [cellA.transform.ref];
- arraySetAdd(dependsOn, cellB.transform.ref);
- arraySetAdd(dependsOn, cellC.transform.ref);
- arraySetAdd(dependsOn, cellD.transform.ref);
- const update = this.getGroup();
- update
- .apply(StateTransforms.Model.MultiStructureSelectionFromExpression, {
- selections: [
- { key: 'a', ref: cellA.transform.ref, expression: StructureElement.Loci.toExpression(a) },
- { key: 'b', ref: cellB.transform.ref, expression: StructureElement.Loci.toExpression(b) },
- { key: 'c', ref: cellC.transform.ref, expression: StructureElement.Loci.toExpression(c) },
- { key: 'd', ref: cellD.transform.ref, expression: StructureElement.Loci.toExpression(d) }
- ],
- isTransitive: true,
- label: 'Dihedral'
- }, { dependsOn, tags: options?.selectionTags })
- .apply(StateTransforms.Representation.StructureSelectionsDihedral3D, {
- customText: options?.customText || '',
- textColor: this.state.options.textColor,
- ...(options?.lineParams as any),
- ...options?.labelParams,
- ...options?.visualParams
- }, { tags: options?.reprTags });
- const state = this.plugin.state.data;
- await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
- }
- async addLabel(a: StructureElement.Loci,
- options?: Omit<StructureMeasurementManagerAddOptions, 'customText' | 'lineParams'> & { visualParams?: Partial<StateTransformer.Params<typeof StateTransforms.Representation.StructureSelectionsLabel3D>> }) {
- const cellA = this.plugin.helpers.substructureParent.get(a.structure);
- if (!cellA) return;
- const dependsOn = [cellA.transform.ref];
- const update = this.getGroup();
- update
- .apply(StateTransforms.Model.MultiStructureSelectionFromExpression, {
- selections: [
- { key: 'a', ref: cellA.transform.ref, expression: StructureElement.Loci.toExpression(a) },
- ],
- isTransitive: true,
- label: 'Label'
- }, { dependsOn, tags: options?.selectionTags })
- .apply(StateTransforms.Representation.StructureSelectionsLabel3D, {
- textColor: this.state.options.textColor,
- ...options?.labelParams,
- ...options?.visualParams
- }, { tags: options?.reprTags });
- const state = this.plugin.state.data;
- await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
- }
- async addOrientation(locis: StructureElement.Loci[]) {
- const selections: { key: string, ref: string, groupId?: string, expression: Expression }[] = [];
- const dependsOn: string[] = [];
- for (let i = 0, il = locis.length; i < il; ++i) {
- const l = locis[i];
- const cell = this.plugin.helpers.substructureParent.get(l.structure);
- if (!cell) continue;
- arraySetAdd(dependsOn, cell.transform.ref);
- selections.push({ key: `l${i}`, ref: cell.transform.ref, expression: StructureElement.Loci.toExpression(l) });
- }
- if (selections.length === 0) return;
- const update = this.getGroup();
- update
- .apply(StateTransforms.Model.MultiStructureSelectionFromExpression, {
- selections,
- isTransitive: true,
- label: 'Orientation'
- }, { dependsOn })
- .apply(StateTransforms.Representation.StructureSelectionsOrientation3D);
- const state = this.plugin.state.data;
- await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
- }
- async addPlane(locis: StructureElement.Loci[]) {
- const selections: { key: string, ref: string, groupId?: string, expression: Expression }[] = [];
- const dependsOn: string[] = [];
- for (let i = 0, il = locis.length; i < il; ++i) {
- const l = locis[i];
- const cell = this.plugin.helpers.substructureParent.get(l.structure);
- if (!cell) continue;
- arraySetAdd(dependsOn, cell.transform.ref);
- selections.push({ key: `l${i}`, ref: cell.transform.ref, expression: StructureElement.Loci.toExpression(l) });
- }
- if (selections.length === 0) return;
- const update = this.getGroup();
- update
- .apply(StateTransforms.Model.MultiStructureSelectionFromExpression, {
- selections,
- isTransitive: true,
- label: 'Plane'
- }, { dependsOn })
- .apply(StateTransforms.Representation.StructureSelectionsPlane3D);
- const state = this.plugin.state.data;
- await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
- }
- async addOrderLabels(locis: StructureElement.Loci[]) {
- const update = this.getGroup();
- const current = this.plugin.state.data.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Selections).withTag(MeasurementOrderLabelTag));
- for (const obj of current)
- update.delete(obj);
- let order = 1;
- for (const loci of locis) {
- const cell = this.plugin.helpers.substructureParent.get(loci.structure);
- if (!cell) continue;
- const dependsOn = [cell.transform.ref];
- update
- .apply(StateTransforms.Model.MultiStructureSelectionFromExpression, {
- selections: [
- { key: 'a', ref: cell.transform.ref, expression: StructureElement.Loci.toExpression(loci) },
- ],
- isTransitive: true,
- label: 'Order'
- }, { dependsOn, tags: MeasurementOrderLabelTag })
- .apply(StateTransforms.Representation.StructureSelectionsLabel3D, {
- textColor: Color.fromRgb(255, 255, 255),
- borderColor: Color.fromRgb(0, 0, 0),
- borderWidth: 0.5,
- textSize: 0.33,
- customText: `${order++}`
- }, { tags: MeasurementOrderLabelTag });
- }
- const state = this.plugin.state.data;
- await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
- }
- private _empty: any[] = [];
- private getTransforms<T extends StateTransformer<A, B, any>, A extends PluginStateObject.Molecule.Structure.Selections, B extends StateObject>(transformer: T) {
- const state = this.plugin.state.data;
- const groupRef = StateSelection.findTagInSubtree(state.tree, StateTransform.RootRef, MeasurementGroupTag);
- const ret = groupRef ? state.select(StateSelection.Generators.ofTransformer(transformer, groupRef)) : this._empty;
- if (ret.length === 0) return this._empty;
- return ret;
- }
- private sync() {
- const labels = [];
- for (const cell of this.getTransforms(StateTransforms.Representation.StructureSelectionsLabel3D) as StructureMeasurementCell[]) {
- const tags = (cell.obj as any)['tags'] as string[];
- if (!tags || !tags.includes(MeasurementOrderLabelTag))
- labels.push(cell);
- }
- const updated = this.updateState({
- labels,
- distances: this.getTransforms(StateTransforms.Representation.StructureSelectionsDistance3D),
- angles: this.getTransforms(StateTransforms.Representation.StructureSelectionsAngle3D),
- dihedrals: this.getTransforms(StateTransforms.Representation.StructureSelectionsDihedral3D),
- orientations: this.getTransforms(StateTransforms.Representation.StructureSelectionsOrientation3D),
- planes: this.getTransforms(StateTransforms.Representation.StructureSelectionsPlane3D),
- });
- if (updated) this.stateUpdated();
- }
- constructor(private plugin: PluginContext) {
- super({ labels: [], distances: [], angles: [], dihedrals: [], orientations: [], planes: [], options: DefaultStructureMeasurementOptions });
- plugin.state.data.events.changed.subscribe(e => {
- if (e.inTransaction || plugin.behaviors.state.isAnimating.value) return;
- this.sync();
- });
- plugin.behaviors.state.isAnimating.subscribe(isAnimating => {
- if (!isAnimating && !plugin.behaviors.state.isUpdating.value) this.sync();
- });
- }
- }
|