diff --git a/src/mol-model-formats/structure/gro.ts b/src/mol-model-formats/structure/gro.ts index 905e171de0d80688a27871d82c627031dfbbe94d..8f141ea1c98305df18e3b54a5dd6ecf64a5b027a 100644 --- a/src/mol-model-formats/structure/gro.ts +++ b/src/mol-model-formats/structure/gro.ts @@ -105,7 +105,7 @@ function getCategories(atoms: GroAtoms) { } } -async function groToMmCif(gro: GroFile) { +function groToMmCif(gro: GroFile) { const categories = getCategories(gro.structures[0].atoms) return { @@ -118,7 +118,7 @@ async function groToMmCif(gro: GroFile) { export function trajectoryFromGRO(gro: GroFile): Task<Model.Trajectory> { return Task.create('Parse GRO', async ctx => { await ctx.update('Converting to mmCIF'); - const cif = await groToMmCif(gro); + const cif = groToMmCif(gro); const format = ModelFormat.mmCIF(cif); return _parse_mmCif(format, ctx); }) diff --git a/src/mol-model-formats/structure/mmcif/atomic.ts b/src/mol-model-formats/structure/mmcif/atomic.ts index 9cedc40191ad24a32cfd38501ab59d25f07aea29..5277a095976c4c7e9327dd7e17d733a304c657b7 100644 --- a/src/mol-model-formats/structure/mmcif/atomic.ts +++ b/src/mol-model-formats/structure/mmcif/atomic.ts @@ -97,7 +97,7 @@ function isHierarchyDataEqual(a: AtomicData, b: AtomicData) { && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>) } -export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model) { +function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model) { const hierarchyOffsets = findHierarchyOffsets(atom_site); const hierarchyData = createHierarchyData(atom_site, sourceIndex, hierarchyOffsets); @@ -105,12 +105,9 @@ export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceInd return { sameAsPrevious: true, hierarchy: previous.atomicHierarchy, - conformation: getConformation(atom_site) }; } - const conformation = getConformation(atom_site) - const hierarchySegments: AtomicSegments = { residueAtomSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, Interval.ofBounds(0, atom_site._rowCount)), chainAtomSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, Interval.ofBounds(0, atom_site._rowCount)), @@ -119,5 +116,11 @@ export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceInd const index = getAtomicIndex(hierarchyData, entities, hierarchySegments); const derived = getAtomicDerivedData(hierarchyData, index, formatData.chemicalComponentMap); const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived }; - return { sameAsPrevious: false, hierarchy, conformation }; + return { sameAsPrevious: false, hierarchy }; +} + +export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model) { + const { sameAsPrevious, hierarchy } = getAtomicHierarchy(atom_site, sourceIndex, entities, formatData, previous) + const conformation = getConformation(atom_site) + return { sameAsPrevious, hierarchy, conformation }; } \ No newline at end of file diff --git a/src/mol-model-formats/structure/psf.ts b/src/mol-model-formats/structure/psf.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab4eae3e12a7c9c2a7d441f554fbe9d9b80dc59d --- /dev/null +++ b/src/mol-model-formats/structure/psf.ts @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { PsfFile } from '../../mol-io/reader/psf/parser'; +import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif'; +import { Column } from '../../mol-data/db'; +import { EntityBuilder } from './common/entity'; +import { ComponentBuilder } from './common/component'; +import { CifCategory, CifField } from '../../mol-io/reader/cif'; +import { guessElementSymbolString } from './util'; +import { MoleculeType, getMoleculeType } from '../../mol-model/structure/model/types'; +import { getChainId } from './common/util'; +import { Task } from '../../mol-task'; +import { ModelFormat } from './format'; +import { Topology } from '../../mol-model/structure/topology/topology'; + +// TODO: shares most of the code with ./gro.ts#getCategories +function getCategories(atoms: PsfFile['atoms']) { + const auth_atom_id = CifField.ofColumn(atoms.atomName) + const auth_comp_id = CifField.ofColumn(atoms.residueName) + + const entityIds = new Array<string>(atoms.count) + const asymIds = new Array<string>(atoms.count) + const seqIds = new Uint32Array(atoms.count) + const ids = new Uint32Array(atoms.count) + + const entityBuilder = new EntityBuilder() + const componentBuilder = new ComponentBuilder(atoms.residueId, atoms.atomName) + + let currentEntityId = '' + let currentAsymIndex = 0 + let currentAsymId = '' + let currentSeqId = 0 + let prevMoleculeType = MoleculeType.Unknown + let prevResidueNumber = -1 + + for (let i = 0, il = atoms.count; i < il; ++i) { + const residueNumber = atoms.residueId.value(i) + if (residueNumber !== prevResidueNumber) { + const compId = atoms.residueName.value(i) + const moleculeType = getMoleculeType(componentBuilder.add(compId, i).type, compId) + + if (moleculeType !== prevMoleculeType || residueNumber !== prevResidueNumber + 1) { + currentAsymId = getChainId(currentAsymIndex) + currentAsymIndex += 1 + currentSeqId = 0 + } + + currentEntityId = entityBuilder.getEntityId(compId, moleculeType, currentAsymId) + currentSeqId += 1 + + prevResidueNumber = residueNumber + prevMoleculeType = moleculeType + } + + entityIds[i] = currentEntityId + asymIds[i] = currentAsymId + seqIds[i] = currentSeqId + ids[i] = i + } + + const auth_asym_id = CifField.ofColumn(Column.ofStringArray(asymIds)) + + const atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = { + auth_asym_id, + auth_atom_id, + auth_comp_id, + auth_seq_id: CifField.ofColumn(atoms.residueId), + B_iso_or_equiv: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)), + Cartn_x: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)), + Cartn_y: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)), + Cartn_z: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)), + group_PDB: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)), + id: CifField.ofColumn(Column.ofIntArray(ids)), + + label_alt_id: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)), + + label_asym_id: auth_asym_id, + label_atom_id: auth_atom_id, + label_comp_id: auth_comp_id, + label_seq_id: CifField.ofColumn(Column.ofIntArray(seqIds)), + label_entity_id: CifField.ofColumn(Column.ofStringArray(entityIds)), + + occupancy: CifField.ofColumn(Column.ofConst(1, atoms.count, Column.Schema.float)), + type_symbol: CifField.ofStrings(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))), + + pdbx_PDB_ins_code: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)), + pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst('1', atoms.count, Column.Schema.str)), + } + + return { + entity: entityBuilder.getEntityCategory(), + chem_comp: componentBuilder.getChemCompCategory(), + atom_site: CifCategory.ofFields('atom_site', atom_site) + } +} + +function psfToMmCif(psf: PsfFile) { + const categories = getCategories(psf.atoms) + + return { + header: psf.id, + categoryNames: Object.keys(categories), + categories + }; +} + +export function topologyFromPsf(psf: PsfFile): Task<Topology> { + return Task.create('Parse PSF', async ctx => { + const label = psf.id + const cif = psfToMmCif(psf); + const format = ModelFormat.mmCIF(cif); + + const { atomIdA, atomIdB } = psf.bonds + + const bonds = { + indexA: Column.ofLambda({ + value: (row: number) => atomIdA.value(row) - 1, + rowCount: atomIdA.rowCount, + schema: atomIdA.schema, + }), + indexB: Column.ofLambda({ + value: (row: number) => atomIdB.value(row) - 1, + rowCount: atomIdB.rowCount, + schema: atomIdB.schema, + }), + order: Column.ofConst(1, psf.bonds.count, Column.Schema.int) + } + + return Topology.create(label, format, bonds) + }) +} \ No newline at end of file diff --git a/src/mol-model/structure.ts b/src/mol-model/structure.ts index 6c6ed3a16d82460f3c83f537e285074d70edbb96..aa63b127e6b1a03c13fe1b72e7f12d9ec5c38f51 100644 --- a/src/mol-model/structure.ts +++ b/src/mol-model/structure.ts @@ -1,10 +1,12 @@ /** - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2019 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> */ export * from './structure/coordinates' +export * from './structure/topology' export * from './structure/model' export * from './structure/structure' export * from './structure/query' diff --git a/src/mol-model/structure/coordinates/coordinates.ts b/src/mol-model/structure/coordinates/coordinates.ts index a590fe1f839d41e68756fe365bfd7cee3b16af78..6ffd44df8f60dd92bd4de77676712f79837b9f4d 100644 --- a/src/mol-model/structure/coordinates/coordinates.ts +++ b/src/mol-model/structure/coordinates/coordinates.ts @@ -6,10 +6,7 @@ import { UUID } from '../../../mol-util'; import { Cell } from '../../../mol-math/geometry/spacegroup/cell'; -import { Model } from '../model'; import { AtomicConformation } from '../model/properties/atomic'; -import { CustomProperties } from '../../structure'; -import { Mutable } from '../../../mol-util/type-helpers'; import { Column } from '../../../mol-data/db'; export interface Frame { @@ -91,37 +88,17 @@ namespace Coordinates { timeOffset } } -} -function getAtomicConformation(frame: Frame, atomId: Column<number>): AtomicConformation { - return { - id: UUID.create22(), - atomId, - occupancy: Column.ofConst(1, frame.elementCount, Column.Schema.int), - B_iso_or_equiv: Column.ofConst(0, frame.elementCount, Column.Schema.float), - x: frame.x, - y: frame.y, - z: frame.z, - } -} - -export function trajectoryFromModelAndCoordinates(model: Model, coordinates: Coordinates): Model.Trajectory { - const trajectory: Mutable<Model.Trajectory> = [] - const { frames } = coordinates - for (let i = 0, il = frames.length; i < il; ++i) { - const f = frames[i] - const m = { - ...model, + export function getAtomicConformation(frame: Frame, atomId: Column<number>): AtomicConformation { + return { id: UUID.create22(), - modelNum: i, - atomicConformation: getAtomicConformation(f, model.atomicConformation.atomId), - // TODO: add support for supplying sphere and gaussian coordinates in addition to atomic coordinates - // coarseConformation: coarse.conformation, - customProperties: new CustomProperties(), - _staticPropertyData: Object.create(null), - _dynamicPropertyData: Object.create(null) + atomId, + occupancy: Column.ofConst(1, frame.elementCount, Column.Schema.int), + B_iso_or_equiv: Column.ofConst(0, frame.elementCount, Column.Schema.float), + xyzDefined: true, + x: frame.x, + y: frame.y, + z: frame.z, } - trajectory.push(m) } - return trajectory } \ No newline at end of file diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index e7a247677c164cafeacab7a2085b295042d20c1f..74cd85af6bf0d1b6652f4e390b654cbef7c67fc2 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -17,6 +17,12 @@ import { SaccharideComponentMap } from '../structure/carbohydrates/constants'; import { ModelFormat } from '../../../mol-model-formats/structure/format'; import { calcModelCenter } from './util'; import { Vec3 } from '../../../mol-math/linear-algebra'; +import { Mutable } from '../../../mol-util/type-helpers'; +import { Coordinates } from '../coordinates'; +import { Topology } from '../topology'; +import { _parse_mmCif } from '../../../mol-model-formats/structure/mmcif/parser'; +import { Task } from '../../../mol-task'; +import { IndexPairBonds } from '../../../mol-model-formats/structure/mmcif/bonds/index-pair'; /** * Interface to the "source data" of the molecule. @@ -84,6 +90,40 @@ export namespace Model { // TODO: is this enough? export type Trajectory = ReadonlyArray<Model> + export function trajectoryFromModelAndCoordinates(model: Model, coordinates: Coordinates): Trajectory { + const trajectory: Mutable<Model.Trajectory> = [] + const { frames } = coordinates + for (let i = 0, il = frames.length; i < il; ++i) { + const f = frames[i] + const m = { + ...model, + id: UUID.create22(), + modelNum: i, + atomicConformation: Coordinates.getAtomicConformation(f, model.atomicConformation.atomId), + // TODO: add support for supplying sphere and gaussian coordinates in addition to atomic coordinates? + // coarseConformation: coarse.conformation, + customProperties: new CustomProperties(), + _staticPropertyData: Object.create(null), + _dynamicPropertyData: Object.create(null) + } + trajectory.push(m) + } + return trajectory + } + + export function trajectoryFromTopologyAndCoordinates(topology: Topology, coordinates: Coordinates): Task<Trajectory> { + return Task.create('Create Trajectory', async ctx => { + const model = (await _parse_mmCif(topology.format, ctx))[0]; + if (!model) throw new Error('found no model') + const trajectory = trajectoryFromModelAndCoordinates(model, coordinates) + const bondData = { pairs: topology.bonds, count: model.atomicHierarchy.atoms._rowCount } + for (const m of trajectory) { + IndexPairBonds.attachFromData(m, bondData) + } + return trajectory + }) + } + const CenterProp = '__Center__' export function getCenter(model: Model): Vec3 { if (model._dynamicPropertyData[CenterProp]) return model._dynamicPropertyData[CenterProp] diff --git a/src/mol-model/structure/topology.ts b/src/mol-model/structure/topology.ts new file mode 100644 index 0000000000000000000000000000000000000000..4fc8e81360f43f3a2c56d433b00f11fe61ceea7a --- /dev/null +++ b/src/mol-model/structure/topology.ts @@ -0,0 +1,7 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export * from './topology/topology' \ No newline at end of file diff --git a/src/mol-model/structure/topology/topology.ts b/src/mol-model/structure/topology/topology.ts new file mode 100644 index 0000000000000000000000000000000000000000..d5e6dbac27cde544ce95dc6784bbb880a2eeab67 --- /dev/null +++ b/src/mol-model/structure/topology/topology.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { UUID } from '../../../mol-util'; +import { Column } from '../../../mol-data/db'; +import { ModelFormat } from '../../../mol-model-formats/structure/format'; + +export { Topology } + +interface Topology { + readonly id: UUID + readonly label: string + + readonly format: ModelFormat + + readonly bonds: { + readonly indexA: Column<number>, + readonly indexB: Column<number> + readonly order: Column<number> + } + + // TODO + // readonly angles: { + // readonly indexA: Column<number> + // readonly indexB: Column<number> + // readonly indexC: Column<number> + // } + + // readonly dihedrals: { + // readonly indexA: Column<number> + // readonly indexB: Column<number> + // readonly indexC: Column<number> + // readonly indexD: Column<number> + // } + + // readonly impropers: { + // readonly indexA: Column<number> + // readonly indexB: Column<number> + // readonly indexC: Column<number> + // readonly indexD: Column<number> + // } + + // TODO: add forces for bonds/angles/dihedrals/impropers +} + +namespace Topology { + export function create(label: string, format: ModelFormat, bonds: Topology['bonds']): Topology { + return { + id: UUID.create22(), + label, + format, + bonds + } + } +} \ No newline at end of file diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 7b77eaebe11e4caad1030c7e27cebf0439b138ac..732cb6917626421f05703761eac5eb08539c10fe 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -22,7 +22,7 @@ import { VolumeStreamingCustomControls } from '../mol-plugin-ui/custom/volume'; export const DefaultPluginSpec: PluginSpec = { actions: [ PluginSpec.Action(StateActions.Structure.DownloadStructure), - PluginSpec.Action(StateActions.Structure.AddTrajectoryFromModelAndCoordinates), + PluginSpec.Action(StateActions.Structure.AddTrajectory), PluginSpec.Action(StateActions.Volume.DownloadDensity), PluginSpec.Action(StateActions.DataFormat.OpenFile), PluginSpec.Action(StateActions.Structure.Create3DRepresentationPreset), diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index a2991425fffa1c28bfc6c779cb3ad9e5a2e4ed59..d63c1a44b5db7571e89d4df7d6332ac042b0f008 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/src/mol-plugin/state/actions/structure.ts @@ -11,7 +11,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { PluginStateObject } from '../objects'; import { StateTransforms } from '../transforms'; import { Download, ParsePsf } from '../transforms/data'; -import { CustomModelProperties, StructureSelectionFromExpression, CustomStructureProperties, CoordinatesFromDcd, TrajectoryFromModelAndCoordinates } from '../transforms/model'; +import { CustomModelProperties, StructureSelectionFromExpression, CustomStructureProperties, CoordinatesFromDcd, TrajectoryFromModelAndCoordinates, TopologyFromPsf } from '../transforms/model'; import { DataFormatProvider, guessCifVariant, DataFormatBuilderOptions } from './data-format'; import { FileInfo } from '../../../mol-util/file-info'; import { Task } from '../../../mol-task'; @@ -96,7 +96,7 @@ export const PsfProvider: DataFormatProvider<any> = { }, getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.String>, options: DataFormatBuilderOptions, state: State) => { return Task.create('PSF default builder', async taskCtx => { - await state.updateTree(data.apply(ParsePsf)).runInContext(taskCtx) + await state.updateTree(data.apply(ParsePsf, {}, { state: { isGhost: true } }).apply(TopologyFromPsf)).runInContext(taskCtx) }) } } @@ -427,18 +427,21 @@ export const StructureFromSelection = StateAction.build({ return state.updateTree(root); }); -export const AddTrajectoryFromModelAndCoordinates = StateAction.build({ - display: { name: 'Add Trajectory', description: 'Add trajectory from existing model and coordinates.' }, +export const AddTrajectory = StateAction.build({ + display: { name: 'Add Trajectory', description: 'Add trajectory from existing model/topology and coordinates.' }, from: PluginStateObject.Root, params(a, ctx: PluginContext) { const state = ctx.state.dataState - const models = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Model)) - const modelOptions = models.map(m => [m.transform.ref, m.obj!.label]) as [string, string][] + const models = [ + ...state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Model)), + ...state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Topology)), + ] + const modelOptions = models.map(t => [t.transform.ref, t.obj!.label]) as [string, string][] const coords = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Coordinates)) - const coordsOptions = coords.map(c => [c.transform.ref, c.obj!.label]) as [string, string][] + const coordOptions = coords.map(c => [c.transform.ref, c.obj!.label]) as [string, string][] return { model: PD.Select(modelOptions.length ? modelOptions[0][0] : '', modelOptions), - coordinates: PD.Select(coordsOptions.length ? coordsOptions[0][0] : '', coordsOptions) + coordinates: PD.Select(coordOptions.length ? coordOptions[0][0] : '', coordOptions) } } })(({ ref, params, state }, ctx: PluginContext) => { diff --git a/src/mol-plugin/state/objects.ts b/src/mol-plugin/state/objects.ts index 9f32b3dea97f57efacbd532b2114462e088dd57c..63be226de7c302669de3bcf14de8164d229aaf13 100644 --- a/src/mol-plugin/state/objects.ts +++ b/src/mol-plugin/state/objects.ts @@ -8,6 +8,7 @@ import { CifFile } from '../../mol-io/reader/cif'; import { PlyFile } from '../../mol-io/reader/ply/schema'; import { Coordinates as _Coordinates } from '../../mol-model/structure'; +import { Topology as _Topology } from '../../mol-model/structure'; import { Model as _Model, Structure as _Structure, StructureElement } from '../../mol-model/structure'; import { VolumeData } from '../../mol-model/volume'; import { PluginBehavior } from '../../mol-plugin/behavior/behavior'; @@ -97,6 +98,7 @@ export namespace PluginStateObject { export namespace Molecule { export class Coordinates extends Create<_Coordinates>({ name: 'Coordinates', typeClass: 'Object' }) { } + export class Topology extends Create<_Topology>({ name: 'Topology', typeClass: 'Object' }) { } export class Model extends Create<_Model>({ name: 'Model', typeClass: 'Object' }) { } export class Trajectory extends Create<ReadonlyArray<_Model>>({ name: 'Trajectory', typeClass: 'Object' }) { } export class Structure extends Create<_Structure>({ name: 'Structure', typeClass: 'Object' }) { } diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index cd86bd9bcb8a9d5daf41cc5742e3861d6278fb3b..5b449c21ad7fdbc966ad573c3ebafdf02f1d615c 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -9,7 +9,7 @@ import { parsePDB } from '../../../mol-io/reader/pdb/parser'; import { Vec3, Mat4, Quat } from '../../../mol-math/linear-algebra'; import { trajectoryFromMmCIF } from '../../../mol-model-formats/structure/mmcif'; import { trajectoryFromPDB } from '../../../mol-model-formats/structure/pdb'; -import { Model, Queries, QueryContext, Structure, StructureQuery, StructureSelection as Sel, StructureElement, trajectoryFromModelAndCoordinates, Coordinates } from '../../../mol-model/structure'; +import { Model, Queries, QueryContext, Structure, StructureQuery, StructureSelection as Sel, StructureElement, Coordinates, Topology } from '../../../mol-model/structure'; import { PluginContext } from '../../../mol-plugin/context'; import { MolScriptBuilder } from '../../../mol-script/language/builder'; import Expression from '../../../mol-script/language/expression'; @@ -30,8 +30,10 @@ import { StructureQueryHelper } from '../../util/structure-query'; import { ModelStructureRepresentation } from '../representation/model'; import { parseDcd } from '../../../mol-io/reader/dcd/parser'; import { coordinatesFromDcd } from '../../../mol-model-formats/structure/dcd'; +import { topologyFromPsf } from '../../../mol-model-formats/structure/psf'; export { CoordinatesFromDcd }; +export { TopologyFromPsf }; export { TrajectoryFromModelAndCoordinates }; export { TrajectoryFromBlob }; export { TrajectoryFromMmCif }; @@ -64,15 +66,41 @@ const CoordinatesFromDcd = PluginStateTransform.BuiltIn({ const parsed = await parseDcd(a.data).runInContext(ctx); if (parsed.isError) throw new Error(parsed.message); const coordinates = await coordinatesFromDcd(parsed.result).runInContext(ctx); - return new SO.Molecule.Coordinates(coordinates, { label: a.label, description: a.description }); + 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.' }, + from: [SO.Format.Psf], + to: SO.Molecule.Topology +})({ + apply({ a }) { + return Task.create('Create Topology', async ctx => { + const topology = await topologyFromPsf(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 + return await Model.trajectoryFromTopologyAndCoordinates(topology, coordinates).runInContext(ctx); + } else if (obj.type === SO.Molecule.Model.type) { + const model = obj.data as Model + return Model.trajectoryFromModelAndCoordinates(model, coordinates); + } + throw new Error('no model/topology found') +} + type TrajectoryFromModelAndCoordinates = typeof TrajectoryFromModelAndCoordinates const TrajectoryFromModelAndCoordinates = PluginStateTransform.BuiltIn({ name: 'trajectory-from-model-and-coordinates', - display: { name: 'Trajectory from Model & Coordinates', description: 'Create a trajectory from existing model and coordinates.' }, + display: { name: 'Trajectory from Topology & Coordinates', description: 'Create a trajectory from existing model/topology and coordinates.' }, from: SO.Root, to: SO.Molecule.Trajectory, params: { @@ -81,10 +109,9 @@ const TrajectoryFromModelAndCoordinates = PluginStateTransform.BuiltIn({ } })({ apply({ params, dependencies }) { - return Task.create('Create trajectory from model and coordinates', async ctx => { - const model = dependencies![params.modelRef].data as Model + return Task.create('Create trajectory from model/topology and coordinates', async ctx => { const coordinates = dependencies![params.coordinatesRef].data as Coordinates - const trajectory = trajectoryFromModelAndCoordinates(model, coordinates); + const trajectory = await getTrajectory(ctx, dependencies![params.modelRef], coordinates); const props = { label: 'Trajectory', description: `${trajectory.length} model${trajectory.length === 1 ? '' : 's'}` }; return new SO.Molecule.Trajectory(trajectory, props); });