structure.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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 { PluginContext } from 'mol-plugin/context';
  8. import { StateAction, StateBuilder, StateSelection, StateTransformer, State } from 'mol-state';
  9. import { ParamDefinition as PD } from 'mol-util/param-definition';
  10. import { PluginStateObject } from '../objects';
  11. import { StateTransforms } from '../transforms';
  12. import { Download } from '../transforms/data';
  13. import { StructureRepresentation3DHelpers } from '../transforms/representation';
  14. import { CustomModelProperties, StructureSelection, CustomStructureProperties } from '../transforms/model';
  15. import { DataFormatProvider, guessCifVariant } from './data-format';
  16. import { FileInfo } from 'mol-util/file-info';
  17. import { Task } from 'mol-task';
  18. import { StructureElement } from 'mol-model/structure';
  19. export const MmcifProvider: DataFormatProvider<any> = {
  20. label: 'mmCIF',
  21. description: 'mmCIF',
  22. stringExtensions: ['cif', 'mmcif', 'mcif'],
  23. binaryExtensions: ['bcif'],
  24. isApplicable: (info: FileInfo, data: Uint8Array | string) => {
  25. if (info.ext === 'mmcif' || info.ext === 'mcif') return true
  26. // assume cif/bcif files that are not DensityServer CIF are mmCIF
  27. if (info.ext === 'cif' || info.ext === 'bcif') return guessCifVariant(info, data) !== 'dscif'
  28. return false
  29. },
  30. getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, state: State) => {
  31. return Task.create('mmCIF default builder', async taskCtx => {
  32. const traj = createModelTree(data, 'cif');
  33. await state.updateTree(createStructureTree(ctx, traj, false)).runInContext(taskCtx)
  34. })
  35. }
  36. }
  37. export const PdbProvider: DataFormatProvider<any> = {
  38. label: 'PDB',
  39. description: 'PDB',
  40. stringExtensions: ['pdb', 'ent'],
  41. binaryExtensions: [],
  42. isApplicable: (info: FileInfo, data: string) => {
  43. return info.ext === 'pdb' || info.ext === 'ent'
  44. },
  45. getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.String>, state: State) => {
  46. return Task.create('PDB default builder', async taskCtx => {
  47. const traj = createModelTree(data, 'pdb');
  48. await state.updateTree(createStructureTree(ctx, traj, false)).runInContext(taskCtx)
  49. })
  50. }
  51. }
  52. export const GroProvider: DataFormatProvider<any> = {
  53. label: 'GRO',
  54. description: 'GRO',
  55. stringExtensions: ['gro'],
  56. binaryExtensions: [],
  57. isApplicable: (info: FileInfo, data: string) => {
  58. return info.ext === 'gro'
  59. },
  60. getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.String>, state: State) => {
  61. return Task.create('GRO default builder', async taskCtx => {
  62. const traj = createModelTree(data, 'gro');
  63. await state.updateTree(createStructureTree(ctx, traj, false)).runInContext(taskCtx)
  64. })
  65. }
  66. }
  67. type StructureFormat = 'pdb' | 'cif' | 'gro'
  68. //
  69. const DownloadStructurePdbIdSourceOptions = PD.Group({
  70. supportProps: PD.Optional(PD.Boolean(false)),
  71. asTrajectory: PD.Optional(PD.Boolean(false, { description: 'Load all entries into a single trajectory.' }))
  72. });
  73. export { DownloadStructure };
  74. type DownloadStructure = typeof DownloadStructure
  75. const DownloadStructure = StateAction.build({
  76. from: PluginStateObject.Root,
  77. display: { name: 'Download Structure', description: 'Load a structure from the provided source and create its default Assembly and visual.' },
  78. params: {
  79. source: PD.MappedStatic('bcif-static', {
  80. 'pdbe-updated': PD.Group({
  81. id: PD.Text('1cbs', { label: 'Id' }),
  82. options: DownloadStructurePdbIdSourceOptions
  83. }, { isFlat: true }),
  84. 'rcsb': PD.Group({
  85. id: PD.Text('1tqn', { label: 'Id' }),
  86. options: DownloadStructurePdbIdSourceOptions
  87. }, { isFlat: true }),
  88. 'pdb-dev': PD.Group({
  89. id: PD.Text('PDBDEV_00000001', { label: 'Id' }),
  90. options: DownloadStructurePdbIdSourceOptions
  91. }, { isFlat: true }),
  92. 'bcif-static': PD.Group({
  93. id: PD.Text('1tqn', { label: 'Id' }),
  94. options: DownloadStructurePdbIdSourceOptions
  95. }, { isFlat: true }),
  96. 'swissmodel': PD.Group({
  97. id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC' }),
  98. options: DownloadStructurePdbIdSourceOptions
  99. }, { isFlat: true, description: 'Loads the best homology model or experimental structure' }),
  100. 'url': PD.Group({
  101. url: PD.Text(''),
  102. format: PD.Select('cif', [['cif', 'CIF'], ['pdb', 'PDB']]),
  103. isBinary: PD.Boolean(false),
  104. options: PD.Group({
  105. supportProps: PD.Optional(PD.Boolean(false))
  106. })
  107. }, { isFlat: true })
  108. }, {
  109. options: [
  110. ['pdbe-updated', 'PDBe Updated'],
  111. ['rcsb', 'RCSB'],
  112. ['pdb-dev', 'PDBDEV'],
  113. ['bcif-static', 'BinaryCIF (static PDBe Updated)'],
  114. ['swissmodel', 'SWISS-MODEL'],
  115. ['url', 'URL']
  116. ]
  117. })
  118. }
  119. })(({ params, state }, ctx: PluginContext) => {
  120. const b = state.build();
  121. const src = params.source;
  122. let downloadParams: StateTransformer.Params<Download>[];
  123. let supportProps = false, asTrajectory = false, format: StructureFormat = 'cif';
  124. switch (src.name) {
  125. case 'url':
  126. downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary }];
  127. supportProps = !!src.params.options.supportProps;
  128. format = src.params.format
  129. break;
  130. case 'pdbe-updated':
  131. downloadParams = getDownloadParams(src.params.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id}`, false);
  132. supportProps = !!src.params.options.supportProps;
  133. asTrajectory = !!src.params.options.asTrajectory;
  134. break;
  135. case 'rcsb':
  136. downloadParams = getDownloadParams(src.params.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB: ${id}`, false);
  137. supportProps = !!src.params.options.supportProps;
  138. asTrajectory = !!src.params.options.asTrajectory;
  139. break;
  140. case 'pdb-dev':
  141. downloadParams = getDownloadParams(src.params.id,
  142. id => {
  143. const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`
  144. return `https://pdb-dev.wwpdb.org/static/cif/${nId.toUpperCase()}.cif`
  145. },
  146. id => id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`,
  147. false
  148. );
  149. supportProps = !!src.params.options.supportProps;
  150. asTrajectory = !!src.params.options.asTrajectory;
  151. break;
  152. case 'bcif-static':
  153. downloadParams = getDownloadParams(src.params.id, id => `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${id.toLowerCase()}`, id => `BinaryCIF: ${id}`, true);
  154. supportProps = !!src.params.options.supportProps;
  155. asTrajectory = !!src.params.options.asTrajectory;
  156. break;
  157. case 'swissmodel':
  158. downloadParams = getDownloadParams(src.params.id, id => `https://swissmodel.expasy.org/repository/uniprot/${id.toUpperCase()}.pdb`, id => `SWISS-MODEL: ${id}`, false);
  159. supportProps = !!src.params.options.supportProps;
  160. asTrajectory = !!src.params.options.asTrajectory;
  161. format = 'pdb'
  162. break;
  163. default: throw new Error(`${(src as any).name} not supported.`);
  164. }
  165. if (downloadParams.length > 0 && asTrajectory) {
  166. const traj = createSingleTrajectoryModel(downloadParams, b);
  167. createStructureTree(ctx, traj, supportProps);
  168. } else {
  169. for (const download of downloadParams) {
  170. const data = b.toRoot().apply(StateTransforms.Data.Download, download, { state: { isGhost: true } });
  171. const traj = createModelTree(data, format);
  172. createStructureTree(ctx, traj, supportProps)
  173. }
  174. }
  175. return state.updateTree(b);
  176. });
  177. function getDownloadParams(src: string, url: (id: string) => string, label: (id: string) => string, isBinary: boolean): StateTransformer.Params<Download>[] {
  178. const ids = src.split(',').map(id => id.trim()).filter(id => !!id && (id.length >= 4 || /^[1-9][0-9]*$/.test(id)));
  179. const ret: StateTransformer.Params<Download>[] = [];
  180. for (const id of ids) {
  181. ret.push({ url: url(id), isBinary, label: label(id) })
  182. }
  183. return ret;
  184. }
  185. function createSingleTrajectoryModel(sources: StateTransformer.Params<Download>[], b: StateBuilder.Root) {
  186. return b.toRoot()
  187. .apply(StateTransforms.Data.DownloadBlob, {
  188. sources: sources.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })),
  189. maxConcurrency: 6
  190. }).apply(StateTransforms.Data.ParseBlob, {
  191. formats: sources.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' }))
  192. })
  193. .apply(StateTransforms.Model.TrajectoryFromBlob)
  194. .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
  195. }
  196. export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: StructureFormat = 'cif') {
  197. let parsed: StateBuilder.To<PluginStateObject.Molecule.Trajectory>
  198. switch (format) {
  199. case 'cif':
  200. parsed = b.apply(StateTransforms.Data.ParseCif, void 0, { state: { isGhost: true } })
  201. .apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { state: { isGhost: true } })
  202. break
  203. case 'pdb':
  204. parsed = b.apply(StateTransforms.Model.TrajectoryFromPDB, void 0, { state: { isGhost: true } });
  205. break
  206. case 'gro':
  207. parsed = b.apply(StateTransforms.Model.TrajectoryFromGRO, void 0, { state: { isGhost: true } });
  208. break
  209. default:
  210. throw new Error('unsupported format')
  211. }
  212. return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
  213. }
  214. function createStructureTree(ctx: PluginContext, b: StateBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean) {
  215. let root = b;
  216. if (supportProps) {
  217. root = root.apply(StateTransforms.Model.CustomModelProperties);
  218. }
  219. const structure = root.apply(StateTransforms.Model.StructureAssemblyFromModel);
  220. complexRepresentation(ctx, structure);
  221. return root;
  222. }
  223. export function complexRepresentation(
  224. ctx: PluginContext, root: StateBuilder.To<PluginStateObject.Molecule.Structure>,
  225. params?: { hideSequence?: boolean, hideHET?: boolean, hideWater?: boolean, hideCoarse?: boolean; }
  226. ) {
  227. if (!params || !params.hideSequence) {
  228. root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
  229. .apply(StateTransforms.Representation.StructureRepresentation3D,
  230. StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'cartoon'));
  231. }
  232. if (!params || !params.hideHET) {
  233. root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
  234. .apply(StateTransforms.Representation.StructureRepresentation3D,
  235. StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick'));
  236. }
  237. if (!params || !params.hideWater) {
  238. root.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
  239. .apply(StateTransforms.Representation.StructureRepresentation3D,
  240. StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', { alpha: 0.51 }));
  241. }
  242. if (!params || !params.hideCoarse) {
  243. root.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' })
  244. .apply(StateTransforms.Representation.StructureRepresentation3D,
  245. StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'spacefill', {}, 'polymer-id'));
  246. }
  247. }
  248. export const CreateComplexRepresentation = StateAction.build({
  249. display: { name: 'Create Complex', description: 'Split the structure into Sequence/Water/Ligands/... ' },
  250. from: PluginStateObject.Molecule.Structure
  251. })(({ ref, state }, ctx: PluginContext) => {
  252. const root = state.build().to(ref);
  253. complexRepresentation(ctx, root);
  254. return state.updateTree(root);
  255. });
  256. export const UpdateTrajectory = StateAction.build({
  257. display: { name: 'Update Trajectory' },
  258. params: {
  259. action: PD.Select<'advance' | 'reset'>('advance', [['advance', 'Advance'], ['reset', 'Reset']]),
  260. by: PD.Optional(PD.Numeric(1, { min: -1, max: 1, step: 1 }))
  261. }
  262. })(({ params, state }) => {
  263. const models = state.selectQ(q => q.ofTransformer(StateTransforms.Model.ModelFromTrajectory));
  264. const update = state.build();
  265. if (params.action === 'reset') {
  266. for (const m of models) {
  267. update.to(m).update({ modelIndex: 0 });
  268. }
  269. } else {
  270. for (const m of models) {
  271. const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]);
  272. if (!parent || !parent.obj) continue;
  273. const traj = parent.obj;
  274. update.to(m).update(old => {
  275. let modelIndex = (old.modelIndex + params.by!) % traj.data.length;
  276. if (modelIndex < 0) modelIndex += traj.data.length;
  277. return { modelIndex };
  278. });
  279. }
  280. }
  281. return state.updateTree(update);
  282. });
  283. export const EnableModelCustomProps = StateAction.build({
  284. display: { name: 'Custom Model Properties', description: 'Enable the addition of custom properties to the model.' },
  285. from: PluginStateObject.Molecule.Model,
  286. params(a, ctx: PluginContext) {
  287. if (!a) return { properties: PD.MultiSelect([], [], { description: 'A list of model property descriptor ids.' }) };
  288. return { properties: ctx.customModelProperties.getSelect(a.data) };
  289. },
  290. isApplicable(a, t, ctx: PluginContext) {
  291. return t.transformer !== CustomModelProperties;
  292. }
  293. })(({ ref, params, state }, ctx: PluginContext) => {
  294. const root = state.build().to(ref).insert(CustomModelProperties, params);
  295. return state.updateTree(root);
  296. });
  297. export const EnableStructureCustomProps = StateAction.build({
  298. display: { name: 'Custom Structure Properties', description: 'Enable the addition of custom properties to the structure.' },
  299. from: PluginStateObject.Molecule.Structure,
  300. params(a, ctx: PluginContext) {
  301. if (!a) return { properties: PD.MultiSelect([], [], { description: 'A list of structure property descriptor ids.' }) };
  302. return { properties: ctx.customStructureProperties.getSelect(a.data) };
  303. },
  304. isApplicable(a, t, ctx: PluginContext) {
  305. return t.transformer !== CustomStructureProperties;
  306. }
  307. })(({ ref, params, state }, ctx: PluginContext) => {
  308. const root = state.build().to(ref).insert(CustomStructureProperties, params);
  309. return state.updateTree(root);
  310. });
  311. export const TransformStructureConformation = StateAction.build({
  312. display: { name: 'Transform Conformation' },
  313. from: PluginStateObject.Molecule.Structure,
  314. params: StateTransforms.Model.TransformStructureConformation.definition.params,
  315. })(({ ref, params, state }) => {
  316. const root = state.build().to(ref).insert(StateTransforms.Model.TransformStructureConformation, params as any);
  317. return state.updateTree(root);
  318. });
  319. export const StructureFromSelection = StateAction.build({
  320. display: { name: 'Selection Structure', description: 'Create a new Structure from the current selection.' },
  321. from: PluginStateObject.Molecule.Structure,
  322. params: {
  323. label: PD.Text()
  324. }
  325. // isApplicable(a, t, ctx: PluginContext) {
  326. // return t.transformer !== CustomModelProperties;
  327. // }
  328. })(({ a, ref, params, state }, plugin: PluginContext) => {
  329. const sel = plugin.helpers.structureSelection.get(a.data);
  330. if (sel.kind === 'empty-loci') return Task.constant('', void 0);
  331. const query = StructureElement.Loci.toScriptExpression(sel);
  332. const root = state.build().to(ref).apply(StructureSelection, { query, label: params.label });
  333. return state.updateTree(root);
  334. });