diff --git a/CHANGELOG.md b/CHANGELOG.md index cf1cf6f6e57212f6c45cf10cd5af7e2d216e060a..0ad012ff055382bca558992c62db278128e636e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Note that since we don't clearly distinguish between a public and private interf ## [Unreleased] - Fix handling of mmcif with empty ``label_*`` fields +- Add LoadTrajectory action ## [v3.3.1] - 2022-02-27 diff --git a/src/mol-plugin-state/actions/structure.ts b/src/mol-plugin-state/actions/structure.ts index 6bdd89273e92c5346ecfaf5d8a10299285079ee4..171824c15a2f69e6f7d9b66b4df19d1954e2bb93 100644 --- a/src/mol-plugin-state/actions/structure.ts +++ b/src/mol-plugin-state/actions/structure.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -10,12 +10,12 @@ import { StateAction, StateSelection, StateTransformer } from '../../mol-state'; import { Task } from '../../mol-task'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../builder/structure/representation-preset'; -import { BuiltInTrajectoryFormat, BuiltInTrajectoryFormats } from '../formats/trajectory'; +import { BuiltInTrajectoryFormat, BuiltInTrajectoryFormats, TrajectoryFormatCategory } from '../formats/trajectory'; import { RootStructureDefinition } from '../helpers/root-structure'; import { PluginStateObject } from '../objects'; import { StateTransforms } from '../transforms'; import { Download } from '../transforms/data'; -import { CustomModelProperties, CustomStructureProperties, TrajectoryFromModelAndCoordinates } from '../transforms/model'; +import { CustomModelProperties, CustomStructureProperties, ModelFromTrajectory, TrajectoryFromModelAndCoordinates } from '../transforms/model'; import { Asset } from '../../mol-util/assets'; import { PluginConfig } from '../../mol-plugin/config'; import { getFileInfo } from '../../mol-util/file-info'; @@ -311,4 +311,122 @@ export const AddTrajectory = StateAction.build({ const structure = await ctx.builders.structure.createStructure(model.selector); await ctx.builders.structure.representation.applyPreset(structure, 'auto'); }).runInContext(taskCtx); +})); + +export const LoadTrajectory = StateAction.build({ + display: { name: 'Load Trajectory', description: 'Load trajectory of model/topology and coordinates from URL or file.' }, + from: PluginStateObject.Root, + params(a, ctx: PluginContext) { + const { options } = ctx.dataFormats; + const modelOptions = options.filter(o => o[2] === TrajectoryFormatCategory || o[0] === 'psf'); + const coordinatesOptions = options.filter(o => o[0] === 'dcd' || o[0] === 'xtc'); + + const modelExtensions: string[] = []; + for (const { name, provider } of ctx.dataFormats.list) { + if (provider.category === TrajectoryFormatCategory || name === 'psf') { + if (provider.binaryExtensions) modelExtensions.push(...provider.binaryExtensions); + if (provider.stringExtensions) modelExtensions.push(...provider.stringExtensions); + } + } + + return { + source: PD.MappedStatic('file', { + url: PD.Group({ + model: PD.Group({ + url: PD.Url(''), + format: PD.Select(modelOptions[0][0], modelOptions), + isBinary: PD.Boolean(false), + }, { isExpanded: true }), + coordinates: PD.Group({ + url: PD.Url(''), + format: PD.Select(coordinatesOptions[0][0], coordinatesOptions), + }, { isExpanded: true }) + }, { isFlat: true }), + file: PD.Group({ + model: PD.File({ accept: modelExtensions.map(e => `.${e}`).join(','), label: 'Model' }), + coordinates: PD.File({ accept: '.dcd,.xtc', label: 'Coordinates' }), + }, { isFlat: true }), + }, { options: [['url', 'URL'], ['file', 'File']] }) + }; + } +})(({ params, state }, ctx: PluginContext) => Task.create('Load Trajectory', taskCtx => { + return state.transaction(async () => { + const s = params.source; + + if (s.name === 'file' && (s.params.model === null || s.params.coordinates === null)) { + ctx.log.error('No file(s) selected'); + return; + } + + if (s.name === 'url' && (!s.params.model || !s.params.coordinates)) { + ctx.log.error('No URL(s) given'); + return; + } + + const processUrl = async (url: string | Asset.Url, format: string, isBinary: boolean) => { + const data = await ctx.builders.data.download({ url, isBinary }); + const provider = ctx.dataFormats.get(format); + + if (!provider) { + ctx.log.warn(`LoadTrajectory: could not find data provider for '${format}'`); + return; + } + + return provider.parse(ctx, data); + }; + + const processFile = async (file: Asset.File | null) => { + if (!file) throw new Error('No file selected'); + + const info = getFileInfo(file.file!); + const isBinary = ctx.dataFormats.binaryExtensions.has(info.ext); + const { data } = await ctx.builders.data.readFile({ file, isBinary }); + const provider = ctx.dataFormats.auto(info, data.cell?.obj!); + + if (!provider) { + ctx.log.warn(`LoadTrajectory: could not find data provider for '${info.name}.${info.ext}'`); + return; + } + + return provider.parse(ctx, data); + }; + + try { + const modelParsed = s.name === 'url' + ? await processUrl(s.params.model.url, s.params.model.format, s.params.model.isBinary) + : await processFile(s.params.model); + + let model; + if ('trajectory' in modelParsed) { + model = await state.build().to(modelParsed.trajectory) + .apply(ModelFromTrajectory, { modelIndex: 0 }) + .commit(); + } else { + model = modelParsed.topology; + } + + // + + const coordinates = s.name === 'url' + ? await processUrl(s.params.coordinates.url, s.params.coordinates.format, true) + : await processFile(s.params.coordinates); + + // + + const dependsOn = [model.ref, coordinates.ref]; + const traj = state.build().toRoot() + .apply(TrajectoryFromModelAndCoordinates, { + modelRef: model.ref, + coordinatesRef: coordinates.ref + }, { dependsOn }) + .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }); + + await state.updateTree(traj).runInContext(taskCtx); + const structure = await ctx.builders.structure.createStructure(traj.selector); + await ctx.builders.structure.representation.applyPreset(structure, 'auto'); + } catch (e) { + console.error(e); + ctx.log.error(`Error loading trajectory`); + } + }).runInContext(taskCtx); })); \ No newline at end of file diff --git a/src/mol-plugin-state/formats/registry.ts b/src/mol-plugin-state/formats/registry.ts index 45e328dace350682327c4ddc641800970a8bed98..d4385d4dfbc9787d29d68ea89aa966c6de6c614b 100644 --- a/src/mol-plugin-state/formats/registry.ts +++ b/src/mol-plugin-state/formats/registry.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> @@ -18,10 +18,10 @@ export class DataFormatRegistry { private _map = new Map<string, DataFormatProvider>(); private _extensions: Set<string> | undefined = undefined; private _binaryExtensions: Set<string> | undefined = undefined; - private _options: [string, string, string][] | undefined = undefined; + private _options: [name: string, label: string, category: string][] | undefined = undefined; - get types(): [string, string][] { - return this._list.map(e => [e.name, e.provider.label] as [string, string]); + get types(): [name: string, label: string][] { + return this._list.map(e => [e.name, e.provider.label] as [name: string, label: string]); } get extensions() { @@ -45,7 +45,7 @@ export class DataFormatRegistry { get options() { if (this._options) return this._options; - const options: [string, string, string][] = []; + const options: [name: string, label: string, category: string][] = []; this._list.forEach(({ name, provider }) => options.push([name, provider.label, provider.category || ''])); this._options = options; return options; diff --git a/src/mol-plugin-state/formats/structure.ts b/src/mol-plugin-state/formats/structure.ts index 567f6ebb2e378970f8d40877f7f17139ebfd5671..7ec62dc06aecccc762cd6b00c3e1e23e1b57ac3a 100644 --- a/src/mol-plugin-state/formats/structure.ts +++ b/src/mol-plugin-state/formats/structure.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -10,7 +10,10 @@ import { DataFormatProvider } from './provider'; export const StructureFormatCategory = 'Structure'; -export const PsfProvider = DataFormatProvider({ +// + +export { PsfProvider }; +const PsfProvider = DataFormatProvider({ label: 'PSF', description: 'PSF', category: StructureFormatCategory, @@ -26,8 +29,14 @@ export const PsfProvider = DataFormatProvider({ return { format: format.selector, topology: topology.selector }; } }); +type PsfProvider = typeof PsfProvider; + +export type TopologyProvider = PsfProvider; + +// -export const DcdProvider = DataFormatProvider({ +export { DcdProvider }; +const DcdProvider = DataFormatProvider({ label: 'DCD', description: 'DCD', category: StructureFormatCategory, @@ -40,8 +49,10 @@ export const DcdProvider = DataFormatProvider({ return coordinates.commit(); } }); +type DcdProvider = typeof DcdProvider; -export const XtcProvider = DataFormatProvider({ +export { XtcProvider }; +const XtcProvider = DataFormatProvider({ label: 'XTC', description: 'XTC', category: StructureFormatCategory, @@ -54,6 +65,11 @@ export const XtcProvider = DataFormatProvider({ return coordinates.commit(); } }); +type XtcProvider = typeof XtcProvider; + +export type CoordinatesProvider = DcdProvider | XtcProvider; + +// export const BuiltInStructureFormats = [ ['psf', PsfProvider] as const, diff --git a/src/mol-plugin/spec.ts b/src/mol-plugin/spec.ts index b2a50f37bfa5c5c2f51cd31a9eb8f9deec6bd36b..48f3c3fabfcd02942150caf224fafe0b696e3e4b 100644 --- a/src/mol-plugin/spec.ts +++ b/src/mol-plugin/spec.ts @@ -64,10 +64,10 @@ namespace PluginSpec { export const DefaultPluginSpec = (): PluginSpec => ({ actions: [ PluginSpec.Action(StateActions.Structure.DownloadStructure), - PluginSpec.Action(StateActions.Structure.AddTrajectory), PluginSpec.Action(StateActions.Volume.DownloadDensity), PluginSpec.Action(StateActions.DataFormat.DownloadFile), PluginSpec.Action(StateActions.DataFormat.OpenFiles), + PluginSpec.Action(StateActions.Structure.LoadTrajectory), PluginSpec.Action(StateActions.Structure.EnableModelCustomProps), PluginSpec.Action(StateActions.Structure.EnableStructureCustomProps),