controls.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. /**
  2. * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import * as React from 'react';
  7. import { PluginUIComponent } from 'molstar/lib/mol-plugin/ui/base';
  8. import { PluginContextContainer } from 'molstar/lib/mol-plugin/ui/plugin';
  9. import { TransformUpdaterControl } from 'molstar/lib/mol-plugin/ui/state/update-transform';
  10. import { StructureToolsWrapper } from 'molstar/lib/mol-plugin/ui/controls';
  11. import { StateElements } from '../helpers';
  12. import { ParameterControls } from 'molstar/lib/mol-plugin/ui/controls/parameters';
  13. import { ParamDefinition as PD } from 'molstar/lib/mol-util/param-definition';
  14. import { PluginCommands } from 'molstar/lib/mol-plugin/command';
  15. import { Canvas3DParams } from 'molstar/lib/mol-canvas3d/canvas3d';
  16. import { StateObject, StateBuilder } from 'molstar/lib/mol-state';
  17. import { PluginStateObject as PSO } from 'molstar/lib/mol-plugin/state/objects';
  18. import { StateTransforms } from 'molstar/lib/mol-plugin/state/transforms';
  19. import { StructureSelectionQueries as Q } from 'molstar/lib/mol-plugin/util/structure-selection-helper';
  20. import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder';
  21. export class ControlsWrapper extends PluginUIComponent {
  22. componentDidMount() {
  23. this.subscribe(this.plugin.state.behavior.currentObject, () => this.forceUpdate());
  24. this.subscribe(this.plugin.events.state.object.updated, () => this.forceUpdate());
  25. }
  26. render() {
  27. return <div className='msp-scrollable-container msp-right-controls'>
  28. <PluginContextContainer plugin={this.plugin}>
  29. <GeneralSettings />
  30. <StructureControls
  31. trajectoryRef={StateElements.Trajectory}
  32. modelRef={StateElements.Model}
  33. assemblyRef={StateElements.Assembly}
  34. />
  35. <TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} header={{ name: 'Volume Controls', description: '' }} />
  36. <StructureToolsWrapper />
  37. </PluginContextContainer>
  38. </div>;
  39. }
  40. }
  41. //
  42. const GeneralSettingsParams = {
  43. spin: Canvas3DParams.trackball.params.spin,
  44. backgroundColor: Canvas3DParams.renderer.params.backgroundColor,
  45. renderStyle: PD.Select('glossy', [['toon', 'Toon'], ['matte', 'Matte'], ['glossy', 'Glossy'], ['metallic', 'Metallic']]),
  46. occlusion: PD.Boolean(false),
  47. }
  48. type GeneralSettingsState = { isCollapsed?: boolean }
  49. class GeneralSettings<P, S extends GeneralSettingsState> extends PluginUIComponent<P, S> {
  50. setSettings = (p: { param: PD.Base<any>, name: string, value: any }) => {
  51. if (p.name === 'spin') {
  52. const trackball = this.plugin.canvas3d.props.trackball;
  53. PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: p.value } } });
  54. } else if (p.name === 'backgroundColor') {
  55. const renderer = this.plugin.canvas3d.props.renderer;
  56. PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: p.value } } });
  57. } else if (p.name === 'renderStyle') {
  58. const postprocessing = this.plugin.canvas3d.props.postprocessing;
  59. const renderer = this.plugin.canvas3d.props.renderer;
  60. if (p.value === 'toon') {
  61. PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
  62. postprocessing: { ...postprocessing, outlineEnable: true, },
  63. renderer: { ...renderer, lightIntensity: 0, ambientIntensity: 1, roughness: 0.4, metalness: 0 }
  64. } });
  65. } else if (p.value === 'matte') {
  66. PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
  67. postprocessing: { ...postprocessing, outlineEnable: false, },
  68. renderer: { ...renderer, lightIntensity: 0.6, ambientIntensity: 0.4, roughness: 1, metalness: 0 }
  69. } });
  70. } else if (p.value === 'glossy') {
  71. PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
  72. postprocessing: { ...postprocessing, outlineEnable: false, },
  73. renderer: { ...renderer, lightIntensity: 0.6, ambientIntensity: 0.4, roughness: 0.4, metalness: 0 }
  74. } });
  75. } else if (p.value === 'metallic') {
  76. PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
  77. postprocessing: { ...postprocessing, outlineEnable: false, },
  78. renderer: { ...renderer, lightIntensity: 0.6, ambientIntensity: 0.4, roughness: 0.6, metalness: 0.4 }
  79. } });
  80. }
  81. } else if (p.name === 'occlusion') {
  82. const postprocessing = this.plugin.canvas3d.props.postprocessing;
  83. PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
  84. postprocessing: { ...postprocessing, occlusionEnable: p.value },
  85. } });
  86. }
  87. }
  88. get values () {
  89. let renderStyle = 'custom'
  90. const postprocessing = this.plugin.canvas3d.props.postprocessing;
  91. const renderer = this.plugin.canvas3d.props.renderer;
  92. if (postprocessing.outlineEnable) {
  93. if (renderer.lightIntensity === 0 && renderer.ambientIntensity === 1 && renderer.roughness === 0.4 && renderer.metalness === 0) {
  94. renderStyle = 'toon'
  95. }
  96. } else if (renderer.lightIntensity === 0.6 && renderer.ambientIntensity === 0.4) {
  97. if (renderer.roughness === 1 && renderer.metalness === 0) {
  98. renderStyle = 'matte'
  99. } else if (renderer.roughness === 0.4 && renderer.metalness === 0) {
  100. renderStyle = 'glossy'
  101. } else if (renderer.roughness === 0.6 && renderer.metalness === 0.4) {
  102. renderStyle = 'metallic'
  103. }
  104. }
  105. return {
  106. spin: this.plugin.canvas3d.props.trackball.spin,
  107. backgroundColor: this.plugin.canvas3d.props.renderer.backgroundColor,
  108. renderStyle,
  109. occlusion: this.plugin.canvas3d.props.postprocessing.occlusionEnable
  110. }
  111. }
  112. componentDidMount() {
  113. this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
  114. }
  115. toggleExpanded = () => {
  116. this.setState({ isCollapsed: !this.state.isCollapsed });
  117. }
  118. state = {
  119. isCollapsed: false
  120. } as Readonly<S>
  121. render() {
  122. const wrapClass = this.state.isCollapsed
  123. ? 'msp-transform-wrapper msp-transform-wrapper-collapsed'
  124. : 'msp-transform-wrapper';
  125. return this.plugin.canvas3d ? <div className={wrapClass}>
  126. <div className='msp-transform-header'>
  127. <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>
  128. General Settings
  129. </button>
  130. </div>
  131. {!this.state.isCollapsed &&
  132. <ParameterControls params={GeneralSettingsParams} values={this.values} onChange={this.setSettings} />
  133. }
  134. </div> : null;
  135. }
  136. }
  137. //
  138. type StructureControlsState = {
  139. representation: string
  140. isCollapsed?: boolean
  141. }
  142. type StructureControlsProps = {
  143. trajectoryRef: string
  144. modelRef: string
  145. assemblyRef: string
  146. }
  147. class StructureControls<P extends StructureControlsProps, S extends StructureControlsState> extends PluginUIComponent<P, S> {
  148. private applyState(tree: StateBuilder) {
  149. return PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
  150. }
  151. onChange = async (p: { param: PD.Base<any>, name: string, value: any }) => {
  152. // console.log('onChange', p.name, p.value)
  153. const { themeCtx } = this.plugin.structureRepresentation
  154. const state = this.plugin.state.dataState;
  155. const tree = state.build();
  156. if (p.name === 'assembly') {
  157. tree.to(StateElements.Assembly).update(
  158. StateTransforms.Model.StructureAssemblyFromModel,
  159. props => ({ ...props, id: p.value })
  160. );
  161. await this.applyState(tree);
  162. const { structureRepresentation: rep } = this.plugin.helpers
  163. await rep.setFromExpression('add', 'cartoon', Q.all)
  164. await rep.setFromExpression('add', 'carbohydrate', Q.all)
  165. await rep.setFromExpression('add', 'ball-and-stick', MS.struct.modifier.union([
  166. MS.struct.combinator.merge([ Q.ligandsPlusConnected, Q.branchedConnectedOnly, Q.water ])
  167. ]))
  168. } else if (p.name === 'model') {
  169. tree.to(StateElements.Model).update(
  170. StateTransforms.Model.ModelFromTrajectory,
  171. props => ({ ...props, modelIndex: p.value })
  172. );
  173. await this.applyState(tree);
  174. } else if (p.name === 'colorThemes') {
  175. const assembly = this.getAssembly()
  176. const dataCtx = { structure: assembly && assembly.data }
  177. Object.keys(p.value).forEach(k => {
  178. const repr = this.getRepresentation(k)
  179. if (repr && repr.params) {
  180. const values = PD.getDefaultValues(themeCtx.colorThemeRegistry.get(name).getParams(dataCtx))
  181. tree.to(repr.transform.ref).update(
  182. StateTransforms.Representation.StructureRepresentation3D,
  183. props => ({ ...props, colorTheme: { name: p.value[k], params: values }})
  184. )
  185. }
  186. })
  187. await this.applyState(tree)
  188. }
  189. }
  190. getRepresentation(type: string) {
  191. return this.plugin.helpers.structureRepresentation.getRepresentation(StateElements.Assembly, type)
  192. }
  193. getParams = () => {
  194. const { themeCtx, registry } = this.plugin.structureRepresentation
  195. const trajectory = this.getTrajectory()
  196. const model = this.getModel()
  197. const assembly = this.getAssembly()
  198. const modelOptions: [number, string][] = []
  199. if (trajectory) {
  200. for (let i = 0, il = trajectory.data.length; i < il; ++i) {
  201. modelOptions.push([i, `${i + 1}`])
  202. }
  203. }
  204. const assemblyOptions: [string, string][] = [['deposited', 'deposited']]
  205. let modelValue = 0
  206. if (model) {
  207. if (trajectory) modelValue = trajectory.data.indexOf(model.data)
  208. const { assemblies } = model.data.symmetry
  209. for (let i = 0, il = assemblies.length; i < il; ++i) {
  210. const a = assemblies[i]
  211. assemblyOptions.push([a.id, `${a.id}: ${a.details}`])
  212. }
  213. }
  214. let assemblyValue = 'deposited'
  215. let colorTypes = themeCtx.colorThemeRegistry.types
  216. let types = registry.types
  217. if (assembly) {
  218. assemblyValue = assembly.data.units[0].conformation.operator.assembly.id
  219. colorTypes = themeCtx.colorThemeRegistry.getApplicableTypes({ structure: assembly.data })
  220. types = registry.getApplicableTypes(assembly.data)
  221. }
  222. const colorThemes: { [k: string]: PD.Any } = {}
  223. for (let i = 0, il = types.length; i < il; ++i) {
  224. const name = types[i][0]
  225. if (this.getRepresentation(name)) {
  226. colorThemes[name] = PD.Select(registry.get(name).defaultColorTheme, colorTypes)
  227. }
  228. }
  229. return {
  230. assembly: PD.Select(assemblyValue, assemblyOptions),
  231. model: PD.Select(modelValue, modelOptions),
  232. symmetry: PD.Select('todo', [['todo', 'todo']]),
  233. colorThemes: PD.Group(colorThemes, { isExpanded: true })
  234. }
  235. }
  236. get values () {
  237. const trajectory = this.getTrajectory()
  238. const model = this.getModel()
  239. const assembly = this.getAssembly()
  240. const { registry } = this.plugin.structureRepresentation
  241. const types = assembly ? registry.getApplicableTypes(assembly.data) : registry.types
  242. const colorThemes: { [k: string]: string } = {}
  243. for (let i = 0, il = types.length; i < il; ++i) {
  244. const type = types[i][0]
  245. const r = this.getRepresentation(type)
  246. colorThemes[type] = r && r.params ? r.params.values.colorTheme.name : registry.get(type).defaultColorTheme
  247. }
  248. return {
  249. assembly: assembly ? (assembly.data.units[0].conformation.operator.assembly.id || 'deposited') : 'deposited',
  250. model: (model && trajectory) ? trajectory.data.indexOf(model.data) : 0,
  251. symmetry: 'todo',
  252. colorThemes
  253. }
  254. }
  255. componentDidMount() {
  256. // const { dataState } = this.plugin.state
  257. // const { trajectoryRef, modelRef, assemblyRef } = this.props
  258. this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => {
  259. // TODO check if in subtree of trajectoryRef
  260. // if ((trajectoryRef !== ref && modelRef !== ref && assemblyRef !== ref) || dataState !== state) return;
  261. this.forceUpdate();
  262. });
  263. this.subscribe(this.plugin.events.state.object.created, ({ ref, state }) => {
  264. // TODO check if in subtree of trajectoryRef
  265. // if ((trajectoryRef !== ref && modelRef !== ref && assemblyRef !== ref) || dataState !== state) return;
  266. this.forceUpdate();
  267. });
  268. }
  269. toggleExpanded = () => {
  270. this.setState({ isCollapsed: !this.state.isCollapsed });
  271. }
  272. state = {
  273. representation: this.plugin.structureRepresentation.registry.types[0][0],
  274. isCollapsed: false
  275. } as Readonly<S>
  276. private getObj<T extends StateObject>(ref: string): T | undefined {
  277. const state = this.plugin.state.dataState;
  278. const cell = state.select(ref)[0];
  279. if (!cell || !cell.obj) return void 0;
  280. return (cell.obj as T);
  281. }
  282. private getTrajectory() { return this.getObj<PSO.Molecule.Trajectory>(this.props.trajectoryRef) }
  283. private getModel() { return this.getObj<PSO.Molecule.Model>(this.props.modelRef) }
  284. private getAssembly() { return this.getObj<PSO.Molecule.Structure>(this.props.assemblyRef) }
  285. render() {
  286. const trajectory = this.getTrajectory()
  287. const model = this.getModel()
  288. const assembly = this.getAssembly()
  289. if (!trajectory || !model || !assembly) return null;
  290. const wrapClass = this.state.isCollapsed
  291. ? 'msp-transform-wrapper msp-transform-wrapper-collapsed'
  292. : 'msp-transform-wrapper';
  293. return this.plugin.canvas3d ? <div className={wrapClass}>
  294. <div className='msp-transform-header'>
  295. <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>
  296. Structure Settings
  297. </button>
  298. </div>
  299. {!this.state.isCollapsed &&
  300. <ParameterControls params={this.getParams()} values={this.values} onChange={this.onChange} />
  301. }
  302. </div> : null;
  303. }
  304. }