structure-representation-interaction.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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. const B = ButtonsType
  23. const M = ModifiersKeys
  24. const Trigger = Binding.Trigger
  25. const DefaultStructureRepresentationInteractionBindings = {
  26. 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}.'),
  27. }
  28. const StructureRepresentationInteractionParams = {
  29. bindings: PD.Value(DefaultStructureRepresentationInteractionBindings, { isHidden: true }),
  30. }
  31. type StructureRepresentationInteractionProps = PD.Values<typeof StructureRepresentationInteractionParams>
  32. export enum StructureRepresentationInteractionTags {
  33. Group = 'structure-interaction-group',
  34. ResidueSel = 'structure-interaction-residue-sel',
  35. ResidueRepr = 'structure-interaction-residue-repr',
  36. SurrSel = 'structure-interaction-surr-sel',
  37. SurrRepr = 'structure-interaction-surr-repr',
  38. SurrNciRepr = 'structure-interaction-surr-nci-repr'
  39. }
  40. const TagSet: Set<StructureRepresentationInteractionTags> = new Set([StructureRepresentationInteractionTags.Group, StructureRepresentationInteractionTags.ResidueSel, StructureRepresentationInteractionTags.ResidueRepr, StructureRepresentationInteractionTags.SurrSel, StructureRepresentationInteractionTags.SurrRepr, StructureRepresentationInteractionTags.SurrNciRepr])
  41. export class StructureRepresentationInteractionBehavior extends PluginBehavior.WithSubscribers<StructureRepresentationInteractionProps> {
  42. private createResVisualParams(s: Structure) {
  43. return createStructureRepresentationParams(this.plugin, s, {
  44. type: 'ball-and-stick',
  45. size: 'uniform'
  46. });
  47. }
  48. private createSurVisualParams(s: Structure) {
  49. return createStructureRepresentationParams(this.plugin, s, {
  50. type: 'ball-and-stick',
  51. color: 'element-symbol',
  52. size: 'uniform'
  53. });
  54. }
  55. private createSurNciVisualParams(s: Structure) {
  56. return createStructureRepresentationParams(this.plugin, s, {
  57. type: InteractionsRepresentationProvider,
  58. color: InteractionTypeColorThemeProvider,
  59. size: BuiltInSizeThemes.uniform
  60. });
  61. }
  62. private ensureShape(cell: StateObjectCell<PluginStateObject.Molecule.Structure>) {
  63. const state = this.plugin.state.dataState, tree = state.tree;
  64. const builder = state.build();
  65. const refs = StateSelection.findUniqueTagsInSubtree(tree, cell.transform.ref, TagSet);
  66. if (!refs['structure-interaction-group']) {
  67. refs['structure-interaction-group'] = builder.to(cell).group(StateTransforms.Misc.CreateGroup,
  68. { label: 'Current Focus' }, { tags: StructureRepresentationInteractionTags.Group }).ref;
  69. }
  70. // Selections
  71. if (!refs[StructureRepresentationInteractionTags.ResidueSel]) {
  72. refs[StructureRepresentationInteractionTags.ResidueSel] = builder.to(refs['structure-interaction-group']).apply(StateTransforms.Model.StructureSelectionFromBundle,
  73. { bundle: { } as any, label: 'Residue' }, { tags: StructureRepresentationInteractionTags.ResidueSel }).ref;
  74. }
  75. if (!refs[StructureRepresentationInteractionTags.SurrSel]) {
  76. refs[StructureRepresentationInteractionTags.SurrSel] = builder.to(refs['structure-interaction-group']).apply(StateTransforms.Model.StructureSelectionFromExpression,
  77. { expression: { } as any, label: 'Surroundings' }, { tags: StructureRepresentationInteractionTags.SurrSel }).ref;
  78. }
  79. // Representations
  80. // TODO: ability to customize how it looks in the behavior params
  81. if (!refs[StructureRepresentationInteractionTags.ResidueRepr]) {
  82. refs[StructureRepresentationInteractionTags.ResidueRepr] = builder.to(refs['structure-interaction-residue-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D,
  83. this.createResVisualParams(cell.obj!.data), { tags: StructureRepresentationInteractionTags.ResidueRepr }).ref;
  84. }
  85. if (!refs[StructureRepresentationInteractionTags.SurrRepr]) {
  86. refs[StructureRepresentationInteractionTags.SurrRepr] = builder.to(refs['structure-interaction-surr-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D,
  87. this.createSurVisualParams(cell.obj!.data), { tags: StructureRepresentationInteractionTags.SurrRepr }).ref;
  88. }
  89. if (!refs[StructureRepresentationInteractionTags.SurrNciRepr]) {
  90. refs[StructureRepresentationInteractionTags.SurrNciRepr] = builder.to(refs['structure-interaction-surr-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D,
  91. this.createSurNciVisualParams(cell.obj!.data), { tags: StructureRepresentationInteractionTags.SurrNciRepr }).ref;
  92. }
  93. return { state, builder, refs };
  94. }
  95. private clear(root: StateTransform.Ref) {
  96. const state = this.plugin.state.dataState;
  97. const groups = state.select(StateSelection.Generators.byRef(root).subtree().withTag(StructureRepresentationInteractionTags.Group));
  98. if (groups.length === 0) return;
  99. const update = state.build();
  100. const bundle = StructureElement.Bundle.Empty;
  101. const expression = MS.struct.generator.empty();
  102. for (const g of groups) {
  103. // TODO: update props of the group node to ghost
  104. const res = StateSelection.findTagInSubtree(state.tree, g.transform.ref, StructureRepresentationInteractionTags.ResidueSel);
  105. const surr = StateSelection.findTagInSubtree(state.tree, g.transform.ref, StructureRepresentationInteractionTags.SurrSel);
  106. if (res) update.to(res).update(StateTransforms.Model.StructureSelectionFromBundle, old => ({ ...old, bundle }));
  107. if (surr) update.to(surr).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression }));
  108. }
  109. PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
  110. }
  111. register(ref: string): void {
  112. let lastLoci: Loci = EmptyLoci;
  113. this.subscribeObservable(this.plugin.events.state.object.removed, o => {
  114. if (!PluginStateObject.Molecule.Structure.is(o.obj) || !StructureElement.Loci.is(lastLoci)) return;
  115. if (lastLoci.structure === o.obj.data) {
  116. lastLoci = EmptyLoci;
  117. }
  118. });
  119. this.subscribeObservable(this.plugin.events.state.object.updated, o => {
  120. if (!PluginStateObject.Molecule.Structure.is(o.oldObj) || !StructureElement.Loci.is(lastLoci)) return;
  121. if (lastLoci.structure === o.oldObj.data) {
  122. lastLoci = EmptyLoci;
  123. }
  124. });
  125. this.subscribeObservable(this.plugin.behaviors.interaction.click, ({ current, button, modifiers }) => {
  126. const { clickInteractionAroundOnly } = this.params.bindings
  127. if (Binding.match(clickInteractionAroundOnly, button, modifiers)) {
  128. if (isEmptyLoci(current.loci)) {
  129. this.clear(StateTransform.RootRef);
  130. lastLoci = current.loci;
  131. return;
  132. }
  133. let loci: StructureElement.Loci;
  134. if (StructureElement.Loci.is(current.loci)) {
  135. loci = current.loci;
  136. } else if (Bond.isLoci(current.loci)) {
  137. loci = Bond.toStructureElementLoci(current.loci);
  138. } else if (Structure.isLoci(current.loci)) {
  139. loci = Structure.toStructureElementLoci(current.loci.structure);
  140. } else {
  141. return;
  142. }
  143. if (StructureElement.Loci.isEmpty(loci)) return;
  144. const parent = this.plugin.helpers.substructureParent.get(loci.structure);
  145. if (!parent || !parent.obj) return;
  146. if (Loci.areEqual(lastLoci, loci)) {
  147. lastLoci = EmptyLoci;
  148. this.clear(parent.transform.ref);
  149. return;
  150. }
  151. lastLoci = loci;
  152. const residueLoci = StructureElement.Loci.extendToWholeResidues(StructureElement.Loci.remap(loci, parent.obj!.data))
  153. const residueBundle = StructureElement.Bundle.fromLoci(residueLoci)
  154. const surroundings = MS.struct.modifier.includeSurroundings({
  155. 0: StructureElement.Bundle.toExpression(residueBundle),
  156. radius: 5,
  157. 'as-whole-residues': true
  158. });
  159. const { state, builder, refs } = this.ensureShape(parent);
  160. builder.to(refs[StructureRepresentationInteractionTags.ResidueSel]!).update(StateTransforms.Model.StructureSelectionFromBundle, old => ({ ...old, bundle: residueBundle }));
  161. builder.to(refs[StructureRepresentationInteractionTags.SurrSel]!).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression: surroundings }));
  162. PluginCommands.State.Update(this.plugin, { state, tree: builder, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
  163. }
  164. });
  165. }
  166. async update(params: StructureRepresentationInteractionProps) {
  167. return false;
  168. }
  169. }
  170. export const StructureRepresentationInteraction = PluginBehavior.create({
  171. name: 'create-structure-representation-interaction',
  172. display: { name: 'Structure Representation Interaction' },
  173. category: 'interaction',
  174. ctor: StructureRepresentationInteractionBehavior,
  175. params: () => StructureRepresentationInteractionParams
  176. });