preset.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { StructureViewerState } from '../types';
  7. import { getStructureSize, StructureSize } from './util';
  8. import { PluginContext } from 'molstar/lib/mol-plugin/context';
  9. import { Structure } from 'molstar/lib/mol-model/structure';
  10. import { Loci, EmptyLoci } from 'molstar/lib/mol-model/loci';
  11. import { Axes3D } from 'molstar/lib/mol-math/geometry';
  12. import { Vec3 } from 'molstar/lib/mol-math/linear-algebra';
  13. import { ValidationReport } from 'molstar/lib/mol-model-props/rcsb/validation-report';
  14. import { StructureSelectionQueries as SSQ } from 'molstar/lib/mol-plugin/util/structure-selection-helper';
  15. import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder';
  16. import { AssemblySymmetry } from 'molstar/lib/mol-model-props/rcsb/assembly-symmetry';
  17. type Target = {
  18. readonly auth_seq_id?: number
  19. readonly label_seq_id?: number
  20. readonly label_comp_id?: number
  21. readonly label_asym_id?: number
  22. readonly pdbx_struct_oper_list_ids?: string[]
  23. }
  24. function targetToLoci(target: Target, structure: Structure): Loci {
  25. return EmptyLoci
  26. }
  27. type ValidationProps = {
  28. kind: 'validation'
  29. colorTheme?: string
  30. showClashes?: boolean
  31. modelIndex?: number
  32. }
  33. type AssemblyProps = {
  34. kind: 'assembly'
  35. assemblyId: string
  36. modelIndex?: number
  37. }
  38. type StandardProps = {
  39. kind: 'standard'
  40. }
  41. type SymmetryProps = {
  42. kind: 'symmetry'
  43. assemblyId?: string
  44. symmetryIndex?: number
  45. }
  46. type FeatureProps = {
  47. kind: 'feature'
  48. assemblyId: string
  49. target: Target
  50. }
  51. export type PresetProps = ValidationProps | AssemblyProps | StandardProps | SymmetryProps | FeatureProps
  52. export class PresetManager {
  53. get customState() {
  54. return this.plugin.customState as StructureViewerState
  55. }
  56. async apply(props?: PresetProps) {
  57. if (!props) props = { kind: 'assembly', assemblyId: 'deposited' }
  58. switch (props.kind) {
  59. case 'assembly':
  60. return this.assembly(props.assemblyId, props.modelIndex)
  61. case 'feature':
  62. return this.feature(props.target, props.assemblyId)
  63. case 'standard':
  64. return this.standard()
  65. case 'symmetry':
  66. return this.symmetry(props.symmetryIndex, props.assemblyId)
  67. case 'validation':
  68. return this.validation(props.colorTheme, props.showClashes, props.modelIndex)
  69. }
  70. }
  71. async default() {
  72. const assembly = this.customState.structureView.getAssembly()
  73. if (!assembly || assembly.data.isEmpty) return
  74. const r = this.plugin.helpers.structureRepresentation
  75. const size = getStructureSize(assembly.data)
  76. if (size === StructureSize.Gigantic) {
  77. await r.clearExcept(['gaussian-surface'])
  78. await r.setFromExpression('only', 'gaussian-surface', SSQ.trace.expression, {
  79. repr: {
  80. radiusOffset: 1,
  81. smoothness: 0.5,
  82. visuals: ['structure-gaussian-surface-mesh']
  83. }
  84. })
  85. } else if(size === StructureSize.Huge) {
  86. await r.clearExcept(['gaussian-surface'])
  87. await r.setFromExpression('add', 'gaussian-surface', SSQ.polymer.expression, {
  88. repr: {
  89. smoothness: 0.5
  90. },
  91. })
  92. } else if(size === StructureSize.Large) {
  93. await r.clearExcept(['cartoon'])
  94. await r.setFromExpression('only', 'cartoon', SSQ.polymer.expression)
  95. } else if(size === StructureSize.Medium) {
  96. await r.clearExcept(['cartoon', 'carbohydrate', 'ball-and-stick'])
  97. await r.setFromExpression('only', 'cartoon', SSQ.polymer.expression)
  98. await r.setFromExpression('only', 'carbohydrate', SSQ.branchedPlusConnected.expression)
  99. await r.setFromExpression('only', 'ball-and-stick', MS.struct.modifier.union([
  100. MS.struct.combinator.merge([
  101. SSQ.ligandPlusConnected.expression,
  102. SSQ.branchedConnectedOnly.expression,
  103. SSQ.disulfideBridges.expression,
  104. SSQ.nonStandardPolymer.expression,
  105. // SSQ.water.expression
  106. ])
  107. ]))
  108. } else if(size === StructureSize.Small) {
  109. await r.clearExcept(['ball-and-stick'])
  110. await r.setFromExpression('only', 'ball-and-stick', MS.struct.modifier.union([
  111. MS.struct.modifier.exceptBy({
  112. 0: MS.struct.generator.all(),
  113. by: SSQ.water.expression
  114. })
  115. ]))
  116. }
  117. }
  118. async standard() {
  119. await this.customState.structureView.setSymmetry(-1)
  120. await this.default()
  121. this.focus()
  122. }
  123. async assembly(assemblyId: string, modelIndex?: number) {
  124. if (modelIndex !== undefined) {
  125. await this.customState.structureView.setModel(modelIndex)
  126. }
  127. await this.customState.structureView.setAssembly(assemblyId)
  128. await this.default()
  129. this.focus()
  130. }
  131. async model(modelIndex: number) {
  132. await this.customState.structureView.setModel(modelIndex)
  133. await this.default()
  134. this.focus()
  135. }
  136. async feature(target: Target, assemblyId?: string, modelIndex?: number) {
  137. if (modelIndex !== undefined) {
  138. await this.customState.structureView.setModel(modelIndex)
  139. }
  140. if (assemblyId !== undefined) {
  141. await this.customState.structureView.setAssembly(assemblyId)
  142. }
  143. const assembly = this.customState.structureView.getAssembly()
  144. if (!assembly || assembly.data.isEmpty) return
  145. const loci = targetToLoci(target, assembly.data)
  146. // TODO show target and surrounding residues in detail if small
  147. this.focus(loci)
  148. }
  149. async symmetry(symmetryIndex?: number, assemblyId?: string) {
  150. if (assemblyId !== undefined) {
  151. await this.customState.structureView.setAssembly(assemblyId)
  152. await this.default()
  153. }
  154. const assembly = this.customState.structureView.getAssembly()
  155. if (!assembly || assembly.data.isEmpty) return
  156. const r = this.plugin.helpers.structureRepresentation
  157. await this.customState.structureView.setSymmetry(symmetryIndex || 0)
  158. r.eachRepresentation((repr, type, update) => {
  159. if (type !== ValidationReport.Tag.Clashes) {
  160. r.setRepresentationParams(repr, type, update, { color: AssemblySymmetry.Tag.Cluster })
  161. }
  162. })
  163. // TODO focus on symmetry axes
  164. this.focus()
  165. }
  166. async validation(colorTheme?: string, showClashes?: boolean, modelIndex?: number) {
  167. if (modelIndex !== undefined) {
  168. this.customState.structureView.setModel(modelIndex)
  169. await this.default()
  170. }
  171. const assembly = this.customState.structureView.getAssembly()
  172. if (!assembly || assembly.data.isEmpty) return
  173. const r = this.plugin.helpers.structureRepresentation
  174. if (showClashes === undefined) {
  175. showClashes = getStructureSize(assembly.data) <= StructureSize.Medium
  176. }
  177. await this.customState.structureView.attachValidationReport()
  178. if (showClashes) {
  179. await r.setFromExpression('only', ValidationReport.Tag.Clashes, SSQ.all.expression)
  180. await r.setFromExpression('add', 'ball-and-stick', SSQ.hasClash.expression)
  181. } else {
  182. await r.setFromExpression('remove', ValidationReport.Tag.Clashes, SSQ.all.expression)
  183. }
  184. if (colorTheme === undefined) colorTheme = ValidationReport.Tag.GeometryQuality
  185. r.eachRepresentation((repr, type, update) => {
  186. if (type !== ValidationReport.Tag.Clashes) {
  187. r.setRepresentationParams(repr, type, update, { color: colorTheme })
  188. }
  189. })
  190. this.focus()
  191. }
  192. focus(loci?: Loci) {
  193. if (!loci) {
  194. const assembly = this.customState.structureView.getAssembly()
  195. if (!assembly || assembly.data.isEmpty) return
  196. loci = Structure.toStructureElementLoci(assembly.data)
  197. }
  198. const principalAxes = Loci.getPrincipalAxes(loci)
  199. if (!principalAxes) return
  200. const extraRadius = 4, minRadius = 8, durationMs = 250
  201. const { origin, dirA, dirC } = principalAxes.boxAxes
  202. const axesRadius = Math.max(...Axes3D.size(Vec3(), principalAxes.boxAxes)) / 2
  203. const radius = Math.max(axesRadius + extraRadius, minRadius)
  204. this.plugin.canvas3d!.camera.focus(origin, radius, radius, durationMs, dirA, dirC);
  205. }
  206. constructor(private plugin: PluginContext) {
  207. }
  208. }