Przeglądaj źródła

Merge pull request #731 from JonStargaryen/master

Parse CCD Files
Alexander Rose 1 rok temu
rodzic
commit
b7a673f38e

+ 2 - 0
CHANGELOG.md

@@ -6,6 +6,8 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+- Allow parsing of CCD ligand files
+- Add dedicated wwPDB CCD extension to align and visualize ideal & model CCD coordinates
 - Make operators in `IndexPairBonds` a directed property
 - Remove erroneous bounding-box overlap test in `Structure.eachUnitPair`
 - Fix `EdgeBuilder.addNextEdge` for loop edges

+ 2 - 0
src/apps/viewer/app.ts

@@ -50,6 +50,7 @@ import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/car
 import { Backgrounds } from '../../extensions/backgrounds';
 import { SbNcbrPartialCharges, SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider } from '../../extensions/sb-ncbr';
 import { wwPDBStructConnExtensionFunctions } from '../../extensions/wwpdb/struct-conn';
+import { wwPDBChemicalComponentDictionary } from '../../extensions/wwpdb/ccd/behavior';
 
 export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
 export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug';
@@ -74,6 +75,7 @@ const Extensions = {
     'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
     'zenodo-import': PluginSpec.Behavior(ZenodoImport),
     'sb-ncbr-partial-charges': PluginSpec.Behavior(SbNcbrPartialCharges),
+    'wwpdb-chemical-component-dictionary': PluginSpec.Behavior(wwPDBChemicalComponentDictionary),
 };
 
 const DefaultViewerOptions = {

+ 47 - 0
src/extensions/wwpdb/ccd/README.md

@@ -0,0 +1,47 @@
+# Chemical Component Dictionary Extension
+
+The [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd) describes all small molecules and monomers found in PDB entries. The dictionary provides a plethora of additional information not present in wwPDB archive structures such as chemical descriptors (SMILES & InChI) and stereochemical assignments, information on bond order and more. Most notably, the CCD provides 2 sets of coordinates:
+- `ideal`: idealized/minimized coordinates, obtained using Molecular Networks' Corina, and if there are issues, OpenEye's OMEGA
+- `model`: coordinates extracted from an archive structure
+
+## How to Load a Component from URL
+1. "Download Structure" -- switch "Source" to "URL"
+2. Enter URL of component, e.g. https://files.rcsb.org/ligands/view/HEM.cif, leave "Format" as is
+3. Click "Apply"
+
+This parses the corresponding component into 2 models (1st: `ideal` coordinates, 2nd: `model` coordinates) and applies the default representaiton to the 1st model. `model` coordinates are available as 2nd model. Click the canvas to re-focus if you don't see anything after switching models due to the coordinates being far away.
+
+## How to Visualize Components
+There's a dedicated representation preset that faciliates the comparison of `ideal` and `model` coordinates.
+
+1. Load a component as described above
+2. Switch structure preset to "Chemical Component" (button in the top-right, in the "Structure" panel)
+
+This creates a dedicated component for `ideal` as well as `model` coordinates and represents them as ball-and-stick. Initially, only `ideal` coordinates are shown. After toggling the visibility of `model` coordinates, they appear superimposed with the `ideal` coordinates. 
+
+## Examples & Test Cases
+Ligand | Description | Details
+-- | -- | --
+https://files.rcsb.org/ligands/view/HEM.cif | metal coordination |
+https://files.rcsb.org/ligands/view/FE.cif | +3 oxidation state |
+https://files.rcsb.org/ligands/view/FE2.cif | +2 oxidation state |
+https://files.rcsb.org/ligands/view/RUC.cif | transition metal | 
+https://files.rcsb.org/ligands/view/SF4.cif | Fe-S cluster | doesn't align nicely
+https://files.rcsb.org/ligands/view/TBR.cif | coords identical | 
+https://files.rcsb.org/ligands/view/OER.cif | coords identical | 
+https://files.rcsb.org/ligands/view/FEA.cif | charges |
+https://files.rcsb.org/ligands/view/PR2.cif | orientation differs | 
+https://files.rcsb.org/ligands/view/03R.cif | some atoms missing |
+https://files.rcsb.org/ligands/view/02U.cif | many atoms missing |
+https://files.rcsb.org/ligands/view/HC0.cif | no ideal coords | unrelated: O and H atoms clashing
+https://files.rcsb.org/ligands/view/Q6O.cif | no model coords |
+https://files.rcsb.org/ligands/view/H0C.cif | big ligand |
+https://files.rcsb.org/ligands/view/2NC.cif | dual representation as PRD and CC |
+https://files.rcsb.org/birds/view/PRDCC_000001.cif | PRDCC |
+https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/BB87Q.cif | extended CCD identifier |
+https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/7ZTVU.cif | extended CCD identifier |
+https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/9QRZS.cif | extended CCD identifier |
+https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/9ABCD.cif | extended CCD identifier |
+https://files.rcsb.org/ligands/view/UNK.cif | CCD special: unknown amino acid | unrelated: some model H are placed far away
+https://files.rcsb.org/ligands/view/UNX.cif | CCD special: unknown atom/ion | no ideal coordinates
+https://files.rcsb.org/ligands/view/UNL.cif | CCD special: unknown ligand | no coordinates whatsoever

+ 33 - 0
src/extensions/wwpdb/ccd/behavior.ts

@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
+ */
+
+import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
+import { ChemicalComponentPreset, ChemicalCompontentTrajectoryHierarchyPreset } from './representation';
+
+export const wwPDBChemicalComponentDictionary = PluginBehavior.create<{ }>({
+    name: 'wwpdb-chemical-component-dictionary',
+    category: 'representation',
+    display: {
+        name: 'wwPDB Chemical Compontent Dictionary',
+        description: 'Custom representation for data loaded from the CCD.'
+    },
+    ctor: class extends PluginBehavior.Handler<{ }> {
+        register(): void {
+            this.ctx.builders.structure.hierarchy.registerPreset(ChemicalCompontentTrajectoryHierarchyPreset);
+            this.ctx.builders.structure.representation.registerPreset(ChemicalComponentPreset);
+        }
+
+        update() {
+            return false;
+        }
+
+        unregister() {
+            this.ctx.builders.structure.hierarchy.unregisterPreset(ChemicalCompontentTrajectoryHierarchyPreset);
+            this.ctx.builders.structure.representation.unregisterPreset(ChemicalComponentPreset);
+        }
+    },
+    params: () => ({ })
+});

+ 165 - 0
src/extensions/wwpdb/ccd/representation.ts

@@ -0,0 +1,165 @@
+/**
+ * Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
+ */
+
+import { PluginStateObject } from '../../../mol-plugin-state/objects';
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+import { StateObjectRef, StateTransform } from '../../../mol-state';
+import { StateTransforms } from '../../../mol-plugin-state/transforms';
+import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../../mol-plugin-state/builder/structure/representation-preset';
+import { PluginContext } from '../../../mol-plugin/context';
+import { Mat4 } from '../../../mol-math/linear-algebra';
+import { Structure } from '../../../mol-model/structure';
+import { CCDFormat } from '../../../mol-model-formats/structure/mmcif';
+import { MinimizeRmsd } from '../../../mol-math/linear-algebra/3d/minimize-rmsd';
+import { SetUtils } from '../../../mol-util/set';
+import { TrajectoryHierarchyPresetProvider } from '../../../mol-plugin-state/builder/structure/hierarchy-preset';
+import { capitalize } from '../../../mol-util/string';
+
+const CCDParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
+    representationPresetParams: PD.Optional(PD.Group(StructureRepresentationPresetProvider.CommonParams)),
+    showOriginalCoordinates: PD.Optional(PD.Boolean(true, { description: `Show original coordinates for 'model' and 'ideal' structure and do not align them.` })),
+    shownCoordinateType: PD.Select('ideal', PD.arrayToOptions(['ideal', 'model', 'both'] as const), { description: `What coordinate sets are visible.` }),
+    ...TrajectoryHierarchyPresetProvider.CommonParams(a, plugin)
+});
+
+export const ChemicalCompontentTrajectoryHierarchyPreset = TrajectoryHierarchyPresetProvider({
+    id: 'preset-trajectory-ccd',
+    display: {
+        name: 'Chemical Component', group: 'Preset',
+        description: 'Shows molecules from the Chemical Component Dictionary.'
+    },
+    isApplicable: o => {
+        return CCDFormat.is(o.data.representative.sourceData);
+    },
+    params: CCDParams,
+    async apply(trajectory, params, plugin) {
+        const tr = StateObjectRef.resolveAndCheck(plugin.state.data, trajectory)?.obj?.data;
+        if (!tr) return {};
+
+        const builder = plugin.builders.structure;
+
+        const idealModel = await builder.createModel(trajectory, { modelIndex: 0 });
+        const idealModelProperties = await builder.insertModelProperties(idealModel, params.modelProperties, { isCollapsed: true });
+
+        const idealStructure = await builder.createStructure(idealModelProperties || idealModel, { name: 'model', params: {} });
+        const idealStructureProperties = await builder.insertStructureProperties(idealStructure, params.structureProperties);
+
+        const representationPreset = params.representationPreset || ChemicalComponentPreset.id;
+        const representationPresetParams = params.representationPresetParams || {};
+        if (representationPresetParams.ignoreHydrogens === undefined) representationPresetParams.ignoreHydrogens = true;
+
+        // degenerate case where either model or ideal coordinates are missing
+        if (tr.frameCount !== 2) {
+            const coordinateType = CCDFormat.CoordinateType.get(idealModel.obj!.data);
+            await builder.representation.applyPreset(idealStructureProperties, representationPreset, { ...representationPresetParams, coordinateType });
+
+            return { models: [idealModel], structures: [idealStructure] };
+        }
+
+        const modelModel = await builder.createModel(trajectory, { modelIndex: 1 });
+        const modelModelProperties = await builder.insertModelProperties(modelModel, params.modelProperties, { isCollapsed: true });
+
+        const modelStructure = await builder.createStructure(modelModelProperties || modelModel, { name: 'model', params: {} });
+        const modelStructureProperties = await builder.insertStructureProperties(modelStructure, params.structureProperties);
+
+        // align ideal and model coordinates
+        if (!params.showOriginalCoordinates) {
+            const [a, b] = getPositionTables(idealStructure.obj!.data, modelStructure.obj!.data);
+            if (!a) {
+                plugin.log.warn(`Cannot align chemical components whose atom sets are disjoint.`);
+            } else {
+                const { bTransform, rmsd } = MinimizeRmsd.compute({ a, b });
+                await transform(plugin, modelStructure.cell!, bTransform);
+                plugin.log.info(`Superposed [model] and [ideal] with RMSD ${rmsd.toFixed(2)}.`);
+            }
+        }
+
+        await builder.representation.applyPreset(idealStructureProperties, representationPreset, { ...representationPresetParams, coordinateType: 'ideal', isHidden: params.shownCoordinateType === 'model' });
+        await builder.representation.applyPreset(modelStructureProperties, representationPreset, { ...representationPresetParams, coordinateType: 'model', isHidden: params.shownCoordinateType === 'ideal' });
+
+        return { models: [idealModel, modelModel], structures: [idealStructure, modelStructure] };
+    }
+});
+
+function getPositionTables(s1: Structure, s2: Structure) {
+    const m1 = getAtomIdSerialMap(s1);
+    const m2 = getAtomIdSerialMap(s2);
+    const intersecting = SetUtils.intersection(new Set(m1.keys()), new Set(m2.keys()));
+
+    const ret = [
+        MinimizeRmsd.Positions.empty(intersecting.size),
+        MinimizeRmsd.Positions.empty(intersecting.size)
+    ];
+    let o = 0;
+    intersecting.forEach(k => {
+        ret[0].x[o] = s1.model.atomicConformation.x[m1.get(k)!];
+        ret[0].y[o] = s1.model.atomicConformation.y[m1.get(k)!];
+        ret[0].z[o] = s1.model.atomicConformation.z[m1.get(k)!];
+        ret[1].x[o] = s2.model.atomicConformation.x[m2.get(k)!];
+        ret[1].y[o] = s2.model.atomicConformation.y[m2.get(k)!];
+        ret[1].z[o] = s2.model.atomicConformation.z[m2.get(k)!];
+        o++;
+    });
+
+    return ret;
+}
+
+function getAtomIdSerialMap(structure: Structure) {
+    const map = new Map<string, number>();
+    const { label_atom_id } = structure.model.atomicHierarchy.atoms;
+    for (let i = 0, il = label_atom_id.rowCount; i < il; ++i) {
+        const id = label_atom_id.value(i);
+        if (!map.has(id)) map.set(id, map.size);
+    }
+    return map;
+}
+
+function transform(plugin: PluginContext, s: StateObjectRef<PluginStateObject.Molecule.Structure>, matrix: Mat4) {
+    const b = plugin.state.data.build().to(s)
+        .insert(StateTransforms.Model.TransformStructureConformation, { transform: { name: 'matrix', params: { data: matrix, transpose: false } } });
+    return plugin.runTask(plugin.state.data.updateTree(b));
+}
+
+export const ChemicalComponentPreset = StructureRepresentationPresetProvider({
+    id: 'preset-structure-representation-chemical-component',
+    display: {
+        name: 'Chemical Component', group: 'Miscellaneous',
+        description: `Show 'Ideal' and 'Model' coordinates of chemical components.`
+    },
+    isApplicable: o => {
+        return CCDFormat.is(o.data.model.sourceData);
+    },
+    params: () => ({
+        ...StructureRepresentationPresetProvider.CommonParams,
+        coordinateType: PD.Select<CCDFormat.CoordinateType>('ideal', PD.arrayToOptions(['ideal', 'model'])),
+        isHidden: PD.Boolean(false)
+    }),
+    async apply(ref, params, plugin) {
+        const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
+        if (!structureCell) return {};
+
+        const { coordinateType, isHidden } = params;
+        const components = {
+            [coordinateType]: await presetStaticComponent(plugin, structureCell, 'all', { label: capitalize(coordinateType), tags: [coordinateType] })
+        };
+
+        const structure = structureCell.obj!.data;
+        const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
+
+        const representations = {
+            [coordinateType]: builder.buildRepresentation(update, components[coordinateType], { type: 'ball-and-stick', typeParams }, { initialState: { isHidden } }),
+        };
+        // sync UI state
+        if (components[coordinateType]?.cell?.state && isHidden) {
+            StateTransform.assignState(components[coordinateType]!.cell!.state, { isHidden });
+        }
+
+        await update.commit({ revertOnError: true });
+        await StructureRepresentationPresetProvider.updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
+
+        return { components, representations };
+    }
+});

+ 142 - 5
src/mol-model-formats/structure/mmcif.ts

@@ -1,25 +1,30 @@
 /**
- * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2023 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>
+ * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  */
 
 import { Model } from '../../mol-model/structure/model/model';
-import { Task } from '../../mol-task';
+import { RuntimeContext, Task } from '../../mol-task';
 import { ModelFormat } from '../format';
 import { CifFrame, CIF } from '../../mol-io/reader/cif';
 import { mmCIF_Database } from '../../mol-io/reader/cif/schema/mmcif';
 import { createModels } from './basic/parser';
 import { ModelSymmetry } from './property/symmetry';
 import { ModelSecondaryStructure } from './property/secondary-structure';
-import { Table } from '../../mol-data/db';
+import { Column, Table } from '../../mol-data/db';
 import { AtomSiteAnisotrop } from './property/anisotropic';
 import { ComponentBond } from './property/bonds/chem_comp';
 import { StructConn } from './property/bonds/struct_conn';
-import { Trajectory } from '../../mol-model/structure';
+import { ArrayTrajectory, Trajectory } from '../../mol-model/structure';
 import { GlobalModelTransformInfo } from '../../mol-model/structure/model/properties/global-transform';
-import { createBasic } from './basic/schema';
+import { BasicSchema, createBasic } from './basic/schema';
+import { CCD_Database } from '../../mol-io/reader/cif/schema/ccd';
+import { EntityBuilder } from './common/entity';
+import { MoleculeType } from '../../mol-model/structure/model/types';
+import { ComponentBuilder } from './common/component';
 
 function modelSymmetryFromMmcif(model: Model) {
     if (!MmcifFormat.is(model.sourceData)) return;
@@ -103,4 +108,136 @@ export function trajectoryFromMmCIF(frame: CifFrame): Task<Trajectory> {
     const format = MmcifFormat.fromFrame(frame);
     const basic = createBasic(format.data.db, true);
     return Task.create('Create mmCIF Model', ctx => createModels(basic, format, ctx));
+}
+
+export { CCDFormat };
+
+type CCDFormat = ModelFormat<CCDFormat.Data>
+
+namespace CCDFormat {
+    export type Data = {
+        db: CCD_Database,
+        frame: CifFrame
+    }
+
+    const CoordinateTypeProp = '__CcdCoordinateType__';
+    export type CoordinateType = 'ideal' | 'model'
+    export const CoordinateType = {
+        get(model: Model): CoordinateType | undefined {
+            return model._staticPropertyData[CoordinateTypeProp];
+        },
+        set(model: Model, type: CoordinateType) {
+            return model._staticPropertyData[CoordinateTypeProp] = type;
+        }
+    };
+
+    export function is(x?: ModelFormat): x is CCDFormat {
+        return x?.kind === 'CCD';
+    }
+
+    export function fromFrame(frame: CifFrame, db?: CCD_Database): CCDFormat {
+        if (!db) db = CIF.schema.CCD(frame);
+        return { kind: 'CCD', name: db._name, data: { db, frame } };
+    }
+}
+
+export function trajectoryFromCCD(frame: CifFrame): Task<Trajectory> {
+    const format = CCDFormat.fromFrame(frame);
+    return Task.create('Create CCD Models', ctx => createCcdModels(format.data.db, CCDFormat.fromFrame(frame), ctx));
+}
+
+async function createCcdModels(data: CCD_Database, format: CCDFormat, ctx: RuntimeContext) {
+    const ideal = await createCcdModel(data, format, { coordinateType: 'ideal', cartn_x: 'pdbx_model_Cartn_x_ideal', cartn_y: 'pdbx_model_Cartn_y_ideal', cartn_z: 'pdbx_model_Cartn_z_ideal' }, ctx);
+    const model = await createCcdModel(data, format, { coordinateType: 'model', cartn_x: 'model_Cartn_x', cartn_y: 'model_Cartn_y', cartn_z: 'model_Cartn_z' }, ctx);
+
+    const models = [];
+    if (ideal) models.push(ideal);
+    if (model) models.push(model);
+    for (let i = 0, il = models.length; i < il; ++i) {
+        Model.TrajectoryInfo.set(models[i], { index: i, size: models.length });
+    }
+
+    return new ArrayTrajectory(models);
+}
+
+type CCDProps = { coordinateType: CCDFormat.CoordinateType, cartn_x: 'model_Cartn_x' | 'pdbx_model_Cartn_x_ideal', cartn_y: 'model_Cartn_y' | 'pdbx_model_Cartn_y_ideal', cartn_z: 'model_Cartn_z' | 'pdbx_model_Cartn_z_ideal' };
+async function createCcdModel(data: CCD_Database, format: CCDFormat, props: CCDProps, ctx: RuntimeContext) {
+    const { chem_comp, chem_comp_atom, chem_comp_bond } = data;
+    const { coordinateType, cartn_x, cartn_y, cartn_z } = props;
+
+    const name = chem_comp.name.value(0);
+    const id = chem_comp.id.value(0);
+
+    const { atom_id, charge, comp_id, pdbx_ordinal, type_symbol } = chem_comp_atom;
+    const atomCount = chem_comp_atom._rowCount;
+
+    const filteredRows: number[] = [];
+    for (let i = 0; i < atomCount; i++) {
+        if (chem_comp_atom[cartn_x].valueKind(i) > 0) continue;
+        filteredRows[filteredRows.length] = i;
+    }
+    const filteredRowCount = filteredRows.length;
+
+    const A = Column.ofConst('A', filteredRowCount, Column.Schema.str);
+    const seq_id = Column.ofConst(1, filteredRowCount, Column.Schema.int);
+    const entity_id = Column.ofConst('1', filteredRowCount, Column.Schema.str);
+    const occupancy = Column.ofConst(1, filteredRowCount, Column.Schema.float);
+    const model_num = Column.ofConst(1, filteredRowCount, Column.Schema.int);
+
+    const filteredAtomId = Column.view(atom_id, filteredRows);
+    const filteredCompId = Column.view(comp_id, filteredRows);
+    const filteredX = Column.view(chem_comp_atom[cartn_x], filteredRows);
+    const filteredY = Column.view(chem_comp_atom[cartn_y], filteredRows);
+    const filteredZ = Column.view(chem_comp_atom[cartn_z], filteredRows);
+    const filteredId = Column.view(pdbx_ordinal, filteredRows);
+    const filteredTypeSymbol = Column.view(type_symbol, filteredRows);
+    const filteredCharge = Column.view(charge, filteredRows);
+
+    const model_atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
+        auth_asym_id: A,
+        auth_atom_id: filteredAtomId,
+        auth_comp_id: filteredCompId,
+        auth_seq_id: seq_id,
+        Cartn_x: filteredX,
+        Cartn_y: filteredY,
+        Cartn_z: filteredZ,
+        id: filteredId,
+
+        label_asym_id: A,
+        label_atom_id: filteredAtomId,
+        label_comp_id: filteredCompId,
+        label_seq_id: seq_id,
+        label_entity_id: entity_id,
+
+        occupancy,
+        type_symbol: filteredTypeSymbol,
+
+        pdbx_PDB_model_num: model_num,
+        pdbx_formal_charge: filteredCharge
+    }, filteredRowCount);
+
+    const entityBuilder = new EntityBuilder();
+    entityBuilder.setNames([[id, `${name} (${coordinateType})`]]);
+    entityBuilder.getEntityId(id, MoleculeType.Unknown, 'A');
+
+    const componentBuilder = new ComponentBuilder(seq_id, type_symbol);
+    componentBuilder.setNames([[id, `${name} (${coordinateType})`]]);
+    componentBuilder.add(id, 0);
+
+    const basicModel = createBasic({
+        entity: entityBuilder.getEntityTable(),
+        chem_comp: componentBuilder.getChemCompTable(),
+        atom_site: model_atom_site
+    });
+    const models = await createModels(basicModel, format, ctx);
+
+    // all ideal or model coordinates might be absent
+    if (!models.representative) return;
+
+    const first = models.representative;
+    const entries = ComponentBond.getEntriesFromChemCompBond(chem_comp_bond);
+    ComponentBond.Provider.set(first, { data: chem_comp_bond, entries });
+    CCDFormat.CoordinateType.set(first, coordinateType);
+
+    return models.representative;
 }

+ 3 - 3
src/mol-plugin-state/transforms/model.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2023 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>
@@ -12,7 +12,7 @@ import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
 import { shapeFromPly } from '../../mol-model-formats/shape/ply';
 import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';
 import { trajectoryFromGRO } from '../../mol-model-formats/structure/gro';
-import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
+import { trajectoryFromCCD, trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
 import { trajectoryFromPDB } from '../../mol-model-formats/structure/pdb';
 import { topologyFromPsf } from '../../mol-model-formats/structure/psf';
 import { Coordinates, Model, Queries, QueryContext, Structure, StructureElement, StructureQuery, StructureSelection as Sel, Topology, ArrayTrajectory, Trajectory } from '../../mol-model/structure';
@@ -303,7 +303,7 @@ const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({
                 const header = params.blockHeader || a.data.blocks[0].header;
                 const block = a.data.blocks.find(b => b.header === header);
                 if (!block) throw new Error(`Data block '${[header]}' not found.`);
-                trajectory = await trajectoryFromMmCIF(block).runInContext(ctx);
+                trajectory = block.categoryNames.includes('chem_comp_atom') ? await trajectoryFromCCD(block).runInContext(ctx) : await trajectoryFromMmCIF(block).runInContext(ctx);
             }
             if (trajectory.frameCount === 0) throw new Error('No models found.');
             const props = trajectoryProps(trajectory);