Преглед на файлове

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 години
родител
ревизия
7a64334261
променени са 46 файла, в които са добавени 1118 реда и са изтрити 986 реда
  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 { Vec3 } from '../../mol-math/linear-algebra';
 import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
 import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
 import { Sequence } from '../../mol-model/sequence';
 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) {
 async function downloadFromPdb(pdb: string) {
@@ -50,8 +52,10 @@ export function residueLabel(model: Model, rI: number) {
 export function printSecStructure(model: Model) {
 export function printSecStructure(model: Model) {
     console.log('\nSecondary Structure\n=============');
     console.log('\nSecondary Structure\n=============');
     const { residues } = model.atomicHierarchy;
     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;
     const count = residues._rowCount;
     let rI = 0;
     let rI = 0;
     while (rI < count) {
     while (rI < count) {
@@ -179,7 +183,8 @@ export function printUnits(structure: Structure) {
 
 
 export function printSymmetryInfo(model: Model) {
 export function printSymmetryInfo(model: Model) {
     console.log('\nSymmetry Info\n=============');
     console.log('\nSymmetry Info\n=============');
-    const { symmetry } = model;
+    const symmetry = ModelSymmetry.Provider.get(model)
+    if (!symmetry) return
     const { size, anglesInRadians } = symmetry.spacegroup.cell;
     const { size, anglesInRadians } = symmetry.spacegroup.cell;
     console.log(`Spacegroup: ${symmetry.spacegroup.name} size: ${Vec3.toString(size)} angles: ${Vec3.toString(anglesInRadians)}`);
     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(', ')}`);
     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 { BuiltInColorThemeName } from '../../mol-theme/color';
 import { AminoAcidNames } from '../../mol-model/structure/model/types';
 import { AminoAcidNames } from '../../mol-model/structure/model/types';
 import { PluginContext } from '../../mol-plugin/context';
 import { PluginContext } from '../../mol-plugin/context';
+import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
 
 
 export interface ModelInfo {
 export interface ModelInfo {
     hetResidues: { name: string, indices: ResidueIndex[] }[],
     hetResidues: { name: string, indices: ResidueIndex[] }[],
@@ -72,10 +73,11 @@ export namespace ModelInfo {
         }
         }
 
 
         const preferredAssemblyId = await pref;
         const preferredAssemblyId = await pref;
+        const symmetry = ModelSymmetry.Provider.get(model)
 
 
         return {
         return {
             hetResidues: hetResidues,
             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
             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 David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -7,7 +7,6 @@
 
 
 import { Column, Table } from '../../../mol-data/db';
 import { Column, Table } from '../../../mol-data/db';
 import { Interval, Segmentation } from '../../../mol-data/int';
 import { Interval, Segmentation } from '../../../mol-data/int';
-import { mmCIF_Database } from '../../../mol-io/reader/cif/schema/mmcif';
 import UUID from '../../../mol-util/uuid';
 import UUID from '../../../mol-util/uuid';
 import { ElementIndex } from '../../../mol-model/structure';
 import { ElementIndex } from '../../../mol-model/structure';
 import { Model } from '../../../mol-model/structure/model/model';
 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 { ElementSymbol } from '../../../mol-model/structure/model/types';
 import { Entities } from '../../../mol-model/structure/model/properties/common';
 import { Entities } from '../../../mol-model/structure/model/properties/common';
 import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
 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) {
 function findHierarchyOffsets(atom_site: AtomSite) {
     if (atom_site._rowCount === 0) return { residues: [], chains: [] };
     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>)
         && 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 hierarchyOffsets = findHierarchyOffsets(atom_site);
     const hierarchyData = createHierarchyData(atom_site, sourceIndex, hierarchyOffsets);
     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 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 };
     const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived };
     return { sameAsPrevious: false, hierarchy };
     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)
     const conformation = getConformation(atom_site)
     return { sameAsPrevious, hierarchy, conformation };
     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 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 { CoarseHierarchy, CoarseConformation, CoarseElementData, CoarseSphereConformation, CoarseGaussianConformation } from '../../../mol-model/structure/model/properties/coarse'
 import { Entities } from '../../../mol-model/structure/model/properties/common';
 import { Entities } from '../../../mol-model/structure/model/properties/common';
 import { Column } from '../../../mol-data/db';
 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 { Mat3, Tensor } from '../../../mol-math/linear-algebra';
 import { ElementIndex, ChainIndex } from '../../../mol-model/structure/model/indexing';
 import { ElementIndex, ChainIndex } from '../../../mol-model/structure/model/indexing';
 import { getCoarseRanges } from '../../../mol-model/structure/model/properties/utils/coarse-ranges';
 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_id: number,
     model_name: string,
     model_name: string,
     model_group_name: string,
     model_group_name: string,
     entities: Entities,
     entities: Entities,
-    atom_site: mmCIF['atom_site'],
+    atom_site: AtomSite,
     atom_site_sourceIndex: Column<number>,
     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;
     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 sphereData = getData(ihm_sphere_obj_site);
     const sphereConformation = getSphereConformation(ihm_sphere_obj_site);
     const sphereConformation = getSphereConformation(ihm_sphere_obj_site);
     const sphereKeys = getCoarseKeys(sphereData, data.entities);
     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 gaussianData = getData(ihm_gaussian_obj_site);
     const gaussianConformation = getGaussianConformation(ihm_gaussian_obj_site);
     const gaussianConformation = getGaussianConformation(ihm_gaussian_obj_site);
     const gaussianKeys = getCoarseKeys(gaussianData, data.entities);
     const gaussianKeys = getCoarseKeys(gaussianData, data.entities);
-    const gaussianRanges = getCoarseRanges(gaussianData, formatData.chemicalComponentMap);
+    const gaussianRanges = getCoarseRanges(gaussianData, properties.chemicalComponentMap);
 
 
     return {
     return {
         hierarchy: {
         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 {
     return {
         x: data.Cartn_x.toArray({ array: Float32Array }),
         x: data.Cartn_x.toArray({ array: Float32Array }),
         y: data.Cartn_y.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: Mat3[] = [];
     const { covariance_matrix: cm } = data;
     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;
     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) };
     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 David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @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 StructureSequence from '../../../mol-model/structure/model/properties/sequence'
 import { Column } from '../../../mol-data/db';
 import { Column } from '../../../mol-data/db';
 import { AtomicHierarchy } from '../../../mol-model/structure/model/properties/atomic';
 import { AtomicHierarchy } from '../../../mol-model/structure/model/properties/atomic';
 import { Entities } from '../../../mol-model/structure/model/properties/common';
 import { Entities } from '../../../mol-model/structure/model/properties/common';
 import { Sequence } from '../../../mol-model/sequence';
 import { Sequence } from '../../../mol-model/sequence';
 import { CoarseHierarchy } from '../../../mol-model/structure/model/properties/coarse';
 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);
         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 byEntityKey: StructureSequence['byEntityKey'] = {};
     const sequences: StructureSequence.Entity[] = [];
     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>
  * @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 { createRangeArray, makeBuckets } from '../../../mol-data/util';
 import { Column, Table } from '../../../mol-data/db';
 import { Column, Table } from '../../../mol-data/db';
 import { RuntimeContext } from '../../../mol-task';
 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>) {
 function isIdentity(xs: ArrayLike<number>) {
     for (let i = 0, _i = xs.length; i < _i; i++) {
     for (let i = 0, _i = xs.length; i < _i; i++) {
@@ -18,7 +21,7 @@ function isIdentity(xs: ArrayLike<number>) {
     return true;
     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 indices = createRangeArray(start, end - 1);
 
 
     const { label_entity_id, label_asym_id, label_seq_id } = atom_site;
     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 {
     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)
         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 David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
  */
 
 
 import { mmCIF_Database } from '../../mol-io/reader/cif/schema/mmcif';
 import { mmCIF_Database } from '../../mol-io/reader/cif/schema/mmcif';
 import { CIF, CifFrame } from '../../mol-io/reader/cif';
 import { CIF, CifFrame } from '../../mol-io/reader/cif';
 
 
+interface Format { readonly kind: string, name: string }
+
 type ModelFormat =
 type ModelFormat =
     | ModelFormat.mmCIF
     | ModelFormat.mmCIF
 
 
 namespace ModelFormat {
 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 }
 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 David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @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 { RuntimeContext } from '../../../mol-task';
-import UUID from '../../../mol-util/uuid';
 import { Model } from '../../../mol-model/structure/model/model';
 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 { ModelFormat } from '../format';
-import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
 import mmCIF_Format = ModelFormat.mmCIF
 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) {
 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 {
     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 {
     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 { Assembly, OperatorGroup, OperatorGroups } from '../../../mol-model/structure/model/properties/symmetry'
 import { Queries as Q } from '../../../mol-model/structure'
 import { Queries as Q } from '../../../mol-model/structure'
 import { StructureProperties } 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 [];
     if (!pdbx_struct_assembly._rowCount) return [];
 
 
-    const matrices = getMatrices(format);
+    const matrices = getMatrices(pdbx_struct_oper_list);
     const assemblies: Assembly[] = [];
     const assemblies: Assembly[] = [];
     for (let i = 0; i < pdbx_struct_assembly._rowCount; i++) {
     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;
     return assemblies;
 }
 }
@@ -27,9 +30,7 @@ export function createAssemblies(format: mmCIF_Format): ReadonlyArray<Assembly>
 type Matrices = Map<string, Mat4>
 type Matrices = Map<string, Mat4>
 type Generator = { assemblyId: string, expression: string, asymIds: string[] }
 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 id = pdbx_struct_assembly.id.value(index);
     const details = pdbx_struct_assembly.details.value(index);
     const details = pdbx_struct_assembly.details.value(index);
     const generators: Generator[] = [];
     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 { id, matrix, vector, _schema } = pdbx_struct_oper_list;
     const matrices = new Map<string, Mat4>();
     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 David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -8,12 +8,14 @@
 import { Model } from '../../../../mol-model/structure/model/model'
 import { Model } from '../../../../mol-model/structure/model/model'
 import { BondType } from '../../../../mol-model/structure/model/types'
 import { BondType } from '../../../../mol-model/structure/model/types'
 import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
 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 { CifWriter } from '../../../../mol-io/writer/cif'
 import { Table } from '../../../../mol-data/db';
 import { Table } from '../../../../mol-data/db';
+import { FormatPropertyProvider } from '../../common/property';
 
 
 export interface ComponentBond {
 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 {
 export namespace ComponentBond {
@@ -24,7 +26,9 @@ export namespace ComponentBond {
             categories: [{
             categories: [{
                 name: 'chem_comp_bond',
                 name: 'chem_comp_bond',
                 instance(ctx) {
                 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;
                     if (!chem_comp_bond) return CifWriter.Category.Empty;
 
 
                     const comp_names = ctx.structures[0].uniqueResidueNames;
                     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 Table.pick(table, mmCIF_Schema.chem_comp_bond, (i: number) => {
             return model.properties.chemicalComponentMap.has(table.comp_id.value(i))
             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);
             let e = new Entry(id);
-            this.entries.set(id, e);
+            entries.set(id, e);
             return 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 id = comp_id.value(i)!;
             const nameA = atom_id_1.value(i)!;
             const nameA = atom_id_1.value(i)!;
             const nameB = atom_id_2.value(i)!;
             const nameB = atom_id_2.value(i)!;
@@ -114,7 +72,7 @@ export namespace ComponentBond {
             const aromatic = pdbx_aromatic_flag.value(i) === 'Y';
             const aromatic = pdbx_aromatic_flag.value(i) === 'Y';
 
 
             if (entry.id !== id) {
             if (entry.id !== id) {
-                entry = compBond.addEntry(id);
+                entry = addEntry(id);
             }
             }
 
 
             let flags: number = BondType.Flag.Covalent;
             let flags: number = BondType.Flag.Covalent;
@@ -132,23 +90,28 @@ export namespace ComponentBond {
             entry.add(nameA, nameB, ord, flags);
             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>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
  */
 
 
-import { Model } from '../../../../mol-model/structure/model/model'
 import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
 import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
 import { IntAdjacencyGraph } from '../../../../mol-math/graph';
 import { IntAdjacencyGraph } from '../../../../mol-math/graph';
 import { Column } from '../../../../mol-data/db';
 import { Column } from '../../../../mol-data/db';
+import { FormatPropertyProvider } from '../../common/property';
 
 
 export type IndexPairBonds = IntAdjacencyGraph<number, { readonly order: ArrayLike<number> }>
 export type IndexPairBonds = IntAdjacencyGraph<number, { readonly order: ArrayLike<number> }>
 
 
@@ -27,6 +27,8 @@ export namespace IndexPairBonds {
         name: 'index_pair_bonds',
         name: 'index_pair_bonds',
     }
     }
 
 
+    export const Provider = FormatPropertyProvider.create<IndexPairBonds>(Descriptor)
+
     export type Data = {
     export type Data = {
         pairs: {
         pairs: {
             indexA: Column<number>,
             indexA: Column<number>,
@@ -36,33 +38,11 @@ export namespace IndexPairBonds {
         count: number
         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 { pairs, count } = data
-
         const indexA = pairs.indexA.toArray()
         const indexA = pairs.indexA.toArray()
         const indexB = pairs.indexB.toArray()
         const indexB = pairs.indexB.toArray()
         const order = pairs.order.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 { Model } from '../../../../mol-model/structure/model/model'
 import { Structure } from '../../../../mol-model/structure'
 import { Structure } from '../../../../mol-model/structure'
 import { BondType } from '../../../../mol-model/structure/model/types'
 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 { 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 { SortedArray } from '../../../../mol-data/int';
 import { CifWriter } from '../../../../mol-io/writer/cif'
 import { CifWriter } from '../../../../mol-io/writer/cif'
 import { ElementIndex, ResidueIndex } from '../../../../mol-model/structure/model/indexing';
 import { ElementIndex, ResidueIndex } from '../../../../mol-model/structure/model/indexing';
 import { getInterBondOrderFromTable } from '../../../../mol-model/structure/model/properties/atomic/bonds';
 import { getInterBondOrderFromTable } from '../../../../mol-model/structure/model/properties/atomic/bonds';
+import { FormatPropertyProvider } from '../../common/property';
 
 
 export interface StructConn {
 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>
     readonly entries: ReadonlyArray<StructConn.Entry>
 }
 }
 
 
@@ -30,27 +31,27 @@ export namespace StructConn {
             categories: [{
             categories: [{
                 name: 'struct_conn',
                 name: 'struct_conn',
                 instance(ctx) {
                 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[] = [];
                     const indices: number[] = [];
-                    for (const e of strConn.entries) {
+                    for (const e of p.entries) {
                         if (hasAtom(structure, e.partnerA.atomIndex) &&
                         if (hasAtom(structure, e.partnerA.atomIndex) &&
                                 hasAtom(structure, e.partnerB.atomIndex)) {
                                 hasAtom(structure, e.partnerB.atomIndex)) {
                             indices[indices.length] = e.rowIndex;
                             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) {
     function hasAtom({ units }: Structure, element: ElementIndex) {
         for (let i = 0, _i = units.length; i < _i; i++) {
         for (let i = 0, _i = units.length; i < _i; i++) {
             if (SortedArray.indexOf(units[i].elements, element) >= 0) return true;
             if (SortedArray.indexOf(units[i].elements, element) >= 0) return true;
@@ -58,31 +59,17 @@ export namespace StructConn {
         return false;
         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 {
     export interface Entry {
@@ -94,27 +81,7 @@ export namespace StructConn {
         partnerB: { residueIndex: ResidueIndex, atomIndex: ElementIndex, symmetry: string }
         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 { conn_type_id, pdbx_dist_value, pdbx_value_order } = struct_conn;
         const p1 = {
         const p1 = {
             label_asym_id: struct_conn.ptnr1_label_asym_id,
             label_asym_id: struct_conn.ptnr1_label_asym_id,
@@ -138,8 +105,10 @@ export namespace StructConn {
         const _p = (row: number, ps: typeof p1) => {
         const _p = (row: number, ps: typeof p1) => {
             if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0;
             if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0;
             const asymId = ps.label_asym_id.value(row);
             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(
             const residueIndex = model.atomicHierarchy.index.findResidue(
-                findEntityIdByAsymId(model, asymId),
+                model.entities.data.id.value(entityIndex),
                 asymId,
                 asymId,
                 ps.auth_seq_id.value(row),
                 ps.auth_seq_id.value(row),
                 ps.ins_code.value(row)
                 ps.ins_code.value(row)
@@ -148,7 +117,7 @@ export namespace StructConn {
             const atomName = ps.label_atom_id.value(row);
             const atomName = ps.label_atom_id.value(row);
             // turns out "mismat" records might not have atom name value
             // turns out "mismat" records might not have atom name value
             if (!atomName) return void 0;
             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;
             if (atomIndex < 0) return void 0;
             return { residueIndex, atomIndex, symmetry: ps.symmetry.value(row) };
             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>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
  */
@@ -7,43 +7,39 @@
 import { Model } from '../../../../mol-model/structure/model/model'
 import { Model } from '../../../../mol-model/structure/model/model'
 import { Table } from '../../../../mol-data/db'
 import { Table } from '../../../../mol-data/db'
 import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
 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 { 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[]
     getIndicesByElement: (element: ElementIndex, kind: Unit.Kind) => number[]
     data: Table<mmCIF_Schema['ihm_cross_link_restraint']>
     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 = {
         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 = {
         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) {
         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 asymId = ps.asym_id.value(row)
             const seqId = ps.seq_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)
                 if (atomicElement >= 0) _add(atomicElementMap, atomicElement as ElementIndex, row)
             } else if (model.coarseHierarchy.isDefined) {
             } else if (model.coarseHierarchy.isDefined) {
                 const sphereElement = model.coarseHierarchy.spheres.findSequenceKey(entityId, asymId, seqId)
                 const sphereElement = model.coarseHierarchy.spheres.findSequenceKey(entityId, asymId, seqId)
@@ -88,20 +89,18 @@ export namespace IHMCrossLinkRestraint {
 
 
         const emptyIndexArray: number[] = [];
         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, p1)
             add(i, p2)
             add(i, p2)
         }
         }
 
 
-        const crossLinkRestraint = {
+        return {
             getIndicesByElement: (element: ElementIndex, kind: Unit.Kind) => {
             getIndicesByElement: (element: ElementIndex, kind: Unit.Kind) => {
                 const map = getMapByKind(kind)
                 const map = getMapByKind(kind)
                 const idx = map.get(element)
                 const idx = map.get(element)
                 return idx !== undefined ? idx : emptyIndexArray
                 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 David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @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 { SecondaryStructureType } from '../../../mol-model/structure/model/types';
 import { AtomicHierarchy } from '../../../mol-model/structure/model/properties/atomic';
 import { AtomicHierarchy } from '../../../mol-model/structure/model/properties/atomic';
 import { SecondaryStructure } from '../../../mol-model/structure/model/properties/seconday-structure';
 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 { 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 = {
 type SecondaryStructureEntry = {
@@ -44,7 +59,7 @@ type SecondaryStructureEntry = {
 type SecondaryStructureMap = Map<string, Map<number, SecondaryStructureEntry[]>>
 type SecondaryStructureMap = Map<string, Map<number, SecondaryStructureEntry[]>>
 type SecondaryStructureData = { type: SecondaryStructureType[], key: number[], elements: SecondaryStructure.Element[] }
 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;
     if (!cat._rowCount) return;
 
 
     const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat;
     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;
     if (!cat._rowCount) return;
 
 
     const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat;
     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 { Unit } from '../../mol-model/structure/structure';
 import { CustomStructureProperty } from '../common/custom-structure-property';
 import { CustomStructureProperty } from '../common/custom-structure-property';
 import { CustomProperty } from '../common/custom-property';
 import { CustomProperty } from '../common/custom-property';
+import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure';
 
 
 function getSecondaryStructureParams(data?: Structure) {
 function getSecondaryStructureParams(data?: Structure) {
     let defaultType = 'mmcif' as 'mmcif' | 'dssp'
     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) {
     for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
         const u = structure.unitSymmetryGroups[i].units[0]
         const u = structure.unitSymmetryGroups[i].units[0]
         if (Unit.isAtomic(u)) {
         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
     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 { toTable } from '../../mol-io/reader/cif/schema';
 import { CifWriter } from '../../mol-io/writer/cif';
 import { CifWriter } from '../../mol-io/writer/cif';
 import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
 import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
+import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
 
 
 export namespace PDBePreferredAssembly {
 export namespace PDBePreferredAssembly {
     export type Property = string
     export type Property = string
 
 
     export function getFirstFromModel(model: Model): Property {
     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 {
     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 CifCategory = CifWriter.Category
 import { Column } from '../../../../mol-data/db';
 import { Column } from '../../../../mol-data/db';
 import { residueIdFields } from './atom_site';
 import { residueIdFields } from './atom_site';
+import { ModelSecondaryStructure } from '../../../../mol-model-formats/structure/property/secondary-structure';
 
 
 export const _struct_conf: CifCategory<CifExportContext> = {
 export const _struct_conf: CifCategory<CifExportContext> = {
     name: 'struct_conf',
     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']) {
 function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContext, kind: SecondaryStructure.Element['kind']) {
     // TODO: encode secondary structure for different models?
     // 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>[] = [];
     const ssElements: SSElement<any>[] = [];
 
 
     for (const unit of ctx.structures[0].units) {
     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 { Model } from './model/model'
 import * as Types from './model/types'
 import * as Types from './model/types'
-import { ModelSymmetry } from './model/properties/symmetry'
+import { Symmetry } from './model/properties/symmetry'
 import StructureSequence from './model/properties/sequence'
 import StructureSequence from './model/properties/sequence'
 
 
 export * from './model/properties/custom/indexed'
 export * from './model/properties/custom/indexed'
 export * from './model/indexing'
 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 David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -8,11 +8,9 @@
 import UUID from '../../../mol-util/uuid';
 import UUID from '../../../mol-util/uuid';
 import StructureSequence from './properties/sequence';
 import StructureSequence from './properties/sequence';
 import { AtomicHierarchy, AtomicConformation, AtomicRanges } from './properties/atomic';
 import { AtomicHierarchy, AtomicConformation, AtomicRanges } from './properties/atomic';
-import { ModelSymmetry } from './properties/symmetry';
 import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
 import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
 import { Entities, ChemicalComponentMap, MissingResidues } from './properties/common';
 import { Entities, ChemicalComponentMap, MissingResidues } from './properties/common';
 import { CustomProperties } from '../common/custom-property';
 import { CustomProperties } from '../common/custom-property';
-import { SecondaryStructure } from './properties/seconday-structure';
 import { SaccharideComponentMap } from '../structure/carbohydrates/constants';
 import { SaccharideComponentMap } from '../structure/carbohydrates/constants';
 import { ModelFormat } from '../../../mol-model-formats/structure/format';
 import { ModelFormat } from '../../../mol-model-formats/structure/format';
 import { calcModelCenter } from './util';
 import { calcModelCenter } from './util';
@@ -22,7 +20,7 @@ import { Coordinates } from '../coordinates';
 import { Topology } from '../topology';
 import { Topology } from '../topology';
 import { _parse_mmCif } from '../../../mol-model-formats/structure/mmcif/parser';
 import { _parse_mmCif } from '../../../mol-model-formats/structure/mmcif/parser';
 import { Task } from '../../../mol-task';
 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.
  * Interface to the "source data" of the molecule.
@@ -47,7 +45,6 @@ export interface Model extends Readonly<{
 
 
     sourceData: ModelFormat,
     sourceData: ModelFormat,
 
 
-    symmetry: ModelSymmetry,
     entities: Entities,
     entities: Entities,
     sequence: StructureSequence,
     sequence: StructureSequence,
 
 
@@ -56,8 +53,6 @@ export interface Model extends Readonly<{
     atomicRanges: AtomicRanges,
     atomicRanges: AtomicRanges,
 
 
     properties: {
     properties: {
-        /** secondary structure provided by the input file */
-        readonly secondaryStructure: SecondaryStructure,
         /** maps modified residue name to its parent */
         /** maps modified residue name to its parent */
         readonly modifiedResidues: Readonly<{
         readonly modifiedResidues: Readonly<{
             parentId: ReadonlyMap<string, string>,
             parentId: ReadonlyMap<string, string>,
@@ -87,7 +82,6 @@ export interface Model extends Readonly<{
 } { }
 } { }
 
 
 export namespace Model {
 export namespace Model {
-    // TODO: is this enough?
     export type Trajectory = ReadonlyArray<Model>
     export type Trajectory = ReadonlyArray<Model>
 
 
     export function trajectoryFromModelAndCoordinates(model: Model, coordinates: Coordinates): Trajectory {
     export function trajectoryFromModelAndCoordinates(model: Model, coordinates: Coordinates): Trajectory {
@@ -117,8 +111,9 @@ export namespace Model {
             if (!model) throw new Error('found no model')
             if (!model) throw new Error('found no model')
             const trajectory = trajectoryFromModelAndCoordinates(model, coordinates)
             const trajectory = trajectoryFromModelAndCoordinates(model, coordinates)
             const bondData = { pairs: topology.bonds, count: model.atomicHierarchy.atoms._rowCount }
             const bondData = { pairs: topology.bonds, count: model.atomicHierarchy.atoms._rowCount }
+            const indexPairBonds = IndexPairBonds.fromData(bondData)
             for (const m of trajectory) {
             for (const m of trajectory) {
-                IndexPairBonds.attachFromData(m, bondData)
+                IndexPairBonds.Provider.set(m, indexPairBonds)
             }
             }
             return trajectory
             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 { Model } from '../../model'
 import { Spacegroup } from '../../../../mol-math/geometry';
 import { Spacegroup } from '../../../../mol-math/geometry';
 import { Vec3 } from '../../../../mol-math/linear-algebra';
 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  */
 /** Determine an atom set and a list of operators that should be applied to that set  */
 export interface OperatorGroup {
 export interface OperatorGroup {
@@ -42,7 +43,7 @@ export namespace Assembly {
     }
     }
 }
 }
 
 
-interface ModelSymmetry {
+interface Symmetry {
     readonly assemblies: ReadonlyArray<Assembly>,
     readonly assemblies: ReadonlyArray<Assembly>,
     readonly spacegroup: Spacegroup,
     readonly spacegroup: Spacegroup,
     readonly isNonStandardCrytalFrame: boolean,
     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 {
     export function findAssembly(model: Model, id: string): Assembly | undefined {
         const _id = id.toLocaleLowerCase();
         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 StructureElement from './element'
 import Unit from './unit'
 import Unit from './unit'
 import { VdwRadius } from '../model/properties/atomic';
 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; }
 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'),
     isNonStandard: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.mon_nstd_flag[0] !== 'y'),
     hasMicroheterogeneity: p(hasMicroheterogeneity),
     hasMicroheterogeneity: p(hasMicroheterogeneity),
     microheterogeneityCompIds: p(microheterogeneityCompIds),
     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),
     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 { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/geometry';
 import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
 import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
 import { RuntimeContext, Task } from '../../../mol-task';
 import { RuntimeContext, Task } from '../../../mol-task';
-import { ModelSymmetry, Model } from '../model';
+import { Symmetry, Model } from '../model';
 import { QueryContext, StructureSelection } from '../query';
 import { QueryContext, StructureSelection } from '../query';
 import Structure from './structure';
 import Structure from './structure';
 import Unit from './unit';
 import Unit from './unit';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 
 namespace StructureSymmetry {
 namespace StructureSymmetry {
     export function buildAssembly(structure: Structure, asmName: string) {
     export function buildAssembly(structure: Structure, asmName: string) {
@@ -21,7 +22,7 @@ namespace StructureSymmetry {
             const models = structure.models;
             const models = structure.models;
             if (models.length !== 1) throw new Error('Can only build assemblies from structures based on 1 model.');
             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.`);
             if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
 
 
             const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] })
             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 { spacegroup, ncsOperators } = symmetry;
     const ncsCount = (ncsOperators && ncsOperators.length) || 0
     const ncsCount = (ncsOperators && ncsOperators.length) || 0
     const operators: SymmetryOperator[] = [];
     const operators: SymmetryOperator[] = [];
@@ -135,7 +136,7 @@ function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3, model
     return operators;
     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)) {
     if (symmetry._operators_333 && Vec3.equals(ref, symmetry._operators_333.ref)) {
         return symmetry._operators_333.operators;
         return symmetry._operators_333.operators;
     }
     }
@@ -161,7 +162,10 @@ async function _buildNCS(ctx: RuntimeContext, structure: Structure) {
     const models = structure.models;
     const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build NCS from structures based on 1 model.');
     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;
     if (!operators || !operators.length) return structure;
     return assembleOperators(structure, operators);
     return assembleOperators(structure, operators);
 }
 }
@@ -170,11 +174,14 @@ async function findSymmetryRange(ctx: RuntimeContext, structure: Structure, ijkM
     const models = structure.models;
     const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
     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;
     if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
 
 
     const modelCenter = Model.getCenter(models[0])
     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);
     return assembleOperators(structure, operators);
 }
 }
 
 
@@ -182,7 +189,9 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius
     const models = structure.models;
     const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
     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;
     const { spacegroup } = symmetry;
     if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
     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 { SortedArray } from '../../../../../mol-data/int';
 import { Vec3, Mat4 } from '../../../../../mol-math/linear-algebra';
 import { Vec3, Mat4 } from '../../../../../mol-math/linear-algebra';
 import StructureElement from '../../element';
 import StructureElement from '../../element';
-import { StructConn } from '../../../../../mol-model-formats/structure/mmcif/bonds';
 import { ElementIndex } from '../../../model/indexing';
 import { ElementIndex } from '../../../model/indexing';
 import { getInterBondOrderFromTable } from '../../../model/properties/atomic/bonds';
 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 { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph';
+import { StructConn } from '../../../../../mol-model-formats/structure/property/bonds/struct_conn';
 
 
 const MAX_RADIUS = 4;
 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 { occupancy: occupancyB } = unitB.model.atomicConformation;
 
 
     const { lookup3d } = unitB;
     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".
     // the lookup queries need to happen in the "unitB space".
     // that means imageA = inverseOperB(operA(aI))
     // 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
             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) {
         if (structConnEntries && structConnEntries.length) {
             let added = false;
             let added = false;
             for (const se of structConnEntries) {
             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 { IntAdjacencyGraph } from '../../../../../mol-math/graph';
 import { BondComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold, DefaultBondComputationProps } from './common';
 import { BondComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold, DefaultBondComputationProps } from './common';
 import { SortedArray } from '../../../../../mol-data/int';
 import { SortedArray } from '../../../../../mol-data/int';
-import { StructConn, ComponentBond } from '../../../../../mol-model-formats/structure/mmcif/bonds';
 import { getIntraBondOrderFromTable } from '../../../model/properties/atomic/bonds';
 import { getIntraBondOrderFromTable } from '../../../model/properties/atomic/bonds';
 import StructureElement from '../../element';
 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 {
 function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], atomCount: number): IntraUnitBonds {
     const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB);
     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 { label_comp_id } = unit.model.atomicHierarchy.residues;
     const query3d = unit.lookup3d;
     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 atomA: StructureElement.UnitIndex[] = [];
     const atomB: StructureElement.UnitIndex[] = [];
     const atomB: StructureElement.UnitIndex[] = [];
@@ -70,7 +71,7 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
             continue // assume `indexPairs` supplies all bonds
             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;
         let hasStructConn = false;
         if (structConnEntries) {
         if (structConnEntries) {
             for (const se of 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 Structure from '../../structure';
 import { PairRestraints, CrossLinkRestraint } from './data';
 import { PairRestraints, CrossLinkRestraint } from './data';
 import { StructureElement } from '../../../structure';
 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 { elements } = unit;
     const elementCount = elements.length;
     const elementCount = elements.length;
     const kind = unit.kind
     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 !== unitB.model) return
     if (unitA.model.sourceData.kind !== 'mmCIF') return
     if (unitA.model.sourceData.kind !== 'mmCIF') return
 
 
-    const restraints = IHMCrossLinkRestraint.fromModel(unitA.model)
+    const restraints = ModelCrossLinkRestraint.Provider.get(unitA.model)
     if (!restraints) return
     if (!restraints) return
 
 
     const rA = new Map<number, StructureElement.UnitIndex>();
     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) {
 function extractIntra(pairs: CrossLinkRestraint[], unit: Unit) {
     if (unit.model.sourceData.kind !== 'mmCIF') return
     if (unit.model.sourceData.kind !== 'mmCIF') return
 
 
-    const restraints = IHMCrossLinkRestraint.fromModel(unit.model)
+    const restraints = ModelCrossLinkRestraint.Provider.get(unit.model)
     if (!restraints) return
     if (!restraints) return
 
 
     const { elements } = unit;
     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 {
     return {
         unitA, indexA, unitB, indexB,
         unitA, indexA, unitB, indexB,
 
 
@@ -89,7 +89,7 @@ function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitInde
 
 
 function extractCrossLinkRestraints(structure: Structure): PairRestraints<CrossLinkRestraint> {
 function extractCrossLinkRestraints(structure: Structure): PairRestraints<CrossLinkRestraint> {
     const pairs: 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)
         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 { Vec3 } from '../../../mol-math/linear-algebra';
 import { RuntimeContext } from '../../../mol-task';
 import { RuntimeContext } from '../../../mol-task';
 import { PluginContext } from '../../context';
 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 { PluginStateObject as SO } from '../objects';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 
 export namespace ModelStructureRepresentation {
 export namespace ModelStructureRepresentation {
     export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates') {
     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 = {
         const modes = {
             deposited: PD.EmptyGroup(),
             deposited: PD.EmptyGroup(),
@@ -58,17 +61,19 @@ export namespace ModelStructureRepresentation {
     async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string) {
     async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string) {
         let asm: Assembly | undefined = void 0;
         let asm: Assembly | undefined = void 0;
 
 
+        const symmetry = ModelSymmetry.Provider.get(model)
+
         // if no id is specified, use the 1st assembly.
         // 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') {
             if (id !== 'deposited') {
                 plugin.log.warn(`Model '${model.entryId}' has no assembly, returning deposited structure.`);
                 plugin.log.warn(`Model '${model.entryId}' has no assembly, returning deposited structure.`);
             }
             }
         } else {
         } else {
-            asm = ModelSymmetry.findAssembly(model, id || '');
+            asm = Symmetry.findAssembly(model, id || '');
             if (!asm) {
             if (!asm) {
                 plugin.log.warn(`Model '${model.entryId}' has no assembly called '${id}', returning deposited structure.`);
                 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 { coordinatesFromDcd } from '../../../mol-model-formats/structure/dcd';
 import { topologyFromPsf } from '../../../mol-model-formats/structure/psf';
 import { topologyFromPsf } from '../../../mol-model-formats/structure/psf';
 import { deepEqual } from '../../../mol-util';
 import { deepEqual } from '../../../mol-util';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 
 export { CoordinatesFromDcd };
 export { CoordinatesFromDcd };
 export { TopologyFromPsf };
 export { TopologyFromPsf };
@@ -300,8 +301,8 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({
         if (!a) {
         if (!a) {
             return { id: PD.Optional(PD.Text('', { label: 'Assembly Id', description: 'Assembly Id. Value \'deposited\' can be used to specify deposited asymmetric unit.' })) };
             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']);
         ids.push(['deposited', 'Deposited']);
         return {
         return {
             id: PD.Optional(PD.Select(ids[0][0], ids, { label: 'Asm Id', description: 'Assembly Id' }))
             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 David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @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 { StructureParams } from '../../../mol-repr/structure/representation';
 import { BuiltInVolumeRepresentationsName } from '../../../mol-repr/volume/registry';
 import { BuiltInVolumeRepresentationsName } from '../../../mol-repr/volume/registry';
 import { VolumeParams } from '../../../mol-repr/volume/representation';
 import { VolumeParams } from '../../../mol-repr/volume/representation';
-import { StateTransformer } from '../../../mol-state';
+import { StateTransformer, StateObject } from '../../../mol-state';
 import { Task } from '../../../mol-task';
 import { Task } from '../../../mol-task';
 import { BuiltInColorThemeName, ColorTheme, BuiltInColorThemes } from '../../../mol-theme/color';
 import { BuiltInColorThemeName, ColorTheme, BuiltInColorThemes } from '../../../mol-theme/color';
 import { BuiltInSizeThemeName, SizeTheme } from '../../../mol-theme/size';
 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 { OrientationRepresentation, OrientationParams } from '../../../mol-repr/shape/loci/orientation';
 import { AngleParams, AngleRepresentation } from '../../../mol-repr/shape/loci/angle';
 import { AngleParams, AngleRepresentation } from '../../../mol-repr/shape/loci/angle';
 import { DihedralParams, DihedralRepresentation } from '../../../mol-repr/shape/loci/dihedral';
 import { DihedralParams, DihedralRepresentation } from '../../../mol-repr/shape/loci/dihedral';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 
 export { StructureRepresentation3D }
 export { StructureRepresentation3D }
 export { StructureRepresentation3DHelpers }
 export { StructureRepresentation3DHelpers }
@@ -649,13 +650,16 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
         ...UnitcellParams,
         ...UnitcellParams,
     }
     }
 })({
 })({
+    isApplicable: a => !!ModelSymmetry.Provider.get(a.data),
     canAutoUpdate({ oldParams, newParams }) {
     canAutoUpdate({ oldParams, newParams }) {
         return true;
         return true;
     },
     },
     apply({ a, params }) {
     apply({ a, params }) {
         return Task.create('Model Unitcell', async ctx => {
         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);
             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 }) {
     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>
  * @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 { ShapeRepresentation } from '../../mol-repr/shape/representation';
 import { Shape } from '../../mol-model/shape';
 import { Shape } from '../../mol-model/shape';
 import { ColorNames } from '../../mol-util/color/names';
 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 { Mat4, Vec3 } from '../../mol-math/linear-algebra';
 import { transformCage, cloneCage } from '../../mol-geo/primitive/cage';
 import { transformCage, cloneCage } from '../../mol-geo/primitive/cage';
 import { radToDeg } from '../../mol-math/misc';
 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 translate05 = Mat4.fromTranslation(Mat4(), Vec3.create(0.5, 0.5, 0.5))
 const unitCage = transformCage(cloneCage(BoxCage()), translate05)
 const unitCage = transformCage(cloneCage(BoxCage()), translate05)
@@ -24,7 +25,7 @@ const tmpRef = Vec3()
 const tmpTranslate = Mat4()
 const tmpTranslate = Mat4()
 
 
 interface UnitcellData {
 interface UnitcellData {
-    symmetry: ModelSymmetry
+    symmetry: Symmetry
     ref: Vec3
     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>) {
 export async function getUnitcellRepresentation(ctx: RuntimeContext, model: Model, params: UnitcellProps, prev?: ShapeRepresentation<UnitcellData, Mesh, Mesh.Params>) {
     const repr = prev || ShapeRepresentation(getUnitcellShape, Mesh.Utils);
     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;
     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 { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
 import { EllipsoidMeshParams, EllipsoidMeshVisual } from '../visual/ellipsoid-mesh';
 import { EllipsoidMeshParams, EllipsoidMeshVisual } from '../visual/ellipsoid-mesh';
 import { UnitKind, UnitKindOptions } from '../../../mol-repr/structure/visual/util/common';
 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 { IntraUnitBondParams, IntraUnitBondVisual } from '../visual/bond-intra-unit-cylinder';
 import { InterUnitBondParams, InterUnitBondVisual } from '../visual/bond-inter-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 { Vec3, Mat3, Tensor, EPSILON } from '../../../mol-math/linear-algebra';
 import { isHydrogen } from '../../../mol-repr/structure/visual/util/common';
 import { isHydrogen } from '../../../mol-repr/structure/visual/util/common';
 import { addEllipsoid } from '../../../mol-geo/geometry/mesh/builder/ellipsoid';
 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 { equalEps } from '../../../mol-math/linear-algebra/3d/common';
 import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
 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 vertexCount = elementCount * sphereVertexCount(detail)
     const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh)
     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)
     if (!atomSiteAnisotrop) return Mesh.createEmpty(mesh)
 
 
     const v = Vec3()
     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>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
  */
@@ -9,15 +9,17 @@ import * as util from 'util'
 import { AttachModelProperty } from '../../property-provider';
 import { AttachModelProperty } from '../../property-provider';
 import { CIF } from '../../../../mol-io/reader/cif';
 import { CIF } from '../../../../mol-io/reader/cif';
 import { getParam } from '../../../common/util';
 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 { 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()
 require('util.promisify').shim()
 const readFile = util.promisify(fs.readFile)
 const readFile = util.promisify(fs.readFile)
 
 
 export const wwPDB_chemCompBond: AttachModelProperty = async ({ model, params }) => {
 export const wwPDB_chemCompBond: AttachModelProperty = async ({ model, params }) => {
     const table = await getChemCompBondTable(getTablePath(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) {
 async function read(path: string) {