component.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. /**
  2. * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { Structure, StructureElement, StructureSelection } from '../../../mol-model/structure';
  7. import { structureAreIntersecting, structureSubtract, structureUnion } from '../../../mol-model/structure/query/utils/structure-set';
  8. import { PluginContext } from '../../../mol-plugin/context';
  9. import { StateBuilder } from '../../../mol-state';
  10. import { Task } from '../../../mol-task';
  11. import { UUID } from '../../../mol-util';
  12. import { Color } from '../../../mol-util/color';
  13. import { ColorNames } from '../../../mol-util/color/names';
  14. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  15. import { StructureRepresentationProvider } from '../../builder/structure/provider';
  16. import { StructureComponentParams } from '../../helpers/structure-component';
  17. import { setStructureOverpaint } from '../../helpers/structure-overpaint';
  18. import { StructureSelectionQuery, StructureSelectionQueryOptions } from '../../helpers/structure-selection-query';
  19. import { HierarchyRef, StructureComponentRef, StructureRef, StructureRepresentationRef } from './hierarchy-state';
  20. export { StructureComponentManager };
  21. class StructureComponentManager {
  22. applyPreset<P = any, S = {}>(structures: StructureRef[], provider: StructureRepresentationProvider<P, S>, params?: P): Promise<any> {
  23. return this.plugin.runTask(this.dataState.transaction(async () => {
  24. await this.clearComponents(structures);
  25. for (const s of structures) {
  26. await this.plugin.builders.structure.representation.structurePreset(s.cell, provider, params);
  27. }
  28. }));
  29. }
  30. clear(structures: StructureRef[]) {
  31. return this.clearComponents(structures);
  32. }
  33. removeRepresentations(components: StructureComponentRef[], pivot: StructureRepresentationRef) {
  34. if (components.length === 0) return;
  35. const index = components[0].representations.indexOf(pivot);
  36. if (index < 0) return;
  37. const toRemove: HierarchyRef[] = [];
  38. for (const c of components) {
  39. if (index >= c.representations.length) continue;
  40. toRemove.push(c.representations[index]);
  41. }
  42. return this.plugin.managers.structure.hierarchy.remove(toRemove);
  43. }
  44. modify(action: StructureComponentManager.ModifyAction, structures?: ReadonlyArray<StructureRef>) {
  45. return this.plugin.runTask(this.dataState.transaction(async () => {
  46. if (!structures) structures = this.plugin.managers.structure.hierarchy.state.currentStructures;
  47. if (structures.length === 0) return;
  48. switch (action.kind) {
  49. case 'add': await this.modifyAdd(action, structures); break;
  50. case 'merge': await this.modifyMerge(action, structures); break;
  51. case 'subtract': await this.modifySubtract(action, structures); break;
  52. case 'color': await this.modifyColor(action, structures); break;
  53. }
  54. }))
  55. }
  56. private async modifyAdd(params: StructureComponentManager.ModifyActionAdd, structures: ReadonlyArray<StructureRef>) {
  57. const componentKey = UUID.create22();
  58. for (const s of structures) {
  59. const component = await this.plugin.builders.structure.tryCreateQueryComponent({
  60. structure: s.cell,
  61. query: params.selection,
  62. key: componentKey,
  63. label: params.label,
  64. });
  65. if (params.representation === 'none' || !component) continue;
  66. await this.plugin.builders.structure.representation.addRepresentation(component, {
  67. repr: this.plugin.structureRepresentation.registry.get(params.representation)
  68. });
  69. }
  70. }
  71. private updateComponent(builder: StateBuilder.Root, component: StructureComponentRef, by: Structure, action: 'union' | 'subtract') {
  72. const structure = component.cell.obj?.data;
  73. if (!structure) return;
  74. if (!structureAreIntersecting(structure, by)) return;
  75. const parent = component.structure.cell.obj?.data!;
  76. const modified = action === 'union' ? structureUnion(parent, [structure, by]) : structureSubtract(structure, by);
  77. if (modified.elementCount === 0) {
  78. builder.delete(component.cell.transform.ref);
  79. } else {
  80. const bundle = StructureElement.Bundle.fromLoci(StructureSelection.toLociWithSourceUnits(StructureSelection.Singletons(parent, modified)));
  81. const params: StructureComponentParams = {
  82. type: { name: 'bundle', params: bundle },
  83. nullIfEmpty: true,
  84. label: component.cell.obj?.label!
  85. };
  86. builder.to(component.cell).update(params)
  87. }
  88. }
  89. private async modifyMerge(params: StructureComponentManager.ModifyActionMerge, structures: ReadonlyArray<StructureRef>) {
  90. return this.plugin.runTask(Task.create('Merge', async taskCtx => {
  91. const b = this.dataState.build();
  92. for (const s of structures) {
  93. const by = await StructureSelectionQuery.getStructure(this.plugin, taskCtx, params.selection, s.cell.obj?.data!);
  94. for (const c of s.components) {
  95. if (params.componentKey !== 'intersecting' && params.componentKey !== c.key) continue;
  96. this.updateComponent(b, c, by, 'union');
  97. }
  98. }
  99. await this.dataState.updateTree(b).runInContext(taskCtx);
  100. }));
  101. }
  102. private async modifySubtract(params: StructureComponentManager.ModifyActionSubtract, structures: ReadonlyArray<StructureRef>) {
  103. return this.plugin.runTask(Task.create('Subtract', async taskCtx => {
  104. const b = this.dataState.build();
  105. for (const s of structures) {
  106. const by = await StructureSelectionQuery.getStructure(this.plugin, taskCtx, params.selection, s.cell.obj?.data!);
  107. for (const c of s.components) {
  108. if (params.componentKey !== 'intersecting' && params.componentKey !== c.key) continue;
  109. this.updateComponent(b, c, by, 'subtract');
  110. }
  111. }
  112. await this.dataState.updateTree(b).runInContext(taskCtx);
  113. }));
  114. }
  115. private async modifyColor(params: StructureComponentManager.ModifyActionColor, structures: ReadonlyArray<StructureRef>) {
  116. const getLoci = (s: Structure) => this.plugin.managers.structure.selection.getLoci(s);
  117. for (const s of structures) {
  118. await setStructureOverpaint(this.plugin, s.components, params.action.name === 'color' ? params.action.params : -1, getLoci);
  119. }
  120. }
  121. private get dataState() {
  122. return this.plugin.state.dataState;
  123. }
  124. private clearComponents(structures: StructureRef[]) {
  125. const deletes = this.dataState.build();
  126. for (const s of structures) {
  127. for (const c of s.components) {
  128. deletes.delete(c.cell.transform.ref);
  129. }
  130. if (s.currentFocus) {
  131. if (s.currentFocus.focus) deletes.delete(s.currentFocus.focus.cell.transform.ref);
  132. if (s.currentFocus.surroundings) deletes.delete(s.currentFocus.surroundings.cell.transform.ref);
  133. }
  134. }
  135. return this.plugin.runTask(this.dataState.updateTree(deletes));
  136. }
  137. constructor(public plugin: PluginContext) {
  138. }
  139. }
  140. namespace StructureComponentManager {
  141. export type ActionType = 'add' | 'merge' | 'subtract' | 'color'
  142. const SelectionParam = PD.Select(StructureSelectionQueryOptions[1][0], StructureSelectionQueryOptions)
  143. function getComponentsOptions(plugin: PluginContext, custom: [string, string][], label?: string) {
  144. const types = [
  145. ...custom,
  146. ...plugin.managers.structure.hierarchy.componentGroups.map(g => [g[0].key!, g[0].cell.obj?.label])
  147. ] as [string, string][];
  148. return PD.Select(types[0][0], types, { label });
  149. }
  150. function getRepresentationTypes(plugin: PluginContext, pivot: StructureRef | undefined, custom: [string, string][], label?: string) {
  151. const types = [
  152. ...custom,
  153. ...(pivot?.cell.obj?.data
  154. ? plugin.structureRepresentation.registry.getApplicableTypes(pivot.cell.obj?.data!)
  155. : plugin.structureRepresentation.registry.types)
  156. ] as [string, string][];
  157. return PD.Select(types[0][0], types, { label });
  158. }
  159. export function getActionParams(plugin: PluginContext, action: ActionType) {
  160. switch (action) {
  161. case 'add':
  162. return {
  163. kind: PD.Value<ActionType>(action, { isHidden: true }),
  164. selection: SelectionParam,
  165. label: PD.Text(''),
  166. representation: getRepresentationTypes(plugin, plugin.managers.structure.hierarchy.state.currentStructures[0], [['none', '< None >']])
  167. };
  168. case 'merge':
  169. case 'subtract':
  170. return {
  171. kind: PD.Value<ActionType>(action, { isHidden: true }),
  172. selection: SelectionParam,
  173. componentKey: getComponentsOptions(plugin, [['intersecting', '< Intersecting >']], 'Target')
  174. };
  175. case 'color':
  176. // TODO: ability to reset
  177. return {
  178. kind: PD.Value<ActionType>(action, { isHidden: true }),
  179. action: PD.MappedStatic('color', {
  180. color: PD.Color(ColorNames.black),
  181. reset: PD.EmptyGroup()
  182. }),
  183. // TODO: filter by representation type
  184. // representation: getRepresentationTypes(plugin, void 0, [['all', '< All >']])
  185. };
  186. }
  187. }
  188. export type ModifyActionAdd = { kind: 'add', selection: StructureSelectionQuery, label: string, representation: string }
  189. export type ModifyActionMerge = { kind: 'merge', selection: StructureSelectionQuery, componentKey: 'intersecting' | string }
  190. export type ModifyActionSubtract = { kind: 'subtract', selection: StructureSelectionQuery, componentKey: 'intersecting' | string }
  191. export type ModifyActionColor = { kind: 'color', action: { name: 'color', params: Color } | { name: 'reset', params: any } } //, representationType?: string }
  192. export type ModifyAction = ModifyActionAdd | ModifyActionMerge | ModifyActionSubtract | ModifyActionColor
  193. }