diff --git a/src/apps/viewer/app.ts b/src/apps/viewer/app.ts index ed48e52f86ead319748dc63108a218f77fa0e2b7..30b586aec77d209dc57e8ca76493773713871870 100644 --- a/src/apps/viewer/app.ts +++ b/src/apps/viewer/app.ts @@ -24,7 +24,8 @@ import { DownloadDensity } from '../../mol-plugin-state/actions/volume'; import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset'; import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset'; import { DataFormatProvider } from '../../mol-plugin-state/formats/provider'; -import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure'; +import { BuiltInTopologyFormat } from '../../mol-plugin-state/formats/topology'; +import { BuiltInCoordinatesFormat } from '../../mol-plugin-state/formats/coordinates'; import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory'; import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume'; import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params'; @@ -455,11 +456,11 @@ export interface VolumeIsovalueInfo { export interface LoadTrajectoryParams { model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean } | { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ } - | { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean } - | { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat }, + | { kind: 'topology-url', url: string, format: BuiltInTopologyFormat, isBinary?: boolean } + | { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuiltInTopologyFormat }, modelLabel?: string, - coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean } - | { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat }, + coordinates: { kind: 'coordinates-url', url: string, format: BuiltInCoordinatesFormat, isBinary?: boolean } + | { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuiltInCoordinatesFormat }, coordinatesLabel?: string, preset?: keyof PresetTrajectoryHierarchy } diff --git a/src/extensions/zenodo/ui.tsx b/src/extensions/zenodo/ui.tsx index 11954681d803b12857e6633d200ffc1dda959e21..7a4ad4a1620d55ed9828c55301f346d2ee8d5403 100644 --- a/src/extensions/zenodo/ui.tsx +++ b/src/extensions/zenodo/ui.tsx @@ -7,6 +7,8 @@ import { DownloadFile } from '../../mol-plugin-state/actions/file'; import { DownloadStructure, LoadTrajectory } from '../../mol-plugin-state/actions/structure'; import { DownloadDensity } from '../../mol-plugin-state/actions/volume'; +import { CoordinatesFormatCategory } from '../../mol-plugin-state/formats/coordinates'; +import { TopologyFormatCategory } from '../../mol-plugin-state/formats/topology'; import { TrajectoryFormatCategory } from '../../mol-plugin-state/formats/trajectory'; import { VolumeFormatCategory } from '../../mol-plugin-state/formats/volume'; import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base'; @@ -64,7 +66,10 @@ function createImportParams(files: ZenodoFile[], plugin: PluginContext) { const compressedOpts: [string, string][] = []; const structureExts = new Map<string, { format: string, isBinary: boolean }>(); + const coordinatesExts = new Map<string, { format: string, isBinary: boolean }>(); + const topologyExts = new Map<string, { format: string, isBinary: boolean }>(); const volumeExts = new Map<string, { format: string, isBinary: boolean }>(); + for (const { provider: { category, binaryExtensions, stringExtensions }, name } of plugin.dataFormats.list) { if (category === TrajectoryFormatCategory) { if (binaryExtensions) for (const e of binaryExtensions) structureExts.set(e, { format: name, isBinary: true }); @@ -72,6 +77,12 @@ function createImportParams(files: ZenodoFile[], plugin: PluginContext) { } else if (category === VolumeFormatCategory) { if (binaryExtensions) for (const e of binaryExtensions) volumeExts.set(e, { format: name, isBinary: true }); if (stringExtensions) for (const e of stringExtensions) volumeExts.set(e, { format: name, isBinary: false }); + } else if (category === CoordinatesFormatCategory) { + if (binaryExtensions) for (const e of binaryExtensions) coordinatesExts.set(e, { format: name, isBinary: true }); + if (stringExtensions) for (const e of stringExtensions) coordinatesExts.set(e, { format: name, isBinary: false }); + } else if (category === TopologyFormatCategory) { + if (binaryExtensions) for (const e of binaryExtensions) topologyExts.set(e, { format: name, isBinary: true }); + if (stringExtensions) for (const e of stringExtensions) topologyExts.set(e, { format: name, isBinary: false }); } } @@ -84,10 +95,12 @@ function createImportParams(files: ZenodoFile[], plugin: PluginContext) { } else if (volumeExts.has(file.type)) { const { format, isBinary } = volumeExts.get(file.type)!; volumeOpts.push([`${file.links.self}|${format}|${isBinary}`, label]); - } else if (file.type === 'psf') { - topologyOpts.push([`${file.links.self}|${file.type}|false`, label]); - } else if (file.type === 'xtc' || file.type === 'dcd') { - coordinatesOpts.push([`${file.links.self}|${file.type}|true`, label]); + } else if (topologyExts.has(file.type)) { + const { format, isBinary } = topologyExts.get(file.type)!; + topologyOpts.push([`${file.links.self}|${format}|${isBinary}`, label]); + } else if (coordinatesExts.has(file.type)) { + const { format, isBinary } = coordinatesExts.get(file.type)!; + coordinatesOpts.push([`${file.links.self}|${format}|${isBinary}`, label]); } else if (file.type === 'zip') { compressedOpts.push([`${file.links.self}|${file.type}|true`, label]); } diff --git a/src/mol-plugin-state/actions/file.ts b/src/mol-plugin-state/actions/file.ts index d0122372843f9d7bd85c350b2c59d64473c08dc4..0f92b915b180d8626daf8cab3a1e460bfc19965a 100644 --- a/src/mol-plugin-state/actions/file.ts +++ b/src/mol-plugin-state/actions/file.ts @@ -22,7 +22,7 @@ async function processFile(file: Asset.File, plugin: PluginContext, format: stri : plugin.dataFormats.get(format); if (!provider) { - plugin.log.warn(`OpenFiles: could not find data provider for '${info.name}.${info.ext}'`); + plugin.log.warn(`OpenFiles: could not find data provider for '${info.ext}'`); await plugin.state.data.build().delete(data).commit(); return; } diff --git a/src/mol-plugin-state/actions/structure.ts b/src/mol-plugin-state/actions/structure.ts index 035185fc9b88f9c9189f310d0b401fae13e792f3..630c5d0cdf9f706e3092dd585b217cbb5e29caaa 100644 --- a/src/mol-plugin-state/actions/structure.ts +++ b/src/mol-plugin-state/actions/structure.ts @@ -20,6 +20,8 @@ import { Asset } from '../../mol-util/assets'; import { PluginConfig } from '../../mol-plugin/config'; import { getFileInfo } from '../../mol-util/file-info'; import { assertUnreachable } from '../../mol-util/type-helpers'; +import { TopologyFormatCategory } from '../formats/topology'; +import { CoordinatesFormatCategory } from '../formats/coordinates'; const DownloadModelRepresentationOptions = (plugin: PluginContext) => { const representationDefault = plugin.config.get(PluginConfig.Structure.DefaultRepresentationPreset) || PresetStructureRepresentations.auto.id; @@ -318,14 +320,18 @@ export const LoadTrajectory = StateAction.build({ 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); + const modelOptions = options.filter(o => o[2] === TrajectoryFormatCategory || o[2] === TopologyFormatCategory); + const coordinatesOptions = options.filter(o => o[2] === CoordinatesFormatCategory); + + const modelExts: string[] = []; + const coordinatesExts: string[] = []; + for (const { provider } of ctx.dataFormats.list) { + if (provider.category === TrajectoryFormatCategory || provider.category === TopologyFormatCategory) { + if (provider.binaryExtensions) modelExts.push(...provider.binaryExtensions); + if (provider.stringExtensions) modelExts.push(...provider.stringExtensions); + } else if (provider.category === CoordinatesFormatCategory) { + if (provider.binaryExtensions) coordinatesExts.push(...provider.binaryExtensions); + if (provider.stringExtensions) coordinatesExts.push(...provider.stringExtensions); } } @@ -343,8 +349,8 @@ export const LoadTrajectory = StateAction.build({ }, { 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' }), + model: PD.File({ accept: modelExts.map(e => `.${e}`).join(','), label: 'Model' }), + coordinates: PD.File({ accept: coordinatesExts.map(e => `.${e}`).join(','), label: 'Coordinates' }), }, { isFlat: true }), }, { options: [['url', 'URL'], ['file', 'File']] }) }; @@ -384,7 +390,7 @@ export const LoadTrajectory = StateAction.build({ 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}'`); + ctx.log.warn(`LoadTrajectory: could not find data provider for '${info.ext}'`); await ctx.state.data.build().delete(data).commit(); return; } diff --git a/src/mol-plugin-state/formats/structure.ts b/src/mol-plugin-state/formats/coordinates.ts similarity index 56% rename from src/mol-plugin-state/formats/structure.ts rename to src/mol-plugin-state/formats/coordinates.ts index 7ec62dc06aecccc762cd6b00c3e1e23e1b57ac3a..a3b4cc7398646e179afd1dd73850b93954599fa7 100644 --- a/src/mol-plugin-state/formats/structure.ts +++ b/src/mol-plugin-state/formats/coordinates.ts @@ -8,38 +8,13 @@ import { StateTransforms } from '../transforms'; import { DataFormatProvider } from './provider'; -export const StructureFormatCategory = 'Structure'; - -// - -export { PsfProvider }; -const PsfProvider = DataFormatProvider({ - label: 'PSF', - description: 'PSF', - category: StructureFormatCategory, - stringExtensions: ['psf'], - parse: async (plugin, data) => { - const format = plugin.state.data.build() - .to(data) - .apply(StateTransforms.Data.ParsePsf, {}, { state: { isGhost: true } }); - const topology = format.apply(StateTransforms.Model.TopologyFromPsf); - - await format.commit(); - - return { format: format.selector, topology: topology.selector }; - } -}); -type PsfProvider = typeof PsfProvider; - -export type TopologyProvider = PsfProvider; - -// +export const CoordinatesFormatCategory = 'Coordinates'; export { DcdProvider }; const DcdProvider = DataFormatProvider({ label: 'DCD', description: 'DCD', - category: StructureFormatCategory, + category: CoordinatesFormatCategory, binaryExtensions: ['dcd'], parse: (plugin, data) => { const coordinates = plugin.state.data.build() @@ -55,7 +30,7 @@ export { XtcProvider }; const XtcProvider = DataFormatProvider({ label: 'XTC', description: 'XTC', - category: StructureFormatCategory, + category: CoordinatesFormatCategory, binaryExtensions: ['xtc'], parse: (plugin, data) => { const coordinates = plugin.state.data.build() @@ -69,12 +44,9 @@ type XtcProvider = typeof XtcProvider; export type CoordinatesProvider = DcdProvider | XtcProvider; -// - -export const BuiltInStructureFormats = [ - ['psf', PsfProvider] as const, +export const BuiltInCoordinatesFormats = [ ['dcd', DcdProvider] as const, ['xtc', XtcProvider] as const, ] as const; -export type BuildInStructureFormat = (typeof BuiltInStructureFormats)[number][0] \ No newline at end of file +export type BuiltInCoordinatesFormat = (typeof BuiltInCoordinatesFormats)[number][0] diff --git a/src/mol-plugin-state/formats/registry.ts b/src/mol-plugin-state/formats/registry.ts index d4385d4dfbc9787d29d68ea89aa966c6de6c614b..b39f428de31a9433642b5600dbe0129d990c6d23 100644 --- a/src/mol-plugin-state/formats/registry.ts +++ b/src/mol-plugin-state/formats/registry.ts @@ -11,7 +11,8 @@ import { DataFormatProvider } from './provider'; import { BuiltInTrajectoryFormats } from './trajectory'; import { BuiltInVolumeFormats } from './volume'; import { BuiltInShapeFormats } from './shape'; -import { BuiltInStructureFormats } from './structure'; +import { BuiltInTopologyFormats } from './topology'; +import { BuiltInCoordinatesFormats } from './coordinates'; export class DataFormatRegistry { private _list: { name: string, provider: DataFormatProvider }[] = []; @@ -53,7 +54,8 @@ export class DataFormatRegistry { constructor() { for (const [id, p] of BuiltInVolumeFormats) this.add(id, p); - for (const [id, p] of BuiltInStructureFormats) this.add(id, p); + for (const [id, p] of BuiltInTopologyFormats) this.add(id, p); + for (const [id, p] of BuiltInCoordinatesFormats) this.add(id, p); for (const [id, p] of BuiltInShapeFormats) this.add(id, p); for (const [id, p] of BuiltInTrajectoryFormats) this.add(id, p); }; diff --git a/src/mol-plugin-state/formats/topology.ts b/src/mol-plugin-state/formats/topology.ts new file mode 100644 index 0000000000000000000000000000000000000000..e23e868aa7903402d31c71cfbc3fcc757e030713 --- /dev/null +++ b/src/mol-plugin-state/formats/topology.ts @@ -0,0 +1,38 @@ +/** + * 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> + */ + +import { StateTransforms } from '../transforms'; +import { DataFormatProvider } from './provider'; + +export const TopologyFormatCategory = 'Topology'; + +export { PsfProvider }; +const PsfProvider = DataFormatProvider({ + label: 'PSF', + description: 'PSF', + category: TopologyFormatCategory, + stringExtensions: ['psf'], + parse: async (plugin, data) => { + const format = plugin.state.data.build() + .to(data) + .apply(StateTransforms.Data.ParsePsf, {}, { state: { isGhost: true } }); + const topology = format.apply(StateTransforms.Model.TopologyFromPsf); + + await format.commit(); + + return { format: format.selector, topology: topology.selector }; + } +}); +type PsfProvider = typeof PsfProvider; + +export type TopologyProvider = PsfProvider; + +export const BuiltInTopologyFormats = [ + ['psf', PsfProvider] as const, +] as const; + +export type BuiltInTopologyFormat = (typeof BuiltInTopologyFormats)[number][0] diff --git a/src/mol-plugin-state/transforms/model.ts b/src/mol-plugin-state/transforms/model.ts index 9cd34a05ba0c011493eecfab94aabac075e83eb4..d45ae7d4d5928342a26237e2a864b80b294e7ed6 100644 --- a/src/mol-plugin-state/transforms/model.ts +++ b/src/mol-plugin-state/transforms/model.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> @@ -42,10 +42,18 @@ import { trajectoryFromXyz } from '../../mol-model-formats/structure/xyz'; import { parseSdf } from '../../mol-io/reader/sdf/parser'; import { trajectoryFromSdf } from '../../mol-model-formats/structure/sdf'; import { assertUnreachable } from '../../mol-util/type-helpers'; +import { parseTrr } from '../../mol-io/reader/trr/parser'; +import { coordinatesFromTrr } from '../../mol-model-formats/structure/trr'; +import { parseNctraj } from '../../mol-io/reader/nctraj/parser'; +import { coordinatesFromNctraj } from '../../mol-model-formats/structure/nctraj'; +import { topologyFromPrmtop } from '../../mol-model-formats/structure/prmtop'; export { CoordinatesFromDcd }; export { CoordinatesFromXtc }; +export { CoordinatesFromTrr }; +export { CoordinatesFromNctraj }; export { TopologyFromPsf }; +export { TopologyFromPrmtop }; export { TrajectoryFromModelAndCoordinates }; export { TrajectoryFromBlob }; export { TrajectoryFromMmCif }; @@ -88,7 +96,7 @@ const CoordinatesFromDcd = PluginStateTransform.BuiltIn({ } }); -type CoordinatesFromXtc = typeof CoordinatesFromDcd +type CoordinatesFromXtc = typeof CoordinatesFromXtc const CoordinatesFromXtc = PluginStateTransform.BuiltIn({ name: 'coordinates-from-xtc', display: { name: 'Parse XTC', description: 'Parse XTC binary data.' }, @@ -105,10 +113,44 @@ const CoordinatesFromXtc = PluginStateTransform.BuiltIn({ } }); +type CoordinatesFromTrr = typeof CoordinatesFromTrr +const CoordinatesFromTrr = PluginStateTransform.BuiltIn({ + name: 'coordinates-from-trr', + display: { name: 'Parse TRR', description: 'Parse TRR binary data.' }, + from: [SO.Data.Binary], + to: SO.Molecule.Coordinates +})({ + apply({ a }) { + return Task.create('Parse TRR', async ctx => { + const parsed = await parseTrr(a.data).runInContext(ctx); + if (parsed.isError) throw new Error(parsed.message); + const coordinates = await coordinatesFromTrr(parsed.result).runInContext(ctx); + return new SO.Molecule.Coordinates(coordinates, { label: a.label, description: 'Coordinates' }); + }); + } +}); + +type CoordinatesFromNctraj = typeof CoordinatesFromNctraj +const CoordinatesFromNctraj = PluginStateTransform.BuiltIn({ + name: 'coordinates-from-nctraj', + display: { name: 'Parse NCTRAJ', description: 'Parse NCTRAJ binary data.' }, + from: [SO.Data.Binary], + to: SO.Molecule.Coordinates +})({ + apply({ a }) { + return Task.create('Parse NCTRAJ', async ctx => { + const parsed = await parseNctraj(a.data).runInContext(ctx); + if (parsed.isError) throw new Error(parsed.message); + const coordinates = await coordinatesFromNctraj(parsed.result).runInContext(ctx); + return new SO.Molecule.Coordinates(coordinates, { label: a.label, description: 'Coordinates' }); + }); + } +}); + type TopologyFromPsf = typeof TopologyFromPsf const TopologyFromPsf = PluginStateTransform.BuiltIn({ name: 'topology-from-psf', - display: { name: 'PSF Topology', description: 'Parse PSF string data.' }, + display: { name: 'PSF Topology', description: 'Create topology from PSF.' }, from: [SO.Format.Psf], to: SO.Molecule.Topology })({ @@ -120,6 +162,21 @@ const TopologyFromPsf = PluginStateTransform.BuiltIn({ } }); +type TopologyFromPrmtop = typeof TopologyFromPrmtop +const TopologyFromPrmtop = PluginStateTransform.BuiltIn({ + name: 'topology-from-prmtop', + display: { name: 'PRMTOP Topology', description: 'Create topology from PRMTOP.' }, + from: [SO.Format.Prmtop], + to: SO.Molecule.Topology +})({ + apply({ a }) { + return Task.create('Create Topology', async ctx => { + const topology = await topologyFromPrmtop(a.data).runInContext(ctx); + return new SO.Molecule.Topology(topology, { label: topology.label || a.label, description: 'Topology' }); + }); + } +}); + async function getTrajectory(ctx: RuntimeContext, obj: StateObject, coordinates: Coordinates) { if (obj.type === SO.Molecule.Topology.type) { const topology = obj.data as Topology;