diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index 0d943027e43235e281d90bb5a6a55059c6e7fcb0..1d817f77a1702d9afd94fbf2831e4f52da238624 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/src/mol-plugin/state/actions/structure.ts @@ -68,6 +68,11 @@ export const GroProvider: DataFormatProvider<any> = { // +const DownloadStructurePdbIdSourceOptions = PD.Group({ + supportProps: PD.makeOptional(PD.Boolean(false)), + asTrajectory: PD.makeOptional(PD.Boolean(false, { description: 'Load all entries into a single trajectory.' })) +}); + export { DownloadStructure }; type DownloadStructure = typeof DownloadStructure const DownloadStructure = StateAction.build({ @@ -77,21 +82,23 @@ const DownloadStructure = StateAction.build({ source: PD.MappedStatic('bcif-static', { 'pdbe-updated': PD.Group({ id: PD.Text('1cbs', { label: 'Id' }), - supportProps: PD.Boolean(false) + options: DownloadStructurePdbIdSourceOptions }, { isFlat: true }), 'rcsb': PD.Group({ id: PD.Text('1tqn', { label: 'Id' }), - supportProps: PD.Boolean(false) + options: DownloadStructurePdbIdSourceOptions }, { isFlat: true }), 'bcif-static': PD.Group({ id: PD.Text('1tqn', { label: 'Id' }), - supportProps: PD.Boolean(false) + options: DownloadStructurePdbIdSourceOptions }, { isFlat: true }), 'url': PD.Group({ url: PD.Text(''), format: PD.Select('cif', [['cif', 'CIF'], ['pdb', 'PDB']]), isBinary: PD.Boolean(false), - supportProps: PD.Boolean(false) + options: PD.Group({ + supportProps: PD.makeOptional(PD.Boolean(false)) + }) }, { isFlat: true }) }, { options: [ @@ -106,27 +113,40 @@ const DownloadStructure = StateAction.build({ const b = state.build(); const src = params.source; let downloadParams: StateTransformer.Params<Download>[]; + let supportProps = false, asTrajectory = false; switch (src.name) { case 'url': downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary }]; + supportProps = !!src.params.options.supportProps; break; case 'pdbe-updated': downloadParams = getDownloadParams(src.params.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id}`, false); + supportProps = !!src.params.options.supportProps; + asTrajectory = !!src.params.options.asTrajectory; break; case 'rcsb': downloadParams = getDownloadParams(src.params.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB: ${id}`, false); + supportProps = !!src.params.options.supportProps; + asTrajectory = !!src.params.options.asTrajectory; break; case 'bcif-static': downloadParams = getDownloadParams(src.params.id, id => `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${id.toLowerCase()}`, id => `BinaryCIF: ${id}`, true); + supportProps = !!src.params.options.supportProps; + asTrajectory = !!src.params.options.asTrajectory; break; default: throw new Error(`${(src as any).name} not supported.`); } - for (const download of downloadParams) { - const data = b.toRoot().apply(StateTransforms.Data.Download, download, { props: { isGhost: true }}); - const traj = createModelTree(data, src.name === 'url' ? src.params.format : 'cif'); - createStructureTree(ctx, traj, params.source.params.supportProps) + if (downloadParams.length > 0 && asTrajectory) { + const traj = createSingleTrajectoryModel(downloadParams, b); + createStructureTree(ctx, traj, supportProps); + } else { + for (const download of downloadParams) { + const data = b.toRoot().apply(StateTransforms.Data.Download, download, { props: { isGhost: true }}); + const traj = createModelTree(data, src.name === 'url' ? src.params.format : 'cif'); + createStructureTree(ctx, traj, supportProps) + } } return state.updateTree(b); }); @@ -140,6 +160,18 @@ function getDownloadParams(src: string, url: (id: string) => string, label: (id: return ret; } +function createSingleTrajectoryModel(sources: StateTransformer.Params<Download>[], b: StateBuilder.Root) { + return b.toRoot() + .apply(StateTransforms.Data.DownloadBlob, { + sources: sources.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })), + maxConcurrency: 6 + }).apply(StateTransforms.Data.ParseBlob, { + formats: sources.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' })) + }) + .apply(StateTransforms.Model.TrajectoryFromBlob) + .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }); +} + function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: 'pdb' | 'cif' | 'gro' = 'cif') { let parsed: StateBuilder.To<PluginStateObject.Molecule.Trajectory> switch (format) { diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index 1acce6c45dcd864bea6956fcb7853a10dbe2eb2f..bd3561172fca5bf2aab01f77ce4ca469bba19375 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -159,8 +159,8 @@ export namespace ParamDefinition { isExpanded?: boolean, isFlat?: boolean } - export function Group<P extends Params>(params: P, info?: Info & { isExpanded?: boolean, isFlat?: boolean }): Group<Values<P>> { - const ret = setInfo<Group<Values<P>>>({ type: 'group', defaultValue: getDefaultValues(params) as any, params }, info); + export function Group<T>(params: For<T>, info?: Info & { isExpanded?: boolean, isFlat?: boolean }): Group<Normalize<T>> { + const ret = setInfo<Group<Normalize<T>>>({ type: 'group', defaultValue: getDefaultValues(params as any as Params) as any, params: params as any as Params }, info); if (info && info.isExpanded) ret.isExpanded = info.isExpanded; if (info && info.isFlat) ret.isFlat = info.isFlat; return ret;