structure-view.ts 14 KB

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