structure.ts 17 KB

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