structure-view.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { Model, Structure } from 'mol-model/structure';
  7. import { getStructureFromModel } from './util';
  8. import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry';
  9. import { getAxesShape } from './assembly-symmetry';
  10. import { Canvas3D } from 'mol-canvas3d/canvas3d';
  11. // import { MeshBuilder } from 'mol-geo/mesh/mesh-builder';
  12. // import { addSphere } from 'mol-geo/mesh/builder/sphere';
  13. // import { Shape } from 'mol-model/shape';
  14. // import { Color } from 'mol-util/color';
  15. // import { computeUnitBoundary } from 'mol-model/structure/structure/util/boundary';
  16. // import { addBoundingBox } from 'mol-geo/mesh/builder/bounding-box';
  17. import { BehaviorSubject } from 'rxjs';
  18. import { App } from './app';
  19. import { StructureRepresentation } from 'mol-repr/structure/representation';
  20. import { ShapeRepresentation, ShapeParams } from 'mol-repr/shape/representation';
  21. export interface StructureView {
  22. readonly app: App
  23. readonly canvas3d: Canvas3D
  24. readonly label: string
  25. readonly models: ReadonlyArray<Model>
  26. readonly structure: Structure | undefined
  27. readonly assemblySymmetry: AssemblySymmetry | undefined
  28. readonly active: { [k: string]: boolean }
  29. readonly structureRepresentations: { [k: string]: StructureRepresentation<any> }
  30. readonly updated: BehaviorSubject<null>
  31. readonly symmetryAxes: ShapeRepresentation<ShapeParams>
  32. setSymmetryAxes(value: boolean): void
  33. setStructureRepresentation(name: string, value: boolean): void
  34. readonly modelId: number
  35. readonly assemblyId: string
  36. readonly symmetryFeatureId: number
  37. setModel(modelId: number): Promise<void>
  38. getModelIds(): { id: number, label: string }[]
  39. setAssembly(assemblyId: string): Promise<void>
  40. getAssemblyIds(): { id: string, label: string }[]
  41. setSymmetryFeature(symmetryFeatureId: number): Promise<void>
  42. getSymmetryFeatureIds(): { id: number, label: string }[]
  43. destroy: () => void
  44. }
  45. interface StructureViewProps {
  46. assemblyId?: string
  47. symmetryFeatureId?: number
  48. }
  49. export async function StructureView(app: App, canvas3d: Canvas3D, models: ReadonlyArray<Model>, props: StructureViewProps = {}): Promise<StructureView> {
  50. const active: { [k: string]: boolean } = {
  51. 'cartoon': true,
  52. 'ball-and-stick': true,
  53. // point: false,
  54. // surface: false,
  55. // carbohydrate: false,
  56. // spacefill: false,
  57. // distanceRestraint: false,
  58. // symmetryAxes: true,
  59. // polymerSphere: false,
  60. }
  61. const structureRepresentations: { [k: string]: StructureRepresentation<any> } = {}
  62. const symmetryAxes = ShapeRepresentation()
  63. const polymerSphere = ShapeRepresentation()
  64. const updated: BehaviorSubject<null> = new BehaviorSubject<null>(null)
  65. let label: string
  66. let model: Model | undefined
  67. let assemblySymmetry: AssemblySymmetry | undefined
  68. let structure: Structure | undefined
  69. let modelId: number
  70. let assemblyId: string
  71. let symmetryFeatureId: number
  72. async function setSymmetryAxes(value: boolean) {
  73. if (!value) {
  74. assemblySymmetry = undefined
  75. } else {
  76. await app.runTask(AssemblySymmetry.attachFromCifOrAPI(models[modelId]), 'Load symmetry annotation')
  77. assemblySymmetry = AssemblySymmetry.get(models[modelId])
  78. }
  79. active.symmetryAxes = value
  80. await setSymmetryFeature()
  81. }
  82. async function setStructureRepresentation(k: string, value: boolean) {
  83. active[k] = value
  84. await createStructureRepr()
  85. }
  86. async function setModel(newModelId: number, newAssemblyId?: string, newSymmetryFeatureId?: number) {
  87. console.log('setModel', newModelId)
  88. modelId = newModelId
  89. model = models[modelId]
  90. if (active.symmetryAxes) {
  91. await AssemblySymmetry.attachFromCifOrAPI(model)
  92. assemblySymmetry = AssemblySymmetry.get(model)
  93. }
  94. await setAssembly(newAssemblyId, newSymmetryFeatureId)
  95. }
  96. function getModelIds() {
  97. const modelIds: { id: number, label: string }[] = []
  98. models.forEach((m, i) => {
  99. modelIds.push({ id: i, label: `${i}: ${m.label} #${m.modelNum}` })
  100. })
  101. return modelIds
  102. }
  103. async function setAssembly(newAssemblyId?: string, newSymmetryFeatureId?: number) {
  104. console.log('setAssembly', newAssemblyId)
  105. if (newAssemblyId !== undefined) {
  106. assemblyId = newAssemblyId
  107. } else if (model && model.symmetry.assemblies.length) {
  108. assemblyId = model.symmetry.assemblies[0].id
  109. } else if (model) {
  110. assemblyId = 'deposited'
  111. } else {
  112. assemblyId = '-1'
  113. }
  114. await getStructure()
  115. await setSymmetryFeature(newSymmetryFeatureId)
  116. }
  117. function getAssemblyIds() {
  118. const assemblyIds: { id: string, label: string }[] = [
  119. { id: 'deposited', label: 'deposited' }
  120. ]
  121. if (model) model.symmetry.assemblies.forEach(a => {
  122. assemblyIds.push({ id: a.id, label: `${a.id}: ${a.details}` })
  123. })
  124. return assemblyIds
  125. }
  126. async function setSymmetryFeature(newSymmetryFeatureId?: number) {
  127. console.log('setSymmetryFeature', newSymmetryFeatureId)
  128. if (newSymmetryFeatureId !== undefined) {
  129. symmetryFeatureId = newSymmetryFeatureId
  130. } else if (assemblySymmetry) {
  131. const s = assemblySymmetry.getSymmetries(assemblyId)
  132. if (s._rowCount) {
  133. symmetryFeatureId = s.id.value(0)
  134. } else {
  135. symmetryFeatureId = -1
  136. }
  137. } else {
  138. symmetryFeatureId = -1
  139. }
  140. await createSymmetryRepr()
  141. }
  142. function getSymmetryFeatureIds() {
  143. const symmetryFeatureIds: { id: number, label: string }[] = []
  144. if (assemblySymmetry) {
  145. const symmetries = assemblySymmetry.getSymmetries(assemblyId)
  146. for (let i = 0, il = symmetries._rowCount; i < il; ++i) {
  147. const id = symmetries.id.value(i)
  148. const kind = symmetries.kind.value(i)
  149. const type = symmetries.type.value(i)
  150. const stoichiometry = symmetries.stoichiometry.value(i)
  151. const label = `${id}: ${kind} ${type} ${stoichiometry}`
  152. symmetryFeatureIds.push({ id, label })
  153. }
  154. }
  155. return symmetryFeatureIds
  156. }
  157. async function getStructure() {
  158. if (model) structure = await app.runTask(getStructureFromModel(model, assemblyId), 'Build structure')
  159. if (model && structure) {
  160. label = `${model.label} - Assembly ${assemblyId}`
  161. } else {
  162. label = ''
  163. }
  164. await createStructureRepr()
  165. }
  166. async function createStructureRepr() {
  167. if (structure) {
  168. console.log('createStructureRepr')
  169. for (const k in active) {
  170. if (active[k]) {
  171. let repr: StructureRepresentation
  172. if (structureRepresentations[k]) {
  173. repr = structureRepresentations[k]
  174. } else {
  175. const provider = app.structureRepresentationRegistry.get(k)
  176. repr = provider.factory(provider.getParams)
  177. structureRepresentations[k] = repr
  178. canvas3d.add(repr)
  179. }
  180. await app.runTask(repr.createOrUpdate(app.reprCtx, {}, {}, structure).run(
  181. progress => app.log(progress)
  182. ), 'Create/update representation')
  183. } else {
  184. if (structureRepresentations[k]) {
  185. canvas3d.remove(structureRepresentations[k])
  186. structureRepresentations[k].destroy()
  187. delete structureRepresentations[k]
  188. }
  189. }
  190. }
  191. canvas3d.camera.setState({ target: structure.boundary.sphere.center })
  192. // const mb = MeshBuilder.create()
  193. // mb.setGroup(0)
  194. // addSphere(mb, structure.boundary.sphere.center, structure.boundary.sphere.radius, 3)
  195. // addBoundingBox(mb, structure.boundary.box, 1, 2, 8)
  196. // for (let i = 0, il = structure.units.length; i < il; ++i) {
  197. // mb.setGroup(1)
  198. // const u = structure.units[i]
  199. // const ci = u.model.atomicHierarchy.chainAtomSegments.index[u.elements[0]]
  200. // const ek = u.model.atomicHierarchy.getEntityKey(ci)
  201. // if (u.model.entities.data.type.value(ek) === 'water') continue
  202. // const boundary = computeUnitBoundary(u)
  203. // addSphere(mb, boundary.sphere.center, boundary.sphere.radius, 3)
  204. // addBoundingBox(mb, boundary.box, 0.5, 2, 8)
  205. // }
  206. // const shape = Shape.create('boundary', mb.getMesh(), [Color(0xCC6633), Color(0x3366CC)], ['sphere boundary'])
  207. // await polymerSphere.createOrUpdate({
  208. // alpha: 0.5,
  209. // doubleSided: false,
  210. // depthMask: false,
  211. // useFog: false // TODO fog not working properly
  212. // }, shape).run()
  213. } else {
  214. for (const k in structureRepresentations) structureRepresentations[k].destroy()
  215. polymerSphere.destroy()
  216. }
  217. canvas3d.add(polymerSphere)
  218. updated.next(null)
  219. canvas3d.requestDraw(true)
  220. console.log('stats', canvas3d.stats)
  221. }
  222. async function createSymmetryRepr() {
  223. if (assemblySymmetry) {
  224. const symmetries = assemblySymmetry.getSymmetries(assemblyId)
  225. if (symmetries._rowCount) {
  226. const axesShape = getAxesShape(symmetryFeatureId, assemblySymmetry)
  227. if (axesShape) {
  228. // const colorTheme = getClusterColorTheme(symmetryFeatureId, assemblySymmetry)
  229. // await structureRepresentations['cartoon'].createOrUpdate({
  230. // colorTheme: 'custom',
  231. // colorFunction: colorTheme.color,
  232. // colorGranularity: colorTheme.granularity,
  233. // }).run()
  234. await symmetryAxes.createOrUpdate(app.reprCtx, {}, {}, axesShape).run()
  235. canvas3d.add(symmetryAxes)
  236. } else {
  237. canvas3d.remove(symmetryAxes)
  238. }
  239. } else {
  240. canvas3d.remove(symmetryAxes)
  241. }
  242. } else {
  243. canvas3d.remove(symmetryAxes)
  244. }
  245. updated.next(null)
  246. canvas3d.requestDraw(true)
  247. }
  248. await setModel(0, props.assemblyId, props.symmetryFeatureId)
  249. return {
  250. app,
  251. canvas3d,
  252. get label() { return label },
  253. models,
  254. get structure() { return structure },
  255. get assemblySymmetry() { return assemblySymmetry },
  256. active,
  257. structureRepresentations,
  258. updated,
  259. symmetryAxes,
  260. setSymmetryAxes,
  261. setStructureRepresentation,
  262. get modelId() { return modelId },
  263. get assemblyId() { return assemblyId },
  264. get symmetryFeatureId() { return symmetryFeatureId },
  265. setModel,
  266. getModelIds,
  267. setAssembly,
  268. getAssemblyIds,
  269. setSymmetryFeature,
  270. getSymmetryFeatureIds,
  271. destroy: () => {
  272. for (const k in structureRepresentations) {
  273. canvas3d.remove(structureRepresentations[k])
  274. structureRepresentations[k].destroy()
  275. }
  276. canvas3d.remove(polymerSphere)
  277. canvas3d.remove(symmetryAxes)
  278. canvas3d.requestDraw(true)
  279. polymerSphere.destroy()
  280. symmetryAxes.destroy()
  281. }
  282. }
  283. }
  284. // // create new structure via query
  285. // const q1 = Q.generators.atoms({
  286. // residueTest: qtx => SP.residue.label_seq_id(qtx.element) < 7
  287. // });
  288. // const newStructure = StructureSelection.unionStructure(await StructureQuery.run(q1, structure));
  289. // // ball+stick for new structure
  290. // const newBallStickRepr = BallAndStickRepresentation()
  291. // await newBallStickRepr.create(newStructure, {
  292. // colorTheme: { name: 'element-symbol' },
  293. // sizeTheme: { name: 'uniform', value: 0.1 },
  294. // useFog: false // TODO fog not working properly
  295. // }).run()
  296. // viewer.add(newBallStickRepr)
  297. // // create a mesh
  298. // const meshBuilder = MeshBuilder.create(256, 128)
  299. // const colors: Color[] = []
  300. // const labels: string[] = []
  301. // // red sphere
  302. // meshBuilder.setGroup(0)
  303. // colors[0] = Color(0xFF2233)
  304. // labels[0] = 'red sphere'
  305. // addSphere(meshBuilder, Vec3.create(0, 0, 0), 4, 2)
  306. // // green cube
  307. // meshBuilder.setGroup(1)
  308. // colors[1] = Color(0x2233FF)
  309. // labels[1] = 'blue cube'
  310. // const t = Mat4.identity()
  311. // Mat4.fromTranslation(t, Vec3.create(10, 0, 0))
  312. // Mat4.scale(t, t, Vec3.create(3, 3, 3))
  313. // meshBuilder.add(t, Box())
  314. // const mesh = meshBuilder.getMesh()
  315. // const mesh = getObjFromUrl('mesh.obj')
  316. // // create shape from mesh
  317. // const shape = Shape.create('myShape', mesh, colors, labels)
  318. // // add representation from shape
  319. // const customRepr = ShapeRepresentation()
  320. // await customRepr.create(shape, {
  321. // colorTheme: { name: 'shape-group' },
  322. // // colorTheme: { name: 'uniform', value: Color(0xFFCC22) },
  323. // useFog: false // TODO fog not working properly
  324. // }).run()
  325. // viewer.add(customRepr)