Browse Source

split mmcif parser into basic part and properties

- basic part is for hierarchy and conformation
- properties is for any additional info

first commit of refactoring with the aim to make the format parsing more modular and be clear about what data is actually needed for the basic part and for properties
Alexander Rose 5 years ago
parent
commit
7a64334261
46 changed files with 1118 additions and 986 deletions
  1. 7 2
      src/apps/structure-info/model.ts
  2. 3 1
      src/examples/proteopedia-wrapper/helpers.ts
  3. 6 9
      src/mol-model-formats/structure/basic/atomic.ts
  4. 17 16
      src/mol-model-formats/structure/basic/coarse.ts
  5. 122 0
      src/mol-model-formats/structure/basic/entities.ts
  6. 209 0
      src/mol-model-formats/structure/basic/parser.ts
  7. 134 0
      src/mol-model-formats/structure/basic/properties.ts
  8. 66 0
      src/mol-model-formats/structure/basic/schema.ts
  9. 5 5
      src/mol-model-formats/structure/basic/sequence.ts
  10. 7 4
      src/mol-model-formats/structure/basic/sort.ts
  11. 20 0
      src/mol-model-formats/structure/basic/util.ts
  12. 55 0
      src/mol-model-formats/structure/common/property.ts
  13. 11 3
      src/mol-model-formats/structure/format.ts
  14. 0 88
      src/mol-model-formats/structure/mmcif/anisotropic.ts
  15. 0 9
      src/mol-model-formats/structure/mmcif/bonds.ts
  16. 0 8
      src/mol-model-formats/structure/mmcif/pair-restraint.ts
  17. 49 500
      src/mol-model-formats/structure/mmcif/parser.ts
  18. 0 26
      src/mol-model-formats/structure/mmcif/util.ts
  19. 60 0
      src/mol-model-formats/structure/property/anisotropic.ts
  20. 11 11
      src/mol-model-formats/structure/property/assembly.ts
  21. 36 73
      src/mol-model-formats/structure/property/bonds/comp.ts
  22. 6 26
      src/mol-model-formats/structure/property/bonds/index-pair.ts
  23. 27 60
      src/mol-model-formats/structure/property/bonds/struct_conn.ts
  24. 31 32
      src/mol-model-formats/structure/property/pair-restraints/cross-links.ts
  25. 0 0
      src/mol-model-formats/structure/property/pair-restraints/predicted-contacts.ts
  26. 34 19
      src/mol-model-formats/structure/property/secondary-structure.ts
  27. 85 0
      src/mol-model-formats/structure/property/symmetry.ts
  28. 5 1
      src/mol-model-props/computed/secondary-structure.ts
  29. 3 2
      src/mol-model-props/pdbe/preferred-assembly.ts
  30. 4 1
      src/mol-model/structure/export/categories/secondary-structure.ts
  31. 2 2
      src/mol-model/structure/model.ts
  32. 4 9
      src/mol-model/structure/model/model.ts
  33. 0 22
      src/mol-model/structure/model/properties/computed.ts
  34. 7 5
      src/mol-model/structure/model/properties/symmetry.ts
  35. 16 2
      src/mol-model/structure/structure/properties.ts
  36. 17 8
      src/mol-model/structure/structure/symmetry.ts
  37. 5 5
      src/mol-model/structure/structure/unit/bonds/inter-compute.ts
  38. 7 6
      src/mol-model/structure/structure/unit/bonds/intra-compute.ts
  39. 6 6
      src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts
  40. 12 7
      src/mol-plugin/state/representation/model.ts
  41. 3 2
      src/mol-plugin/state/transforms/model.ts
  42. 7 3
      src/mol-plugin/state/transforms/representation.ts
  43. 11 7
      src/mol-plugin/util/model-unitcell.ts
  44. 1 1
      src/mol-repr/structure/representation/ellipsoid.ts
  45. 2 2
      src/mol-repr/structure/visual/ellipsoid-mesh.ts
  46. 5 3
      src/servers/model/properties/providers/wwpdb.ts

+ 7 - 2
src/apps/structure-info/model.ts

@@ -16,6 +16,8 @@ import { openCif, downloadCif } from './helpers';
 import { Vec3 } from '../../mol-math/linear-algebra';
 import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
 import { Sequence } from '../../mol-model/sequence';
+import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure';
+import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
 
 
 async function downloadFromPdb(pdb: string) {
@@ -50,8 +52,10 @@ export function residueLabel(model: Model, rI: number) {
 export function printSecStructure(model: Model) {
     console.log('\nSecondary Structure\n=============');
     const { residues } = model.atomicHierarchy;
-    const { key, elements } = model.properties.secondaryStructure;
+    const secondaryStructure = ModelSecondaryStructure.Provider.get(model);
+    if (!secondaryStructure) return
 
+    const { key, elements } = secondaryStructure
     const count = residues._rowCount;
     let rI = 0;
     while (rI < count) {
@@ -179,7 +183,8 @@ export function printUnits(structure: Structure) {
 
 export function printSymmetryInfo(model: Model) {
     console.log('\nSymmetry Info\n=============');
-    const { symmetry } = model;
+    const symmetry = ModelSymmetry.Provider.get(model)
+    if (!symmetry) return
     const { size, anglesInRadians } = symmetry.spacegroup.cell;
     console.log(`Spacegroup: ${symmetry.spacegroup.name} size: ${Vec3.toString(size)} angles: ${Vec3.toString(anglesInRadians)}`);
     console.log(`Assembly names: ${symmetry.assemblies.map(a => a.id).join(', ')}`);

+ 3 - 1
src/examples/proteopedia-wrapper/helpers.ts

@@ -9,6 +9,7 @@ import { BuiltInStructureRepresentationsName } from '../../mol-repr/structure/re
 import { BuiltInColorThemeName } from '../../mol-theme/color';
 import { AminoAcidNames } from '../../mol-model/structure/model/types';
 import { PluginContext } from '../../mol-plugin/context';
+import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
 
 export interface ModelInfo {
     hetResidues: { name: string, indices: ResidueIndex[] }[],
@@ -72,10 +73,11 @@ export namespace ModelInfo {
         }
 
         const preferredAssemblyId = await pref;
+        const symmetry = ModelSymmetry.Provider.get(model)
 
         return {
             hetResidues: hetResidues,
-            assemblies: model.symmetry.assemblies.map(a => ({ id: a.id, details: a.details, isPreferred: a.id === preferredAssemblyId })),
+            assemblies: symmetry ? symmetry.assemblies.map(a => ({ id: a.id, details: a.details, isPreferred: a.id === preferredAssemblyId })) : [],
             preferredAssemblyId
         };
     }

+ 6 - 9
src/mol-model-formats/structure/mmcif/atomic.ts → src/mol-model-formats/structure/basic/atomic.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 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>
@@ -7,7 +7,6 @@
 
 import { Column, Table } from '../../../mol-data/db';
 import { Interval, Segmentation } from '../../../mol-data/int';
-import { mmCIF_Database } from '../../../mol-io/reader/cif/schema/mmcif';
 import UUID from '../../../mol-util/uuid';
 import { ElementIndex } from '../../../mol-model/structure';
 import { Model } from '../../../mol-model/structure/model/model';
@@ -16,9 +15,7 @@ import { getAtomicIndex } from '../../../mol-model/structure/model/properties/ut
 import { ElementSymbol } from '../../../mol-model/structure/model/types';
 import { Entities } from '../../../mol-model/structure/model/properties/common';
 import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
-import { FormatData } from './parser';
-
-type AtomSite = mmCIF_Database['atom_site']
+import { AtomSite } from './schema';
 
 function findHierarchyOffsets(atom_site: AtomSite) {
     if (atom_site._rowCount === 0) return { residues: [], chains: [] };
@@ -98,7 +95,7 @@ function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
         && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>)
 }
 
-function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model) {
+function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
     const hierarchyOffsets = findHierarchyOffsets(atom_site);
     const hierarchyData = createHierarchyData(atom_site, sourceIndex, hierarchyOffsets);
 
@@ -115,13 +112,13 @@ function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, en
     }
 
     const index = getAtomicIndex(hierarchyData, entities, hierarchySegments);
-    const derived = getAtomicDerivedData(hierarchyData, index, formatData.chemicalComponentMap);
+    const derived = getAtomicDerivedData(hierarchyData, index, chemicalComponentMap);
     const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived };
     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)
+export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
+    const { sameAsPrevious, hierarchy } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, previous)
     const conformation = getConformation(atom_site)
     return { sameAsPrevious, hierarchy, conformation };
 }

+ 17 - 16
src/mol-model-formats/structure/mmcif/ihm.ts → src/mol-model-formats/structure/basic/coarse.ts

@@ -1,10 +1,10 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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 { mmCIF_Database as mmCIF, mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif'
 import { CoarseHierarchy, CoarseConformation, CoarseElementData, CoarseSphereConformation, CoarseGaussianConformation } from '../../../mol-model/structure/model/properties/coarse'
 import { Entities } from '../../../mol-model/structure/model/properties/common';
 import { Column } from '../../../mol-data/db';
@@ -14,35 +14,36 @@ import { Segmentation, Interval } from '../../../mol-data/int';
 import { Mat3, Tensor } from '../../../mol-math/linear-algebra';
 import { ElementIndex, ChainIndex } from '../../../mol-model/structure/model/indexing';
 import { getCoarseRanges } from '../../../mol-model/structure/model/properties/utils/coarse-ranges';
-import { FormatData } from './parser';
+import { IhmSphereObjSite, IhmGaussianObjSite, AtomSite, BasicSchema } from './schema';
+import { Model } from '../../../mol-model/structure';
 
-export interface IHMData {
+export interface CoarseData {
     model_id: number,
     model_name: string,
     model_group_name: string,
     entities: Entities,
-    atom_site: mmCIF['atom_site'],
+    atom_site: AtomSite,
     atom_site_sourceIndex: Column<number>,
-    ihm_sphere_obj_site: mmCIF['ihm_sphere_obj_site'],
-    ihm_gaussian_obj_site: mmCIF['ihm_gaussian_obj_site']
+    ihm_sphere_obj_site: IhmSphereObjSite,
+    ihm_gaussian_obj_site: IhmGaussianObjSite
 }
 
-export const EmptyIHMCoarse = { hierarchy: CoarseHierarchy.Empty, conformation: void 0 as any }
+export const EmptyCoarse = { hierarchy: CoarseHierarchy.Empty, conformation: void 0 as any }
 
-export function getIHMCoarse(data: IHMData, formatData: FormatData): { hierarchy: CoarseHierarchy, conformation: CoarseConformation } {
+export function getCoarse(data: CoarseData, properties: Model['properties']): { hierarchy: CoarseHierarchy, conformation: CoarseConformation } {
     const { ihm_sphere_obj_site, ihm_gaussian_obj_site } = data;
 
-    if (ihm_sphere_obj_site._rowCount === 0 && ihm_gaussian_obj_site._rowCount === 0) return EmptyIHMCoarse;
+    if (ihm_sphere_obj_site._rowCount === 0 && ihm_gaussian_obj_site._rowCount === 0) return EmptyCoarse;
 
     const sphereData = getData(ihm_sphere_obj_site);
     const sphereConformation = getSphereConformation(ihm_sphere_obj_site);
     const sphereKeys = getCoarseKeys(sphereData, data.entities);
-    const sphereRanges = getCoarseRanges(sphereData, formatData.chemicalComponentMap);
+    const sphereRanges = getCoarseRanges(sphereData, properties.chemicalComponentMap);
 
     const gaussianData = getData(ihm_gaussian_obj_site);
     const gaussianConformation = getGaussianConformation(ihm_gaussian_obj_site);
     const gaussianKeys = getCoarseKeys(gaussianData, data.entities);
-    const gaussianRanges = getCoarseRanges(gaussianData, formatData.chemicalComponentMap);
+    const gaussianRanges = getCoarseRanges(gaussianData, properties.chemicalComponentMap);
 
     return {
         hierarchy: {
@@ -58,7 +59,7 @@ export function getIHMCoarse(data: IHMData, formatData: FormatData): { hierarchy
     };
 }
 
-function getSphereConformation(data: mmCIF['ihm_sphere_obj_site']): CoarseSphereConformation {
+function getSphereConformation(data: IhmSphereObjSite): CoarseSphereConformation {
     return {
         x: data.Cartn_x.toArray({ array: Float32Array }),
         y: data.Cartn_y.toArray({ array: Float32Array }),
@@ -68,8 +69,8 @@ function getSphereConformation(data: mmCIF['ihm_sphere_obj_site']): CoarseSphere
     };
 }
 
-function getGaussianConformation(data: mmCIF['ihm_gaussian_obj_site']): CoarseGaussianConformation {
-    const matrix_space = mmCIF_Schema.ihm_gaussian_obj_site.covariance_matrix.space;
+function getGaussianConformation(data: IhmGaussianObjSite): CoarseGaussianConformation {
+    const matrix_space = BasicSchema.ihm_gaussian_obj_site.covariance_matrix.space;
     const covariance_matrix: Mat3[] = [];
     const { covariance_matrix: cm } = data;
 
@@ -98,7 +99,7 @@ function getSegments(asym_id: Column<string>, seq_id_begin: Column<number>, seq_
     }
 }
 
-function getData(data: mmCIF['ihm_sphere_obj_site'] | mmCIF['ihm_gaussian_obj_site']): CoarseElementData {
+function getData(data: IhmSphereObjSite | IhmGaussianObjSite): CoarseElementData {
     const { entity_id, seq_id_begin, seq_id_end, asym_id } = data;
     return { count: entity_id.rowCount, entity_id, asym_id, seq_id_begin, seq_id_end, ...getSegments(asym_id, seq_id_begin, seq_id_end) };
 }

+ 122 - 0
src/mol-model-formats/structure/basic/entities.ts

@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) 2017-2020 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 { Column, Table } from '../../../mol-data/db';
+import { Entities, EntitySubtype } from '../../../mol-model/structure/model/properties/common';
+import { getEntityType, getEntitySubtype } from '../../../mol-model/structure/model/types';
+import { ElementIndex, EntityIndex } from '../../../mol-model/structure/model';
+import { BasicData, BasicSchema, Entity } from './schema';
+
+export function getEntities(data: BasicData): Entities {
+    let entityData: Entity
+
+    if (!data.entity.id.isDefined) {
+        const entityIds = new Set<string>()
+
+        const ids: ReturnType<Entity['id']['value']>[] = []
+        const types: ReturnType<Entity['type']['value']>[] = []
+
+        const { label_entity_id, label_comp_id } = data.atom_site;
+        for (let i = 0 as ElementIndex, il = data.atom_site._rowCount; i < il; i++) {
+            const entityId = label_entity_id.value(i);
+            if (!entityIds.has(entityId)) {
+                ids.push(entityId)
+                types.push(getEntityType(label_comp_id.value(i)))
+                entityIds.add(entityId)
+            }
+        }
+
+        const { entity_id: sphere_entity_id } = data.ihm_sphere_obj_site;
+        for (let i = 0 as ElementIndex, il = data.ihm_sphere_obj_site._rowCount; i < il; i++) {
+            const entityId = sphere_entity_id.value(i);
+            if (!entityIds.has(entityId)) {
+                ids.push(entityId)
+                types.push('polymer')
+                entityIds.add(entityId)
+            }
+        }
+
+        const { entity_id: gaussian_entity_id } = data.ihm_gaussian_obj_site;
+        for (let i = 0 as ElementIndex, il = data.ihm_gaussian_obj_site._rowCount; i < il; i++) {
+            const entityId = gaussian_entity_id.value(i);
+            if (!entityIds.has(entityId)) {
+                ids.push(entityId)
+                types.push('polymer')
+                entityIds.add(entityId)
+            }
+        }
+
+        entityData = Table.ofPartialColumns(BasicSchema.entity, {
+            id: Column.ofArray({ array: ids, schema: BasicSchema.entity.id }),
+            type: Column.ofArray({ array: types, schema: BasicSchema.entity.type }),
+        }, ids.length)
+    } else {
+        entityData = data.entity;
+    }
+
+    const getEntityIndex = Column.createIndexer<string, EntityIndex>(entityData.id)
+
+    //
+
+    const subtypes: EntitySubtype[] = new Array(entityData._rowCount)
+    subtypes.fill('other')
+
+    const entityIds = new Set<string>()
+    let assignSubtype = false
+
+    if (data.entity_poly && data.entity_poly.type.isDefined) {
+        const { entity_id, type, _rowCount } = data.entity_poly
+        for (let i = 0; i < _rowCount; ++i) {
+            const entityId = entity_id.value(i)
+            subtypes[getEntityIndex(entityId)] = type.value(i)
+            entityIds.add(entityId)
+        }
+    } else {
+        assignSubtype = true
+    }
+
+    if (data.pdbx_entity_branch && data.pdbx_entity_branch.entity_id.isDefined) {
+        const { entity_id, type, _rowCount } = data.pdbx_entity_branch
+        for (let i = 0; i < _rowCount; ++i) {
+            const entityId = entity_id.value(i)
+            subtypes[getEntityIndex(entityId)] = type.value(i)
+            entityIds.add(entityId)
+        }
+    } else {
+        assignSubtype = true
+    }
+
+    if (assignSubtype) {
+        const chemCompType = new Map<string, string>()
+        if (data.chem_comp) {
+            const { id, type } = data.chem_comp;
+            for (let i = 0, il = data.chem_comp._rowCount; i < il; i++) {
+                chemCompType.set(id.value(i), type.value(i))
+            }
+        }
+
+        if (data.atom_site) {
+            const { label_entity_id, label_comp_id } = data.atom_site;
+            for (let i = 0 as ElementIndex, il = data.atom_site._rowCount; i < il; i++) {
+                const entityId = label_entity_id.value(i);
+                if (!entityIds.has(entityId)) {
+                    const compId = label_comp_id.value(i)
+                    const compType = chemCompType.get(compId) || ''
+                    subtypes[getEntityIndex(entityId)] = getEntitySubtype(compId, compType)
+                    entityIds.add(entityId)
+                }
+            }
+        }
+        // TODO how to handle coarse?
+    }
+
+    const subtypeColumn = Column.ofArray({ array: subtypes, schema: EntitySubtype })
+
+    //
+
+    return { data: entityData, subtype: subtypeColumn, getEntityIndex };
+}

+ 209 - 0
src/mol-model-formats/structure/basic/parser.ts

@@ -0,0 +1,209 @@
+/**
+ * Copyright (c) 2017-2020 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 { Column, Table } from '../../../mol-data/db';
+import { RuntimeContext } from '../../../mol-task';
+import UUID from '../../../mol-util/uuid';
+import { Model } from '../../../mol-model/structure/model/model';
+import { Entities } from '../../../mol-model/structure/model/properties/common';
+import { CustomProperties } from '../../../mol-model/structure';
+import { getAtomicHierarchyAndConformation } from './atomic';
+import { getCoarse, EmptyCoarse, CoarseData } from './coarse';
+import { getSequence } from './sequence';
+import { sortAtomSite } from './sort';
+import { ModelFormat } from '../format';
+import { getAtomicRanges } from '../../../mol-model/structure/model/properties/utils/atomic-ranges';
+import { AtomSite, BasicData } from './schema';
+import { getProperties } from './properties';
+import { getEntities } from './entities';
+import { getModelGroupName } from './util';
+
+export async function _parse_basic(data: BasicData, format: ModelFormat, ctx: RuntimeContext) {
+    const properties = getProperties(data)
+    const isIHM = data.ihm_model_list._rowCount > 0;
+    return isIHM ? await readIntegrative(ctx, data, properties, format) : await readStandard(ctx, data, properties, format);
+}
+
+/** Standard atomic model */
+function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: Model['properties'], format: ModelFormat, previous?: Model): Model {
+
+    const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, previous);
+    const modelNum = atom_site.pdbx_PDB_model_num.value(0)
+    if (previous && atomic.sameAsPrevious) {
+        return {
+            ...previous,
+            id: UUID.create22(),
+            modelNum,
+            atomicConformation: atomic.conformation,
+            _dynamicPropertyData: Object.create(null)
+        };
+    }
+
+    const coarse = EmptyCoarse;
+    const sequence = getSequence(data, entities, atomic.hierarchy, coarse.hierarchy, properties.modifiedResidues.parentId)
+    const atomicRanges = getAtomicRanges(atomic.hierarchy, entities, atomic.conformation, sequence)
+
+    const entry = data.entry.id.valueKind(0) === Column.ValueKind.Present
+        ? data.entry.id.value(0) : format.name;
+
+    const label: string[] = []
+    if (entry) label.push(entry)
+    if (data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(data.struct.title.value(0))
+
+    return {
+        id: UUID.create22(),
+        entryId: entry,
+        label: label.join(' | '),
+        entry,
+        sourceData: format,
+        modelNum,
+        entities,
+        sequence,
+        atomicHierarchy: atomic.hierarchy,
+        atomicConformation: atomic.conformation,
+        atomicRanges,
+        coarseHierarchy: coarse.hierarchy,
+        coarseConformation: coarse.conformation,
+        properties,
+        customProperties: new CustomProperties(),
+        _staticPropertyData: Object.create(null),
+        _dynamicPropertyData: Object.create(null)
+    };
+}
+
+/** Integrative model with atomic/coarse parts */
+function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Model['properties'], format: ModelFormat): Model {
+    const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap);
+    const coarse = getCoarse(ihm, properties);
+    const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy, properties.modifiedResidues.parentId)
+    const atomicRanges = getAtomicRanges(atomic.hierarchy, ihm.entities, atomic.conformation, sequence)
+
+    const entry = data.entry.id.valueKind(0) === Column.ValueKind.Present
+        ? data.entry.id.value(0) : format.name;
+
+    const label: string[] = []
+    if (entry) label.push(entry)
+    if (format.data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(format.data.struct.title.value(0))
+    if (ihm.model_group_name) label.push(ihm.model_name)
+    if (ihm.model_group_name) label.push(ihm.model_group_name)
+
+    return {
+        id: UUID.create22(),
+        entryId: entry,
+        label: label.join(' | '),
+        entry,
+        sourceData: format,
+        modelNum: ihm.model_id,
+        entities: ihm.entities,
+        sequence,
+        atomicHierarchy: atomic.hierarchy,
+        atomicConformation: atomic.conformation,
+        atomicRanges,
+        coarseHierarchy: coarse.hierarchy,
+        coarseConformation: coarse.conformation,
+        properties,
+        customProperties: new CustomProperties(),
+        _staticPropertyData: Object.create(null),
+        _dynamicPropertyData: Object.create(null)
+    };
+}
+
+function findModelEnd(num: Column<number>, startIndex: number) {
+    const rowCount = num.rowCount;
+    if (!num.isDefined) return rowCount;
+    let endIndex = startIndex + 1;
+    while (endIndex < rowCount && num.areValuesEqual(startIndex, endIndex)) endIndex++;
+    return endIndex;
+}
+
+async function readStandard(ctx: RuntimeContext, data: BasicData, properties: Model['properties'], format: ModelFormat) {
+    const models: Model[] = [];
+
+    if (data.atom_site) {
+        const atomCount = data.atom_site.id.rowCount;
+        const entities = getEntities(data)
+
+        let modelStart = 0;
+        while (modelStart < atomCount) {
+            const modelEnd = findModelEnd(data.atom_site.pdbx_PDB_model_num, modelStart);
+            const { atom_site, sourceIndex } = await sortAtomSite(ctx, data.atom_site, modelStart, modelEnd);
+            const model = createStandardModel(data, atom_site, sourceIndex, entities, properties, format, models.length > 0 ? models[models.length - 1] : void 0);
+            models.push(model);
+            modelStart = modelEnd;
+        }
+    }
+    return models;
+}
+
+function splitTable<T extends Table<any>>(table: T, col: Column<number>) {
+    const ret = new Map<number, { table: T, start: number, end: number }>()
+    const rowCount = table._rowCount;
+    let modelStart = 0;
+    while (modelStart < rowCount) {
+        const modelEnd = findModelEnd(col, modelStart);
+        const id = col.value(modelStart);
+        ret.set(id, {
+            table: Table.window(table, table._schema, modelStart, modelEnd) as T,
+            start: modelStart,
+            end: modelEnd
+        });
+        modelStart = modelEnd;
+    }
+    return ret;
+}
+
+
+
+async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties: Model['properties'], format: ModelFormat) {
+    const entities = getEntities(data)
+    // when `atom_site.ihm_model_id` is undefined fall back to `atom_site.pdbx_PDB_model_num`
+    const atom_sites_modelColumn = data.atom_site.ihm_model_id.isDefined
+        ? data.atom_site.ihm_model_id : data.atom_site.pdbx_PDB_model_num
+    const atom_sites = splitTable(data.atom_site, atom_sites_modelColumn)
+
+    // TODO: will coarse IHM records require sorting or will we trust it?
+    // ==> Probably implement a sort as as well and store the sourceIndex same as with atomSite
+    // If the sorting is implemented, updated mol-model/structure/properties: atom.sourceIndex
+    const sphere_sites = splitTable(data.ihm_sphere_obj_site, data.ihm_sphere_obj_site.model_id);
+    const gauss_sites = splitTable(data.ihm_gaussian_obj_site, data.ihm_gaussian_obj_site.model_id);
+
+    const models: Model[] = [];
+
+    if (data.ihm_model_list) {
+        const { model_id, model_name } = data.ihm_model_list;
+        for (let i = 0; i < data.ihm_model_list._rowCount; i++) {
+            const id = model_id.value(i);
+
+            let atom_site, atom_site_sourceIndex;
+            if (atom_sites.has(id)) {
+                const e = atom_sites.get(id)!;
+                // need to sort `data.atom_site` as `e.start` and `e.end` are indices into that
+                const { atom_site: sorted, sourceIndex } = await sortAtomSite(ctx, data.atom_site, e.start, e.end);
+                atom_site = sorted;
+                atom_site_sourceIndex = sourceIndex;
+            } else {
+                atom_site = Table.window(data.atom_site, data.atom_site._schema, 0, 0);
+                atom_site_sourceIndex = Column.ofIntArray([]);
+            }
+
+            const ihm: CoarseData = {
+                model_id: id,
+                model_name: model_name.value(i),
+                model_group_name: getModelGroupName(id, data),
+                entities: entities,
+                atom_site,
+                atom_site_sourceIndex,
+                ihm_sphere_obj_site: sphere_sites.has(id) ? sphere_sites.get(id)!.table : Table.window(data.ihm_sphere_obj_site, data.ihm_sphere_obj_site._schema, 0, 0),
+                ihm_gaussian_obj_site: gauss_sites.has(id) ? gauss_sites.get(id)!.table : Table.window(data.ihm_gaussian_obj_site, data.ihm_gaussian_obj_site._schema, 0, 0)
+            };
+            const model = createIntegrativeModel(data, ihm, properties, format);
+            models.push(model);
+        }
+    }
+
+    return models;
+}

+ 134 - 0
src/mol-model-formats/structure/basic/properties.ts

@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2017-2020 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 { Model } from '../../../mol-model/structure/model/model';
+import { ChemicalComponent, MissingResidue } from '../../../mol-model/structure/model/properties/common';
+import { getMoleculeType, MoleculeType, getDefaultChemicalComponent } from '../../../mol-model/structure/model/types';
+import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
+import { memoize1 } from '../../../mol-util/memoize';
+import { BasicData } from './schema';
+import { Table } from '../../../mol-data/db';
+
+function getModifiedResidueNameMap(data: BasicData): Model['properties']['modifiedResidues'] {
+    const parentId = new Map<string, string>();
+    const details = new Map<string, string>();
+
+    const c = data.pdbx_struct_mod_residue;
+    const comp_id = c.label_comp_id.isDefined ? c.label_comp_id : c.auth_comp_id;
+    const parent_id = c.parent_comp_id, details_data = c.details;
+
+    for (let i = 0; i < c._rowCount; i++) {
+        const id = comp_id.value(i);
+        parentId.set(id, parent_id.value(i));
+        details.set(id, details_data.value(i));
+    }
+
+    return { parentId, details };
+}
+
+function getMissingResidues(data: BasicData): Model['properties']['missingResidues'] {
+    const map = new Map<string, MissingResidue>();
+    const getKey = (model_num: number, asym_id: string, seq_id: number) => {
+        return `${model_num}|${asym_id}|${seq_id}`
+    }
+
+    const c = data.pdbx_unobs_or_zero_occ_residues
+    for (let i = 0, il = c._rowCount; i < il; ++i) {
+        const key = getKey(c.PDB_model_num.value(i), c.label_asym_id.value(i), c.label_seq_id.value(i))
+        map.set(key, { polymer_flag: c.polymer_flag.value(i), occupancy_flag: c.occupancy_flag.value(i) })
+    }
+
+    return {
+        has: (model_num: number, asym_id: string, seq_id: number) => {
+            return map.has(getKey(model_num, asym_id, seq_id))
+        },
+        get: (model_num: number, asym_id: string, seq_id: number) => {
+            return map.get(getKey(model_num, asym_id, seq_id))
+        },
+        size: map.size
+    }
+}
+
+function getChemicalComponentMap(data: BasicData): Model['properties']['chemicalComponentMap'] {
+    const map = new Map<string, ChemicalComponent>();
+
+    if (data.chem_comp._rowCount > 0) {
+        const { id } = data.chem_comp
+        for (let i = 0, il = id.rowCount; i < il; ++i) {
+            map.set(id.value(i), Table.getRow(data.chem_comp, i))
+        }
+    } else {
+        const uniqueNames = getUniqueComponentNames(data);
+        uniqueNames.forEach(n => {
+            map.set(n, getDefaultChemicalComponent(n));
+        });
+    }
+    return map
+}
+
+function getSaccharideComponentMap(data: BasicData): SaccharideComponentMap {
+    const map = new Map<string, SaccharideComponent>();
+
+    if (data.pdbx_chem_comp_identifier._rowCount > 0) {
+        // note that `pdbx_chem_comp_identifier` does not contain
+        // a 'SNFG CARBOHYDRATE SYMBOL' entry for 'Unknown' saccharide components
+        // so we always need to check `chem_comp` for those
+        const { comp_id, type, identifier } = data.pdbx_chem_comp_identifier
+        for (let i = 0, il = comp_id.rowCount; i < il; ++i) {
+            if (type.value(i) === 'SNFG CARBOHYDRATE SYMBOL' ||
+                type.value(i) === 'SNFG CARB SYMBOL' // legacy, to be removed from mmCIF dictionary
+            ) {
+                const snfgName = identifier.value(i)
+                const saccharideComp = SaccharidesSnfgMap.get(snfgName)
+                if (saccharideComp) {
+                    map.set(comp_id.value(i), saccharideComp)
+                } else {
+                    console.warn(`Unknown SNFG name '${snfgName}'`)
+                }
+            }
+        }
+    }
+
+    if (data.chem_comp._rowCount > 0) {
+        const { id, type  } = data.chem_comp
+        for (let i = 0, il = id.rowCount; i < il; ++i) {
+            const _id = id.value(i)
+            if (map.has(_id)) continue
+            const _type = type.value(i)
+            if (SaccharideCompIdMap.has(_id)) {
+                map.set(_id, SaccharideCompIdMap.get(_id)!)
+            } else if (getMoleculeType(_type, _id) === MoleculeType.Saccharide) {
+                map.set(_id, UnknownSaccharideComponent)
+            }
+        }
+    } else {
+        const uniqueNames = getUniqueComponentNames(data)
+        SaccharideCompIdMap.forEach((v, k) => {
+            if (!map.has(k) && uniqueNames.has(k)) map.set(k, v)
+        })
+    }
+    return map
+}
+
+const getUniqueComponentNames = memoize1((data: BasicData) => {
+    const uniqueNames = new Set<string>()
+    const { label_comp_id, auth_comp_id } = data.atom_site
+    const comp_id = label_comp_id.isDefined ? label_comp_id : auth_comp_id;
+    for (let i = 0, il = comp_id.rowCount; i < il; ++i) {
+        uniqueNames.add(comp_id.value(i))
+    }
+    return uniqueNames
+})
+
+export function getProperties(data: BasicData): Model['properties'] {
+    return {
+        modifiedResidues: getModifiedResidueNameMap(data),
+        missingResidues: getMissingResidues(data),
+        chemicalComponentMap: getChemicalComponentMap(data),
+        saccharideComponentMap: getSaccharideComponentMap(data)
+    }
+}

+ 66 - 0
src/mol-model-formats/structure/basic/schema.ts

@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
+import { Table } from '../../../mol-data/db';
+
+export type Entry = Table<mmCIF_Schema['entry']>
+export type Struct = Table<mmCIF_Schema['struct']>
+export type StructAsym = Table<mmCIF_Schema['struct_asym']>
+export type IhmModelList = Table<mmCIF_Schema['ihm_model_list']>
+export type IhmModelGroup = Table<mmCIF_Schema['ihm_model_group']>
+export type IhmModelGroupLink = Table<mmCIF_Schema['ihm_model_group_link']>
+export type Entity = Table<mmCIF_Schema['entity']>
+export type EntityPoly = Table<mmCIF_Schema['entity_poly']>
+export type EntityPolySeq = Table<mmCIF_Schema['entity_poly_seq']>
+export type EntityBranch = Table<mmCIF_Schema['pdbx_entity_branch']>
+export type ChemComp = Table<mmCIF_Schema['chem_comp']>
+export type ChemCompIdentifier = Table<mmCIF_Schema['pdbx_chem_comp_identifier']>
+export type StructModResidue = Table<mmCIF_Schema['pdbx_struct_mod_residue']>
+export type AtomSite = Table<mmCIF_Schema['atom_site']>
+export type IhmSphereObjSite = Table<mmCIF_Schema['ihm_sphere_obj_site']>
+export type IhmGaussianObjSite =Table<mmCIF_Schema['ihm_gaussian_obj_site']>
+export type UnobsOrZeroOccResidues =Table<mmCIF_Schema['pdbx_unobs_or_zero_occ_residues']>
+
+export const BasicSchema = {
+    entry: mmCIF_Schema.entry,
+    struct: mmCIF_Schema.struct,
+    struct_asym: mmCIF_Schema.struct_asym,
+    ihm_model_list: mmCIF_Schema.ihm_model_list,
+    ihm_model_group: mmCIF_Schema.ihm_model_group,
+    ihm_model_group_link: mmCIF_Schema.ihm_model_group_link,
+    entity: mmCIF_Schema.entity,
+    entity_poly: mmCIF_Schema.entity_poly,
+    entity_poly_seq: mmCIF_Schema.entity_poly_seq,
+    pdbx_entity_branch: mmCIF_Schema.pdbx_entity_branch,
+    chem_comp: mmCIF_Schema.chem_comp,
+    pdbx_chem_comp_identifier: mmCIF_Schema.pdbx_chem_comp_identifier,
+    pdbx_struct_mod_residue: mmCIF_Schema.pdbx_struct_mod_residue,
+    atom_site: mmCIF_Schema.atom_site,
+    ihm_sphere_obj_site: mmCIF_Schema.ihm_sphere_obj_site,
+    ihm_gaussian_obj_site: mmCIF_Schema.ihm_gaussian_obj_site,
+    pdbx_unobs_or_zero_occ_residues: mmCIF_Schema.pdbx_unobs_or_zero_occ_residues,
+}
+
+export interface BasicData {
+    entry: Entry
+    struct: Struct
+    struct_asym: StructAsym
+    ihm_model_list: IhmModelList
+    ihm_model_group: IhmModelGroup
+    ihm_model_group_link: IhmModelGroupLink
+    entity: Entity
+    entity_poly: EntityPoly
+    entity_poly_seq: EntityPolySeq
+    pdbx_entity_branch: EntityBranch
+    chem_comp: ChemComp
+    pdbx_chem_comp_identifier: ChemCompIdentifier
+    pdbx_struct_mod_residue: StructModResidue
+    atom_site: AtomSite
+    ihm_sphere_obj_site: IhmSphereObjSite
+    ihm_gaussian_obj_site: IhmGaussianObjSite
+    pdbx_unobs_or_zero_occ_residues: UnobsOrZeroOccResidues
+}

+ 5 - 5
src/mol-model-formats/structure/mmcif/sequence.ts → src/mol-model-formats/structure/basic/sequence.ts

@@ -1,24 +1,24 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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 { mmCIF_Database as mmCIF } from '../../../mol-io/reader/cif/schema/mmcif'
 import StructureSequence from '../../../mol-model/structure/model/properties/sequence'
 import { Column } from '../../../mol-data/db';
 import { AtomicHierarchy } from '../../../mol-model/structure/model/properties/atomic';
 import { Entities } from '../../../mol-model/structure/model/properties/common';
 import { Sequence } from '../../../mol-model/sequence';
 import { CoarseHierarchy } from '../../../mol-model/structure/model/properties/coarse';
+import { BasicData } from './schema';
 
-export function getSequence(cif: mmCIF, entities: Entities, atomicHierarchy: AtomicHierarchy, coarseHierarchy: CoarseHierarchy, modResMap: ReadonlyMap<string, string>): StructureSequence {
-    if (!cif.entity_poly_seq._rowCount) {
+export function getSequence(data: BasicData, entities: Entities, atomicHierarchy: AtomicHierarchy, coarseHierarchy: CoarseHierarchy, modResMap: ReadonlyMap<string, string>): StructureSequence {
+    if (!data.entity_poly_seq || !data.entity_poly_seq._rowCount) {
         return StructureSequence.fromHierarchy(entities, atomicHierarchy, coarseHierarchy, modResMap);
     }
 
-    const { entity_id, num, mon_id } = cif.entity_poly_seq;
+    const { entity_id, num, mon_id } = data.entity_poly_seq;
 
     const byEntityKey: StructureSequence['byEntityKey'] = {};
     const sequences: StructureSequence.Entity[] = [];

+ 7 - 4
src/mol-model-formats/structure/mmcif/sort.ts → src/mol-model-formats/structure/basic/sort.ts

@@ -4,12 +4,15 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { mmCIF_Database } from '../../../mol-io/reader/cif/schema/mmcif';
 import { createRangeArray, makeBuckets } from '../../../mol-data/util';
 import { Column, Table } from '../../../mol-data/db';
 import { RuntimeContext } from '../../../mol-task';
+import { AtomSite } from './schema';
 
-export type SortedAtomSite = mmCIF_Database['atom_site'] & { sourceIndex: Column<number> }
+export type SortedAtomSite = {
+    atom_site: AtomSite
+    sourceIndex: Column<number>
+}
 
 function isIdentity(xs: ArrayLike<number>) {
     for (let i = 0, _i = xs.length; i < _i; i++) {
@@ -18,7 +21,7 @@ function isIdentity(xs: ArrayLike<number>) {
     return true;
 }
 
-export async function sortAtomSite(ctx: RuntimeContext, atom_site: mmCIF_Database['atom_site'], start: number, end: number) {
+export async function sortAtomSite(ctx: RuntimeContext, atom_site: AtomSite, start: number, end: number): Promise<SortedAtomSite> {
     const indices = createRangeArray(start, end - 1);
 
     const { label_entity_id, label_asym_id, label_seq_id } = atom_site;
@@ -42,7 +45,7 @@ export async function sortAtomSite(ctx: RuntimeContext, atom_site: mmCIF_Databas
     }
 
     return {
-        atom_site: Table.view(atom_site, atom_site._schema, indices) as mmCIF_Database['atom_site'],
+        atom_site: Table.view(atom_site, atom_site._schema, indices) as AtomSite,
         sourceIndex: Column.ofIntArray(indices)
     };
 }

+ 20 - 0
src/mol-model-formats/structure/basic/util.ts

@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2017-2020 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 { BasicData } from './schema';
+import { Table } from '../../../mol-data/db';
+
+export function getModelGroupName(model_id: number, data: BasicData) {
+    const { ihm_model_group, ihm_model_group_link } = data;
+
+    const link = Table.pickRow(ihm_model_group_link, i => ihm_model_group_link.model_id.value(i) === model_id)
+    if (link) {
+        const group = Table.pickRow(ihm_model_group, i => ihm_model_group.id.value(i) === link.group_id)
+        if (group) return group.name
+    }
+    return ''
+}

+ 55 - 0
src/mol-model-formats/structure/common/property.ts

@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { CustomPropertyDescriptor, Model } from '../../../mol-model/structure';
+import { ModelFormat } from '../format';
+
+class FormatRegistry<T> {
+    map = new Map<ModelFormat['kind'], (model: Model) => T | undefined>()
+
+    add(kind: ModelFormat['kind'], obtain: (model: Model) => T | undefined) {
+        this.map.set(kind, obtain)
+    }
+
+    get(kind: ModelFormat['kind']) {
+        return this.map.get(kind)
+    }
+}
+
+export { FormatPropertyProvider as FormatPropertyProvider }
+
+interface FormatPropertyProvider<T> {
+    readonly descriptor: CustomPropertyDescriptor
+    readonly formatRegistry: FormatRegistry<T>
+    get(model: Model): T | undefined
+    set(model: Model, value: T): void
+}
+
+namespace FormatPropertyProvider {
+    export function create<T>(descriptor: CustomPropertyDescriptor): FormatPropertyProvider<T> {
+        const { name } = descriptor
+        const formatRegistry = new FormatRegistry<T>()
+
+        return {
+            descriptor,
+            formatRegistry,
+            get(model: Model): T | undefined {
+                if (model._staticPropertyData[name]) return model._staticPropertyData[name]
+                if (model.customProperties.has(descriptor)) return
+
+                const obtain = formatRegistry.get(model.sourceData.kind)
+                if (!obtain) return
+
+                model._staticPropertyData[name] = obtain(model)
+                model.customProperties.add(descriptor)
+                return model._staticPropertyData[name]
+            },
+            set(model: Model, value: T) {
+                model._staticPropertyData[name] = value
+            }
+        }
+    }
+}

+ 11 - 3
src/mol-model-formats/structure/format.ts

@@ -1,18 +1,26 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020 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 { mmCIF_Database } from '../../mol-io/reader/cif/schema/mmcif';
 import { CIF, CifFrame } from '../../mol-io/reader/cif';
 
+interface Format { readonly kind: string, name: string }
+
 type ModelFormat =
     | ModelFormat.mmCIF
 
 namespace ModelFormat {
-    export interface mmCIF { kind: 'mmCIF', data: mmCIF_Database, frame: CifFrame }
-    export function mmCIF(frame: CifFrame, data?: mmCIF_Database): mmCIF { return { kind: 'mmCIF', data: data || CIF.schema.mmCIF(frame), frame }; }
+    export interface mmCIF extends Format {
+        readonly kind: 'mmCIF', data: mmCIF_Database, frame: CifFrame
+    }
+    export function mmCIF(frame: CifFrame, data?: mmCIF_Database): mmCIF {
+        if (!data) data = CIF.schema.mmCIF(frame)
+        return { kind: 'mmCIF', name: data._name, data, frame };
+    }
 }
 
 export { ModelFormat }

+ 0 - 88
src/mol-model-formats/structure/mmcif/anisotropic.ts

@@ -1,88 +0,0 @@
-/**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { Table } from '../../../mol-data/db';
-import { Model, CustomPropertyDescriptor } from '../../../mol-model/structure';
-import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
-import { CifWriter } from '../../../mol-io/writer/cif';
-
-export interface AtomSiteAnisotrop {
-    data: Table<AtomSiteAnisotrop.Schema['atom_site_anisotrop']>
-    /** maps atom_site-index to atom_site_anisotrop-index */
-    elementToAnsiotrop: Int32Array
-}
-
-export namespace AtomSiteAnisotrop {
-    export function getAtomSiteAnisotrop(model: Model) {
-        if (model.sourceData.kind !== 'mmCIF') return void 0;
-        const { atom_site_anisotrop } = model.sourceData.data
-        return Table.ofColumns(Schema.atom_site_anisotrop, atom_site_anisotrop);
-    }
-
-    export const PropName = '__AtomSiteAnisotrop__';
-    export function get(model: Model): AtomSiteAnisotrop | undefined {
-        if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName]
-        if (!model.customProperties.has(Descriptor)) return void 0;
-
-        const data = getAtomSiteAnisotrop(model);
-        if (!data) return void 0;
-
-        const prop = { data, elementToAnsiotrop: getElementToAnsiotrop(model, data) }
-        set(model, prop)
-
-        return prop;
-    }
-    function set(model: Model, prop: AtomSiteAnisotrop) {
-        (model._staticPropertyData[PropName] as AtomSiteAnisotrop) = prop;
-    }
-
-    export const Schema = { atom_site_anisotrop: mmCIF_Schema['atom_site_anisotrop'] };
-    export type Schema = typeof Schema
-
-    export const Descriptor: CustomPropertyDescriptor = {
-        name: 'atom_site_anisotrop',
-        cifExport: {
-            prefix: '',
-            categories: [{
-                name: 'atom_site_anisotrop',
-                instance(ctx) {
-                    const atom_site_anisotrop = getAtomSiteAnisotrop(ctx.firstModel);
-                    if (!atom_site_anisotrop) return CifWriter.Category.Empty;
-                    return CifWriter.Category.ofTable(atom_site_anisotrop);
-                }
-            }]
-        }
-    };
-
-    function getElementToAnsiotrop(model: Model, data: Table<Schema['atom_site_anisotrop']>) {
-        const { atomId } = model.atomicConformation
-        const atomIdToElement = new Int32Array(atomId.rowCount)
-        atomIdToElement.fill(-1)
-        for (let i = 0, il = atomId.rowCount; i < il; i++) {
-            atomIdToElement[atomId.value(i)] = i
-        }
-
-        const { id } = data
-        const elementToAnsiotrop = new Int32Array(atomId.rowCount)
-        elementToAnsiotrop.fill(-1)
-        for (let i = 0, il = id.rowCount; i < il; ++i) {
-            const ei = atomIdToElement[id.value(i)]
-            if (ei !== -1) elementToAnsiotrop[ei] = i
-        }
-
-        return elementToAnsiotrop
-    }
-
-    export async function attachFromMmCif(model: Model) {
-        if (model.customProperties.has(Descriptor)) return true;
-        if (model.sourceData.kind !== 'mmCIF') return false;
-        const atomSiteAnisotrop = getAtomSiteAnisotrop(model);
-        if (!atomSiteAnisotrop || atomSiteAnisotrop._rowCount === 0) return false;
-
-        model.customProperties.add(Descriptor);
-        return true;
-    }
-}

+ 0 - 9
src/mol-model-formats/structure/mmcif/bonds.ts

@@ -1,9 +0,0 @@
-/**
- * Copyright (c) 2017-2018 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 './bonds/comp'
-export * from './bonds/struct_conn'

+ 0 - 8
src/mol-model-formats/structure/mmcif/pair-restraint.ts

@@ -1,8 +0,0 @@
-/**
- * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-export * from './pair-restraints/cross-links'
-// export * from './pair-restraints/predicted-contacts'

+ 49 - 500
src/mol-model-formats/structure/mmcif/parser.ts

@@ -1,529 +1,78 @@
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 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 { Column, Table } from '../../../mol-data/db';
-import { mmCIF_Database, mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
-import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/geometry';
-import { Tensor, Vec3, Mat3 } from '../../../mol-math/linear-algebra';
+import { Table } from '../../../mol-data/db';
+import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
 import { RuntimeContext } from '../../../mol-task';
-import UUID from '../../../mol-util/uuid';
 import { Model } from '../../../mol-model/structure/model/model';
-import { Entities, ChemicalComponent, MissingResidue, EntitySubtype } from '../../../mol-model/structure/model/properties/common';
-import { CustomProperties } from '../../../mol-model/structure';
-import { ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry';
-import { createAssemblies } from './assembly';
-import { getAtomicHierarchyAndConformation } from './atomic';
-import { ComponentBond } from './bonds';
-import { getIHMCoarse, EmptyIHMCoarse, IHMData } from './ihm';
-import { getSecondaryStructure } from './secondary-structure';
-import { getSequence } from './sequence';
-import { sortAtomSite } from './sort';
-import { StructConn } from './bonds/struct_conn';
-import { getMoleculeType, MoleculeType, getEntityType, getEntitySubtype, getDefaultChemicalComponent } from '../../../mol-model/structure/model/types';
 import { ModelFormat } from '../format';
-import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
 import mmCIF_Format = ModelFormat.mmCIF
-import { memoize1 } from '../../../mol-util/memoize';
-import { ElementIndex, EntityIndex } from '../../../mol-model/structure/model';
-import { AtomSiteAnisotrop } from './anisotropic';
-import { getAtomicRanges } from '../../../mol-model/structure/model/properties/utils/atomic-ranges';
+import { AtomSiteAnisotrop } from '../property/anisotropic';
+import { _parse_basic } from '../basic/parser';
+import { ModelSymmetry } from '../property/symmetry';
+import { ModelSecondaryStructure } from '../property/secondary-structure';
+import { ComponentBond } from '../property/bonds/comp';
+import { StructConn } from '../property/bonds/struct_conn';
+import { ModelCrossLinkRestraint } from '../property/pair-restraints/cross-links';
 
 export async function _parse_mmCif(format: mmCIF_Format, ctx: RuntimeContext) {
-    const formatData = getFormatData(format)
-    const isIHM = format.data.ihm_model_list._rowCount > 0;
-    return isIHM ? await readIHM(ctx, format, formatData) : await readStandard(ctx, format, formatData);
+    return _parse_basic(format.data, format, ctx)
 }
 
-type AtomSite = mmCIF_Database['atom_site']
-
-function getSymmetry(format: mmCIF_Format): ModelSymmetry {
-    const assemblies = createAssemblies(format);
-    const spacegroup = getSpacegroup(format);
-    const isNonStandardCrytalFrame = checkNonStandardCrystalFrame(format, spacegroup);
-    return { assemblies, spacegroup, isNonStandardCrytalFrame, ncsOperators: getNcsOperators(format) };
-}
-
-function checkNonStandardCrystalFrame(format: mmCIF_Format, spacegroup: Spacegroup) {
-    const { atom_sites } = format.data;
-    if (atom_sites._rowCount === 0) return false;
-    // TODO: parse atom_sites transform and check if it corresponds to the toFractional matrix
-    return false;
-}
-
-function getSpacegroupNameOrNumber(symmetry: mmCIF_Format['data']['symmetry']) {
-    const groupNumber = symmetry['Int_Tables_number'].value(0);
-    const groupName = symmetry['space_group_name_H-M'].value(0);
-    if (!symmetry['Int_Tables_number'].isDefined) return groupName
-    if (!symmetry['space_group_name_H-M'].isDefined) return groupNumber
-    return groupName
-}
-
-function getSpacegroup(format: mmCIF_Format): Spacegroup {
-    const { symmetry, cell } = format.data;
-    if (symmetry._rowCount === 0 || cell._rowCount === 0) return Spacegroup.ZeroP1;
-    const nameOrNumber = getSpacegroupNameOrNumber(symmetry)
-    const spaceCell = SpacegroupCell.create(nameOrNumber,
-        Vec3.create(cell.length_a.value(0), cell.length_b.value(0), cell.length_c.value(0)),
-        Vec3.scale(Vec3.zero(), Vec3.create(cell.angle_alpha.value(0), cell.angle_beta.value(0), cell.angle_gamma.value(0)), Math.PI / 180));
-
-    return Spacegroup.create(spaceCell);
-}
-
-function getNcsOperators(format: mmCIF_Format) {
-    const { struct_ncs_oper } = format.data;
-    if (struct_ncs_oper._rowCount === 0) return void 0;
-    const { id, matrix, vector } = struct_ncs_oper;
-
-    const matrixSpace = mmCIF_Schema.struct_ncs_oper.matrix.space, vectorSpace = mmCIF_Schema.struct_ncs_oper.vector.space;
-
-    const opers: SymmetryOperator[] = [];
-    for (let i = 0; i < struct_ncs_oper._rowCount; i++) {
-        const m = Tensor.toMat3(Mat3(), matrixSpace, matrix.value(i));
-        const v = Tensor.toVec3(Vec3(), vectorSpace, vector.value(i));
-        if (!SymmetryOperator.checkIfRotationAndTranslation(m, v)) continue;
-        // ignore non-identity 'given' NCS operators
-        if (struct_ncs_oper.code.value(i) === 'given' && !Mat3.isIdentity(m) && !Vec3.isZero(v)) continue;
-        const ncsId = id.value(i)
-        opers[opers.length] = SymmetryOperator.ofRotationAndOffset(`ncs_${ncsId}`, m, v, ncsId);
-    }
-    return opers;
-}
-
-function getModifiedResidueNameMap(format: mmCIF_Format): Model['properties']['modifiedResidues'] {
-    const data = format.data.pdbx_struct_mod_residue;
-    const parentId = new Map<string, string>();
-    const details = new Map<string, string>();
-    const comp_id = data.label_comp_id.isDefined ? data.label_comp_id : data.auth_comp_id;
-    const parent_id = data.parent_comp_id, details_data = data.details;
-
-    for (let i = 0; i < data._rowCount; i++) {
-        const id = comp_id.value(i);
-        parentId.set(id, parent_id.value(i));
-        details.set(id, details_data.value(i));
-    }
-
-    return { parentId, details };
-}
-
-function getMissingResidues(format: mmCIF_Format): Model['properties']['missingResidues'] {
-    const map = new Map<string, MissingResidue>();
-    const c = format.data.pdbx_unobs_or_zero_occ_residues
-
-    const getKey = (model_num: number, asym_id: string, seq_id: number) => {
-        return `${model_num}|${asym_id}|${seq_id}`
-    }
-
-    for (let i = 0, il = c._rowCount; i < il; ++i) {
-        const key = getKey(c.PDB_model_num.value(i), c.label_asym_id.value(i), c.label_seq_id.value(i))
-        map.set(key, { polymer_flag: c.polymer_flag.value(i), occupancy_flag: c.occupancy_flag.value(i) })
-    }
-
-    return {
-        has: (model_num: number, asym_id: string, seq_id: number) => {
-            return map.has(getKey(model_num, asym_id, seq_id))
-        },
-        get: (model_num: number, asym_id: string, seq_id: number) => {
-            return map.get(getKey(model_num, asym_id, seq_id))
-        },
-        size: map.size
-    }
+function modelSymmetryFromMmcif(model: Model) {
+    if (model.sourceData.kind !== 'mmCIF') return;
+    return ModelSymmetry.fromData(model.sourceData.data)
 }
+ModelSymmetry.Provider.formatRegistry.add('mmCIF', modelSymmetryFromMmcif)
 
-function getChemicalComponentMap(format: mmCIF_Format): Model['properties']['chemicalComponentMap'] {
-    const map = new Map<string, ChemicalComponent>();
-    const { chem_comp } = format.data
-
-    if (chem_comp._rowCount > 0) {
-        const { id } = chem_comp
-        for (let i = 0, il = id.rowCount; i < il; ++i) {
-            map.set(id.value(i), Table.getRow(chem_comp, i))
-        }
-    } else {
-        const uniqueNames = getUniqueComponentNames(format);
-        uniqueNames.forEach(n => {
-            map.set(n, getDefaultChemicalComponent(n));
-        });
-    }
-    return map
+function secondaryStructureFromMmcif(model: Model) {
+    if (model.sourceData.kind !== 'mmCIF') return;
+    const { struct_conf, struct_sheet_range } = model.sourceData.data
+    return ModelSecondaryStructure.fromStruct(struct_conf, struct_sheet_range, model.atomicHierarchy)
 }
+ModelSecondaryStructure.Provider.formatRegistry.add('mmCIF', secondaryStructureFromMmcif)
 
-function getSaccharideComponentMap(format: mmCIF_Format): SaccharideComponentMap {
-    const map = new Map<string, SaccharideComponent>();
-
-    if (format.data.pdbx_chem_comp_identifier._rowCount > 0) {
-        // note that `pdbx_chem_comp_identifier` does not contain
-        // a 'SNFG CARBOHYDRATE SYMBOL' entry for 'Unknown' saccharide components
-        // so we always need to check `chem_comp` for those
-        const { comp_id, type, identifier } = format.data.pdbx_chem_comp_identifier
-        for (let i = 0, il = comp_id.rowCount; i < il; ++i) {
-            if (type.value(i) === 'SNFG CARBOHYDRATE SYMBOL' ||
-                type.value(i) === 'SNFG CARB SYMBOL' // legacy, to be removed from mmCIF dictionary
-            ) {
-                const snfgName = identifier.value(i)
-                const saccharideComp = SaccharidesSnfgMap.get(snfgName)
-                if (saccharideComp) {
-                    map.set(comp_id.value(i), saccharideComp)
-                } else {
-                    console.warn(`Unknown SNFG name '${snfgName}'`)
-                }
-            }
-        }
-    }
-
-    if (format.data.chem_comp._rowCount > 0) {
-        const { id, type  } = format.data.chem_comp
-        for (let i = 0, il = id.rowCount; i < il; ++i) {
-            const _id = id.value(i)
-            if (map.has(_id)) continue
-            const _type = type.value(i)
-            if (SaccharideCompIdMap.has(_id)) {
-                map.set(_id, SaccharideCompIdMap.get(_id)!)
-            } else if (getMoleculeType(_type, _id) === MoleculeType.Saccharide) {
-                map.set(_id, UnknownSaccharideComponent)
-            }
-        }
-    } else {
-        const uniqueNames = getUniqueComponentNames(format)
-        SaccharideCompIdMap.forEach((v, k) => {
-            if (!map.has(k) && uniqueNames.has(k)) map.set(k, v)
-        })
-    }
-    return map
+function atomSiteAnisotropFromMmcif(model: Model) {
+    if (model.sourceData.kind !== 'mmCIF') return;
+    const { atom_site_anisotrop } = model.sourceData.data
+    const data = Table.ofColumns(mmCIF_Schema['atom_site_anisotrop'], atom_site_anisotrop);
+    const elementToAnsiotrop = AtomSiteAnisotrop.getElementToAnsiotrop(model, data)
+    return { data, elementToAnsiotrop }
 }
+AtomSiteAnisotrop.Provider.formatRegistry.add('mmCIF', atomSiteAnisotropFromMmcif)
 
-const getUniqueComponentNames = memoize1((format: mmCIF_Format) => {
-    const uniqueNames = new Set<string>()
-    const data = format.data.atom_site
-    const comp_id = data.label_comp_id.isDefined ? data.label_comp_id : data.auth_comp_id;
-    for (let i = 0, il = comp_id.rowCount; i < il; ++i) {
-        uniqueNames.add(comp_id.value(i))
-    }
-    return uniqueNames
-})
-
-export interface FormatData {
-    modifiedResidues: Model['properties']['modifiedResidues']
-    missingResidues: Model['properties']['missingResidues']
-    chemicalComponentMap: Model['properties']['chemicalComponentMap']
-    saccharideComponentMap: Model['properties']['saccharideComponentMap']
-}
-
-function getFormatData(format: mmCIF_Format): FormatData {
+function componentBondFromMmcif(model: Model) {
+    if (model.sourceData.kind !== 'mmCIF') return;
+    const { chem_comp_bond } = model.sourceData.data;
+    if (chem_comp_bond._rowCount === 0) return;
     return {
-        modifiedResidues: getModifiedResidueNameMap(format),
-        missingResidues: getMissingResidues(format),
-        chemicalComponentMap: getChemicalComponentMap(format),
-        saccharideComponentMap: getSaccharideComponentMap(format)
+        data: chem_comp_bond,
+        entries: ComponentBond.getEntriesFromChemCompBond(chem_comp_bond)
     }
 }
+ComponentBond.Provider.formatRegistry.add('mmCIF', componentBondFromMmcif)
 
-function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model): Model {
-    const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, formatData, previous);
-    const modelNum = atom_site.pdbx_PDB_model_num.value(0)
-    if (previous && atomic.sameAsPrevious) {
-        return {
-            ...previous,
-            id: UUID.create22(),
-            modelNum,
-            atomicConformation: atomic.conformation,
-            _dynamicPropertyData: Object.create(null)
-        };
-    }
-
-    const coarse = EmptyIHMCoarse;
-    const sequence = getSequence(format.data, entities, atomic.hierarchy, coarse.hierarchy, formatData.modifiedResidues.parentId)
-    const atomicRanges = getAtomicRanges(atomic.hierarchy, entities, atomic.conformation, sequence)
-
-    const entry = format.data.entry.id.valueKind(0) === Column.ValueKind.Present
-        ? format.data.entry.id.value(0)
-        : format.data._name;
-
-    const label: string[] = []
-    if (entry) label.push(entry)
-    if (format.data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(format.data.struct.title.value(0))
-
-    return {
-        id: UUID.create22(),
-        entryId: entry,
-        label: label.join(' | '),
-        entry,
-        sourceData: format,
-        modelNum,
-        entities,
-        symmetry: getSymmetry(format),
-        sequence,
-        atomicHierarchy: atomic.hierarchy,
-        atomicConformation: atomic.conformation,
-        atomicRanges,
-        coarseHierarchy: coarse.hierarchy,
-        coarseConformation: coarse.conformation,
-        properties: {
-            secondaryStructure: getSecondaryStructure(format.data, atomic.hierarchy),
-            ...formatData
-        },
-        customProperties: new CustomProperties(),
-        _staticPropertyData: Object.create(null),
-        _dynamicPropertyData: Object.create(null)
-    };
-}
-
-function createModelIHM(format: mmCIF_Format, data: IHMData, formatData: FormatData): Model {
-    const atomic = getAtomicHierarchyAndConformation(data.atom_site, data.atom_site_sourceIndex, data.entities, formatData);
-    const coarse = getIHMCoarse(data, formatData);
-    const sequence = getSequence(format.data, data.entities, atomic.hierarchy, coarse.hierarchy, formatData.modifiedResidues.parentId)
-    const atomicRanges = getAtomicRanges(atomic.hierarchy, data.entities, atomic.conformation, sequence)
-
-    const entry = format.data.entry.id.valueKind(0) === Column.ValueKind.Present
-        ? format.data.entry.id.value(0)
-        : format.data._name;
-
-    const label: string[] = []
-    if (entry) label.push(entry)
-    if (format.data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(format.data.struct.title.value(0))
-    if (data.model_group_name) label.push(data.model_name)
-    if (data.model_group_name) label.push(data.model_group_name)
-
+function structConnFromMmcif(model: Model) {
+    if (model.sourceData.kind !== 'mmCIF') return;
+    const { struct_conn } = model.sourceData.data;
+    if (struct_conn._rowCount === 0) return;
+    const entries = StructConn.getEntriesFromStructConn(struct_conn, model)
     return {
-        id: UUID.create22(),
-        entryId: entry,
-        label: label.join(' | '),
-        entry,
-        sourceData: format,
-        modelNum: data.model_id,
-        entities: data.entities,
-        symmetry: getSymmetry(format),
-        sequence,
-        atomicHierarchy: atomic.hierarchy,
-        atomicConformation: atomic.conformation,
-        atomicRanges,
-        coarseHierarchy: coarse.hierarchy,
-        coarseConformation: coarse.conformation,
-        properties: {
-            secondaryStructure: getSecondaryStructure(format.data, atomic.hierarchy),
-            ...formatData
-        },
-        customProperties: new CustomProperties(),
-        _staticPropertyData: Object.create(null),
-        _dynamicPropertyData: Object.create(null)
-    };
-}
-
-function attachProps(model: Model) {
-    ComponentBond.attachFromMmCif(model);
-    StructConn.attachFromMmCif(model);
-    AtomSiteAnisotrop.attachFromMmCif(model);
-}
-
-function findModelEnd(num: Column<number>, startIndex: number) {
-    const rowCount = num.rowCount;
-    if (!num.isDefined) return rowCount;
-    let endIndex = startIndex + 1;
-    while (endIndex < rowCount && num.areValuesEqual(startIndex, endIndex)) endIndex++;
-    return endIndex;
-}
-
-function getEntities(format: mmCIF_Format): Entities {
-    let entityData: Table<mmCIF_Schema['entity']>
-
-    if (!format.data.entity.id.isDefined) {
-        const entityIds = new Set<string>()
-
-        const ids: mmCIF_Schema['entity']['id']['T'][] = []
-        const types: mmCIF_Schema['entity']['type']['T'][] = []
-
-        const { label_entity_id, label_comp_id } = format.data.atom_site;
-        for (let i = 0 as ElementIndex, il = format.data.atom_site._rowCount; i < il; i++) {
-            const entityId = label_entity_id.value(i);
-            if (!entityIds.has(entityId)) {
-                ids.push(entityId)
-                types.push(getEntityType(label_comp_id.value(i)))
-                entityIds.add(entityId)
-            }
-        }
-
-        const { entity_id: sphere_entity_id } = format.data.ihm_sphere_obj_site;
-        for (let i = 0 as ElementIndex, il = format.data.ihm_sphere_obj_site._rowCount; i < il; i++) {
-            const entityId = sphere_entity_id.value(i);
-            if (!entityIds.has(entityId)) {
-                ids.push(entityId)
-                types.push('polymer')
-                entityIds.add(entityId)
-            }
-        }
-
-        const { entity_id: gaussian_entity_id } = format.data.ihm_gaussian_obj_site;
-        for (let i = 0 as ElementIndex, il = format.data.ihm_gaussian_obj_site._rowCount; i < il; i++) {
-            const entityId = gaussian_entity_id.value(i);
-            if (!entityIds.has(entityId)) {
-                ids.push(entityId)
-                types.push('polymer')
-                entityIds.add(entityId)
-            }
-        }
-
-        entityData = Table.ofColumns(mmCIF_Schema.entity, {
-            ...format.data.entity,
-            id: Column.ofArray({ array: ids, schema: mmCIF_Schema.entity.id }),
-            type: Column.ofArray({ array: types, schema: mmCIF_Schema.entity.type }),
-        })
-    } else {
-        entityData = format.data.entity;
-    }
-
-    const getEntityIndex = Column.createIndexer<string, EntityIndex>(entityData.id)
-
-    //
-
-    const subtypes: EntitySubtype[] = new Array(entityData._rowCount)
-    subtypes.fill('other')
-
-    const entityIds = new Set<string>()
-    let assignSubtype = false
-
-    if (format.data.entity_poly.entity_id.isDefined) {
-        const { entity_id, type, _rowCount } = format.data.entity_poly
-        for (let i = 0; i < _rowCount; ++i) {
-            const entityId = entity_id.value(i)
-            subtypes[getEntityIndex(entityId)] = type.value(i)
-            entityIds.add(entityId)
-        }
-    } else {
-        assignSubtype = true
-    }
-
-    if (format.data.pdbx_entity_branch.entity_id.isDefined) {
-        const { entity_id, type, _rowCount } = format.data.pdbx_entity_branch
-        for (let i = 0; i < _rowCount; ++i) {
-            const entityId = entity_id.value(i)
-            subtypes[getEntityIndex(entityId)] = type.value(i)
-            entityIds.add(entityId)
-        }
-    } else {
-        assignSubtype = true
-    }
-
-    if (assignSubtype) {
-        const chemCompType = new Map<string, string>()
-        const { id, type } = format.data.chem_comp;
-        for (let i = 0, il = format.data.chem_comp._rowCount; i < il; i++) {
-            chemCompType.set(id.value(i), type.value(i))
-        }
-
-        const { label_entity_id, label_comp_id } = format.data.atom_site;
-        for (let i = 0 as ElementIndex, il = format.data.atom_site._rowCount; i < il; i++) {
-            const entityId = label_entity_id.value(i);
-            if (!entityIds.has(entityId)) {
-                const compId = label_comp_id.value(i)
-                const compType = chemCompType.get(compId) || ''
-                subtypes[getEntityIndex(entityId)] = getEntitySubtype(compId, compType)
-                entityIds.add(entityId)
-            }
-        }
-        // TODO how to handle coarse?
-    }
-
-    const subtypeColumn = Column.ofArray({ array: subtypes, schema: EntitySubtype })
-
-    //
-
-    return { data: entityData, subtype: subtypeColumn, getEntityIndex };
-}
-
-async function readStandard(ctx: RuntimeContext, format: mmCIF_Format, formatData: FormatData) {
-    const atomCount = format.data.atom_site._rowCount;
-    const entities = getEntities(format)
-
-    const models: Model[] = [];
-    let modelStart = 0;
-    while (modelStart < atomCount) {
-        const modelEnd = findModelEnd(format.data.atom_site.pdbx_PDB_model_num, modelStart);
-        const { atom_site, sourceIndex } = await sortAtomSite(ctx, format.data.atom_site, modelStart, modelEnd);
-        const model = createStandardModel(format, atom_site, sourceIndex, entities, formatData, models.length > 0 ? models[models.length - 1] : void 0);
-        attachProps(model);
-        models.push(model);
-        modelStart = modelEnd;
+        data: struct_conn,
+        byAtomIndex: StructConn.getAtomIndexFromEntries(entries),
+        entries,
     }
-    return models;
 }
+StructConn.Provider.formatRegistry.add('mmCIF', structConnFromMmcif)
 
-function splitTable<T extends Table<any>>(table: T, col: Column<number>) {
-    const ret = new Map<number, { table: T, start: number, end: number }>()
-    const rowCount = table._rowCount;
-    let modelStart = 0;
-    while (modelStart < rowCount) {
-        const modelEnd = findModelEnd(col, modelStart);
-        const id = col.value(modelStart);
-        ret.set(id, {
-            table: Table.window(table, table._schema, modelStart, modelEnd) as T,
-            start: modelStart,
-            end: modelEnd
-        });
-        modelStart = modelEnd;
-    }
-    return ret;
+function crossLinkRestraintFromMmcif(model: Model) {
+    if (model.sourceData.kind !== 'mmCIF') return;
+    const { ihm_cross_link_restraint } = model.sourceData.data;
+    if (ihm_cross_link_restraint._rowCount === 0) return;
+    return ModelCrossLinkRestraint.fromTable(ihm_cross_link_restraint, model)
 }
-
-function getModelGroupName(model_id: number, format: mmCIF_Format) {
-    const { ihm_model_group, ihm_model_group_link } = format.data;
-
-    const link = Table.pickRow(ihm_model_group_link, i => ihm_model_group_link.model_id.value(i) === model_id)
-    if (link) {
-        const group = Table.pickRow(ihm_model_group, i => ihm_model_group.id.value(i) === link.group_id)
-        if (group) return group.name
-    }
-    return ''
-}
-
-async function readIHM(ctx: RuntimeContext, format: mmCIF_Format, formatData: FormatData) {
-    // when `atom_site.ihm_model_id` is undefined fall back to `atom_site.pdbx_PDB_model_num`
-    const atom_sites_modelColumn = format.data.atom_site.ihm_model_id.isDefined ? format.data.atom_site.ihm_model_id : format.data.atom_site.pdbx_PDB_model_num
-
-    const { ihm_model_list } = format.data;
-    const entities = getEntities(format)
-
-    const atom_sites = splitTable(format.data.atom_site, atom_sites_modelColumn);
-    // TODO: will coarse IHM records require sorting or will we trust it?
-    // ==> Probably implement a sort as as well and store the sourceIndex same as with atomSite
-    // If the sorting is implemented, updated mol-model/structure/properties: atom.sourceIndex
-    const sphere_sites = splitTable(format.data.ihm_sphere_obj_site, format.data.ihm_sphere_obj_site.model_id);
-    const gauss_sites = splitTable(format.data.ihm_gaussian_obj_site, format.data.ihm_gaussian_obj_site.model_id);
-
-    const models: Model[] = [];
-
-    const { model_id, model_name } = ihm_model_list;
-    for (let i = 0; i < ihm_model_list._rowCount; i++) {
-        const id = model_id.value(i);
-
-        let atom_site, atom_site_sourceIndex;
-        if (atom_sites.has(id)) {
-            const e = atom_sites.get(id)!;
-            // need to sort `format.data.atom_site` as `e.start` and `e.end` are indices into that
-            const { atom_site: sorted, sourceIndex } = await sortAtomSite(ctx, format.data.atom_site, e.start, e.end);
-            atom_site = sorted;
-            atom_site_sourceIndex = sourceIndex;
-        } else {
-            atom_site = Table.window(format.data.atom_site, format.data.atom_site._schema, 0, 0);
-            atom_site_sourceIndex = Column.ofIntArray([]);
-        }
-
-        const data: IHMData = {
-            model_id: id,
-            model_name: model_name.value(i),
-            model_group_name: getModelGroupName(id, format),
-            entities: entities,
-            atom_site,
-            atom_site_sourceIndex,
-            ihm_sphere_obj_site: sphere_sites.has(id) ? sphere_sites.get(id)!.table : Table.window(format.data.ihm_sphere_obj_site, format.data.ihm_sphere_obj_site._schema, 0, 0),
-            ihm_gaussian_obj_site: gauss_sites.has(id) ? gauss_sites.get(id)!.table : Table.window(format.data.ihm_gaussian_obj_site, format.data.ihm_gaussian_obj_site._schema, 0, 0)
-        };
-        const model = createModelIHM(format, data, formatData);
-        attachProps(model);
-        models.push(model);
-    }
-
-    return models;
-}
+ModelCrossLinkRestraint.Provider.formatRegistry.add('mmCIF', crossLinkRestraintFromMmcif)

+ 0 - 26
src/mol-model-formats/structure/mmcif/util.ts

@@ -1,26 +0,0 @@
-/**
- * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { Model } from '../../../mol-model/structure/model'
-import { ElementIndex } from '../../../mol-model/structure/model/indexing';
-
-export function findEntityIdByAsymId(model: Model, asymId: string) {
-    if (model.sourceData.kind !== 'mmCIF') return ''
-    const { struct_asym } = model.sourceData.data
-    for (let i = 0, n = struct_asym._rowCount; i < n; ++i) {
-        if (struct_asym.id.value(i) === asymId) return struct_asym.entity_id.value(i)
-    }
-    return ''
-}
-
-export function findAtomIndexByLabelName(model: Model, residueIndex: number, atomName: string, altLoc: string | null): ElementIndex {
-    const { offsets } = model.atomicHierarchy.residueAtomSegments;
-    const { label_atom_id, label_alt_id } = model.atomicHierarchy.atoms;
-    for (let i = offsets[residueIndex], n = offsets[residueIndex + 1]; i < n; ++i) {
-        if (label_atom_id.value(i) === atomName && (!altLoc || label_alt_id.value(i) === altLoc)) return i as ElementIndex;
-    }
-    return -1 as ElementIndex;
-}

+ 60 - 0
src/mol-model-formats/structure/property/anisotropic.ts

@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Table } from '../../../mol-data/db';
+import { Model, CustomPropertyDescriptor } from '../../../mol-model/structure';
+import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
+import { CifWriter } from '../../../mol-io/writer/cif';
+import { FormatPropertyProvider } from '../common/property';
+
+export { AtomSiteAnisotrop }
+
+type Anisotrop = Table<mmCIF_Schema['atom_site_anisotrop']>
+
+interface AtomSiteAnisotrop {
+    data: Anisotrop
+    /** maps atom_site-index to atom_site_anisotrop-index */
+    elementToAnsiotrop: Int32Array
+}
+
+namespace AtomSiteAnisotrop {
+    export const Descriptor: CustomPropertyDescriptor = {
+        name: 'atom_site_anisotrop',
+        cifExport: {
+            prefix: '',
+            categories: [{
+                name: 'atom_site_anisotrop',
+                instance(ctx) {
+                    const p = Provider.get(ctx.firstModel);
+                    if (!p) return CifWriter.Category.Empty;
+                    // TODO filter to write only data for elements that exist in model
+                    return CifWriter.Category.ofTable(p.data);
+                }
+            }]
+        }
+    };
+
+    export const Provider = FormatPropertyProvider.create<AtomSiteAnisotrop>(Descriptor)
+
+    export function getElementToAnsiotrop(model: Model, data: Anisotrop) {
+        const { atomId } = model.atomicConformation
+        const atomIdToElement = new Int32Array(atomId.rowCount)
+        atomIdToElement.fill(-1)
+        for (let i = 0, il = atomId.rowCount; i < il; i++) {
+            atomIdToElement[atomId.value(i)] = i
+        }
+
+        const { id } = data
+        const elementToAnsiotrop = new Int32Array(atomId.rowCount)
+        elementToAnsiotrop.fill(-1)
+        for (let i = 0, il = id.rowCount; i < il; ++i) {
+            const ei = atomIdToElement[id.value(i)]
+            if (ei !== -1) elementToAnsiotrop[ei] = i
+        }
+
+        return elementToAnsiotrop
+    }
+}

+ 11 - 11
src/mol-model-formats/structure/mmcif/assembly.ts → src/mol-model-formats/structure/property/assembly.ts

@@ -9,17 +9,20 @@ import { SymmetryOperator } from '../../../mol-math/geometry/symmetry-operator'
 import { Assembly, OperatorGroup, OperatorGroups } from '../../../mol-model/structure/model/properties/symmetry'
 import { Queries as Q } from '../../../mol-model/structure'
 import { StructureProperties } from '../../../mol-model/structure';
-import { ModelFormat } from '../format';
-import mmCIF_Format = ModelFormat.mmCIF
+import { Table } from '../../../mol-data/db';
+import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
 
-export function createAssemblies(format: mmCIF_Format): ReadonlyArray<Assembly> {
-    const { pdbx_struct_assembly } = format.data;
+type StructAssembly = Table<mmCIF_Schema['pdbx_struct_assembly']>
+type StructAssemblyGen = Table<mmCIF_Schema['pdbx_struct_assembly_gen']>
+type StructOperList = Table<mmCIF_Schema['pdbx_struct_oper_list']>
+
+export function createAssemblies(pdbx_struct_assembly: StructAssembly, pdbx_struct_assembly_gen: StructAssemblyGen, pdbx_struct_oper_list: StructOperList): ReadonlyArray<Assembly> {
     if (!pdbx_struct_assembly._rowCount) return [];
 
-    const matrices = getMatrices(format);
+    const matrices = getMatrices(pdbx_struct_oper_list);
     const assemblies: Assembly[] = [];
     for (let i = 0; i < pdbx_struct_assembly._rowCount; i++) {
-        assemblies[assemblies.length] = createAssembly(format, i, matrices);
+        assemblies[assemblies.length] = createAssembly(pdbx_struct_assembly, pdbx_struct_assembly_gen, i, matrices);
     }
     return assemblies;
 }
@@ -27,9 +30,7 @@ export function createAssemblies(format: mmCIF_Format): ReadonlyArray<Assembly>
 type Matrices = Map<string, Mat4>
 type Generator = { assemblyId: string, expression: string, asymIds: string[] }
 
-function createAssembly(format: mmCIF_Format, index: number, matrices: Matrices): Assembly {
-    const { pdbx_struct_assembly, pdbx_struct_assembly_gen } = format.data;
-
+function createAssembly(pdbx_struct_assembly: StructAssembly, pdbx_struct_assembly_gen: StructAssemblyGen, index: number, matrices: Matrices): Assembly {
     const id = pdbx_struct_assembly.id.value(index);
     const details = pdbx_struct_assembly.details.value(index);
     const generators: Generator[] = [];
@@ -70,8 +71,7 @@ function operatorGroupsProvider(generators: Generator[], matrices: Matrices): ()
     }
 }
 
-function getMatrices({ data }: mmCIF_Format): Matrices {
-    const { pdbx_struct_oper_list } = data;
+function getMatrices(pdbx_struct_oper_list: StructOperList): Matrices {
     const { id, matrix, vector, _schema } = pdbx_struct_oper_list;
     const matrices = new Map<string, Mat4>();
 

+ 36 - 73
src/mol-model-formats/structure/mmcif/bonds/comp.ts → src/mol-model-formats/structure/property/bonds/comp.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 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>
@@ -8,12 +8,14 @@
 import { Model } from '../../../../mol-model/structure/model/model'
 import { BondType } from '../../../../mol-model/structure/model/types'
 import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
-import { mmCIF_Database, mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
+import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
 import { CifWriter } from '../../../../mol-io/writer/cif'
 import { Table } from '../../../../mol-data/db';
+import { FormatPropertyProvider } from '../../common/property';
 
 export interface ComponentBond {
-    entries: Map<string, ComponentBond.Entry>
+    readonly data: Table<mmCIF_Schema['chem_comp_bond']>
+    readonly entries: ReadonlyMap<string, ComponentBond.Entry>
 }
 
 export namespace ComponentBond {
@@ -24,7 +26,9 @@ export namespace ComponentBond {
             categories: [{
                 name: 'chem_comp_bond',
                 instance(ctx) {
-                    const chem_comp_bond = getChemCompBond(ctx.firstModel);
+                    const p = Provider.get(ctx.firstModel);
+                    if (!p) return CifWriter.Category.Empty;
+                    const chem_comp_bond = p.data;
                     if (!chem_comp_bond) return CifWriter.Category.Empty;
 
                     const comp_names = ctx.structures[0].uniqueResidueNames;
@@ -40,73 +44,27 @@ export namespace ComponentBond {
         }
     }
 
-    export function attachFromMmCif(model: Model): boolean {
-        if (model.customProperties.has(Descriptor)) return true;
-        if (model.sourceData.kind !== 'mmCIF') return false;
-        const { chem_comp_bond } = model.sourceData.data;
-        if (chem_comp_bond._rowCount === 0) return false;
+    export const Provider = FormatPropertyProvider.create<ComponentBond>(Descriptor)
 
-        model.customProperties.add(Descriptor);
-        model._staticPropertyData.__ComponentBondData__ = chem_comp_bond;
-        return true;
-    }
-
-    export function attachFromExternalData(model: Model, table: mmCIF_Database['chem_comp_bond'], force = false) {
-        if (!force && model.customProperties.has(Descriptor)) return true;
-        if (model._staticPropertyData.__ComponentBondData__) delete model._staticPropertyData.__ComponentBondData__;
-        const chem_comp_bond = chemCompBondFromTable(model, table);
-        if (chem_comp_bond._rowCount === 0) return false;
-
-        model.customProperties.add(Descriptor);
-        model._staticPropertyData.__ComponentBondData__ = chem_comp_bond;
-        return true;
-    }
-
-    function chemCompBondFromTable(model: Model, table: mmCIF_Database['chem_comp_bond']): mmCIF_Database['chem_comp_bond'] {
+    export function chemCompBondFromTable(model: Model, table: Table<mmCIF_Schema['chem_comp_bond']>): Table<mmCIF_Schema['chem_comp_bond']> {
         return Table.pick(table, mmCIF_Schema.chem_comp_bond, (i: number) => {
             return model.properties.chemicalComponentMap.has(table.comp_id.value(i))
         })
     }
 
-    export class ComponentBondImpl implements ComponentBond {
-        entries: Map<string, ComponentBond.Entry> = new Map();
+    export function getEntriesFromChemCompBond(data: Table<mmCIF_Schema['chem_comp_bond']>) {
+        const entries: Map<string, Entry> = new Map();
 
-        addEntry(id: string) {
+        function addEntry(id: string) {
             let e = new Entry(id);
-            this.entries.set(id, e);
+            entries.set(id, e);
             return e;
         }
-    }
 
-    export class Entry {
-        map: Map<string, Map<string, { order: number, flags: number }>> = new Map();
-
-        add(a: string, b: string, order: number, flags: number, swap = true) {
-            let e = this.map.get(a);
-            if (e !== void 0) {
-                let f = e.get(b);
-                if (f === void 0) {
-                    e.set(b, { order, flags });
-                }
-            } else {
-                let map = new Map<string, { order: number, flags: number }>();
-                map.set(b, { order, flags });
-                this.map.set(a, map);
-            }
+        const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount } = data;
 
-            if (swap) this.add(b, a, order, flags, false);
-        }
-
-        constructor(public id: string) {
-        }
-    }
-
-    export function parseChemCompBond(data: mmCIF_Database['chem_comp_bond']): ComponentBond {
-        const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount: rowCount } = data;
-
-        const compBond = new ComponentBondImpl();
-        let entry = compBond.addEntry(comp_id.value(0)!);
-        for (let i = 0; i < rowCount; i++) {
+        let entry = addEntry(comp_id.value(0)!);
+        for (let i = 0; i < _rowCount; i++) {
             const id = comp_id.value(i)!;
             const nameA = atom_id_1.value(i)!;
             const nameB = atom_id_2.value(i)!;
@@ -114,7 +72,7 @@ export namespace ComponentBond {
             const aromatic = pdbx_aromatic_flag.value(i) === 'Y';
 
             if (entry.id !== id) {
-                entry = compBond.addEntry(id);
+                entry = addEntry(id);
             }
 
             let flags: number = BondType.Flag.Covalent;
@@ -132,23 +90,28 @@ export namespace ComponentBond {
             entry.add(nameA, nameB, ord, flags);
         }
 
-        return compBond;
+        return entries
     }
 
-    function getChemCompBond(model: Model) {
-        return model._staticPropertyData.__ComponentBondData__ as mmCIF_Database['chem_comp_bond'];
-    }
+    export class Entry {
+        readonly map: Map<string, Map<string, { order: number, flags: number }>> = new Map();
 
-    export const PropName = '__ComponentBond__';
-    export function get(model: Model): ComponentBond | undefined {
-        if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName];
-        if (!model.customProperties.has(Descriptor)) return void 0;
+        add(a: string, b: string, order: number, flags: number, swap = true) {
+            let e = this.map.get(a);
+            if (e !== void 0) {
+                let f = e.get(b);
+                if (f === void 0) {
+                    e.set(b, { order, flags });
+                }
+            } else {
+                let map = new Map<string, { order: number, flags: number }>();
+                map.set(b, { order, flags });
+                this.map.set(a, map);
+            }
 
-        const chem_comp_bond = getChemCompBond(model);
-        if (!chem_comp_bond) return void 0;
+            if (swap) this.add(b, a, order, flags, false);
+        }
 
-        const chemComp = parseChemCompBond(chem_comp_bond);
-        model._staticPropertyData[PropName] = chemComp;
-        return chemComp;
+        constructor(public readonly id: string) { }
     }
 }

+ 6 - 26
src/mol-model-formats/structure/mmcif/bonds/index-pair.ts → src/mol-model-formats/structure/property/bonds/index-pair.ts

@@ -1,13 +1,13 @@
 /**
- * Copyright (c) 2019 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 Mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Model } from '../../../../mol-model/structure/model/model'
 import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
 import { IntAdjacencyGraph } from '../../../../mol-math/graph';
 import { Column } from '../../../../mol-data/db';
+import { FormatPropertyProvider } from '../../common/property';
 
 export type IndexPairBonds = IntAdjacencyGraph<number, { readonly order: ArrayLike<number> }>
 
@@ -27,6 +27,8 @@ export namespace IndexPairBonds {
         name: 'index_pair_bonds',
     }
 
+    export const Provider = FormatPropertyProvider.create<IndexPairBonds>(Descriptor)
+
     export type Data = {
         pairs: {
             indexA: Column<number>,
@@ -36,33 +38,11 @@ export namespace IndexPairBonds {
         count: number
     }
 
-    export function attachFromData(model: Model, data: Data): boolean {
-        if (model.customProperties.has(Descriptor)) return true;
-
-        model.customProperties.add(Descriptor);
-        model._staticPropertyData.__IndexPairBondsData__ = data;
-        return true;
-    }
-
-    function getIndexPairBonds(model: Model) {
-        return model._staticPropertyData.__IndexPairBondsData__ as Data;
-    }
-
-    export const PropName = '__IndexPairBonds__';
-    export function get(model: Model): IndexPairBonds | undefined {
-        if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName];
-        if (!model.customProperties.has(Descriptor)) return void 0;
-
-        const data = getIndexPairBonds(model);
-        if (!data) return void 0;
+    export function fromData(data: Data) {
         const { pairs, count } = data
-
         const indexA = pairs.indexA.toArray()
         const indexB = pairs.indexB.toArray()
         const order = pairs.order.toArray()
-
-        const indexPairBonds = getGraph(indexA, indexB, order, count);
-        model._staticPropertyData[PropName] = indexPairBonds;
-        return indexPairBonds;
+        return getGraph(indexA, indexB, order, count);
     }
 }

+ 27 - 60
src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts → src/mol-model-formats/structure/property/bonds/struct_conn.ts

@@ -8,17 +8,18 @@
 import { Model } from '../../../../mol-model/structure/model/model'
 import { Structure } from '../../../../mol-model/structure'
 import { BondType } from '../../../../mol-model/structure/model/types'
-import { findEntityIdByAsymId, findAtomIndexByLabelName } from '../util'
-import { Column } from '../../../../mol-data/db'
+import { Column, Table } from '../../../../mol-data/db'
 import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
-import { mmCIF_Database } from '../../../../mol-io/reader/cif/schema/mmcif';
+import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
 import { SortedArray } from '../../../../mol-data/int';
 import { CifWriter } from '../../../../mol-io/writer/cif'
 import { ElementIndex, ResidueIndex } from '../../../../mol-model/structure/model/indexing';
 import { getInterBondOrderFromTable } from '../../../../mol-model/structure/model/properties/atomic/bonds';
+import { FormatPropertyProvider } from '../../common/property';
 
 export interface StructConn {
-    getAtomEntries(atomIndex: ElementIndex): ReadonlyArray<StructConn.Entry>,
+    readonly data: Table<mmCIF_Schema['struct_conn']>
+    readonly byAtomIndex: Map<ElementIndex, ReadonlyArray<StructConn.Entry>>
     readonly entries: ReadonlyArray<StructConn.Entry>
 }
 
@@ -30,27 +31,27 @@ export namespace StructConn {
             categories: [{
                 name: 'struct_conn',
                 instance(ctx) {
-                    const structure = ctx.structures[0], model = structure.model;
-                    const struct_conn = getStructConn(model);
-                    if (!struct_conn) return CifWriter.Category.Empty;
+                    const p = Provider.get(ctx.firstModel);
+                    if (!p || p.entries.length === 0) return CifWriter.Category.Empty;
 
-                    const strConn = get(model);
-                    if (!strConn || strConn.entries.length === 0) return CifWriter.Category.Empty;
+                    const structure = ctx.structures[0]
 
                     const indices: number[] = [];
-                    for (const e of strConn.entries) {
+                    for (const e of p.entries) {
                         if (hasAtom(structure, e.partnerA.atomIndex) &&
                                 hasAtom(structure, e.partnerB.atomIndex)) {
                             indices[indices.length] = e.rowIndex;
                         }
                     }
 
-                    return CifWriter.Category.ofTable(struct_conn, indices);
+                    return CifWriter.Category.ofTable(p.data, indices);
                 }
             }]
         }
     }
 
+    export const Provider = FormatPropertyProvider.create<StructConn>(Descriptor)
+
     function hasAtom({ units }: Structure, element: ElementIndex) {
         for (let i = 0, _i = units.length; i < _i; i++) {
             if (SortedArray.indexOf(units[i].elements, element) >= 0) return true;
@@ -58,31 +59,17 @@ export namespace StructConn {
         return false;
     }
 
-    const _emptyEntry: Entry[] = [];
-
-    class StructConnImpl implements StructConn {
-        private _atomIndex: Map<number, StructConn.Entry[]> | undefined = void 0;
-
-        private getAtomIndex() {
-            if (this._atomIndex) return this._atomIndex;
-            const m = this._atomIndex = new Map();
-            for (const e of this.entries) {
-                const { partnerA: { atomIndex: iA }, partnerB: { atomIndex: iB } } = e;
-                if (m.has(iA)) m.get(iA)!.push(e);
-                else m.set(iA, [e]);
-
-                if (m.has(iB)) m.get(iB)!.push(e);
-                else m.set(iB, [e]);
-            }
-            return this._atomIndex;
-        }
-
-        getAtomEntries(atomIndex: ElementIndex): ReadonlyArray<StructConn.Entry> {
-            return this.getAtomIndex().get(atomIndex) || _emptyEntry;
-        }
+    export function getAtomIndexFromEntries(entries: StructConn['entries']) {
+        const m = new Map();
+        for (const e of entries) {
+            const { partnerA: { atomIndex: iA }, partnerB: { atomIndex: iB } } = e;
+            if (m.has(iA)) m.get(iA)!.push(e);
+            else m.set(iA, [e]);
 
-        constructor(public entries: StructConn.Entry[]) {
+            if (m.has(iB)) m.get(iB)!.push(e);
+            else m.set(iB, [e]);
         }
+        return m;
     }
 
     export interface Entry {
@@ -94,27 +81,7 @@ export namespace StructConn {
         partnerB: { residueIndex: ResidueIndex, atomIndex: ElementIndex, symmetry: string }
     }
 
-    export function attachFromMmCif(model: Model): boolean {
-        if (model.customProperties.has(Descriptor)) return true;
-        if (model.sourceData.kind !== 'mmCIF') return false;
-        const { struct_conn } = model.sourceData.data;
-        if (struct_conn._rowCount === 0) return false;
-        model.customProperties.add(Descriptor);
-        model._staticPropertyData.__StructConnData__ = struct_conn;
-        return true;
-    }
-
-    function getStructConn(model: Model) {
-        return model._staticPropertyData.__StructConnData__ as mmCIF_Database['struct_conn'];
-    }
-
-    export const PropName = '__StructConn__';
-    export function get(model: Model): StructConn | undefined {
-        if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName];
-        if (!model.customProperties.has(Descriptor)) return void 0;
-
-        const struct_conn = getStructConn(model);
-
+    export function getEntriesFromStructConn(struct_conn: Table<mmCIF_Schema['struct_conn']>, model: Model): StructConn['entries'] {
         const { conn_type_id, pdbx_dist_value, pdbx_value_order } = struct_conn;
         const p1 = {
             label_asym_id: struct_conn.ptnr1_label_asym_id,
@@ -138,8 +105,10 @@ export namespace StructConn {
         const _p = (row: number, ps: typeof p1) => {
             if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0;
             const asymId = ps.label_asym_id.value(row);
+            const entityIndex = model.atomicHierarchy.index.findEntity(asymId);
+            if (entityIndex < 0) return void 0;
             const residueIndex = model.atomicHierarchy.index.findResidue(
-                findEntityIdByAsymId(model, asymId),
+                model.entities.data.id.value(entityIndex),
                 asymId,
                 ps.auth_seq_id.value(row),
                 ps.ins_code.value(row)
@@ -148,7 +117,7 @@ export namespace StructConn {
             const atomName = ps.label_atom_id.value(row);
             // turns out "mismat" records might not have atom name value
             if (!atomName) return void 0;
-            const atomIndex = findAtomIndexByLabelName(model, residueIndex, atomName, ps.label_alt_id.value(row));
+            const atomIndex = model.atomicHierarchy.index.findAtomOnResidue(residueIndex, atomName, ps.label_alt_id.value(row));
             if (atomIndex < 0) return void 0;
             return { residueIndex, atomIndex, symmetry: ps.symmetry.value(row) };
         }
@@ -194,8 +163,6 @@ export namespace StructConn {
             });
         }
 
-        const ret = new StructConnImpl(entries);
-        model._staticPropertyData[PropName] = ret;
-        return ret;
+        return entries;
     }
 }

+ 31 - 32
src/mol-model-formats/structure/mmcif/pair-restraints/cross-links.ts → src/mol-model-formats/structure/property/pair-restraints/cross-links.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 Mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -7,43 +7,39 @@
 import { Model } from '../../../../mol-model/structure/model/model'
 import { Table } from '../../../../mol-data/db'
 import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
-import { findAtomIndexByLabelName } from '../util';
-import { Unit } from '../../../../mol-model/structure';
+import { Unit, CustomPropertyDescriptor } from '../../../../mol-model/structure';
 import { ElementIndex } from '../../../../mol-model/structure/model/indexing';
+import { FormatPropertyProvider } from '../../common/property';
 
-function findAtomIndex(model: Model, entityId: string, asymId: string, seqId: number, atomId: string) {
-    if (!model.atomicHierarchy.atoms.auth_atom_id.isDefined) return -1
-    const residueIndex = model.atomicHierarchy.index.findResidue(entityId, asymId, seqId)
-    if (residueIndex < 0) return -1
-    return findAtomIndexByLabelName(model, residueIndex, atomId, '') as ElementIndex
-}
+export { ModelCrossLinkRestraint }
 
-export interface IHMCrossLinkRestraint {
+interface ModelCrossLinkRestraint {
     getIndicesByElement: (element: ElementIndex, kind: Unit.Kind) => number[]
     data: Table<mmCIF_Schema['ihm_cross_link_restraint']>
 }
 
-export namespace IHMCrossLinkRestraint {
-    export const PropName = '__CrossLinkRestraint__';
-    export function fromModel(model: Model): IHMCrossLinkRestraint | undefined {
-        if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName]
+namespace ModelCrossLinkRestraint {
+    export const Descriptor: CustomPropertyDescriptor = {
+        name: 'ihm_cross_link_restraint',
+        // TODO cifExport
+    }
+
+    export const Provider = FormatPropertyProvider.create<ModelCrossLinkRestraint>(Descriptor)
 
-        if (model.sourceData.kind !== 'mmCIF') return
-        const { ihm_cross_link_restraint } = model.sourceData.data;
-        if (!ihm_cross_link_restraint._rowCount) return
+    export function fromTable(table: Table<mmCIF_Schema['ihm_cross_link_restraint']>, model: Model): ModelCrossLinkRestraint {
 
         const p1 = {
-            entity_id: ihm_cross_link_restraint.entity_id_1,
-            asym_id: ihm_cross_link_restraint.asym_id_1,
-            seq_id: ihm_cross_link_restraint.seq_id_1,
-            atom_id: ihm_cross_link_restraint.atom_id_1,
+            entity_id: table.entity_id_1,
+            asym_id: table.asym_id_1,
+            seq_id: table.seq_id_1,
+            atom_id: table.atom_id_1,
         }
 
         const p2: typeof p1 = {
-            entity_id: ihm_cross_link_restraint.entity_id_2,
-            asym_id: ihm_cross_link_restraint.asym_id_2,
-            seq_id: ihm_cross_link_restraint.seq_id_2,
-            atom_id: ihm_cross_link_restraint.atom_id_2,
+            entity_id: table.entity_id_2,
+            asym_id: table.asym_id_2,
+            seq_id: table.seq_id_2,
+            atom_id: table.atom_id_2,
         }
 
         function _add(map: Map<ElementIndex, number[]>, element: ElementIndex, row: number) {
@@ -57,8 +53,13 @@ export namespace IHMCrossLinkRestraint {
             const asymId = ps.asym_id.value(row)
             const seqId = ps.seq_id.value(row)
 
-            if (ihm_cross_link_restraint.model_granularity.value(row) === 'by-atom') {
-                const atomicElement = findAtomIndex(model, entityId, asymId, seqId, ps.atom_id.value(row))
+            if (table.model_granularity.value(row) === 'by-atom') {
+                const atomicElement = model.atomicHierarchy.index.findAtom({
+                    auth_seq_id: seqId,
+                    label_asym_id: asymId,
+                    label_atom_id: ps.atom_id.value(row),
+                    label_entity_id: entityId,
+                })
                 if (atomicElement >= 0) _add(atomicElementMap, atomicElement as ElementIndex, row)
             } else if (model.coarseHierarchy.isDefined) {
                 const sphereElement = model.coarseHierarchy.spheres.findSequenceKey(entityId, asymId, seqId)
@@ -88,20 +89,18 @@ export namespace IHMCrossLinkRestraint {
 
         const emptyIndexArray: number[] = [];
 
-        for (let i = 0; i < ihm_cross_link_restraint._rowCount; ++i) {
+        for (let i = 0; i < table._rowCount; ++i) {
             add(i, p1)
             add(i, p2)
         }
 
-        const crossLinkRestraint = {
+        return {
             getIndicesByElement: (element: ElementIndex, kind: Unit.Kind) => {
                 const map = getMapByKind(kind)
                 const idx = map.get(element)
                 return idx !== undefined ? idx : emptyIndexArray
             },
-            data: ihm_cross_link_restraint
+            data: table
         }
-        model._staticPropertyData[PropName] = crossLinkRestraint
-        return crossLinkRestraint
     }
 }

+ 0 - 0
src/mol-model-formats/structure/mmcif/pair-restraints/predicted-contacts.ts → src/mol-model-formats/structure/property/pair-restraints/predicted-contacts.ts


+ 34 - 19
src/mol-model-formats/structure/mmcif/secondary-structure.ts → src/mol-model-formats/structure/property/secondary-structure.ts

@@ -1,36 +1,51 @@
 
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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 { mmCIF_Database as mmCIF, mmCIF_Database } from '../../../mol-io/reader/cif/schema/mmcif'
+import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif'
 import { SecondaryStructureType } from '../../../mol-model/structure/model/types';
 import { AtomicHierarchy } from '../../../mol-model/structure/model/properties/atomic';
 import { SecondaryStructure } from '../../../mol-model/structure/model/properties/seconday-structure';
-import { Column } from '../../../mol-data/db';
+import { Column, Table } from '../../../mol-data/db';
 import { ChainIndex, ResidueIndex } from '../../../mol-model/structure/model/indexing';
+import { FormatPropertyProvider } from '../common/property';
+import { CustomPropertyDescriptor } from '../../../mol-model/structure';
 
-export function getSecondaryStructure(data: mmCIF_Database, hierarchy: AtomicHierarchy): SecondaryStructure {
-    const map: SecondaryStructureMap = new Map();
-    const elements: SecondaryStructure.Element[] = [{ kind: 'none' }];
-    addHelices(data.struct_conf, map, elements);
-    // must add Helices 1st because of 'key' value assignment.
-    addSheets(data.struct_sheet_range, map, data.struct_conf._rowCount, elements);
+export { ModelSecondaryStructure }
 
-    const n = hierarchy.residues._rowCount
-    const getIndex = (rI: ResidueIndex) => rI
+type StructConf = Table<mmCIF_Schema['struct_conf']>
+type StructSheetRange = Table<mmCIF_Schema['struct_sheet_range']>
 
-    const secStruct: SecondaryStructureData = {
-        type: new Int32Array(n) as any,
-        key: new Int32Array(n) as any,
-        elements
+namespace ModelSecondaryStructure {
+    export const Descriptor: CustomPropertyDescriptor = {
+        name: 'model_secondary_structure',
     };
 
-    if (map.size > 0) assignSecondaryStructureRanges(hierarchy, map, secStruct);
-    return SecondaryStructure(secStruct.type, secStruct.key, secStruct.elements, getIndex);
+    export const Provider = FormatPropertyProvider.create<SecondaryStructure>(Descriptor)
+
+    export function fromStruct(conf: StructConf, sheetRange: StructSheetRange, hierarchy: AtomicHierarchy): SecondaryStructure {
+        const map: SecondaryStructureMap = new Map();
+        const elements: SecondaryStructure.Element[] = [{ kind: 'none' }];
+        addHelices(conf, map, elements);
+        // must add Helices 1st because of 'key' value assignment.
+        addSheets(sheetRange, map, conf._rowCount, elements);
+
+        const n = hierarchy.residues._rowCount
+        const getIndex = (rI: ResidueIndex) => rI
+
+        const secStruct: SecondaryStructureData = {
+            type: new Int32Array(n) as any,
+            key: new Int32Array(n) as any,
+            elements
+        };
+
+        if (map.size > 0) assignSecondaryStructureRanges(hierarchy, map, secStruct);
+        return SecondaryStructure(secStruct.type, secStruct.key, secStruct.elements, getIndex);
+    }
 }
 
 type SecondaryStructureEntry = {
@@ -44,7 +59,7 @@ type SecondaryStructureEntry = {
 type SecondaryStructureMap = Map<string, Map<number, SecondaryStructureEntry[]>>
 type SecondaryStructureData = { type: SecondaryStructureType[], key: number[], elements: SecondaryStructure.Element[] }
 
-function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap, elements: SecondaryStructure.Element[]) {
+function addHelices(cat: StructConf, map: SecondaryStructureMap, elements: SecondaryStructure.Element[]) {
     if (!cat._rowCount) return;
 
     const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat;
@@ -90,7 +105,7 @@ function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap, eleme
     }
 }
 
-function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap, sheetCount: number, elements: SecondaryStructure.Element[]) {
+function addSheets(cat: StructSheetRange, map: SecondaryStructureMap, sheetCount: number, elements: SecondaryStructure.Element[]) {
     if (!cat._rowCount) return;
 
     const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat;

+ 85 - 0
src/mol-model-formats/structure/property/symmetry.ts

@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2017-2020 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 { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
+import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/geometry';
+import { Tensor, Vec3, Mat3 } from '../../../mol-math/linear-algebra';
+import { Symmetry as _ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry';
+import { createAssemblies } from './assembly';
+import { CustomPropertyDescriptor } from '../../../mol-model/structure';
+import { FormatPropertyProvider } from '../common/property';
+import { Table } from '../../../mol-data/db';
+
+export { ModelSymmetry }
+
+namespace ModelSymmetry {
+    export const Descriptor: CustomPropertyDescriptor = {
+        name: 'model_symmetry',
+    };
+
+    export const Provider = FormatPropertyProvider.create<_ModelSymmetry>(Descriptor)
+
+    type Data = {
+        symmetry: Table<mmCIF_Schema['symmetry']>
+        cell: Table<mmCIF_Schema['cell']>
+        struct_ncs_oper: Table<mmCIF_Schema['struct_ncs_oper']>
+        atom_sites: Table<mmCIF_Schema['atom_sites']>
+        pdbx_struct_assembly: Table<mmCIF_Schema['pdbx_struct_assembly']>
+        pdbx_struct_assembly_gen: Table<mmCIF_Schema['pdbx_struct_assembly_gen']>
+        pdbx_struct_oper_list: Table<mmCIF_Schema['pdbx_struct_oper_list']>
+    }
+
+    export function fromData(data: Data): _ModelSymmetry {
+        const assemblies = createAssemblies(data.pdbx_struct_assembly, data.pdbx_struct_assembly_gen, data.pdbx_struct_oper_list);
+        const spacegroup = getSpacegroup(data.symmetry, data.cell);
+        const isNonStandardCrytalFrame = checkNonStandardCrystalFrame(data.atom_sites, spacegroup);
+        return { assemblies, spacegroup, isNonStandardCrytalFrame, ncsOperators: getNcsOperators(data.struct_ncs_oper) };
+    }
+}
+
+function checkNonStandardCrystalFrame(atom_sites: Table<mmCIF_Schema['atom_sites']>, spacegroup: Spacegroup) {
+    if (atom_sites._rowCount === 0) return false;
+    // TODO: parse atom_sites transform and check if it corresponds to the toFractional matrix
+    return false;
+}
+
+function getSpacegroupNameOrNumber(symmetry: Table<mmCIF_Schema['symmetry']>) {
+    const groupNumber = symmetry['Int_Tables_number'].value(0);
+    const groupName = symmetry['space_group_name_H-M'].value(0);
+    if (!symmetry['Int_Tables_number'].isDefined) return groupName
+    if (!symmetry['space_group_name_H-M'].isDefined) return groupNumber
+    return groupName
+}
+
+function getSpacegroup(symmetry: Table<mmCIF_Schema['symmetry']>, cell: Table<mmCIF_Schema['cell']>): Spacegroup {
+    if (symmetry._rowCount === 0 || cell._rowCount === 0) return Spacegroup.ZeroP1;
+    const nameOrNumber = getSpacegroupNameOrNumber(symmetry)
+    const spaceCell = SpacegroupCell.create(nameOrNumber,
+        Vec3.create(cell.length_a.value(0), cell.length_b.value(0), cell.length_c.value(0)),
+        Vec3.scale(Vec3.zero(), Vec3.create(cell.angle_alpha.value(0), cell.angle_beta.value(0), cell.angle_gamma.value(0)), Math.PI / 180));
+
+    return Spacegroup.create(spaceCell);
+}
+
+function getNcsOperators(struct_ncs_oper: Table<mmCIF_Schema['struct_ncs_oper']>) {
+    if (struct_ncs_oper._rowCount === 0) return void 0;
+    const { id, matrix, vector } = struct_ncs_oper;
+
+    const matrixSpace = mmCIF_Schema.struct_ncs_oper.matrix.space, vectorSpace = mmCIF_Schema.struct_ncs_oper.vector.space;
+
+    const opers: SymmetryOperator[] = [];
+    for (let i = 0; i < struct_ncs_oper._rowCount; i++) {
+        const m = Tensor.toMat3(Mat3(), matrixSpace, matrix.value(i));
+        const v = Tensor.toVec3(Vec3(), vectorSpace, vector.value(i));
+        if (!SymmetryOperator.checkIfRotationAndTranslation(m, v)) continue;
+        // ignore non-identity 'given' NCS operators
+        if (struct_ncs_oper.code.value(i) === 'given' && !Mat3.isIdentity(m) && !Vec3.isZero(v)) continue;
+        const ncsId = id.value(i)
+        opers[opers.length] = SymmetryOperator.ofRotationAndOffset(`ncs_${ncsId}`, m, v, ncsId);
+    }
+    return opers;
+}

+ 5 - 1
src/mol-model-props/computed/secondary-structure.ts

@@ -11,6 +11,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { Unit } from '../../mol-model/structure/structure';
 import { CustomStructureProperty } from '../common/custom-structure-property';
 import { CustomProperty } from '../common/custom-property';
+import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure';
 
 function getSecondaryStructureParams(data?: Structure) {
     let defaultType = 'mmcif' as 'mmcif' | 'dssp'
@@ -83,7 +84,10 @@ async function computeMmcif(structure: Structure): Promise<SecondaryStructureVal
     for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
         const u = structure.unitSymmetryGroups[i].units[0]
         if (Unit.isAtomic(u)) {
-            map.set(u.invariantId, u.model.properties.secondaryStructure)
+            const secondaryStructure = ModelSecondaryStructure.Provider.get(u.model)
+            if (secondaryStructure) {
+                map.set(u.invariantId, secondaryStructure)
+            }
         }
     }
     return map

+ 3 - 2
src/mol-model-props/pdbe/preferred-assembly.ts

@@ -8,13 +8,14 @@ import { Column, Table } from '../../mol-data/db';
 import { toTable } from '../../mol-io/reader/cif/schema';
 import { CifWriter } from '../../mol-io/writer/cif';
 import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
+import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
 
 export namespace PDBePreferredAssembly {
     export type Property = string
 
     export function getFirstFromModel(model: Model): Property {
-        const asm = model.symmetry.assemblies;
-        return asm.length ? asm[0].id : '';
+        const symmetry = ModelSymmetry.Provider.get(model)
+        return symmetry?.assemblies.length ? symmetry.assemblies[0].id : '';
     }
 
     export function get(model: Model): Property {

+ 4 - 1
src/mol-model/structure/export/categories/secondary-structure.ts

@@ -13,6 +13,7 @@ import CifField = CifWriter.Field
 import CifCategory = CifWriter.Category
 import { Column } from '../../../../mol-data/db';
 import { residueIdFields } from './atom_site';
+import { ModelSecondaryStructure } from '../../../../mol-model-formats/structure/property/secondary-structure';
 
 export const _struct_conf: CifCategory<CifExportContext> = {
     name: 'struct_conf',
@@ -70,8 +71,10 @@ interface SSElement<T extends SecondaryStructure.Element> {
 
 function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContext, kind: SecondaryStructure.Element['kind']) {
     // TODO: encode secondary structure for different models?
-    const { key, elements } = ctx.structures[0].model.properties.secondaryStructure;
+    const secondaryStructure = ModelSecondaryStructure.Provider.get(ctx.firstModel)
+    if (!secondaryStructure) return [] as SSElement<T>[]
 
+    const { key, elements } = secondaryStructure;
     const ssElements: SSElement<any>[] = [];
 
     for (const unit of ctx.structures[0].units) {

+ 2 - 2
src/mol-model/structure/model.ts

@@ -6,9 +6,9 @@
 
 import { Model } from './model/model'
 import * as Types from './model/types'
-import { ModelSymmetry } from './model/properties/symmetry'
+import { Symmetry } from './model/properties/symmetry'
 import StructureSequence from './model/properties/sequence'
 
 export * from './model/properties/custom/indexed'
 export * from './model/indexing'
-export { Model, Types, ModelSymmetry, StructureSequence }
+export { Model, Types, Symmetry, StructureSequence }

+ 4 - 9
src/mol-model/structure/model/model.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 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>
@@ -8,11 +8,9 @@
 import UUID from '../../../mol-util/uuid';
 import StructureSequence from './properties/sequence';
 import { AtomicHierarchy, AtomicConformation, AtomicRanges } from './properties/atomic';
-import { ModelSymmetry } from './properties/symmetry';
 import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
 import { Entities, ChemicalComponentMap, MissingResidues } from './properties/common';
 import { CustomProperties } from '../common/custom-property';
-import { SecondaryStructure } from './properties/seconday-structure';
 import { SaccharideComponentMap } from '../structure/carbohydrates/constants';
 import { ModelFormat } from '../../../mol-model-formats/structure/format';
 import { calcModelCenter } from './util';
@@ -22,7 +20,7 @@ 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';
+import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
 
 /**
  * Interface to the "source data" of the molecule.
@@ -47,7 +45,6 @@ export interface Model extends Readonly<{
 
     sourceData: ModelFormat,
 
-    symmetry: ModelSymmetry,
     entities: Entities,
     sequence: StructureSequence,
 
@@ -56,8 +53,6 @@ export interface Model extends Readonly<{
     atomicRanges: AtomicRanges,
 
     properties: {
-        /** secondary structure provided by the input file */
-        readonly secondaryStructure: SecondaryStructure,
         /** maps modified residue name to its parent */
         readonly modifiedResidues: Readonly<{
             parentId: ReadonlyMap<string, string>,
@@ -87,7 +82,6 @@ export interface Model extends Readonly<{
 } { }
 
 export namespace Model {
-    // TODO: is this enough?
     export type Trajectory = ReadonlyArray<Model>
 
     export function trajectoryFromModelAndCoordinates(model: Model, coordinates: Coordinates): Trajectory {
@@ -117,8 +111,9 @@ export namespace Model {
             if (!model) throw new Error('found no model')
             const trajectory = trajectoryFromModelAndCoordinates(model, coordinates)
             const bondData = { pairs: topology.bonds, count: model.atomicHierarchy.atoms._rowCount }
+            const indexPairBonds = IndexPairBonds.fromData(bondData)
             for (const m of trajectory) {
-                IndexPairBonds.attachFromData(m, bondData)
+                IndexPairBonds.Provider.set(m, indexPairBonds)
             }
             return trajectory
         })

+ 0 - 22
src/mol-model/structure/model/properties/computed.ts

@@ -1,22 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-// TODO: stuff like "residue type/flags", isRingAtom, rings, bonds??, wrap these in a computation?
-// secondary structure is also a computed property
-
-// import { SecondaryStructureType } from '../constants'
-
-
-// interface SecondaryStructure {
-//     ofResidue: ArrayLike<SecondaryStructureType>,
-//     // atom segmentation??
-//     segments: Segmentation
-// }
-
-// interface Conformation {
-//     positions: Conformation,
-//     secondaryStructure: SecondaryStructure
-// }

+ 7 - 5
src/mol-model/structure/model/properties/symmetry.ts

@@ -10,6 +10,7 @@ import { StructureQuery } from '../../query'
 import { Model } from '../../model'
 import { Spacegroup } from '../../../../mol-math/geometry';
 import { Vec3 } from '../../../../mol-math/linear-algebra';
+import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
 
 /** Determine an atom set and a list of operators that should be applied to that set  */
 export interface OperatorGroup {
@@ -42,7 +43,7 @@ export namespace Assembly {
     }
 }
 
-interface ModelSymmetry {
+interface Symmetry {
     readonly assemblies: ReadonlyArray<Assembly>,
     readonly spacegroup: Spacegroup,
     readonly isNonStandardCrytalFrame: boolean,
@@ -58,13 +59,14 @@ interface ModelSymmetry {
     }
 }
 
-namespace ModelSymmetry {
-    export const Default: ModelSymmetry = { assemblies: [], spacegroup: Spacegroup.ZeroP1, isNonStandardCrytalFrame: false };
+namespace Symmetry {
+    export const Default: Symmetry = { assemblies: [], spacegroup: Spacegroup.ZeroP1, isNonStandardCrytalFrame: false };
 
     export function findAssembly(model: Model, id: string): Assembly | undefined {
         const _id = id.toLocaleLowerCase();
-        return arrayFind(model.symmetry.assemblies, a => a.id.toLowerCase() === _id);
+        const symmetry = ModelSymmetry.Provider.get(model)
+        return symmetry ? arrayFind(symmetry.assemblies, a => a.id.toLowerCase() === _id) : undefined;
     }
 }
 
-export { ModelSymmetry }
+export { Symmetry }

+ 16 - 2
src/mol-model/structure/structure/properties.ts

@@ -7,6 +7,8 @@
 import StructureElement from './element'
 import Unit from './unit'
 import { VdwRadius } from '../model/properties/atomic';
+import { ModelSecondaryStructure } from '../../../mol-model-formats/structure/property/secondary-structure';
+import { SecondaryStructureType } from '../model/types';
 
 function p<T>(p: StructureElement.Property<T>) { return p; }
 
@@ -103,8 +105,20 @@ const residue = {
     isNonStandard: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.mon_nstd_flag[0] !== 'y'),
     hasMicroheterogeneity: p(hasMicroheterogeneity),
     microheterogeneityCompIds: p(microheterogeneityCompIds),
-    secondary_structure_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.type[l.unit.residueIndex[l.element]]),
-    secondary_structure_key: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.key[l.unit.residueIndex[l.element]]),
+    // TODO implement as symbol in SecondaryStructureProvider (not ModelSecondaryStructure.Provider)
+    secondary_structure_type: p(l => {
+        if (!Unit.isAtomic(l.unit)) notAtomic()
+        const secondaryStructure = ModelSecondaryStructure.Provider.get(l.unit.model)
+        if (secondaryStructure) return secondaryStructure.type[l.unit.residueIndex[l.element]]
+        else return SecondaryStructureType.Flag.NA
+    }),
+    // TODO implement as symbol in SecondaryStructureProvider (not ModelSecondaryStructure.Provider)
+    secondary_structure_key: p(l => {
+        if (!Unit.isAtomic(l.unit)) notAtomic()
+        const secondaryStructure = ModelSecondaryStructure.Provider.get(l.unit.model)
+        if (secondaryStructure) return secondaryStructure.key[l.unit.residueIndex[l.element]]
+        else return -1
+    }),
     chem_comp_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.type),
 }
 

+ 17 - 8
src/mol-model/structure/structure/symmetry.ts

@@ -10,10 +10,11 @@ import { EquivalenceClasses } from '../../../mol-data/util';
 import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/geometry';
 import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
 import { RuntimeContext, Task } from '../../../mol-task';
-import { ModelSymmetry, Model } from '../model';
+import { Symmetry, Model } from '../model';
 import { QueryContext, StructureSelection } from '../query';
 import Structure from './structure';
 import Unit from './unit';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 namespace StructureSymmetry {
     export function buildAssembly(structure: Structure, asmName: string) {
@@ -21,7 +22,7 @@ namespace StructureSymmetry {
             const models = structure.models;
             if (models.length !== 1) throw new Error('Can only build assemblies from structures based on 1 model.');
 
-            const assembly = ModelSymmetry.findAssembly(models[0], asmName);
+            const assembly = Symmetry.findAssembly(models[0], asmName);
             if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
 
             const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] })
@@ -95,7 +96,7 @@ namespace StructureSymmetry {
     }
 }
 
-function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) {
+function getOperators(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) {
     const { spacegroup, ncsOperators } = symmetry;
     const ncsCount = (ncsOperators && ncsOperators.length) || 0
     const operators: SymmetryOperator[] = [];
@@ -135,7 +136,7 @@ function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3, model
     return operators;
 }
 
-function getOperatorsCached333(symmetry: ModelSymmetry, ref: Vec3) {
+function getOperatorsCached333(symmetry: Symmetry, ref: Vec3) {
     if (symmetry._operators_333 && Vec3.equals(ref, symmetry._operators_333.ref)) {
         return symmetry._operators_333.operators;
     }
@@ -161,7 +162,10 @@ async function _buildNCS(ctx: RuntimeContext, structure: Structure) {
     const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build NCS from structures based on 1 model.');
 
-    const operators = models[0].symmetry.ncsOperators;
+    const symmetry = ModelSymmetry.Provider.get(models[0])
+    if (!symmetry) return structure
+
+    const operators = symmetry.ncsOperators;
     if (!operators || !operators.length) return structure;
     return assembleOperators(structure, operators);
 }
@@ -170,11 +174,14 @@ async function findSymmetryRange(ctx: RuntimeContext, structure: Structure, ijkM
     const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
 
-    const { spacegroup } = models[0].symmetry;
+    const symmetry = ModelSymmetry.Provider.get(models[0])
+    if (!symmetry) return structure
+
+    const { spacegroup } = symmetry;
     if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
 
     const modelCenter = Model.getCenter(models[0])
-    const operators = getOperators(models[0].symmetry, ijkMin, ijkMax, modelCenter);
+    const operators = getOperators(symmetry, ijkMin, ijkMax, modelCenter);
     return assembleOperators(structure, operators);
 }
 
@@ -182,7 +189,9 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius
     const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
 
-    const symmetry = models[0].symmetry;
+    const symmetry = ModelSymmetry.Provider.get(models[0])
+    if (!symmetry) return structure
+
     const { spacegroup } = symmetry;
     if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
 

+ 5 - 5
src/mol-model/structure/structure/unit/bonds/inter-compute.ts

@@ -13,11 +13,11 @@ import { InterUnitBonds, InterUnitEdgeProps } from './data';
 import { SortedArray } from '../../../../../mol-data/int';
 import { Vec3, Mat4 } from '../../../../../mol-math/linear-algebra';
 import StructureElement from '../../element';
-import { StructConn } from '../../../../../mol-model-formats/structure/mmcif/bonds';
 import { ElementIndex } from '../../../model/indexing';
 import { getInterBondOrderFromTable } from '../../../model/properties/atomic/bonds';
-import { IndexPairBonds } from '../../../../../mol-model-formats/structure/mmcif/bonds/index-pair';
+import { IndexPairBonds } from '../../../../../mol-model-formats/structure/property/bonds/index-pair';
 import { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph';
+import { StructConn } from '../../../../../mol-model-formats/structure/property/bonds/struct_conn';
 
 const MAX_RADIUS = 4;
 
@@ -46,8 +46,8 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
     const { occupancy: occupancyB } = unitB.model.atomicConformation;
 
     const { lookup3d } = unitB;
-    const structConn = unitA.model === unitB.model && unitA.model.sourceData.kind === 'mmCIF' ? StructConn.get(unitA.model) : void 0;
-    const indexPairs = unitA.model === unitB.model ? IndexPairBonds.get(unitA.model) : void 0;
+    const structConn = unitA.model === unitB.model && StructConn.Provider.get(unitA.model)
+    const indexPairs = unitA.model === unitB.model && IndexPairBonds.Provider.get(unitA.model)
 
     // the lookup queries need to happen in the "unitB space".
     // that means imageA = inverseOperB(operA(aI))
@@ -75,7 +75,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
             continue // assume `indexPairs` supplies all bonds
         }
 
-        const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
+        const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.byAtomIndex.get(aI);
         if (structConnEntries && structConnEntries.length) {
             let added = false;
             for (const se of structConnEntries) {

+ 7 - 6
src/mol-model/structure/structure/unit/bonds/intra-compute.ts

@@ -11,10 +11,11 @@ import Unit from '../../unit'
 import { IntAdjacencyGraph } from '../../../../../mol-math/graph';
 import { BondComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold, DefaultBondComputationProps } from './common';
 import { SortedArray } from '../../../../../mol-data/int';
-import { StructConn, ComponentBond } from '../../../../../mol-model-formats/structure/mmcif/bonds';
 import { getIntraBondOrderFromTable } from '../../../model/properties/atomic/bonds';
 import StructureElement from '../../element';
-import { IndexPairBonds } from '../../../../../mol-model-formats/structure/mmcif/bonds/index-pair';
+import { IndexPairBonds } from '../../../../../mol-model-formats/structure/property/bonds/index-pair';
+import { ComponentBond } from '../../../../../mol-model-formats/structure/property/bonds/comp';
+import { StructConn } from '../../../../../mol-model-formats/structure/property/bonds/struct_conn';
 
 function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], atomCount: number): IntraUnitBonds {
     const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB);
@@ -41,9 +42,9 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
     const { label_comp_id } = unit.model.atomicHierarchy.residues;
     const query3d = unit.lookup3d;
 
-    const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.get(unit.model) : void 0;
-    const component = unit.model.sourceData.kind === 'mmCIF' ? ComponentBond.get(unit.model) : void 0;
-    const indexPairs = IndexPairBonds.get(unit.model)
+    const structConn = StructConn.Provider.get(unit.model)
+    const component = ComponentBond.Provider.get(unit.model)
+    const indexPairs = IndexPairBonds.Provider.get(unit.model)
 
     const atomA: StructureElement.UnitIndex[] = [];
     const atomB: StructureElement.UnitIndex[] = [];
@@ -70,7 +71,7 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
             continue // assume `indexPairs` supplies all bonds
         }
 
-        const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
+        const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.byAtomIndex.get(aI);
         let hasStructConn = false;
         if (structConnEntries) {
             for (const se of structConnEntries) {

+ 6 - 6
src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts

@@ -8,9 +8,9 @@ import Unit from '../../unit';
 import Structure from '../../structure';
 import { PairRestraints, CrossLinkRestraint } from './data';
 import { StructureElement } from '../../../structure';
-import { IHMCrossLinkRestraint } from '../../../../../mol-model-formats/structure/mmcif/pair-restraint';
+import { ModelCrossLinkRestraint } from '../../../../../mol-model-formats/structure/property/pair-restraints/cross-links';
 
-function _addRestraints(map: Map<number, number>, unit: Unit, restraints: IHMCrossLinkRestraint) {
+function _addRestraints(map: Map<number, number>, unit: Unit, restraints: ModelCrossLinkRestraint) {
     const { elements } = unit;
     const elementCount = elements.length;
     const kind = unit.kind
@@ -25,7 +25,7 @@ function extractInter(pairs: CrossLinkRestraint[], unitA: Unit, unitB: Unit) {
     if (unitA.model !== unitB.model) return
     if (unitA.model.sourceData.kind !== 'mmCIF') return
 
-    const restraints = IHMCrossLinkRestraint.fromModel(unitA.model)
+    const restraints = ModelCrossLinkRestraint.Provider.get(unitA.model)
     if (!restraints) return
 
     const rA = new Map<number, StructureElement.UnitIndex>();
@@ -47,7 +47,7 @@ function extractInter(pairs: CrossLinkRestraint[], unitA: Unit, unitB: Unit) {
 function extractIntra(pairs: CrossLinkRestraint[], unit: Unit) {
     if (unit.model.sourceData.kind !== 'mmCIF') return
 
-    const restraints = IHMCrossLinkRestraint.fromModel(unit.model)
+    const restraints = ModelCrossLinkRestraint.Provider.get(unit.model)
     if (!restraints) return
 
     const { elements } = unit;
@@ -75,7 +75,7 @@ function extractIntra(pairs: CrossLinkRestraint[], unit: Unit) {
     })
 }
 
-function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitIndex, unitB: Unit, indexB: StructureElement.UnitIndex, restraints: IHMCrossLinkRestraint, row: number): CrossLinkRestraint {
+function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitIndex, unitB: Unit, indexB: StructureElement.UnitIndex, restraints: ModelCrossLinkRestraint, row: number): CrossLinkRestraint {
     return {
         unitA, indexA, unitB, indexB,
 
@@ -89,7 +89,7 @@ function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitInde
 
 function extractCrossLinkRestraints(structure: Structure): PairRestraints<CrossLinkRestraint> {
     const pairs: CrossLinkRestraint[] = []
-    if (!structure.models.some(m => IHMCrossLinkRestraint.fromModel(m))) {
+    if (!structure.models.some(m => ModelCrossLinkRestraint.Provider.get(m))) {
         return new PairRestraints(pairs)
     }
 

+ 12 - 7
src/mol-plugin/state/representation/model.ts

@@ -11,13 +11,16 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 import { RuntimeContext } from '../../../mol-task';
 import { PluginContext } from '../../context';
-import { Assembly, ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry';
+import { Assembly, Symmetry } from '../../../mol-model/structure/model/properties/symmetry';
 import { PluginStateObject as SO } from '../objects';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 export namespace ModelStructureRepresentation {
     export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates') {
-        const assemblyIds = model ? model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]) : [];
-        const showSymm = !model ? true : !SpacegroupCell.isZero(model.symmetry.spacegroup.cell);
+        const symmetry = model && ModelSymmetry.Provider.get(model)
+
+        const assemblyIds = symmetry ? symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]) : [];
+        const showSymm = !symmetry ? true : !SpacegroupCell.isZero(symmetry.spacegroup.cell);
 
         const modes = {
             deposited: PD.EmptyGroup(),
@@ -58,17 +61,19 @@ export namespace ModelStructureRepresentation {
     async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string) {
         let asm: Assembly | undefined = void 0;
 
+        const symmetry = ModelSymmetry.Provider.get(model)
+
         // if no id is specified, use the 1st assembly.
-        if (!id && model.symmetry.assemblies.length !== 0) {
-            id = model.symmetry.assemblies[0].id;
+        if (!id && symmetry && symmetry.assemblies.length !== 0) {
+            id = symmetry.assemblies[0].id;
         }
 
-        if (model.symmetry.assemblies.length === 0) {
+        if (!symmetry || symmetry.assemblies.length === 0) {
             if (id !== 'deposited') {
                 plugin.log.warn(`Model '${model.entryId}' has no assembly, returning deposited structure.`);
             }
         } else {
-            asm = ModelSymmetry.findAssembly(model, id || '');
+            asm = Symmetry.findAssembly(model, id || '');
             if (!asm) {
                 plugin.log.warn(`Model '${model.entryId}' has no assembly called '${id}', returning deposited structure.`);
             }

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

@@ -32,6 +32,7 @@ import { parseDcd } from '../../../mol-io/reader/dcd/parser';
 import { coordinatesFromDcd } from '../../../mol-model-formats/structure/dcd';
 import { topologyFromPsf } from '../../../mol-model-formats/structure/psf';
 import { deepEqual } from '../../../mol-util';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 export { CoordinatesFromDcd };
 export { TopologyFromPsf };
@@ -300,8 +301,8 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({
         if (!a) {
             return { id: PD.Optional(PD.Text('', { label: 'Assembly Id', description: 'Assembly Id. Value \'deposited\' can be used to specify deposited asymmetric unit.' })) };
         }
-        const model = a.data;
-        const ids = model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]);
+        const assemblies = ModelSymmetry.Provider.get(a.data)?.assemblies || []
+        const ids = assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]);
         ids.push(['deposited', 'Deposited']);
         return {
             id: PD.Optional(PD.Select(ids[0][0], ids, { label: 'Asm Id', description: 'Assembly Id' }))

+ 7 - 3
src/mol-plugin/state/transforms/representation.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
@@ -13,7 +13,7 @@ import { BuiltInStructureRepresentationsName } from '../../../mol-repr/structure
 import { StructureParams } from '../../../mol-repr/structure/representation';
 import { BuiltInVolumeRepresentationsName } from '../../../mol-repr/volume/registry';
 import { VolumeParams } from '../../../mol-repr/volume/representation';
-import { StateTransformer } from '../../../mol-state';
+import { StateTransformer, StateObject } from '../../../mol-state';
 import { Task } from '../../../mol-task';
 import { BuiltInColorThemeName, ColorTheme, BuiltInColorThemes } from '../../../mol-theme/color';
 import { BuiltInSizeThemeName, SizeTheme } from '../../../mol-theme/size';
@@ -36,6 +36,7 @@ import { LabelParams, LabelRepresentation } from '../../../mol-repr/shape/loci/l
 import { OrientationRepresentation, OrientationParams } from '../../../mol-repr/shape/loci/orientation';
 import { AngleParams, AngleRepresentation } from '../../../mol-repr/shape/loci/angle';
 import { DihedralParams, DihedralRepresentation } from '../../../mol-repr/shape/loci/dihedral';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 export { StructureRepresentation3D }
 export { StructureRepresentation3DHelpers }
@@ -649,13 +650,16 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
         ...UnitcellParams,
     }
 })({
+    isApplicable: a => !!ModelSymmetry.Provider.get(a.data),
     canAutoUpdate({ oldParams, newParams }) {
         return true;
     },
     apply({ a, params }) {
         return Task.create('Model Unitcell', async ctx => {
+            const symmetry = ModelSymmetry.Provider.get(a.data)
+            if (!symmetry) return StateObject.Null
             const repr = await getUnitcellRepresentation(ctx, a.data, params);
-            return new SO.Shape.Representation3D({ repr, source: a }, { label: `Unitcell`, description: a.data.symmetry.spacegroup.name });
+            return new SO.Shape.Representation3D({ repr, source: a }, { label: `Unitcell`, description: symmetry.spacegroup.name });
         });
     },
     update({ a, b, newParams }) {

+ 11 - 7
src/mol-plugin/util/model-unitcell.ts

@@ -1,10 +1,10 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Model, ModelSymmetry } from '../../mol-model/structure';
+import { Model, Symmetry } from '../../mol-model/structure';
 import { ShapeRepresentation } from '../../mol-repr/shape/representation';
 import { Shape } from '../../mol-model/shape';
 import { ColorNames } from '../../mol-util/color/names';
@@ -16,6 +16,7 @@ import { BoxCage } from '../../mol-geo/primitive/box';
 import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
 import { transformCage, cloneCage } from '../../mol-geo/primitive/cage';
 import { radToDeg } from '../../mol-math/misc';
+import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
 
 const translate05 = Mat4.fromTranslation(Mat4(), Vec3.create(0.5, 0.5, 0.5))
 const unitCage = transformCage(cloneCage(BoxCage()), translate05)
@@ -24,7 +25,7 @@ const tmpRef = Vec3()
 const tmpTranslate = Mat4()
 
 interface UnitcellData {
-    symmetry: ModelSymmetry
+    symmetry: Symmetry
     ref: Vec3
 }
 
@@ -54,11 +55,14 @@ function getUnitcellMesh(data: UnitcellData, props: UnitcellProps, mesh?: Mesh)
 
 export async function getUnitcellRepresentation(ctx: RuntimeContext, model: Model, params: UnitcellProps, prev?: ShapeRepresentation<UnitcellData, Mesh, Mesh.Params>) {
     const repr = prev || ShapeRepresentation(getUnitcellShape, Mesh.Utils);
-    const data = {
-        symmetry: model.symmetry,
-        ref: Vec3.transformMat4(Vec3(), Model.getCenter(model), model.symmetry.spacegroup.cell.toFractional)
+    const symmetry = ModelSymmetry.Provider.get(model)
+    if (symmetry) {
+        const data = {
+            symmetry,
+            ref: Vec3.transformMat4(Vec3(), Model.getCenter(model), symmetry.spacegroup.cell.toFractional)
+        }
+        await repr.createOrUpdate(params, data).runInContext(ctx);
     }
-    await repr.createOrUpdate(params, data).runInContext(ctx);
     return repr;
 }
 

+ 1 - 1
src/mol-repr/structure/representation/ellipsoid.ts

@@ -11,7 +11,7 @@ import { Structure } from '../../../mol-model/structure';
 import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
 import { EllipsoidMeshParams, EllipsoidMeshVisual } from '../visual/ellipsoid-mesh';
 import { UnitKind, UnitKindOptions } from '../../../mol-repr/structure/visual/util/common';
-import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/mmcif/anisotropic';
+import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/property/anisotropic';
 import { IntraUnitBondParams, IntraUnitBondVisual } from '../visual/bond-intra-unit-cylinder';
 import { InterUnitBondParams, InterUnitBondVisual } from '../visual/bond-inter-unit-cylinder';
 

+ 2 - 2
src/mol-repr/structure/visual/ellipsoid-mesh.ts

@@ -17,7 +17,7 @@ import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
 import { Vec3, Mat3, Tensor, EPSILON } from '../../../mol-math/linear-algebra';
 import { isHydrogen } from '../../../mol-repr/structure/visual/util/common';
 import { addEllipsoid } from '../../../mol-geo/geometry/mesh/builder/ellipsoid';
-import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/mmcif/anisotropic'
+import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/property/anisotropic'
 import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
 import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
 
@@ -62,7 +62,7 @@ export function createEllipsoidMesh(ctx: VisualContext, unit: Unit, structure: S
     const vertexCount = elementCount * sphereVertexCount(detail)
     const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh)
 
-    const atomSiteAnisotrop = AtomSiteAnisotrop.get(model)
+    const atomSiteAnisotrop = AtomSiteAnisotrop.Provider.get(model)
     if (!atomSiteAnisotrop) return Mesh.createEmpty(mesh)
 
     const v = Vec3()

+ 5 - 3
src/servers/model/properties/providers/wwpdb.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -9,15 +9,17 @@ import * as util from 'util'
 import { AttachModelProperty } from '../../property-provider';
 import { CIF } from '../../../../mol-io/reader/cif';
 import { getParam } from '../../../common/util';
-import { ComponentBond } from '../../../../mol-model-formats/structure/mmcif/bonds';
 import { mmCIF_Database, mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
+import { ComponentBond } from '../../../../mol-model-formats/structure/property/bonds/comp';
 
 require('util.promisify').shim()
 const readFile = util.promisify(fs.readFile)
 
 export const wwPDB_chemCompBond: AttachModelProperty = async ({ model, params }) => {
     const table = await getChemCompBondTable(getTablePath(params))
-    return ComponentBond.attachFromExternalData(model, table, true)
+    const data = ComponentBond.chemCompBondFromTable(model, table)
+    const entries = ComponentBond.getEntriesFromChemCompBond(data)
+    return ComponentBond.Provider.set(model, { entries, data })
 }
 
 async function read(path: string) {