structure.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. /**
  2. * Copyright (c) 2018-2020 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, StateSelection, StateTransformer } from '../../mol-state';
  9. import { Task } from '../../mol-task';
  10. import { FileInfo } from '../../mol-util/file-info';
  11. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  12. import { BuiltInTrajectoryFormat } from '../formats/trajectory';
  13. import { RootStructureDefinition } from '../helpers/root-structure';
  14. import { PluginStateObject } from '../objects';
  15. import { StateTransforms } from '../transforms';
  16. import { Download, ParsePsf } from '../transforms/data';
  17. import { CoordinatesFromDcd, CustomModelProperties, CustomStructureProperties, TopologyFromPsf, TrajectoryFromModelAndCoordinates } from '../transforms/model';
  18. import { DataFormatProvider, guessCifVariant } from './data-format';
  19. import { applyTrajectoryHierarchyPreset } from '../builder/structure/hierarchy-preset';
  20. import { PresetStructureReprentations } from '../builder/structure/representation-preset';
  21. // TODO make unitcell creation part of preset
  22. export const MmcifProvider: DataFormatProvider<PluginStateObject.Data.String | PluginStateObject.Data.Binary> = {
  23. label: 'mmCIF',
  24. description: 'mmCIF',
  25. stringExtensions: ['cif', 'mmcif', 'mcif'],
  26. binaryExtensions: ['bcif'],
  27. isApplicable: (info: FileInfo, data: Uint8Array | string) => {
  28. if (info.ext === 'mmcif' || info.ext === 'mcif') return true
  29. // assume cif/bcif files that are not DensityServer CIF are mmCIF
  30. if (info.ext === 'cif' || info.ext === 'bcif') return guessCifVariant(info, data) !== 'dscif'
  31. return false
  32. },
  33. getDefaultBuilder: (ctx: PluginContext, data, options) => {
  34. return Task.create('mmCIF default builder', async () => {
  35. const trajectory = await ctx.builders.structure.parseTrajectory(data, 'mmcif');
  36. const representationPreset = options.visuals ? 'auto' : 'empty';
  37. await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
  38. })
  39. }
  40. }
  41. export const PdbProvider: DataFormatProvider<any> = {
  42. label: 'PDB',
  43. description: 'PDB',
  44. stringExtensions: ['pdb', 'ent'],
  45. binaryExtensions: [],
  46. isApplicable: (info: FileInfo, data: string) => {
  47. return info.ext === 'pdb' || info.ext === 'ent'
  48. },
  49. getDefaultBuilder: (ctx: PluginContext, data, options) => {
  50. return Task.create('PDB default builder', async () => {
  51. const trajectory = await ctx.builders.structure.parseTrajectory(data, 'pdb');
  52. const representationPreset = options.visuals ? 'auto' : 'empty';
  53. await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
  54. })
  55. }
  56. }
  57. export const GroProvider: DataFormatProvider<any> = {
  58. label: 'GRO',
  59. description: 'GRO',
  60. stringExtensions: ['gro'],
  61. binaryExtensions: [],
  62. isApplicable: (info: FileInfo, data: string) => {
  63. return info.ext === 'gro'
  64. },
  65. getDefaultBuilder: (ctx: PluginContext, data, options) => {
  66. return Task.create('GRO default builder', async () => {
  67. const trajectory = await ctx.builders.structure.parseTrajectory(data, 'gro');
  68. const representationPreset = options.visuals ? 'auto' : 'empty';
  69. await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
  70. })
  71. }
  72. }
  73. export const Provider3dg: DataFormatProvider<any> = {
  74. label: '3DG',
  75. description: '3DG',
  76. stringExtensions: ['3dg'],
  77. binaryExtensions: [],
  78. isApplicable: (info: FileInfo, data: string) => {
  79. return info.ext === '3dg'
  80. },
  81. getDefaultBuilder: (ctx: PluginContext, data, options) => {
  82. return Task.create('3DG default builder', async () => {
  83. const trajectory = await ctx.builders.structure.parseTrajectory(data, '3dg');
  84. const representationPreset = options.visuals ? 'auto' : 'empty';
  85. await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
  86. })
  87. }
  88. }
  89. export const PsfProvider: DataFormatProvider<any> = {
  90. label: 'PSF',
  91. description: 'PSF',
  92. stringExtensions: ['psf'],
  93. binaryExtensions: [],
  94. isApplicable: (info: FileInfo, data: string) => {
  95. return info.ext === 'psf'
  96. },
  97. getDefaultBuilder: (ctx: PluginContext, data, options, state) => {
  98. return Task.create('PSF default builder', async taskCtx => {
  99. const build = state.build().to(data).apply(ParsePsf, {}, { state: { isGhost: true } }).apply(TopologyFromPsf)
  100. await state.updateTree(build).runInContext(taskCtx)
  101. })
  102. }
  103. }
  104. export const DcdProvider: DataFormatProvider<any> = {
  105. label: 'DCD',
  106. description: 'DCD',
  107. stringExtensions: [],
  108. binaryExtensions: ['dcd'],
  109. isApplicable: (info: FileInfo, data: string) => {
  110. return info.ext === 'dcd'
  111. },
  112. getDefaultBuilder: (ctx: PluginContext, data, options, state) => {
  113. return Task.create('DCD default builder', async taskCtx => {
  114. const build = state.build().to(data).apply(CoordinatesFromDcd);
  115. await state.updateTree(build).runInContext(taskCtx)
  116. })
  117. }
  118. }
  119. //
  120. const DownloadModelRepresentationOptions = (plugin: PluginContext) => PD.Group({
  121. type: RootStructureDefinition.getParams(void 0, 'auto').type,
  122. representation: PD.Select(PresetStructureReprentations.auto.id,
  123. plugin.builders.structure.representation.getPresets().map(p => [p.id, p.display.name] as any),
  124. { description: 'Which representation preset to use.' }),
  125. asTrajectory: PD.Optional(PD.Boolean(false, { description: 'Load all entries into a single trajectory.' }))
  126. }, { isExpanded: false });
  127. export { DownloadStructure };
  128. type DownloadStructure = typeof DownloadStructure
  129. const DownloadStructure = StateAction.build({
  130. from: PluginStateObject.Root,
  131. display: { name: 'Download Structure', description: 'Load a structure from the provided source and create its representation.' },
  132. params: (_, plugin: PluginContext) => {
  133. const options = DownloadModelRepresentationOptions(plugin);
  134. return {
  135. source: PD.MappedStatic('pdb', {
  136. 'pdb': PD.Group({
  137. provider: PD.Group({
  138. id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
  139. server: PD.MappedStatic('rcsb', {
  140. 'rcsb': PD.Group({
  141. encoding: PD.Select('bcif', [['cif', 'cif'], ['bcif', 'bcif']] as ['cif' | 'bcif', string][]),
  142. }, { label: 'RCSB PDB', isFlat: true }),
  143. 'pdbe': PD.Group({
  144. variant: PD.Select('updated', [['updated', 'Updated'], ['archival', 'Archival']] as ['updated' | 'archival', string][]),
  145. }, { label: 'PDBe', isFlat: true }),
  146. }),
  147. }, { pivot: 'id' }),
  148. options
  149. }, { isFlat: true, label: 'PDB' }),
  150. 'pdb-dev': PD.Group({
  151. id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma separated ids.' }),
  152. options
  153. }, { isFlat: true, label: 'PDBDEV' }),
  154. 'bcif-static': PD.Group({
  155. id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
  156. options
  157. }, { isFlat: true, label: 'BinaryCIF (static PDBe Updated)' }),
  158. 'swissmodel': PD.Group({
  159. id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma separated ACs.' }),
  160. options
  161. }, { isFlat: true, label: 'SWISS-MODEL', description: 'Loads the best homology model or experimental structure' }),
  162. 'url': PD.Group({
  163. url: PD.Text(''),
  164. format: PD.Select('mmcif', [['mmcif', 'CIF'], ['pdb', 'PDB']] as ['mmcif' | 'pdb', string][]),
  165. isBinary: PD.Boolean(false),
  166. options
  167. }, { isFlat: true, label: 'URL' })
  168. })
  169. }
  170. }
  171. })(({ params, state }, plugin: PluginContext) => Task.create('Download Structure', async ctx => {
  172. plugin.behaviors.layout.leftPanelTabName.next('data');
  173. const src = params.source;
  174. let downloadParams: StateTransformer.Params<Download>[];
  175. let asTrajectory = false, format: BuiltInTrajectoryFormat = 'mmcif';
  176. switch (src.name) {
  177. case 'url':
  178. downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary }];
  179. format = src.params.format
  180. break;
  181. case 'pdb':
  182. downloadParams = src.params.provider.server.name === 'pdbe'
  183. ? src.params.provider.server.params.variant === 'updated'
  184. ? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id} (updated cif)`, false)
  185. : getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}.cif`, id => `PDBe: ${id} (cif)`, false)
  186. : src.params.provider.server.params.encoding === 'cif'
  187. ? getDownloadParams(src.params.provider.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB: ${id} (cif)`, false)
  188. : getDownloadParams(src.params.provider.id, id => `https://models.rcsb.org/${id.toUpperCase()}.bcif`, id => `RCSB: ${id} (bcif)`, true);
  189. asTrajectory = !!src.params.options.asTrajectory;
  190. break;
  191. case 'pdb-dev':
  192. downloadParams = getDownloadParams(src.params.id,
  193. id => {
  194. const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`
  195. return `https://pdb-dev.wwpdb.org/cif/${nId.toUpperCase()}.cif`
  196. },
  197. id => id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`,
  198. false
  199. );
  200. asTrajectory = !!src.params.options.asTrajectory;
  201. break;
  202. case 'bcif-static':
  203. downloadParams = getDownloadParams(src.params.id, id => `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${id.toLowerCase()}`, id => `BinaryCIF: ${id}`, true);
  204. asTrajectory = !!src.params.options.asTrajectory;
  205. break;
  206. case 'swissmodel':
  207. downloadParams = getDownloadParams(src.params.id, id => `https://swissmodel.expasy.org/repository/uniprot/${id.toUpperCase()}.pdb`, id => `SWISS-MODEL: ${id}`, false);
  208. asTrajectory = !!src.params.options.asTrajectory;
  209. format = 'pdb'
  210. break;
  211. default: throw new Error(`${(src as any).name} not supported.`);
  212. }
  213. const representationPreset: any = params.source.params.options.representation || PresetStructureReprentations.auto.id;
  214. const showUnitcell = representationPreset !== PresetStructureReprentations.empty.id;
  215. const structure = src.params.options.type.name === 'auto' ? void 0 : src.params.options.type;
  216. await state.transaction(async () => {
  217. if (downloadParams.length > 0 && asTrajectory) {
  218. const blob = await plugin.builders.data.downloadBlob({
  219. sources: downloadParams.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })),
  220. maxConcurrency: 6
  221. }, { state: { isGhost: true } });
  222. const trajectory = await plugin.builders.structure.parseTrajectory(blob, { formats: downloadParams.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' })) });
  223. await applyTrajectoryHierarchyPreset(plugin, trajectory, 'first-model', {
  224. structure,
  225. showUnitcell,
  226. representationPreset
  227. });
  228. } else {
  229. for (const download of downloadParams) {
  230. const data = await plugin.builders.data.download(download, { state: { isGhost: true } });
  231. const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
  232. await applyTrajectoryHierarchyPreset(plugin, trajectory, 'first-model', {
  233. structure,
  234. showUnitcell,
  235. representationPreset
  236. });
  237. }
  238. }
  239. }).runInContext(ctx);
  240. }));
  241. function getDownloadParams(src: string, url: (id: string) => string, label: (id: string) => string, isBinary: boolean): StateTransformer.Params<Download>[] {
  242. const ids = src.split(',').map(id => id.trim()).filter(id => !!id && (id.length >= 4 || /^[1-9][0-9]*$/.test(id)));
  243. const ret: StateTransformer.Params<Download>[] = [];
  244. for (const id of ids) {
  245. ret.push({ url: url(id), isBinary, label: label(id) })
  246. }
  247. return ret;
  248. }
  249. export const UpdateTrajectory = StateAction.build({
  250. display: { name: 'Update Trajectory' },
  251. params: {
  252. action: PD.Select<'advance' | 'reset'>('advance', [['advance', 'Advance'], ['reset', 'Reset']]),
  253. by: PD.Optional(PD.Numeric(1, { min: -1, max: 1, step: 1 }))
  254. }
  255. })(({ params, state }) => {
  256. const models = state.selectQ(q => q.ofTransformer(StateTransforms.Model.ModelFromTrajectory));
  257. const update = state.build();
  258. if (params.action === 'reset') {
  259. for (const m of models) {
  260. update.to(m).update({ modelIndex: 0 });
  261. }
  262. } else {
  263. for (const m of models) {
  264. const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]);
  265. if (!parent || !parent.obj) continue;
  266. const traj = parent.obj;
  267. update.to(m).update(old => {
  268. let modelIndex = (old.modelIndex + params.by!) % traj.data.length;
  269. if (modelIndex < 0) modelIndex += traj.data.length;
  270. return { modelIndex };
  271. });
  272. }
  273. }
  274. return state.updateTree(update);
  275. });
  276. export const EnableModelCustomProps = StateAction.build({
  277. display: { name: 'Custom Model Properties', description: 'Enable parameters for custom properties of the model.' },
  278. from: PluginStateObject.Molecule.Model,
  279. params(a, ctx: PluginContext) {
  280. return ctx.customModelProperties.getParams(a?.data)
  281. },
  282. isApplicable(a, t, ctx: PluginContext) {
  283. return t.transformer !== CustomModelProperties;
  284. }
  285. })(({ ref, params }, ctx: PluginContext) => ctx.builders.structure.insertModelProperties(ref, params));
  286. export const EnableStructureCustomProps = StateAction.build({
  287. display: { name: 'Custom Structure Properties', description: 'Enable parameters for custom properties of the structure.' },
  288. from: PluginStateObject.Molecule.Structure,
  289. params(a, ctx: PluginContext) {
  290. return ctx.customStructureProperties.getParams(a?.data)
  291. },
  292. isApplicable(a, t, ctx: PluginContext) {
  293. return t.transformer !== CustomStructureProperties;
  294. }
  295. })(({ ref, params }, ctx: PluginContext) => ctx.builders.structure.insertStructureProperties(ref, params));
  296. export const AddTrajectory = StateAction.build({
  297. display: { name: 'Add Trajectory', description: 'Add trajectory from existing model/topology and coordinates.' },
  298. from: PluginStateObject.Root,
  299. params(a, ctx: PluginContext) {
  300. const state = ctx.state.data
  301. const models = [
  302. ...state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Model)),
  303. ...state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Topology)),
  304. ]
  305. const modelOptions = models.map(t => [t.transform.ref, t.obj!.label]) as [string, string][]
  306. const coords = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Coordinates))
  307. const coordOptions = coords.map(c => [c.transform.ref, c.obj!.label]) as [string, string][]
  308. return {
  309. model: PD.Select(modelOptions.length ? modelOptions[0][0] : '', modelOptions),
  310. coordinates: PD.Select(coordOptions.length ? coordOptions[0][0] : '', coordOptions)
  311. }
  312. }
  313. })(({ params, state }, ctx: PluginContext) => Task.create('Add Trajectory', taskCtx => {
  314. return state.transaction(async () => {
  315. const dependsOn = [params.model, params.coordinates];
  316. const model = state.build().toRoot()
  317. .apply(TrajectoryFromModelAndCoordinates, {
  318. modelRef: params.model,
  319. coordinatesRef: params.coordinates
  320. }, { dependsOn })
  321. .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
  322. await state.updateTree(model).runInContext(taskCtx);
  323. const structure = await ctx.builders.structure.createStructure(model.selector);
  324. await ctx.builders.structure.representation.applyPreset(structure, 'auto');
  325. }).runInContext(taskCtx)
  326. }));