model.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /**
  2. * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { parsePDB } from 'mol-io/reader/pdb/parser';
  8. import { Vec3 } from 'mol-math/linear-algebra';
  9. import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif';
  10. import { trajectoryFromPDB } from 'mol-model-formats/structure/pdb';
  11. import { Model, ModelSymmetry, Queries, QueryContext, Structure, StructureQuery, StructureSelection as Sel, StructureSymmetry } from 'mol-model/structure';
  12. import { Assembly } from 'mol-model/structure/model/properties/symmetry';
  13. import { PluginContext } from 'mol-plugin/context';
  14. import { MolScriptBuilder } from 'mol-script/language/builder';
  15. import Expression from 'mol-script/language/expression';
  16. import { compile } from 'mol-script/runtime/query/compiler';
  17. import { StateObject } from 'mol-state';
  18. import { RuntimeContext, Task } from 'mol-task';
  19. import { ParamDefinition as PD } from 'mol-util/param-definition';
  20. import { stringToWords } from 'mol-util/string';
  21. import { PluginStateObject as SO, PluginStateTransform } from '../objects';
  22. export { TrajectoryFromMmCif };
  23. export { TrajectoryFromPDB };
  24. export { ModelFromTrajectory };
  25. export { StructureFromModel };
  26. export { StructureAssemblyFromModel };
  27. export { StructureSymmetryFromModel };
  28. export { StructureSelection };
  29. export { StructureComplexElement };
  30. export { CustomModelProperties };
  31. type TrajectoryFromMmCif = typeof TrajectoryFromMmCif
  32. const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({
  33. name: 'trajectory-from-mmcif',
  34. display: { name: 'Trajectory from mmCIF', description: 'Identify and create all separate models in the specified CIF data block' },
  35. from: SO.Format.Cif,
  36. to: SO.Molecule.Trajectory,
  37. params(a) {
  38. if (!a) {
  39. return {
  40. blockHeader: PD.makeOptional(PD.Text(void 0, { description: 'Header of the block to parse. If none is specifed, the 1st data block in the file is used.' }))
  41. };
  42. }
  43. const { blocks } = a.data;
  44. return {
  45. blockHeader: PD.makeOptional(PD.Select(blocks[0] && blocks[0].header, blocks.map(b => [b.header, b.header] as [string, string]), { description: 'Header of the block to parse' }))
  46. };
  47. }
  48. })({
  49. isApplicable: a => a.data.blocks.length > 0,
  50. apply({ a, params }) {
  51. return Task.create('Parse mmCIF', async ctx => {
  52. const header = params.blockHeader || a.data.blocks[0].header;
  53. const block = a.data.blocks.find(b => b.header === header);
  54. if (!block) throw new Error(`Data block '${[header]}' not found.`);
  55. const models = await trajectoryFromMmCIF(block).runInContext(ctx);
  56. if (models.length === 0) throw new Error('No models found.');
  57. const props = { label: models[0].label, description: `${models.length} model${models.length === 1 ? '' : 's'}` };
  58. return new SO.Molecule.Trajectory(models, props);
  59. });
  60. }
  61. });
  62. type TrajectoryFromPDB = typeof TrajectoryFromPDB
  63. const TrajectoryFromPDB = PluginStateTransform.BuiltIn({
  64. name: 'trajectory-from-pdb',
  65. display: { name: 'Parse PDB', description: 'Parse PDB string and create trajectory.' },
  66. from: [SO.Data.String],
  67. to: SO.Molecule.Trajectory
  68. })({
  69. apply({ a }) {
  70. return Task.create('Parse PDB', async ctx => {
  71. const parsed = await parsePDB(a.data).runInContext(ctx);
  72. if (parsed.isError) throw new Error(parsed.message);
  73. const models = await trajectoryFromPDB(parsed.result).runInContext(ctx);
  74. const props = { label: models[0].label, description: `${models.length} model${models.length === 1 ? '' : 's'}` };
  75. return new SO.Molecule.Trajectory(models, props);
  76. });
  77. }
  78. });
  79. const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1;
  80. type ModelFromTrajectory = typeof ModelFromTrajectory
  81. const ModelFromTrajectory = PluginStateTransform.BuiltIn({
  82. name: 'model-from-trajectory',
  83. display: { name: 'Molecular Model', description: 'Create a molecular model from specified index in a trajectory.' },
  84. from: SO.Molecule.Trajectory,
  85. to: SO.Molecule.Model,
  86. params: a => {
  87. if (!a) {
  88. return { modelIndex: PD.Numeric(0, {}, { description: 'Zero-based index of the model' }) };
  89. }
  90. return { modelIndex: PD.Converted(plus1, minus1, PD.Numeric(1, { min: 1, max: a.data.length, step: 1 }, { description: 'Model Index' })) }
  91. }
  92. })({
  93. isApplicable: a => a.data.length > 0,
  94. apply({ a, params }) {
  95. if (params.modelIndex < 0 || params.modelIndex >= a.data.length) throw new Error(`Invalid modelIndex ${params.modelIndex}`);
  96. const model = a.data[params.modelIndex];
  97. const props = a.data.length === 1
  98. ? { label: `${model.label}` }
  99. : { label: `${model.label}:${model.modelNum}`, description: `Model ${model.modelNum} of ${a.data.length}` };
  100. return new SO.Molecule.Model(model, props);
  101. }
  102. });
  103. type StructureFromModel = typeof StructureFromModel
  104. const StructureFromModel = PluginStateTransform.BuiltIn({
  105. name: 'structure-from-model',
  106. display: { name: 'Structure from Model', description: 'Create a molecular structure from the specified model.' },
  107. from: SO.Molecule.Model,
  108. to: SO.Molecule.Structure
  109. })({
  110. apply({ a }) {
  111. let s = Structure.ofModel(a.data);
  112. const props = { label: a.data.label, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` };
  113. return new SO.Molecule.Structure(s, props);
  114. }
  115. });
  116. function structureDesc(s: Structure) {
  117. return s.elementCount === 1 ? '1 element' : `${s.elementCount} elements`;
  118. }
  119. type StructureAssemblyFromModel = typeof StructureAssemblyFromModel
  120. const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({
  121. name: 'structure-assembly-from-model',
  122. display: { name: 'Structure Assembly', description: 'Create a molecular structure assembly.' },
  123. from: SO.Molecule.Model,
  124. to: SO.Molecule.Structure,
  125. params(a) {
  126. if (!a) {
  127. return { id: PD.makeOptional(PD.Text('', { label: 'Assembly Id', description: 'Assembly Id. Value \'deposited\' can be used to specify deposited asymmetric unit.' })) };
  128. }
  129. const model = a.data;
  130. const ids = model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]);
  131. ids.push(['deposited', 'Deposited']);
  132. return { id: PD.makeOptional(PD.Select(ids[0][0], ids, { label: 'Asm Id', description: 'Assembly Id' })) };
  133. }
  134. })({
  135. apply({ a, params }, plugin: PluginContext) {
  136. return Task.create('Build Assembly', async ctx => {
  137. const model = a.data;
  138. let id = params.id;
  139. let asm: Assembly | undefined = void 0;
  140. // if no id is specified, use the 1st assembly.
  141. if (!id && model.symmetry.assemblies.length !== 0) {
  142. id = model.symmetry.assemblies[0].id;
  143. }
  144. if (model.symmetry.assemblies.length === 0) {
  145. if (id !== 'deposited') {
  146. plugin.log.warn(`Model '${a.data.label}' has no assembly, returning deposited structure.`);
  147. }
  148. } else {
  149. asm = ModelSymmetry.findAssembly(model, id || '');
  150. if (!asm) {
  151. plugin.log.warn(`Model '${a.data.label}' has no assembly called '${id}', returning deposited structure.`);
  152. }
  153. }
  154. const base = Structure.ofModel(model);
  155. if (!asm) {
  156. const label = { label: a.data.label, description: structureDesc(base) };
  157. return new SO.Molecule.Structure(base, label);
  158. }
  159. id = asm.id;
  160. const s = await StructureSymmetry.buildAssembly(base, id!).runInContext(ctx);
  161. const props = { label: `Assembly ${id}`, description: structureDesc(s) };
  162. return new SO.Molecule.Structure(s, props);
  163. })
  164. }
  165. });
  166. type StructureSymmetryFromModel = typeof StructureSymmetryFromModel
  167. const StructureSymmetryFromModel = PluginStateTransform.BuiltIn({
  168. name: 'structure-symmetry-from-model',
  169. display: { name: 'Structure Symmetry', description: 'Create a molecular structure symmetry.' },
  170. from: SO.Molecule.Model,
  171. to: SO.Molecule.Structure,
  172. params(a) {
  173. return {
  174. ijkMin: PD.Vec3(Vec3.create(-1, -1, -1), { label: 'Min IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }),
  175. ijkMax: PD.Vec3(Vec3.create(1, 1, 1), { label: 'Max IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } })
  176. }
  177. }
  178. })({
  179. apply({ a, params }, plugin: PluginContext) {
  180. return Task.create('Build Symmetry', async ctx => {
  181. const { ijkMin, ijkMax } = params
  182. const model = a.data;
  183. const base = Structure.ofModel(model);
  184. const s = await StructureSymmetry.buildSymmetryRange(base, ijkMin, ijkMax).runInContext(ctx);
  185. const props = { label: `Symmetry [${ijkMin}] to [${ijkMax}]`, description: structureDesc(s) };
  186. return new SO.Molecule.Structure(s, props);
  187. })
  188. }
  189. });
  190. type StructureSelection = typeof StructureSelection
  191. const StructureSelection = PluginStateTransform.BuiltIn({
  192. name: 'structure-selection',
  193. display: { name: 'Structure Selection', description: 'Create a molecular structure from the specified query expression.' },
  194. from: SO.Molecule.Structure,
  195. to: SO.Molecule.Structure,
  196. params: {
  197. query: PD.Value<Expression>(MolScriptBuilder.struct.generator.all, { isHidden: true }),
  198. label: PD.makeOptional(PD.Text('', { isHidden: true }))
  199. }
  200. })({
  201. apply({ a, params }) {
  202. // TODO: use cache, add "update"
  203. const compiled = compile<Sel>(params.query);
  204. const result = compiled(new QueryContext(a.data));
  205. const s = Sel.unionStructure(result);
  206. const props = { label: `${params.label || 'Selection'}`, description: structureDesc(s) };
  207. return new SO.Molecule.Structure(s, props);
  208. }
  209. });
  210. namespace StructureComplexElement {
  211. export type Types = 'atomic-sequence' | 'water' | 'atomic-het' | 'spheres'
  212. }
  213. const StructureComplexElementTypes: [StructureComplexElement.Types, StructureComplexElement.Types][] = ['atomic-sequence', 'water', 'atomic-het', 'spheres'].map(t => [t, t] as any);
  214. type StructureComplexElement = typeof StructureComplexElement
  215. const StructureComplexElement = PluginStateTransform.BuiltIn({
  216. name: 'structure-complex-element',
  217. display: { name: 'Complex Element', description: 'Create a molecular structure from the specified model.' },
  218. from: SO.Molecule.Structure,
  219. to: SO.Molecule.Structure,
  220. params: { type: PD.Select<StructureComplexElement.Types>('atomic-sequence', StructureComplexElementTypes, { isHidden: true }) }
  221. })({
  222. apply({ a, params }) {
  223. // TODO: update function.
  224. let query: StructureQuery, label: string;
  225. switch (params.type) {
  226. case 'atomic-sequence': query = Queries.internal.atomicSequence(); label = 'Sequence'; break;
  227. case 'water': query = Queries.internal.water(); label = 'Water'; break;
  228. case 'atomic-het': query = Queries.internal.atomicHet(); label = 'HET Groups/Ligands'; break;
  229. case 'spheres': query = Queries.internal.spheres(); label = 'Coarse Spheres'; break;
  230. default: throw new Error(`${params.type} is a not valid complex element.`);
  231. }
  232. const result = query(new QueryContext(a.data));
  233. const s = Sel.unionStructure(result);
  234. if (s.elementCount === 0) return StateObject.Null;
  235. return new SO.Molecule.Structure(s, { label, description: structureDesc(s) });
  236. }
  237. });
  238. type CustomModelProperties = typeof CustomModelProperties
  239. const CustomModelProperties = PluginStateTransform.BuiltIn({
  240. name: 'custom-model-properties',
  241. display: { name: 'Custom Model Properties' },
  242. from: SO.Molecule.Model,
  243. to: SO.Molecule.Model,
  244. params: (a, ctx: PluginContext) => {
  245. if (!a) return { properties: PD.MultiSelect([], [], { description: 'A list of property descriptor ids.' }) };
  246. return { properties: ctx.customModelProperties.getSelect(a.data) };
  247. }
  248. })({
  249. apply({ a, params }, ctx: PluginContext) {
  250. return Task.create('Custom Props', async taskCtx => {
  251. await attachProps(a.data, ctx, taskCtx, params.properties);
  252. return new SO.Molecule.Model(a.data, { label: 'Props', description: `${params.properties.length} Selected` });
  253. });
  254. }
  255. });
  256. async function attachProps(model: Model, ctx: PluginContext, taskCtx: RuntimeContext, names: string[]) {
  257. for (const name of names) {
  258. const p = ctx.customModelProperties.get(name);
  259. await p.attach(model).runInContext(taskCtx);
  260. }
  261. }