structure-representation-interaction.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /**
  2. * Copyright (c) 2019-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 { InteractionsRepresentationProvider } from '../../../../mol-model-props/computed/representations/interactions';
  8. import { InteractionTypeColorThemeProvider } from '../../../../mol-model-props/computed/themes/interaction-type';
  9. import { EmptyLoci, isEmptyLoci, Loci } from '../../../../mol-model/loci';
  10. import { Bond, Structure, StructureElement } from '../../../../mol-model/structure';
  11. import { createStructureRepresentationParams } from '../../../../mol-plugin-state/helpers/structure-representation-params';
  12. import { PluginStateObject } from '../../../../mol-plugin-state/objects';
  13. import { StateTransforms } from '../../../../mol-plugin-state/transforms';
  14. import { PluginBehavior } from '../../../../mol-plugin/behavior';
  15. import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
  16. import { StateObjectCell, StateSelection, StateTransform } from '../../../../mol-state';
  17. import { BuiltInSizeThemes } from '../../../../mol-theme/size';
  18. import { Binding } from '../../../../mol-util/binding';
  19. import { ButtonsType, ModifiersKeys } from '../../../../mol-util/input/input-observer';
  20. import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
  21. import { PluginCommands } from '../../../commands';
  22. import { PluginContext } from '../../../context';
  23. const B = ButtonsType
  24. const M = ModifiersKeys
  25. const Trigger = Binding.Trigger
  26. const DefaultStructureRepresentationInteractionBindings = {
  27. clickInteractionAroundOnly: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Show the structure interaction around only the clicked element using ${triggers}.'),
  28. }
  29. // const StructureRepresentationInteractionParams = {
  30. // bindings: PD.Value(DefaultStructureRepresentationInteractionBindings, { isHidden: true }),
  31. // }
  32. const StructureRepresentationInteractionParams = (plugin: PluginContext) => {
  33. const reprParams = StateTransforms.Representation.StructureRepresentation3D.definition.params!(void 0, plugin) as PD.Params;
  34. return {
  35. bindings: PD.Value(DefaultStructureRepresentationInteractionBindings, { isHidden: true }),
  36. focusParams: PD.Group(reprParams, {
  37. label: 'Focus',
  38. customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', size: 'uniform' })
  39. }),
  40. surroundingsParams: PD.Group(reprParams, {
  41. label: 'Surroundings',
  42. customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', color: 'element-symbol', size: 'uniform' })
  43. }),
  44. nciParams: PD.Group(reprParams, {
  45. label: 'Non-covalent Int.',
  46. // customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', color: 'element-symbol', size: 'uniform' })
  47. customDefault: createStructureRepresentationParams(plugin, void 0, {
  48. type: InteractionsRepresentationProvider,
  49. color: InteractionTypeColorThemeProvider,
  50. size: BuiltInSizeThemes.uniform
  51. })
  52. })
  53. };
  54. }
  55. type StructureRepresentationInteractionProps = PD.ValuesFor<ReturnType<typeof StructureRepresentationInteractionParams>>
  56. //PD.Values<typeof StructureRepresentationInteractionParams>
  57. export enum StructureRepresentationInteractionTags {
  58. Group = 'structure-interaction-group',
  59. ResidueSel = 'structure-interaction-residue-sel',
  60. ResidueRepr = 'structure-interaction-residue-repr',
  61. SurrSel = 'structure-interaction-surr-sel',
  62. SurrRepr = 'structure-interaction-surr-repr',
  63. SurrNciRepr = 'structure-interaction-surr-nci-repr'
  64. }
  65. const TagSet: Set<StructureRepresentationInteractionTags> = new Set([StructureRepresentationInteractionTags.Group, StructureRepresentationInteractionTags.ResidueSel, StructureRepresentationInteractionTags.ResidueRepr, StructureRepresentationInteractionTags.SurrSel, StructureRepresentationInteractionTags.SurrRepr, StructureRepresentationInteractionTags.SurrNciRepr])
  66. export class StructureRepresentationInteractionBehavior extends PluginBehavior.WithSubscribers<StructureRepresentationInteractionProps> {
  67. private ensureShape(cell: StateObjectCell<PluginStateObject.Molecule.Structure>) {
  68. const state = this.plugin.state.dataState, tree = state.tree;
  69. const builder = state.build();
  70. const refs = StateSelection.findUniqueTagsInSubtree(tree, cell.transform.ref, TagSet);
  71. if (!refs['structure-interaction-group']) {
  72. refs['structure-interaction-group'] = builder
  73. .to(cell)
  74. .group(StateTransforms.Misc.CreateGroup, { label: 'Current Focus' }, { tags: StructureRepresentationInteractionTags.Group }).ref;
  75. }
  76. // Selections
  77. if (!refs[StructureRepresentationInteractionTags.ResidueSel]) {
  78. refs[StructureRepresentationInteractionTags.ResidueSel] = builder
  79. .to(refs['structure-interaction-group'])
  80. .apply(StateTransforms.Model.StructureSelectionFromBundle,
  81. { bundle: { } as any, label: 'Focus' }, { tags: StructureRepresentationInteractionTags.ResidueSel }).ref;
  82. }
  83. if (!refs[StructureRepresentationInteractionTags.SurrSel]) {
  84. refs[StructureRepresentationInteractionTags.SurrSel] = builder
  85. .to(refs['structure-interaction-group'])
  86. .apply(StateTransforms.Model.StructureSelectionFromExpression,
  87. { expression: { } as any, label: 'Surroundings' }, { tags: StructureRepresentationInteractionTags.SurrSel }).ref;
  88. }
  89. // Representations
  90. if (!refs[StructureRepresentationInteractionTags.ResidueRepr]) {
  91. refs[StructureRepresentationInteractionTags.ResidueRepr] = builder
  92. .to(refs['structure-interaction-residue-sel']!)
  93. .apply(StateTransforms.Representation.StructureRepresentation3D, this.params.focusParams, { tags: StructureRepresentationInteractionTags.ResidueRepr }).ref;
  94. }
  95. if (!refs[StructureRepresentationInteractionTags.SurrRepr]) {
  96. refs[StructureRepresentationInteractionTags.SurrRepr] = builder
  97. .to(refs['structure-interaction-surr-sel']!)
  98. .apply(StateTransforms.Representation.StructureRepresentation3D,this.params.nciParams, { tags: StructureRepresentationInteractionTags.SurrRepr }).ref;
  99. }
  100. if (!refs[StructureRepresentationInteractionTags.SurrNciRepr]) {
  101. refs[StructureRepresentationInteractionTags.SurrNciRepr] = builder
  102. .to(refs['structure-interaction-surr-sel']!)
  103. .apply(StateTransforms.Representation.StructureRepresentation3D, this.params.surroundingsParams, { tags: StructureRepresentationInteractionTags.SurrNciRepr }).ref;
  104. }
  105. return { state, builder, refs };
  106. }
  107. private clear(root: StateTransform.Ref) {
  108. const state = this.plugin.state.dataState;
  109. const groups = state.select(StateSelection.Generators.byRef(root).subtree().withTag(StructureRepresentationInteractionTags.Group));
  110. if (groups.length === 0) return;
  111. const update = state.build();
  112. const bundle = StructureElement.Bundle.Empty;
  113. const expression = MS.struct.generator.empty();
  114. for (const g of groups) {
  115. // TODO: update props of the group node to ghost
  116. const res = StateSelection.findTagInSubtree(state.tree, g.transform.ref, StructureRepresentationInteractionTags.ResidueSel);
  117. const surr = StateSelection.findTagInSubtree(state.tree, g.transform.ref, StructureRepresentationInteractionTags.SurrSel);
  118. if (res) update.to(res).update(StateTransforms.Model.StructureSelectionFromBundle, old => ({ ...old, bundle }));
  119. if (surr) update.to(surr).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression }));
  120. }
  121. PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
  122. }
  123. register(ref: string): void {
  124. let lastLoci: Loci = EmptyLoci;
  125. this.subscribeObservable(this.plugin.events.state.object.removed, o => {
  126. if (!PluginStateObject.Molecule.Structure.is(o.obj) || !StructureElement.Loci.is(lastLoci)) return;
  127. if (lastLoci.structure === o.obj.data) {
  128. lastLoci = EmptyLoci;
  129. }
  130. });
  131. this.subscribeObservable(this.plugin.events.state.object.updated, o => {
  132. if (!PluginStateObject.Molecule.Structure.is(o.oldObj) || !StructureElement.Loci.is(lastLoci)) return;
  133. if (lastLoci.structure === o.oldObj.data) {
  134. lastLoci = EmptyLoci;
  135. }
  136. });
  137. this.subscribeObservable(this.plugin.behaviors.interaction.click, ({ current, button, modifiers }) => {
  138. const { clickInteractionAroundOnly } = this.params.bindings
  139. if (Binding.match(clickInteractionAroundOnly, button, modifiers)) {
  140. if (isEmptyLoci(current.loci)) {
  141. this.clear(StateTransform.RootRef);
  142. lastLoci = current.loci;
  143. return;
  144. }
  145. let loci: StructureElement.Loci;
  146. if (StructureElement.Loci.is(current.loci)) {
  147. loci = current.loci;
  148. } else if (Bond.isLoci(current.loci)) {
  149. loci = Bond.toStructureElementLoci(current.loci);
  150. } else if (Structure.isLoci(current.loci)) {
  151. loci = Structure.toStructureElementLoci(current.loci.structure);
  152. } else {
  153. return;
  154. }
  155. if (StructureElement.Loci.isEmpty(loci)) return;
  156. const parent = this.plugin.helpers.substructureParent.get(loci.structure);
  157. if (!parent || !parent.obj) return;
  158. if (Loci.areEqual(lastLoci, loci)) {
  159. lastLoci = EmptyLoci;
  160. this.clear(parent.transform.ref);
  161. return;
  162. }
  163. lastLoci = loci;
  164. const residueLoci = StructureElement.Loci.extendToWholeResidues(StructureElement.Loci.remap(loci, parent.obj!.data))
  165. const residueBundle = StructureElement.Bundle.fromLoci(residueLoci)
  166. const surroundings = MS.struct.modifier.includeSurroundings({
  167. 0: StructureElement.Bundle.toExpression(residueBundle),
  168. radius: 5,
  169. 'as-whole-residues': true
  170. });
  171. const { state, builder, refs } = this.ensureShape(parent);
  172. builder.to(refs[StructureRepresentationInteractionTags.ResidueSel]!).update(StateTransforms.Model.StructureSelectionFromBundle, old => ({ ...old, bundle: residueBundle }));
  173. builder.to(refs[StructureRepresentationInteractionTags.SurrSel]!).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression: surroundings }));
  174. PluginCommands.State.Update(this.plugin, { state, tree: builder, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
  175. }
  176. });
  177. }
  178. async update(params: StructureRepresentationInteractionProps) {
  179. this.params = params;
  180. const state = this.plugin.state.dataState;
  181. const builder = state.build();
  182. const all = StateSelection.Generators.root.subtree();
  183. for (const repr of state.select(all.withTag(StructureRepresentationInteractionTags.ResidueRepr))) {
  184. builder.to(repr).update(this.params.focusParams);
  185. }
  186. for (const repr of state.select(all.withTag(StructureRepresentationInteractionTags.SurrRepr))) {
  187. builder.to(repr).update(this.params.surroundingsParams);
  188. }
  189. for (const repr of state.select(all.withTag(StructureRepresentationInteractionTags.SurrNciRepr))) {
  190. builder.to(repr).update(this.params.nciParams);
  191. }
  192. await PluginCommands.State.Update(this.plugin, { state, tree: builder, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
  193. return true;
  194. }
  195. }
  196. export const StructureRepresentationInteraction = PluginBehavior.create({
  197. name: 'create-structure-representation-interaction',
  198. display: { name: 'Structure Representation Interaction' },
  199. category: 'interaction',
  200. ctor: StructureRepresentationInteractionBehavior,
  201. params: (_, plugin) => StructureRepresentationInteractionParams(plugin)
  202. });