index.ts 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. /**
  2. * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { BehaviorSubject } from 'rxjs';
  7. import { debounceTime, skip } from 'rxjs/operators';
  8. import { AlphaOrbital, Basis } from '../../extensions/alpha-orbitals/data-model';
  9. import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/spherical-functions';
  10. import { BasisAndOrbitals, CreateOrbitalDensityVolume, CreateOrbitalRepresentation3D, CreateOrbitalVolume, StaticBasisAndOrbitals } from '../../extensions/alpha-orbitals/transforms';
  11. import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
  12. import { PluginStateObject } from '../../mol-plugin-state/objects';
  13. import { createPluginUI } from '../../mol-plugin-ui/react18';
  14. import { PluginUIContext } from '../../mol-plugin-ui/context';
  15. import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
  16. import { PluginCommands } from '../../mol-plugin/commands';
  17. import { PluginConfig } from '../../mol-plugin/config';
  18. import { StateObjectSelector, StateTransformer } from '../../mol-state';
  19. import { Color } from '../../mol-util/color';
  20. import { ColorNames } from '../../mol-util/color/names';
  21. import { ParamDefinition } from '../../mol-util/param-definition';
  22. import { mountControls } from './controls';
  23. import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
  24. import './index.html';
  25. require('mol-plugin-ui/skin/light.scss');
  26. import { setDebugMode, setTimingMode, consoleStats } from '../../mol-util/debug';
  27. interface DemoInput {
  28. moleculeSdf: string,
  29. basis: Basis,
  30. order: SphericalBasisOrder,
  31. orbitals: AlphaOrbital[]
  32. }
  33. interface Params {
  34. show: { name: 'orbital', params: { index: number } } | { name: 'density', params: {} },
  35. isoValue: number,
  36. gpuSurface: boolean
  37. }
  38. type Selectors = {
  39. type: 'orbital',
  40. volume: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalVolume>,
  41. positive: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
  42. negative: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
  43. } | {
  44. type: 'density',
  45. volume: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalDensityVolume>,
  46. positive: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
  47. }
  48. export class AlphaOrbitalsExample {
  49. plugin: PluginUIContext;
  50. async init(target: string | HTMLElement) {
  51. const defaultSpec = DefaultPluginUISpec();
  52. this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
  53. ...defaultSpec,
  54. layout: {
  55. initial: {
  56. isExpanded: false,
  57. showControls: false
  58. },
  59. },
  60. components: {
  61. controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
  62. },
  63. canvas3d: {
  64. camera: {
  65. helper: { axes: { name: 'off', params: {} } }
  66. }
  67. },
  68. config: [
  69. [PluginConfig.Viewport.ShowExpand, false],
  70. [PluginConfig.Viewport.ShowControls, false],
  71. [PluginConfig.Viewport.ShowSelectionMode, false],
  72. [PluginConfig.Viewport.ShowAnimation, false],
  73. ]
  74. });
  75. this.plugin.managers.interactivity.setProps({ granularity: 'element' });
  76. if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
  77. PluginCommands.Toast.Show(this.plugin, {
  78. title: 'Error',
  79. message: `Browser/device does not support required WebGL extension (OES_texture_float).`
  80. });
  81. return;
  82. }
  83. this.load({
  84. moleculeSdf: DemoMoleculeSDF,
  85. ...DemoOrbitals
  86. });
  87. mountControls(this, document.getElementById('controls')!);
  88. }
  89. readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
  90. readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: true });
  91. private selectors?: Selectors = void 0;
  92. private basis?: StateObjectSelector<BasisAndOrbitals> = void 0;
  93. private currentParams: Params = { ...this.state.value };
  94. private clearVolume() {
  95. if (!this.selectors) return;
  96. const v = this.selectors.volume;
  97. this.selectors = void 0;
  98. return this.plugin.build().delete(v).commit();
  99. }
  100. private async syncVolume() {
  101. if (!this.basis?.isOk) return;
  102. const state = this.state.value;
  103. if (state.show.name !== this.selectors?.type) {
  104. await this.clearVolume();
  105. }
  106. const update = this.plugin.build();
  107. if (state.show.name === 'orbital') {
  108. if (!this.selectors) {
  109. const volume = update
  110. .to(this.basis)
  111. .apply(CreateOrbitalVolume, { index: state.show.params.index });
  112. const positive = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('positive', ColorNames.blue)).selector;
  113. const negative = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('negative', ColorNames.red)).selector;
  114. this.selectors = { type: 'orbital', volume: volume.selector, positive, negative };
  115. } else {
  116. const index = state.show.params.index;
  117. update.to(this.selectors.volume).update(CreateOrbitalVolume, () => ({ index }));
  118. }
  119. } else {
  120. if (!this.selectors) {
  121. const volume = update
  122. .to(this.basis)
  123. .apply(CreateOrbitalDensityVolume);
  124. const positive = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('positive', ColorNames.blue)).selector;
  125. this.selectors = { type: 'density', volume: volume.selector, positive };
  126. }
  127. }
  128. await update.commit();
  129. if (this.currentParams.gpuSurface !== this.state.value.gpuSurface) {
  130. await this.setIsovalue();
  131. }
  132. this.currentParams = this.state.value;
  133. }
  134. private setIsovalue() {
  135. if (!this.selectors) return;
  136. this.currentParams = this.state.value;
  137. const update = this.plugin.build();
  138. update.to(this.selectors.positive).update(this.volumeParams('positive', ColorNames.blue));
  139. if (this.selectors?.type === 'orbital') {
  140. update.to(this.selectors.negative).update(this.volumeParams('negative', ColorNames.red));
  141. }
  142. return update.commit();
  143. }
  144. private volumeParams(kind: 'positive' | 'negative', color: Color): StateTransformer.Params<typeof CreateOrbitalRepresentation3D> {
  145. return {
  146. alpha: 0.85,
  147. color,
  148. kind,
  149. relativeIsovalue: this.state.value.isoValue,
  150. pickable: false,
  151. xrayShaded: true,
  152. tryUseGpu: true
  153. };
  154. }
  155. async load(input: DemoInput) {
  156. await this.plugin.clear();
  157. const data = await this.plugin.builders.data.rawData({ data: input.moleculeSdf }, { state: { isGhost: true } });
  158. const trajectory = await this.plugin.builders.structure.parseTrajectory(data, 'mol');
  159. const model = await this.plugin.builders.structure.createModel(trajectory);
  160. const structure = await this.plugin.builders.structure.createStructure(model);
  161. const all = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'all');
  162. if (all) await this.plugin.builders.structure.representation.addRepresentation(all, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } });
  163. this.basis = await this.plugin.build().toRoot()
  164. .apply(StaticBasisAndOrbitals, { basis: input.basis, order: input.order, orbitals: input.orbitals })
  165. .commit();
  166. await this.syncVolume();
  167. this.params.next({
  168. show: ParamDefinition.MappedStatic('orbital', {
  169. 'orbital': ParamDefinition.Group({
  170. index: ParamDefinition.Numeric(32, { min: 0, max: input.orbitals.length - 1 }, { immediateUpdate: true, isEssential: true }),
  171. }),
  172. 'density': ParamDefinition.EmptyGroup()
  173. }, { cycle: true }),
  174. isoValue: ParamDefinition.Numeric(this.currentParams.isoValue, { min: 0.5, max: 3, step: 0.1 }, { immediateUpdate: true, isEssential: false }),
  175. gpuSurface: ParamDefinition.Boolean(this.currentParams.gpuSurface, { isHidden: true })
  176. });
  177. this.state.pipe(skip(1), debounceTime(1000 / 24)).subscribe(async params => {
  178. if (params.show.name !== this.currentParams.show.name
  179. || (params.show.name === 'orbital' && this.currentParams.show.name === 'orbital' && params.show.params.index !== this.currentParams.show.params.index)) {
  180. this.syncVolume();
  181. } else if (params.isoValue !== this.currentParams.isoValue || params.gpuSurface !== this.currentParams.gpuSurface) {
  182. this.setIsovalue();
  183. }
  184. });
  185. }
  186. }
  187. (window as any).AlphaOrbitalsExample = new AlphaOrbitalsExample();
  188. (window as any).AlphaOrbitalsExample.setDebugMode = setDebugMode;
  189. (window as any).AlphaOrbitalsExample.setTimingMode = setTimingMode;
  190. (window as any).AlphaOrbitalsExample.consoleStats = consoleStats;