jolecule.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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 { StateTree, StateBuilder, StateAction, State } from '../../../mol-state';
  7. import { StateTransforms } from '../../../mol-plugin/state/transforms';
  8. import { createModelTree } from '../../../mol-plugin/state/actions/structure';
  9. import { PluginContext } from '../../../mol-plugin/context';
  10. import { PluginStateObject } from '../../../mol-plugin/state/objects';
  11. import { ParamDefinition } from '../../../mol-util/param-definition';
  12. import { PluginCommands } from '../../../mol-plugin/command';
  13. import { Vec3 } from '../../../mol-math/linear-algebra';
  14. import { PluginStateSnapshotManager } from '../../../mol-plugin/state/snapshots';
  15. import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
  16. import { Text } from '../../../mol-geo/geometry/text/text';
  17. import { UUID } from '../../../mol-util';
  18. import { ColorNames } from '../../../mol-util/color/names';
  19. import { Camera } from '../../../mol-canvas3d/camera';
  20. import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
  21. import { createDefaultStructureComplex } from '../../../mol-plugin/util/structure-complex-helper';
  22. export const CreateJoleculeState = StateAction.build({
  23. display: { name: 'Jolecule State Import' },
  24. params: { id: ParamDefinition.Text('1mbo') },
  25. from: PluginStateObject.Root
  26. })(async ({ ref, state, params }, plugin: PluginContext) => {
  27. try {
  28. const id = params.id.trim().toLowerCase();
  29. const data = await plugin.runTask(plugin.fetch({ url: `https://jolecule.appspot.com/pdb/${id}.views.json`, type: 'json' })) as JoleculeSnapshot[];
  30. data.sort((a, b) => a.order - b.order);
  31. await PluginCommands.State.RemoveObject.dispatch(plugin, { state, ref });
  32. plugin.state.snapshots.clear();
  33. const template = createTemplate(plugin, state, id);
  34. const snapshots = data.map((e, idx) => buildSnapshot(plugin, template, { e, idx, len: data.length }));
  35. for (const s of snapshots) {
  36. plugin.state.snapshots.add(s);
  37. }
  38. PluginCommands.State.Snapshots.Apply.dispatch(plugin, { id: snapshots[0].snapshot.id });
  39. } catch (e) {
  40. plugin.log.error(`Jolecule Failed: ${e}`);
  41. }
  42. });
  43. interface JoleculeSnapshot {
  44. order: number,
  45. distances: { i_atom1: number, i_atom2: number }[],
  46. labels: { i_atom: number, text: string }[],
  47. camera: { up: Vec3, pos: Vec3, in: Vec3, slab: { z_front: number, z_back: number, zoom: number } },
  48. selected: number[],
  49. text: string
  50. }
  51. function createTemplate(plugin: PluginContext, state: State, id: string) {
  52. const b = new StateBuilder.Root(state.tree);
  53. const data = b.toRoot().apply(StateTransforms.Data.Download, { url: `https://www.ebi.ac.uk/pdbe/static/entry/${id}_updated.cif` }, { state: { isGhost: true }});
  54. const model = createModelTree(data, 'cif');
  55. const structure = model.apply(StateTransforms.Model.StructureFromModel);
  56. createDefaultStructureComplex(plugin, structure);
  57. return { tree: b.getTree(), structure: structure.ref };
  58. }
  59. const labelOptions: ParamDefinition.Values<Text.Params> = {
  60. ...ParamDefinition.getDefaultValues(Text.Params),
  61. tether: true,
  62. sizeFactor: 1.3,
  63. attachment: 'bottom-right',
  64. offsetZ: 10,
  65. background: true,
  66. backgroundMargin: 0.2,
  67. backgroundColor: ColorNames.skyblue,
  68. backgroundOpacity: 0.9
  69. }
  70. // const distanceLabelOptions = {
  71. // ...ParamDefinition.getDefaultValues(Text.Params),
  72. // sizeFactor: 1,
  73. // offsetX: 0,
  74. // offsetY: 0,
  75. // offsetZ: 10,
  76. // background: true,
  77. // backgroundMargin: 0.2,
  78. // backgroundColor: ColorNames.snow,
  79. // backgroundOpacity: 0.9
  80. // }
  81. function buildSnapshot(plugin: PluginContext, template: { tree: StateTree, structure: string }, params: { e: JoleculeSnapshot, idx: number, len: number }): PluginStateSnapshotManager.Entry {
  82. const b = new StateBuilder.Root(template.tree);
  83. let i = 0;
  84. for (const l of params.e.labels) {
  85. const expression = createExpression([l.i_atom]);
  86. const group = b.to(template.structure)
  87. .group(StateTransforms.Misc.CreateGroup, { label: `Label ${++i}` });
  88. group
  89. .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: 'Atom' })
  90. .apply(StateTransforms.Representation.StructureLabels3D, {
  91. target: { name: 'static-text', params: { value: l.text || '' } },
  92. options: labelOptions
  93. });
  94. group
  95. .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: MS.struct.modifier.wholeResidues([ expression ]), label: 'Residue' })
  96. .apply(StateTransforms.Representation.StructureRepresentation3D,
  97. StructureRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'ball-and-stick', { }));
  98. }
  99. if (params.e.selected && params.e.selected.length > 0) {
  100. b.to(template.structure)
  101. .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: createExpression(params.e.selected), label: `Selected` })
  102. .apply(StateTransforms.Representation.StructureRepresentation3D,
  103. StructureRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'ball-and-stick'));
  104. }
  105. // TODO
  106. // for (const l of params.e.distances) {
  107. // b.to('structure')
  108. // .apply(StateTransforms.Model.StructureSelectionFromExpression, { query: createQuery([l.i_atom1, l.i_atom2]), label: `Distance ${++i}` })
  109. // .apply(StateTransforms.Representation.StructureLabels3D, {
  110. // target: { name: 'static-text', params: { value: l. || '' } },
  111. // options: labelOptions
  112. // });
  113. // }
  114. return PluginStateSnapshotManager.Entry({
  115. id: UUID.create22(),
  116. data: { tree: StateTree.toJSON(b.getTree()) },
  117. camera: {
  118. current: getCameraSnapshot(params.e.camera),
  119. transitionStyle: 'animate',
  120. transitionDurationInMs: 350
  121. }
  122. }, {
  123. name: params.e.text
  124. });
  125. }
  126. function getCameraSnapshot(e: JoleculeSnapshot['camera']): Camera.Snapshot {
  127. const direction = Vec3.sub(Vec3(), e.pos, e.in);
  128. Vec3.normalize(direction, direction);
  129. const up = Vec3.sub(Vec3(), e.pos, e.up);
  130. Vec3.normalize(up, up);
  131. const s: Camera.Snapshot = {
  132. mode: 'perspective',
  133. fov: Math.PI / 4,
  134. position: Vec3.scaleAndAdd(Vec3(), e.pos, direction, e.slab.zoom),
  135. target: e.pos,
  136. radius: (e.slab.z_back - e.slab.z_front) / 2,
  137. fog: 50,
  138. up,
  139. };
  140. return s;
  141. }
  142. function createExpression(atomIndices: number[]) {
  143. if (atomIndices.length === 0) return MS.struct.generator.empty();
  144. return MS.struct.generator.atomGroups({
  145. 'atom-test': atomIndices.length === 1
  146. ? MS.core.rel.eq([MS.struct.atomProperty.core.sourceIndex(), atomIndices[0]])
  147. : MS.core.set.has([MS.set.apply(null, atomIndices), MS.struct.atomProperty.core.sourceIndex()]),
  148. 'group-by': 0
  149. });
  150. }