hierarchy.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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, HierarchyRef, 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. private syncCurrent<T extends HierarchyRef>(all: ReadonlyArray<T>, added: Set<StateTransform.Ref>): T[] {
  61. const current = this.seletionSet;
  62. const newCurrent: T[] = [];
  63. for (const r of all) {
  64. const ref = r.cell.transform.ref;
  65. if (current.has(ref) || added.has(ref)) newCurrent.push(r);
  66. }
  67. if (newCurrent.length === 0) return all.length > 0 ? [all[0]] : [];
  68. return newCurrent;
  69. }
  70. private sync(notify: boolean) {
  71. if (!notify && this.dataState.inUpdate) return;
  72. if (this.state.syncedTree === this.dataState.tree) {
  73. if (notify && !this.state.notified) {
  74. this.state.notified = true;
  75. this.behaviors.selection.next({ hierarchy: this.state.hierarchy, ...this.state.selection });
  76. }
  77. return;
  78. }
  79. this.state.syncedTree = this.dataState.tree;
  80. const update = buildStructureHierarchy(this.plugin.state.data, this.current);
  81. if (!update.changed) {
  82. return;
  83. }
  84. const { hierarchy } = update;
  85. const trajectories = this.syncCurrent(hierarchy.trajectories, update.added);
  86. const models = this.syncCurrent(hierarchy.models, update.added);
  87. const structures = this.syncCurrent(hierarchy.structures, update.added);
  88. this._currentComponentGroups = void 0;
  89. this._currentSelectionSet = void 0;
  90. this.state.hierarchy = hierarchy;
  91. this.state.selection.trajectories = trajectories;
  92. this.state.selection.models = models;
  93. this.state.selection.structures = structures;
  94. if (notify) {
  95. this.state.notified = true;
  96. this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
  97. } else {
  98. this.state.notified = false;
  99. }
  100. }
  101. updateCurrent(refs: HierarchyRef[], action: 'add' | 'remove') {
  102. const hierarchy = this.current;
  103. const set = action === 'add'
  104. ? SetUtils.union(this.seletionSet, new Set(refs.map(r => r.cell.transform.ref)))
  105. : SetUtils.difference(this.seletionSet, new Set(refs.map(r => r.cell.transform.ref)));
  106. const trajectories = [];
  107. const models = [];
  108. const structures = [];
  109. for (const t of hierarchy.trajectories) {
  110. if (set.has(t.cell.transform.ref)) trajectories.push(t);
  111. }
  112. for (const m of hierarchy.models) {
  113. if (set.has(m.cell.transform.ref)) models.push(m);
  114. }
  115. for (const s of hierarchy.structures) {
  116. if (set.has(s.cell.transform.ref)) structures.push(s);
  117. }
  118. this._currentComponentGroups = void 0;
  119. this._currentSelectionSet = void 0;
  120. this.state.selection.trajectories = trajectories;
  121. this.state.selection.models = models;
  122. this.state.selection.structures = structures;
  123. this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
  124. }
  125. remove(refs: (HierarchyRef | string)[], canUndo?: boolean) {
  126. if (refs.length === 0) return;
  127. const deletes = this.plugin.state.data.build();
  128. for (const r of refs) deletes.delete(typeof r === 'string' ? r : r.cell.transform.ref);
  129. return this.plugin.updateDataState(deletes, { canUndo: canUndo ? 'Remove' : false });
  130. }
  131. toggleVisibility(refs: ReadonlyArray<HierarchyRef>, action?: 'show' | 'hide') {
  132. if (refs.length === 0) return;
  133. const isHidden = action !== void 0
  134. ? (action === 'show' ? false : true)
  135. : !refs[0].cell.state.isHidden;
  136. for (const c of refs) {
  137. setSubtreeVisibility(this.dataState, c.cell.transform.ref, isHidden);
  138. }
  139. }
  140. applyPreset<P = any, S = {}>(trajectories: ReadonlyArray<TrajectoryRef>, provider: TrajectoryHierarchyPresetProvider<P, S>, params?: P): Promise<any> {
  141. return this.plugin.dataTransaction(async () => {
  142. for (const t of trajectories) {
  143. if (t.models.length > 0) {
  144. await this.clearTrajectory(t);
  145. }
  146. await this.plugin.builders.structure.hierarchy.applyPreset(t.cell, provider, params);
  147. }
  148. });
  149. }
  150. async updateStructure(s: StructureRef, params: any) {
  151. await this.plugin.dataTransaction(async () => {
  152. const root = StateTree.getDecoratorRoot(this.dataState.tree, s.cell.transform.ref);
  153. const children = this.dataState.tree.children.get(root).toArray();
  154. await this.remove(children, false);
  155. await this.plugin.state.updateTransform(this.plugin.state.data, s.cell.transform.ref, params, 'Structure Type');
  156. await this.plugin.builders.structure.representation.applyPreset(s.cell.transform.ref, 'auto');
  157. }, { canUndo: 'Structure Type' });
  158. PluginCommands.Camera.Reset(this.plugin);
  159. }
  160. private clearTrajectory(trajectory: TrajectoryRef) {
  161. const builder = this.dataState.build();
  162. for (const m of trajectory.models) {
  163. builder.delete(m.cell);
  164. }
  165. return this.plugin.updateDataState(builder);
  166. }
  167. constructor(private plugin: PluginContext) {
  168. super();
  169. this.subscribe(plugin.state.data.events.changed, e => {
  170. if (e.inTransaction || plugin.behaviors.state.isAnimating.value) return;
  171. this.sync(true);
  172. });
  173. this.subscribe(plugin.behaviors.state.isAnimating, isAnimating => {
  174. if (!isAnimating && !plugin.behaviors.state.isUpdating.value) this.sync(true);
  175. });
  176. }
  177. }
  178. export namespace StructureHierarchyManager {
  179. export function getComponentGroups(structures: ReadonlyArray<StructureRef>): StructureComponentRef[][] {
  180. if (!structures.length) return [];
  181. if (structures.length === 1) return structures[0].components.map(c => [c]);
  182. const groups: StructureComponentRef[][] = [];
  183. const map = new Map<string, StructureComponentRef[]>();
  184. for (const s of structures) {
  185. for (const c of s.components) {
  186. const key = c.key;
  187. if (!key) continue;
  188. let component = map.get(key);
  189. if (!component) {
  190. component = [];
  191. map.set(key, component);
  192. groups.push(component);
  193. }
  194. component.push(c);
  195. }
  196. }
  197. return groups;
  198. }
  199. export function getSelectedStructuresDescription(plugin: PluginContext) {
  200. const { structures } = plugin.managers.structure.hierarchy.selection;
  201. if (structures.length === 0) return '';
  202. if (structures.length === 1) {
  203. const s = structures[0];
  204. const data = s.cell.obj?.data;
  205. if (!data) return s.cell.obj?.label || 'Structure';
  206. const model = data.models[0] || data.representativeModel || data.masterModel;
  207. if (!model) return s.cell.obj?.label || 'Structure';
  208. const entryId = model.entryId;
  209. if (s.model?.trajectory?.models && s.model.trajectory.models.length === 1) return entryId;
  210. if (s.model) return `${s.model.cell.obj?.label} | ${entryId}`;
  211. return entryId;
  212. }
  213. const p = structures[0];
  214. const t = p?.model?.trajectory;
  215. let sameTraj = true;
  216. for (const s of structures) {
  217. if (s?.model?.trajectory !== t) {
  218. sameTraj = false;
  219. break;
  220. }
  221. }
  222. return sameTraj && t ? `${t.cell.obj?.label} | ${structures.length} structures` : `${structures.length} structures`;
  223. }
  224. }