structure-focus-representation.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /**
  2. * Copyright (c) 2019-2021 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 { InteractionsRepresentationProvider } from '../../../../mol-model-props/computed/representations/interactions';
  8. import { InteractionTypeColorThemeProvider } from '../../../../mol-model-props/computed/themes/interaction-type';
  9. import { StructureElement } from '../../../../mol-model/structure';
  10. import { createStructureRepresentationParams } from '../../../../mol-plugin-state/helpers/structure-representation-params';
  11. import { PluginStateObject } from '../../../../mol-plugin-state/objects';
  12. import { StateTransforms } from '../../../../mol-plugin-state/transforms';
  13. import { PluginBehavior } from '../../../behavior';
  14. import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
  15. import { StateObjectCell, StateSelection, StateTransform } from '../../../../mol-state';
  16. import { SizeTheme } from '../../../../mol-theme/size';
  17. import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
  18. import { PluginCommands } from '../../../commands';
  19. import { PluginContext } from '../../../context';
  20. const StructureFocusRepresentationParams = (plugin: PluginContext) => {
  21. const reprParams = StateTransforms.Representation.StructureRepresentation3D.definition.params!(void 0, plugin) as PD.Params;
  22. return {
  23. expandRadius: PD.Numeric(5, { min: 1, max: 10, step: 1 }),
  24. targetParams: PD.Group(reprParams, {
  25. label: 'Target',
  26. customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', size: 'physical', typeParams: { sizeFactor: 0.26, alpha: 0.51, adjustCylinderLength: true } })
  27. }),
  28. surroundingsParams: PD.Group(reprParams, {
  29. label: 'Surroundings',
  30. customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', size: 'physical', typeParams: { sizeFactor: 0.16 } })
  31. }),
  32. nciParams: PD.Group(reprParams, {
  33. label: 'Non-covalent Int.',
  34. customDefault: createStructureRepresentationParams(plugin, void 0, {
  35. type: InteractionsRepresentationProvider,
  36. color: InteractionTypeColorThemeProvider,
  37. size: SizeTheme.BuiltIn.uniform
  38. })
  39. }),
  40. components: PD.MultiSelect(FocusComponents, PD.arrayToOptions(FocusComponents)),
  41. excludeTargetFromSurroundings: PD.Boolean(false, { label: 'Exclude Target', description: 'Exclude the focus "target" from the surroudings component.' }),
  42. ignoreHydrogens: PD.Boolean(false)
  43. };
  44. };
  45. const FocusComponents = ['target' as const, 'surroundings' as const, 'interactions' as const];
  46. type StructureFocusRepresentationProps = PD.ValuesFor<ReturnType<typeof StructureFocusRepresentationParams>>
  47. export enum StructureFocusRepresentationTags {
  48. TargetSel = 'structure-focus-target-sel',
  49. TargetRepr = 'structure-focus-target-repr',
  50. SurrSel = 'structure-focus-surr-sel',
  51. SurrRepr = 'structure-focus-surr-repr',
  52. SurrNciRepr = 'structure-focus-surr-nci-repr'
  53. }
  54. const TagSet: Set<StructureFocusRepresentationTags> = new Set([StructureFocusRepresentationTags.TargetSel, StructureFocusRepresentationTags.TargetRepr, StructureFocusRepresentationTags.SurrSel, StructureFocusRepresentationTags.SurrRepr, StructureFocusRepresentationTags.SurrNciRepr]);
  55. class StructureFocusRepresentationBehavior extends PluginBehavior.WithSubscribers<StructureFocusRepresentationProps> {
  56. private get surrLabel() { return `[Focus] Surroundings (${this.params.expandRadius} Å)`; }
  57. private getReprParams(reprParams: PD.Values<PD.Params>) {
  58. return {
  59. ...this.params.targetParams,
  60. type: {
  61. name: reprParams.type.name,
  62. params: { ...reprParams.type.params, ignoreHydrogens: this.params.ignoreHydrogens }
  63. }
  64. };
  65. }
  66. private ensureShape(cell: StateObjectCell<PluginStateObject.Molecule.Structure>) {
  67. const state = this.plugin.state.data, tree = state.tree;
  68. const builder = state.build();
  69. const refs = StateSelection.findUniqueTagsInSubtree(tree, cell.transform.ref, TagSet);
  70. // Selections
  71. if (!refs[StructureFocusRepresentationTags.TargetSel]) {
  72. refs[StructureFocusRepresentationTags.TargetSel] = builder
  73. .to(cell)
  74. .apply(StateTransforms.Model.StructureSelectionFromBundle,
  75. { bundle: StructureElement.Bundle.Empty, label: '[Focus] Target' }, { tags: StructureFocusRepresentationTags.TargetSel }).ref;
  76. }
  77. if (!refs[StructureFocusRepresentationTags.SurrSel]) {
  78. refs[StructureFocusRepresentationTags.SurrSel] = builder
  79. .to(cell)
  80. .apply(StateTransforms.Model.StructureSelectionFromExpression,
  81. { expression: MS.struct.generator.empty(), label: this.surrLabel }, { tags: StructureFocusRepresentationTags.SurrSel }).ref;
  82. }
  83. const components = this.params.components;
  84. // Representations
  85. if (components.indexOf('target') >= 0 && !refs[StructureFocusRepresentationTags.TargetRepr]) {
  86. refs[StructureFocusRepresentationTags.TargetRepr] = builder
  87. .to(refs[StructureFocusRepresentationTags.TargetSel]!)
  88. .apply(StateTransforms.Representation.StructureRepresentation3D, this.getReprParams(this.params.targetParams), { tags: StructureFocusRepresentationTags.TargetRepr }).ref;
  89. }
  90. if (components.indexOf('surroundings') >= 0 && !refs[StructureFocusRepresentationTags.SurrRepr]) {
  91. refs[StructureFocusRepresentationTags.SurrRepr] = builder
  92. .to(refs[StructureFocusRepresentationTags.SurrSel]!)
  93. .apply(StateTransforms.Representation.StructureRepresentation3D, this.getReprParams(this.params.surroundingsParams), { tags: StructureFocusRepresentationTags.SurrRepr }).ref;
  94. }
  95. if (components.indexOf('interactions') >= 0 && !refs[StructureFocusRepresentationTags.SurrNciRepr] && cell.obj && InteractionsRepresentationProvider.isApplicable(cell.obj?.data)) {
  96. refs[StructureFocusRepresentationTags.SurrNciRepr] = builder
  97. .to(refs[StructureFocusRepresentationTags.SurrSel]!)
  98. .apply(StateTransforms.Representation.StructureRepresentation3D, this.params.nciParams, { tags: StructureFocusRepresentationTags.SurrNciRepr }).ref;
  99. }
  100. return { state, builder, refs };
  101. }
  102. private clear(root: StateTransform.Ref) {
  103. const state = this.plugin.state.data;
  104. this.currentSource = void 0;
  105. const foci = state.select(StateSelection.Generators.byRef(root).subtree().withTag(StructureFocusRepresentationTags.TargetSel));
  106. const surrs = state.select(StateSelection.Generators.byRef(root).subtree().withTag(StructureFocusRepresentationTags.SurrSel));
  107. if (foci.length === 0 && surrs.length === 0) return;
  108. const update = state.build();
  109. const bundle = StructureElement.Bundle.Empty;
  110. for (const f of foci) {
  111. update.to(f).update(StateTransforms.Model.StructureSelectionFromBundle, old => ({ ...old, bundle }));
  112. }
  113. const expression = MS.struct.generator.empty();
  114. for (const s of surrs) {
  115. update.to(s).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression }));
  116. }
  117. return PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
  118. }
  119. private currentSource: StructureElement.Loci | undefined = void 0;
  120. private async focus(sourceLoci: StructureElement.Loci) {
  121. const parent = this.plugin.helpers.substructureParent.get(sourceLoci.structure);
  122. if (!parent || !parent.obj) return;
  123. this.currentSource = sourceLoci;
  124. const loci = StructureElement.Loci.remap(sourceLoci, parent.obj!.data);
  125. const residueLoci = StructureElement.Loci.extendToWholeResidues(loci);
  126. const residueBundle = StructureElement.Bundle.fromLoci(residueLoci);
  127. const target = StructureElement.Bundle.toExpression(residueBundle);
  128. let surroundings = MS.struct.modifier.includeSurroundings({
  129. 0: target,
  130. radius: this.params.expandRadius,
  131. 'as-whole-residues': true
  132. });
  133. if (this.params.excludeTargetFromSurroundings) {
  134. surroundings = MS.struct.modifier.exceptBy({
  135. 0: surroundings,
  136. by: target
  137. });
  138. }
  139. const { state, builder, refs } = this.ensureShape(parent);
  140. builder.to(refs[StructureFocusRepresentationTags.TargetSel]!).update(StateTransforms.Model.StructureSelectionFromBundle, old => ({ ...old, bundle: residueBundle }));
  141. builder.to(refs[StructureFocusRepresentationTags.SurrSel]!).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression: surroundings, label: this.surrLabel }));
  142. await PluginCommands.State.Update(this.plugin, { state, tree: builder, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
  143. }
  144. register(ref: string): void {
  145. this.subscribeObservable(this.plugin.managers.structure.focus.behaviors.current, (entry) => {
  146. if (entry) this.focus(entry.loci);
  147. else this.clear(StateTransform.RootRef);
  148. });
  149. }
  150. async update(params: StructureFocusRepresentationProps) {
  151. const old = this.params;
  152. this.params = params;
  153. if (old.excludeTargetFromSurroundings !== params.excludeTargetFromSurroundings) {
  154. if (this.currentSource) {
  155. this.focus(this.currentSource);
  156. }
  157. return true;
  158. }
  159. const state = this.plugin.state.data;
  160. const builder = state.build();
  161. const all = StateSelection.Generators.root.subtree();
  162. const components = this.params.components;
  163. // TODO: create component if previously didnt exist
  164. let hasComponent = components.indexOf('target') >= 0;
  165. for (const repr of state.select(all.withTag(StructureFocusRepresentationTags.TargetRepr))) {
  166. if (!hasComponent) builder.delete(repr.transform.ref);
  167. else builder.to(repr).update(this.getReprParams(this.params.targetParams));
  168. }
  169. hasComponent = components.indexOf('surroundings') >= 0;
  170. for (const repr of state.select(all.withTag(StructureFocusRepresentationTags.SurrRepr))) {
  171. if (!hasComponent) builder.delete(repr.transform.ref);
  172. else builder.to(repr).update(this.getReprParams(this.params.surroundingsParams));
  173. }
  174. hasComponent = components.indexOf('interactions') >= 0;
  175. for (const repr of state.select(all.withTag(StructureFocusRepresentationTags.SurrNciRepr))) {
  176. if (!hasComponent) builder.delete(repr.transform.ref);
  177. else builder.to(repr).update(this.params.nciParams);
  178. }
  179. await PluginCommands.State.Update(this.plugin, { state, tree: builder, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
  180. if (params.expandRadius !== old.expandRadius) {
  181. if (this.currentSource) {
  182. this.focus(this.currentSource);
  183. }
  184. return true;
  185. }
  186. return true;
  187. }
  188. }
  189. export const StructureFocusRepresentation = PluginBehavior.create({
  190. name: 'create-structure-focus-representation',
  191. display: { name: 'Structure Focus Representation' },
  192. category: 'interaction',
  193. ctor: StructureFocusRepresentationBehavior,
  194. params: (_, plugin) => StructureFocusRepresentationParams(plugin)
  195. });