hierarchy.ts 11 KB

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