structure-view.ts 12 KB

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