hierarchy.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { Structure } from '../../../mol-model/structure';
  8. import { setSubtreeVisibility } from '../../../mol-plugin/behavior/static/state';
  9. import { PluginCommands } from '../../../mol-plugin/commands';
  10. import { PluginContext } from '../../../mol-plugin/context';
  11. import { StateTransform, StateTree } from '../../../mol-state';
  12. import { SetUtils } from '../../../mol-util/set';
  13. import { TrajectoryHierarchyPresetProvider } from '../../builder/structure/hierarchy-preset';
  14. import { PluginComponent } from '../../component';
  15. import { PluginStateObject } from '../../objects';
  16. import { buildStructureHierarchy, StructureHierarchyRef, ModelRef, StructureComponentRef, StructureHierarchy, StructureRef, TrajectoryRef } from './hierarchy-state';
  17. export class StructureHierarchyManager extends PluginComponent {
  18. private state = {
  19. syncedTree: this.dataState.tree,
  20. notified: false,
  21. hierarchy: StructureHierarchy(),
  22. selection: {
  23. trajectories: [] as ReadonlyArray<TrajectoryRef>,
  24. models: [] as ReadonlyArray<ModelRef>,
  25. structures: [] as ReadonlyArray<StructureRef>
  26. }
  27. };
  28. readonly behaviors = {
  29. selection: this.ev.behavior({
  30. hierarchy: this.current,
  31. trajectories: this.selection.trajectories,
  32. models: this.selection.models,
  33. structures: this.selection.structures
  34. })
  35. };
  36. private get dataState() {
  37. return this.plugin.state.data;
  38. }
  39. private _currentComponentGroups: ReturnType<typeof StructureHierarchyManager['getComponentGroups']> | undefined = void 0;
  40. get currentComponentGroups() {
  41. if (this._currentComponentGroups) return this._currentComponentGroups;
  42. this._currentComponentGroups = StructureHierarchyManager.getComponentGroups(this.selection.structures);
  43. return this._currentComponentGroups;
  44. }
  45. private _currentSelectionSet: Set<StateTransform.Ref> | undefined = void 0;
  46. get seletionSet() {
  47. if (this._currentSelectionSet) return this._currentSelectionSet;
  48. this._currentSelectionSet = new Set();
  49. for (const r of this.selection.trajectories) this._currentSelectionSet.add(r.cell.transform.ref);
  50. for (const r of this.selection.models) this._currentSelectionSet.add(r.cell.transform.ref);
  51. for (const r of this.selection.structures) this._currentSelectionSet.add(r.cell.transform.ref);
  52. return this._currentSelectionSet;
  53. }
  54. get current() {
  55. this.sync(false);
  56. return this.state.hierarchy;
  57. }
  58. get selection() {
  59. this.sync(false);
  60. return this.state.selection;
  61. }
  62. getStructuresWithSelection() {
  63. const xs = this.plugin.managers.structure.hierarchy.current.structures;
  64. const ret: StructureRef[] = [];
  65. for (const s of xs) {
  66. if (this.plugin.managers.structure.selection.structureHasSelection(s)) {
  67. ret.push(s);
  68. }
  69. }
  70. return ret;
  71. }
  72. findStructure(structure: Structure | undefined): StructureRef | undefined {
  73. if (!structure) return undefined;
  74. const parent = this.plugin.helpers.substructureParent.get(structure);
  75. if (!parent) return undefined;
  76. const root = this.plugin.state.data.selectQ(q => q.byValue(parent).rootOfType(PluginStateObject.Molecule.Structure))[0];
  77. if (!root) return undefined;
  78. return this.behaviors.selection.value.structures.find(s => s.cell === root);
  79. }
  80. private syncCurrent<T extends StructureHierarchyRef>(all: ReadonlyArray<T>, added: Set<StateTransform.Ref>): T[] {
  81. const current = this.seletionSet;
  82. const newCurrent: T[] = [];
  83. for (const r of all) {
  84. const ref = r.cell.transform.ref;
  85. if (current.has(ref) || added.has(ref)) newCurrent.push(r);
  86. }
  87. if (newCurrent.length === 0) return all.length > 0 ? [all[0]] : [];
  88. return newCurrent;
  89. }
  90. private sync(notify: boolean) {
  91. if (!notify && this.dataState.inUpdate) return;
  92. if (this.state.syncedTree === this.dataState.tree) {
  93. if (notify && !this.state.notified) {
  94. this.state.notified = true;
  95. this.behaviors.selection.next({ hierarchy: this.state.hierarchy, ...this.state.selection });
  96. }
  97. return;
  98. }
  99. this.state.syncedTree = this.dataState.tree;
  100. const update = buildStructureHierarchy(this.plugin.state.data, this.current);
  101. if (!update.changed) {
  102. return;
  103. }
  104. const { hierarchy } = update;
  105. const trajectories = this.syncCurrent(hierarchy.trajectories, update.added);
  106. const models = this.syncCurrent(hierarchy.models, update.added);
  107. const structures = this.syncCurrent(hierarchy.structures, update.added);
  108. this._currentComponentGroups = void 0;
  109. this._currentSelectionSet = void 0;
  110. this.state.hierarchy = hierarchy;
  111. this.state.selection.trajectories = trajectories;
  112. this.state.selection.models = models;
  113. this.state.selection.structures = structures;
  114. if (notify) {
  115. this.state.notified = true;
  116. this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
  117. } else {
  118. this.state.notified = false;
  119. }
  120. }
  121. updateCurrent(refs: StructureHierarchyRef[], action: 'add' | 'remove') {
  122. const hierarchy = this.current;
  123. const set = action === 'add'
  124. ? SetUtils.union(this.seletionSet, new Set(refs.map(r => r.cell.transform.ref)))
  125. : SetUtils.difference(this.seletionSet, new Set(refs.map(r => r.cell.transform.ref)));
  126. const trajectories = [];
  127. const models = [];
  128. const structures = [];
  129. for (const t of hierarchy.trajectories) {
  130. if (set.has(t.cell.transform.ref)) trajectories.push(t);
  131. }
  132. for (const m of hierarchy.models) {
  133. if (set.has(m.cell.transform.ref)) models.push(m);
  134. }
  135. for (const s of hierarchy.structures) {
  136. if (set.has(s.cell.transform.ref)) structures.push(s);
  137. }
  138. this._currentComponentGroups = void 0;
  139. this._currentSelectionSet = void 0;
  140. this.state.selection.trajectories = trajectories;
  141. this.state.selection.models = models;
  142. this.state.selection.structures = structures;
  143. this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
  144. }
  145. remove(refs: (StructureHierarchyRef | string)[], canUndo?: boolean) {
  146. if (refs.length === 0) return;
  147. const deletes = this.plugin.state.data.build();
  148. for (const r of refs) deletes.delete(typeof r === 'string' ? r : r.cell.transform.ref);
  149. return deletes.commit({ canUndo: canUndo ? 'Remove' : false });
  150. }
  151. toggleVisibility(refs: ReadonlyArray<StructureHierarchyRef>, action?: 'show' | 'hide') {
  152. if (refs.length === 0) return;
  153. const isHidden = action !== void 0
  154. ? (action === 'show' ? false : true)
  155. : !refs[0].cell.state.isHidden;
  156. for (const c of refs) {
  157. setSubtreeVisibility(this.dataState, c.cell.transform.ref, isHidden);
  158. }
  159. }
  160. applyPreset<P = any, S = {}>(trajectories: ReadonlyArray<TrajectoryRef>, provider: TrajectoryHierarchyPresetProvider<P, S>, params?: P): Promise<any> {
  161. return this.plugin.dataTransaction(async () => {
  162. for (const t of trajectories) {
  163. if (t.models.length > 0) {
  164. await this.clearTrajectory(t);
  165. }
  166. await this.plugin.builders.structure.hierarchy.applyPreset(t.cell, provider, params);
  167. }
  168. });
  169. }
  170. async updateStructure(s: StructureRef, params: any) {
  171. await this.plugin.dataTransaction(async () => {
  172. const root = StateTree.getDecoratorRoot(this.dataState.tree, s.cell.transform.ref);
  173. const children = this.dataState.tree.children.get(root).toArray();
  174. await this.remove(children, false);
  175. await this.plugin.state.updateTransform(this.plugin.state.data, s.cell.transform.ref, params, 'Structure Type');
  176. await this.plugin.builders.structure.representation.applyPreset(s.cell.transform.ref, 'auto');
  177. }, { canUndo: 'Structure Type' });
  178. PluginCommands.Camera.Reset(this.plugin);
  179. }
  180. private clearTrajectory(trajectory: TrajectoryRef) {
  181. const builder = this.dataState.build();
  182. for (const m of trajectory.models) {
  183. builder.delete(m.cell);
  184. }
  185. return builder.commit();
  186. }
  187. constructor(private plugin: PluginContext) {
  188. super();
  189. this.subscribe(plugin.state.data.events.changed, e => {
  190. if (e.inTransaction || plugin.behaviors.state.isAnimating.value) return;
  191. this.sync(true);
  192. });
  193. this.subscribe(plugin.behaviors.state.isAnimating, isAnimating => {
  194. if (!isAnimating && !plugin.behaviors.state.isUpdating.value) this.sync(true);
  195. });
  196. }
  197. }
  198. export namespace StructureHierarchyManager {
  199. export function getComponentGroups(structures: ReadonlyArray<StructureRef>): StructureComponentRef[][] {
  200. if (!structures.length) return [];
  201. if (structures.length === 1) return structures[0].components.map(c => [c]);
  202. const groups: StructureComponentRef[][] = [];
  203. const map = new Map<string, StructureComponentRef[]>();
  204. for (const s of structures) {
  205. for (const c of s.components) {
  206. const key = c.key;
  207. if (!key) continue;
  208. let component = map.get(key);
  209. if (!component) {
  210. component = [];
  211. map.set(key, component);
  212. groups.push(component);
  213. }
  214. component.push(c);
  215. }
  216. }
  217. return groups;
  218. }
  219. export function getSelectedStructuresDescription(plugin: PluginContext) {
  220. const { structures } = plugin.managers.structure.hierarchy.selection;
  221. if (structures.length === 0) return '';
  222. if (structures.length === 1) {
  223. const s = structures[0];
  224. const data = s.cell.obj?.data;
  225. if (!data) return s.cell.obj?.label || 'Structure';
  226. const model = data.models[0] || data.representativeModel || data.masterModel;
  227. if (!model) return s.cell.obj?.label || 'Structure';
  228. const entryId = model.entryId;
  229. if (s.model?.trajectory?.models && s.model.trajectory.models.length === 1) return entryId;
  230. if (s.model) return `${s.model.cell.obj?.label} | ${entryId}`;
  231. return entryId;
  232. }
  233. const p = structures[0];
  234. const t = p?.model?.trajectory;
  235. let sameTraj = true;
  236. for (const s of structures) {
  237. if (s?.model?.trajectory !== t) {
  238. sameTraj = false;
  239. break;
  240. }
  241. }
  242. return sameTraj && t ? `${t.cell.obj?.label} | ${structures.length} structures` : `${structures.length} structures`;
  243. }
  244. }