ソースを参照

Merge branch 'master' into plugin

David Sehnal 5 年 前
コミット
d7b232b00b
100 ファイル変更2183 行追加2173 行削除
  1. 1 0
      README.md
  2. 0 60
      data/cif-core.csv
  3. 2 2
      data/rcsb-graphql/codegen.yml
  4. 147 459
      package-lock.json
  5. 12 12
      package.json
  6. 1 1
      src/apps/basic-wrapper/index.ts
  7. 3 3
      src/apps/chem-comp-bond/create-table.ts
  8. 1 0
      src/apps/state-docs/pd-to-md.ts
  9. 8 3
      src/apps/structure-info/model.ts
  10. 11 9
      src/apps/viewer/extensions/cellpack/model.ts
  11. 1 1
      src/examples/proteopedia-wrapper/coloring.ts
  12. 3 1
      src/examples/proteopedia-wrapper/helpers.ts
  13. 1 1
      src/examples/proteopedia-wrapper/index.ts
  14. 5 4
      src/mol-canvas3d/camera.ts
  15. 87 60
      src/mol-canvas3d/canvas3d.ts
  16. 1 1
      src/mol-canvas3d/helper/bounding-sphere-helper.ts
  17. 9 1
      src/mol-data/db/column.ts
  18. 27 8
      src/mol-data/db/table.ts
  19. 2 2
      src/mol-gl/_spec/renderer.spec.ts
  20. 55 0
      src/mol-gl/commit-queue.ts
  21. 37 60
      src/mol-gl/scene.ts
  22. 19 0
      src/mol-io/reader/_spec/cif.spec.ts
  23. 2 2
      src/mol-io/reader/cif.ts
  24. 3 3
      src/mol-io/reader/cif/schema/bird.ts
  25. 2 2
      src/mol-io/reader/cif/schema/ccd.ts
  26. 6 6
      src/mol-io/reader/cif/schema/cif-core.ts
  27. 20 20
      src/mol-io/reader/cif/schema/mmcif.ts
  28. 40 1
      src/mol-io/reader/cif/text/parser.ts
  29. 1 1
      src/mol-io/writer/cif/encoder.ts
  30. 42 38
      src/mol-model-formats/structure/3dg.ts
  31. 9 12
      src/mol-model-formats/structure/basic/atomic.ts
  32. 17 16
      src/mol-model-formats/structure/basic/coarse.ts
  33. 122 0
      src/mol-model-formats/structure/basic/entities.ts
  34. 210 0
      src/mol-model-formats/structure/basic/parser.ts
  35. 134 0
      src/mol-model-formats/structure/basic/properties.ts
  36. 81 0
      src/mol-model-formats/structure/basic/schema.ts
  37. 5 5
      src/mol-model-formats/structure/basic/sequence.ts
  38. 7 4
      src/mol-model-formats/structure/basic/sort.ts
  39. 20 0
      src/mol-model-formats/structure/basic/util.ts
  40. 8 10
      src/mol-model-formats/structure/common/component.ts
  41. 16 14
      src/mol-model-formats/structure/common/entity.ts
  42. 55 0
      src/mol-model-formats/structure/common/property.ts
  43. 3 13
      src/mol-model-formats/structure/format.ts
  44. 41 42
      src/mol-model-formats/structure/gro.ts
  45. 85 4
      src/mol-model-formats/structure/mmcif.ts
  46. 0 88
      src/mol-model-formats/structure/mmcif/anisotropic.ts
  47. 0 9
      src/mol-model-formats/structure/mmcif/bonds.ts
  48. 0 250
      src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts
  49. 0 8
      src/mol-model-formats/structure/mmcif/pair-restraint.ts
  50. 0 529
      src/mol-model-formats/structure/mmcif/parser.ts
  51. 0 26
      src/mol-model-formats/structure/mmcif/util.ts
  52. 6 5
      src/mol-model-formats/structure/pdb.ts
  53. 2 2
      src/mol-model-formats/structure/pdb/to-cif.ts
  54. 60 0
      src/mol-model-formats/structure/property/anisotropic.ts
  55. 11 11
      src/mol-model-formats/structure/property/assembly.ts
  56. 36 73
      src/mol-model-formats/structure/property/bonds/comp.ts
  57. 6 26
      src/mol-model-formats/structure/property/bonds/index-pair.ts
  58. 168 0
      src/mol-model-formats/structure/property/bonds/struct_conn.ts
  59. 31 32
      src/mol-model-formats/structure/property/pair-restraints/cross-links.ts
  60. 0 0
      src/mol-model-formats/structure/property/pair-restraints/predicted-contacts.ts
  61. 34 19
      src/mol-model-formats/structure/property/secondary-structure.ts
  62. 85 0
      src/mol-model-formats/structure/property/symmetry.ts
  63. 37 42
      src/mol-model-formats/structure/psf.ts
  64. 18 7
      src/mol-model-props/common/custom-property.ts
  65. 3 2
      src/mol-model-props/common/wrapper.ts
  66. 4 3
      src/mol-model-props/computed/accessible-surface-area/shrake-rupley/area.ts
  67. 2 1
      src/mol-model-props/computed/accessible-surface-area/shrake-rupley/radii.ts
  68. 9 5
      src/mol-model-props/computed/interactions/interactions.ts
  69. 5 4
      src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts
  70. 4 4
      src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts
  71. 10 5
      src/mol-model-props/computed/secondary-structure.ts
  72. 6 4
      src/mol-model-props/pdbe/preferred-assembly.ts
  73. 3 2
      src/mol-model-props/pdbe/struct-ref-domain.ts
  74. 42 11
      src/mol-model-props/pdbe/structure-quality-report.ts
  75. 59 6
      src/mol-model-props/pdbe/themes/structure-quality-report.ts
  76. 4 3
      src/mol-model-props/rcsb/assembly-symmetry.ts
  77. 3 3
      src/mol-model-props/rcsb/graphql/types.ts
  78. 16 12
      src/mol-model-props/rcsb/representations/validation-report-clashes.ts
  79. 36 13
      src/mol-model-props/rcsb/themes/geometry-quality.ts
  80. 3 2
      src/mol-model-props/rcsb/validation-report.ts
  81. 2 2
      src/mol-model/structure/export/categories/modified-residues.ts
  82. 8 4
      src/mol-model/structure/export/categories/secondary-structure.ts
  83. 5 4
      src/mol-model/structure/export/categories/utils.ts
  84. 2 2
      src/mol-model/structure/model.ts
  85. 6 11
      src/mol-model/structure/model/model.ts
  86. 5 3
      src/mol-model/structure/model/properties/atomic/hierarchy.ts
  87. 0 22
      src/mol-model/structure/model/properties/computed.ts
  88. 3 3
      src/mol-model/structure/model/properties/custom/indexed.ts
  89. 7 5
      src/mol-model/structure/model/properties/symmetry.ts
  90. 12 2
      src/mol-model/structure/model/properties/utils/atomic-index.ts
  91. 9 2
      src/mol-model/structure/model/types.ts
  92. 15 12
      src/mol-model/structure/query/context.ts
  93. 4 2
      src/mol-model/structure/query/queries/filters.ts
  94. 5 0
      src/mol-model/structure/query/queries/generators.ts
  95. 3 3
      src/mol-model/structure/query/queries/internal.ts
  96. 7 0
      src/mol-model/structure/query/queries/modifiers.ts
  97. 1 1
      src/mol-model/structure/query/utils/builders.ts
  98. 6 2
      src/mol-model/structure/query/utils/structure-distance.ts
  99. 15 3
      src/mol-model/structure/structure/element/location.ts
  100. 3 2
      src/mol-model/structure/structure/element/loci.ts

+ 1 - 0
README.md

@@ -91,6 +91,7 @@ Install CIFTools `npm install ciftools -g`
     cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
     cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
     cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
+    cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/cif-core.ts -p CifCore -aa
 
 **GraphQL schemas**
 

+ 0 - 60
data/cif-core.csv

@@ -1,60 +0,0 @@
-audit.block_doi
-
-database_code.depnum_ccdc_archive
-
-chemical.name_systematic
-chemical.name_common
-chemical.melting_point
-
-chemical_formula.moiety
-chemical_formula.sum
-chemical_formula.weight
-
-atom_type.symbol
-atom_type.description
-
-atom_type_scat.dispersion_real
-atom_type_scat.dispersion_imag
-atom_type_scat.source
-
-space_group.crystal_system
-space_group.name_H-M_full
-space_group_symop.operation_xyz
-
-cell.length_a
-cell.length_b
-cell.length_c
-cell.angle_alpha
-cell.angle_beta
-cell.angle_gamma
-cell.volume
-cell.formula_units_Z
-
-atom_site.label
-atom_site.type_symbol
-atom_site.fract_x
-atom_site.fract_y
-atom_site.fract_z
-atom_site.U_iso_or_equiv
-atom_site.adp_type
-atom_site.occupancy
-atom_site.calc_flag
-atom_site.refinement_flags
-atom_site.disorder_assembly
-atom_site.disorder_group
-
-atom_site.site_symmetry_multiplicity
-
-atom_site_aniso.label
-atom_site_aniso.U_11
-atom_site_aniso.U_22
-atom_site_aniso.U_33
-atom_site_aniso.U_23
-atom_site_aniso.U_13
-atom_site_aniso.U_12
-
-geom_bond.atom_site_label_1
-geom_bond.atom_site_label_2
-geom_bond.distance
-geom_bond.site_symmetry_2
-geom_bond.publ_flag

+ 2 - 2
data/rcsb-graphql/codegen.yml

@@ -1,7 +1,7 @@
-schema: http://data-beta.rcsb.org/graphql
+schema: https://data-beta.rcsb.org/graphql
 documents: './src/mol-model-props/rcsb/graphql/symmetry.gql.ts'
 generates:
-  './src/mol-model-props/rcsb/graphql/types.d.ts':
+  './src/mol-model-props/rcsb/graphql/types.ts':
     plugins:
       - add: '/* eslint-disable */'
       - time

ファイルの差分が大きいため隠しています
+ 147 - 459
package-lock.json


+ 12 - 12
package.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "0.5.0-dev.1",
+  "version": "0.5.1",
   "description": "A comprehensive macromolecular library.",
   "homepage": "https://github.com/molstar/molstar#readme",
   "repository": {
@@ -72,9 +72,9 @@
     "@graphql-codegen/typescript-graphql-request": "^1.12.2",
     "@graphql-codegen/typescript-operations": "^1.12.2",
     "@types/cors": "^2.8.6",
-    "@typescript-eslint/eslint-plugin": "^2.19.2",
-    "@typescript-eslint/eslint-plugin-tslint": "^2.19.2",
-    "@typescript-eslint/parser": "^2.19.2",
+    "@typescript-eslint/eslint-plugin": "^2.20.0",
+    "@typescript-eslint/eslint-plugin-tslint": "^2.20.0",
+    "@typescript-eslint/parser": "^2.20.0",
     "benchmark": "^2.1.4",
     "circular-dependency-plugin": "^5.2.0",
     "concurrently": "^5.1.0",
@@ -82,7 +82,7 @@
     "css-loader": "^3.4.2",
     "eslint": "^6.8.0",
     "extra-watch-webpack-plugin": "^1.0.3",
-    "file-loader": "^5.0.2",
+    "file-loader": "^5.1.0",
     "fs-extra": "^8.1.0",
     "http-server": "^0.12.1",
     "jest": "^25.1.0",
@@ -95,20 +95,20 @@
     "sass-loader": "^8.0.2",
     "simple-git": "^1.131.0",
     "style-loader": "^1.1.3",
-    "ts-jest": "^25.2.0",
-    "typescript": "^3.7.5",
-    "webpack": "^4.41.5",
-    "webpack-cli": "^3.3.10"
+    "ts-jest": "^25.2.1",
+    "typescript": "^3.8.2",
+    "webpack": "^4.41.6",
+    "webpack-cli": "^3.3.11"
   },
   "dependencies": {
     "@types/argparse": "^1.0.38",
     "@types/benchmark": "^1.0.31",
     "@types/compression": "1.7.0",
     "@types/express": "^4.17.2",
-    "@types/jest": "^25.1.2",
-    "@types/node": "^13.7.0",
+    "@types/jest": "^25.1.3",
+    "@types/node": "^13.7.4",
     "@types/node-fetch": "^2.5.4",
-    "@types/react": "^16.9.19",
+    "@types/react": "^16.9.22",
     "@types/react-dom": "^16.9.5",
     "@types/swagger-ui-dist": "3.0.5",
     "argparse": "^1.0.10",

+ 1 - 1
src/apps/basic-wrapper/index.ts

@@ -63,7 +63,7 @@ class BasicWrapper {
 
         return parsed
             .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
-            .apply(StateTransforms.Model.CustomModelProperties, { properties: [StripedResidues.propertyProvider.descriptor.name] }, { ref: 'props', state: { isGhost: false } })
+            .apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [StripedResidues.propertyProvider.descriptor.name], properties: {} }, { ref: 'props', state: { isGhost: false } })
             .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
     }
 

+ 3 - 3
src/apps/chem-comp-bond/create-table.ts

@@ -144,9 +144,9 @@ async function createBonds() {
     const comp_id: string[] = []
     const atom_id_1: string[] = []
     const atom_id_2: string[] = []
-    const value_order: string[] = []
-    const pdbx_aromatic_flag: string[] = []
-    const pdbx_stereo_config: string[] = []
+    const value_order: typeof mmCIF_chemCompBond_schema['value_order']['T'][] = [] 
+    const pdbx_aromatic_flag: typeof mmCIF_chemCompBond_schema['pdbx_aromatic_flag']['T'][] = []
+    const pdbx_stereo_config: typeof mmCIF_chemCompBond_schema['pdbx_stereo_config']['T'][] = []
     const molstar_protonation_variant: string[] = []
 
     function addBonds(compId: string, ccb: CCB, protonationVariant: boolean) {

+ 1 - 0
src/apps/state-docs/pd-to-md.ts

@@ -22,6 +22,7 @@ function paramInfo(param: PD.Any, offset: number): string {
         case 'color-list': return `One of ${oToS(param.options)}`;
         case 'vec3': return `3D vector [x, y, z]`;
         case 'file': return `JavaScript File Handle`;
+        case 'file-list': return `JavaScript FileList Handle`;
         case 'select': return `One of ${oToS(param.options)}`;
         case 'text': return 'String';
         case 'interval': return `Interval [min, max]`;

+ 8 - 3
src/apps/structure-info/model.ts

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

+ 11 - 9
src/apps/viewer/extensions/cellpack/model.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>
  */
@@ -11,7 +11,7 @@ import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
 import { Ingredient, CellPacking, Cell } from './data';
 import { getFromPdb, getFromCellPackDB } from './util';
 import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../../../mol-model/structure';
-import { trajectoryFromMmCIF } from '../../../../mol-model-formats/structure/mmcif';
+import { trajectoryFromMmCIF, MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
 import { trajectoryFromPDB } from '../../../../mol-model-formats/structure/pdb';
 import { Mat4, Vec3, Quat } from '../../../../mol-math/linear-algebra';
 import { SymmetryOperator } from '../../../../mol-math/geometry';
@@ -28,11 +28,10 @@ import { compile } from '../../../../mol-script/runtime/query/compiler';
 import { UniformColorThemeProvider } from '../../../../mol-theme/color/uniform';
 import { ThemeRegistryContext } from '../../../../mol-theme/theme';
 import { ColorTheme } from '../../../../mol-theme/color';
-import { _parse_mmCif } from '../../../../mol-model-formats/structure/mmcif/parser';
-import { ModelFormat } from '../../../../mol-model-formats/structure/format';
 import { CifCategory, CifField } from '../../../../mol-io/reader/cif';
 import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
 import { Column } from '../../../../mol-data/db';
+import { createModels } from '../../../../mol-model-formats/structure/basic/parser';
 
 function getCellPackModelUrl(fileName: string, baseUrl: string) {
     return `${baseUrl}/results/${fileName}`
@@ -124,7 +123,10 @@ function getAssembly(transforms: Mat4[], structure: Structure) {
 }
 
 function getCifCurve(name: string, transforms: Mat4[], model: Model) {
-    const d = model.sourceData.data.atom_site
+    if (!MmcifFormat.is(model.sourceData)) throw new Error('mmcif source data needed')
+
+    const { db } = model.sourceData.data
+    const d = db.atom_site
     const n = d._rowCount
     const rowCount = n * transforms.length
 
@@ -201,8 +203,8 @@ function getCifCurve(name: string, transforms: Mat4[], model: Model) {
     }
 
     const categories = {
-        entity: CifCategory.ofTable('entity', model.sourceData.data.entity),
-        chem_comp: CifCategory.ofTable('chem_comp', model.sourceData.data.chem_comp),
+        entity: CifCategory.ofTable('entity', db.entity),
+        chem_comp: CifCategory.ofTable('chem_comp', db.chem_comp),
         atom_site: CifCategory.ofFields('atom_site', _atom_site)
     }
 
@@ -217,8 +219,8 @@ async function getCurve(name: string, transforms: Mat4[], model: Model) {
     const cif = getCifCurve(name, transforms, model)
 
     const curveModelTask = Task.create('Curve Model', async ctx => {
-        const format = ModelFormat.mmCIF(cif)
-        const models = await _parse_mmCif(format, ctx)
+        const format = MmcifFormat.fromFrame(cif)
+        const models = await createModels(format.data.db, format, ctx)
         return models[0]
     })
 

+ 1 - 1
src/examples/proteopedia-wrapper/coloring.ts

@@ -54,7 +54,7 @@ export function createProteopediaCustomTheme(colors: number[]) {
         const colors = props.colors, colorCount = colors.length, defaultColor = colors[0].color;
 
         if (ctx.structure) {
-            const l = StructureElement.Location.create()
+            const l = StructureElement.Location.create(ctx.structure)
             const { models } = ctx.structure
             const asymIdSerialMap = new Map<string, number>()
             for (let i = 0, il = models.length; i < il; ++i) {

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

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

+ 1 - 1
src/examples/proteopedia-wrapper/index.ts

@@ -94,7 +94,7 @@ class MolStarProteopediaWrapper {
         const model = this.state.build().to(StateElements.Model);
 
         const s = model
-            .apply(StateTransforms.Model.CustomModelProperties, { properties: [EvolutionaryConservation.propertyProvider.descriptor.name] }, { ref: StateElements.ModelProps, state: { isGhost: false } })
+            .apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [EvolutionaryConservation.propertyProvider.descriptor.name], properties: {} }, { ref: StateElements.ModelProps, state: { isGhost: false } })
             .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: StateElements.Assembly });
 
         s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: StateElements.Sequence });

+ 5 - 4
src/mol-canvas3d/camera.ts

@@ -264,12 +264,13 @@ function updatePers(camera: Camera) {
 function updateClip(camera: Camera) {
     const { radiusNear, radiusFar, mode, fog, clipFar } = camera.state
 
-    const cDist = Vec3.distance(camera.position, camera.target)
-    let near = cDist - radiusNear
-    let far = cDist + (clipFar ? radiusNear : radiusFar)
+    const normalizedFar = clipFar ? radiusNear : radiusFar
+    const cameraDistance = Vec3.distance(camera.position, camera.target)
+    let near = cameraDistance - radiusNear
+    let far = cameraDistance + normalizedFar
 
     const fogNearFactor = -(50 - fog) / 50
-    let fogNear = cDist - (radiusNear * fogNearFactor)
+    let fogNear = cameraDistance - (normalizedFar * fogNearFactor)
     let fogFar = far
 
     if (mode === 'perspective') {

+ 87 - 60
src/mol-canvas3d/canvas3d.ts

@@ -2,6 +2,7 @@
  * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import { BehaviorSubject, Subscription } from 'rxjs';
@@ -37,7 +38,7 @@ import { isDebugMode } from '../mol-util/debug';
 
 export const Canvas3DParams = {
     cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
-    cameraFog: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
+    cameraFog: PD.Numeric(50, { min: 0, max: 100, step: 1 }),
     cameraClipFar: PD.Boolean(true),
     cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
     transparentBackground: PD.Boolean(false),
@@ -56,30 +57,33 @@ export { Canvas3D }
 interface Canvas3D {
     readonly webgl: WebGLContext,
 
-    add: (repr: Representation.Any) => Promise<void>
-    remove: (repr: Representation.Any) => Promise<void>
-    update: (repr?: Representation.Any, keepBoundingSphere?: boolean) => void
-    clear: () => void
-
-    // draw: (force?: boolean) => void
-    requestDraw: (force?: boolean) => void
-    animate: () => void
-    identify: (x: number, y: number) => PickingId | undefined
-    mark: (loci: Representation.Loci, action: MarkerAction) => void
-    getLoci: (pickingId: PickingId) => Representation.Loci
+    add(repr: Representation.Any): void
+    remove(repr: Representation.Any): void
+    /**
+     * This function must be called if animate() is not set up so that add/remove actions take place.
+     */
+    commit(isSynchronous?: boolean): void
+    update(repr?: Representation.Any, keepBoundingSphere?: boolean): void
+    clear(): void
+
+    requestDraw(force?: boolean): void
+    animate(): void
+    identify(x: number, y: number): PickingId | undefined
+    mark(loci: Representation.Loci, action: MarkerAction): void
+    getLoci(pickingId: PickingId): Representation.Loci
 
     readonly didDraw: BehaviorSubject<now.Timestamp>
     readonly reprCount: BehaviorSubject<number>
 
-    handleResize: () => void
+    handleResize(): void
     /** Focuses camera on scene's bounding sphere, centered and zoomed. */
-    resetCamera: () => void
+    requestCameraReset(durationMs?: number): void
     readonly camera: Camera
     readonly boundingSphere: Readonly<Sphere3D>
-    downloadScreenshot: () => void
-    getPixelData: (variant: GraphicsRenderVariant) => PixelData
-    setProps: (props: Partial<Canvas3DProps>) => void
-    getImagePass: () => ImagePass
+    downloadScreenshot(): void
+    getPixelData(variant: GraphicsRenderVariant): PixelData
+    setProps(props: Partial<Canvas3DProps>): void
+    getImagePass(): ImagePass
 
     /** Returns a copy of the current Canvas3D instance props */
     readonly props: Readonly<Canvas3DProps>
@@ -87,7 +91,7 @@ interface Canvas3D {
     readonly stats: RendererStats
     readonly interaction: Canvas3dInteractionHelper['events']
 
-    dispose: () => void
+    dispose(): void
 }
 
 const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
@@ -186,6 +190,7 @@ namespace Canvas3D {
 
         let drawPending = false
         let cameraResetRequested = false
+        let nextCameraResetDuration: number | undefined = void 0
 
         function getLoci(pickingId: PickingId) {
             let loci: Loci = EmptyLoci
@@ -220,7 +225,7 @@ namespace Canvas3D {
         }
 
         function render(force: boolean) {
-            if (scene.isCommiting || webgl.isContextLost) return false
+            if (webgl.isContextLost) return false
 
             let didRender = false
             controls.update(currentTime)
@@ -262,7 +267,9 @@ namespace Canvas3D {
 
         function animate() {
             currentTime = now();
+            commit();
             camera.transition.tick(currentTime);
+
             draw(false);
             if (!camera.transition.inTransition && !webgl.isContextLost) {
                 interactionHelper.tick(currentTime);
@@ -274,22 +281,35 @@ namespace Canvas3D {
             return webgl.isContextLost ? undefined : pickPass.identify(x, y)
         }
 
-        async function commit(renderObjects?: readonly GraphicsRenderObject[]) {
-            scene.update(renderObjects, false)
+        function commit(isSynchronous: boolean = false) {
+            const allCommited = commitScene(isSynchronous);
+            // Only reset the camera after the full scene has been commited.
+            if (allCommited) resolveCameraReset();
+        }
 
-            return runTask(scene.commit()).then(() => {
-                if (cameraResetRequested && !scene.isCommiting) {
-                    const { center, radius } = scene.boundingSphere
-                    camera.focus(center, radius, radius)
-                    cameraResetRequested = false
-                }
-                if (debugHelper.isEnabled) debugHelper.update()
-                requestDraw(true)
-                reprCount.next(reprRenderObjects.size)
-            })
+        function resolveCameraReset() {
+            if (!cameraResetRequested) return;
+            const { center, radius } = scene.boundingSphere;
+            const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration
+            camera.focus(center, radius, radius, duration);
+            nextCameraResetDuration = void 0;
+            cameraResetRequested = false;
+        }
+
+        const sceneCommitTimeoutMs = 250;
+        function commitScene(isSynchronous: boolean) {
+            if (!scene.needsCommit) return true;
+
+            if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
+
+            if (debugHelper.isEnabled) debugHelper.update();
+            reprCount.next(reprRenderObjects.size);
+            return true;
         }
 
         function add(repr: Representation.Any) {
+            registerAutoUpdate(repr);
+
             const oldRO = reprRenderObjects.get(repr)
             const newRO = new Set<GraphicsRenderObject>()
             repr.renderObjects.forEach(o => newRO.add(o))
@@ -303,7 +323,35 @@ namespace Canvas3D {
                 repr.renderObjects.forEach(o => scene.add(o))
             }
             reprRenderObjects.set(repr, newRO)
-            return commit(repr.renderObjects)
+
+            scene.update(repr.renderObjects, false)
+        }
+
+        function remove(repr: Representation.Any) {
+            unregisterAutoUpdate(repr);
+
+            const renderObjects = reprRenderObjects.get(repr)
+            if (renderObjects) {
+                renderObjects.forEach(o => scene.remove(o))
+                reprRenderObjects.delete(repr)
+                scene.update(repr.renderObjects, false, true)
+            }
+        }
+
+        function registerAutoUpdate(repr: Representation.Any) {
+            if (reprUpdatedSubscriptions.has(repr)) return;
+
+            reprUpdatedSubscriptions.set(repr, repr.updated.subscribe(_ => {
+                if (!repr.state.syncManually) add(repr);
+            }))
+        }
+
+        function unregisterAutoUpdate(repr: Representation.Any) {
+            const updatedSubscription = reprUpdatedSubscriptions.get(repr);
+            if (updatedSubscription) {
+                updatedSubscription.unsubscribe();
+                reprUpdatedSubscriptions.delete(repr);
+            }
         }
 
         handleResize()
@@ -311,25 +359,9 @@ namespace Canvas3D {
         return {
             webgl,
 
-            add: (repr: Representation.Any) => {
-                reprUpdatedSubscriptions.set(repr, repr.updated.subscribe(_ => {
-                    if (!repr.state.syncManually) add(repr)
-                }))
-                return add(repr)
-            },
-            remove: (repr: Representation.Any) => {
-                const updatedSubscription = reprUpdatedSubscriptions.get(repr)
-                if (updatedSubscription) {
-                    updatedSubscription.unsubscribe()
-                }
-                const renderObjects = reprRenderObjects.get(repr)
-                if (renderObjects) {
-                    renderObjects.forEach(o => scene.remove(o))
-                    reprRenderObjects.delete(repr)
-                    return commit()
-                }
-                return Promise.resolve()
-            },
+            add,
+            remove,
+            commit,
             update: (repr, keepSphere) => {
                 if (repr) {
                     if (!reprRenderObjects.has(repr)) return;
@@ -356,14 +388,9 @@ namespace Canvas3D {
             getLoci,
 
             handleResize,
-            resetCamera: () => {
-                if (scene.isCommiting) {
-                    cameraResetRequested = true
-                } else {
-                    const { center, radius } = scene.boundingSphere
-                    camera.focus(center, radius, radius, p.cameraResetDurationMs)
-                    requestDraw(true);
-                }
+            requestCameraReset: (durationMs) => {
+                nextCameraResetDuration = durationMs;
+                cameraResetRequested = true;
             },
             camera,
             boundingSphere: scene.boundingSphere,

+ 1 - 1
src/mol-canvas3d/helper/bounding-sphere-helper.ts

@@ -82,7 +82,7 @@ export class BoundingSphereHelper {
         })
 
         this.scene.update(void 0, false)
-        this.scene.syncCommit()
+        this.scene.commit()
     }
 
     syncVisibility() {

+ 9 - 1
src/mol-data/db/column.ts

@@ -37,7 +37,7 @@ namespace Column {
         export type Coordinate = { '@type': 'coord', T: number } & Base<'float'>
 
         export type Tensor = { '@type': 'tensor', T: Tensors.Data, space: Tensors.Space, baseType: Int | Float } & Base<'tensor'>
-        export type Aliased<T> = { '@type': 'aliased', T: T } & Base<'str' | 'int'>
+        export type Aliased<T> = { '@type': 'aliased', T: T } & Base<T extends string ? 'str' : 'int'>
         export type List<T extends number|string> = { '@type': 'list', T: T[], separator: string, itemParse: (x: string) => T } & Base<'list'>
 
         export const str: Str = { '@type': 'str', T: '', valueType: 'str' };
@@ -137,6 +137,14 @@ namespace Column {
         return arrayColumn({ array, schema: Schema.str });
     }
 
+    export function ofStringAliasArray<T extends string>(array: ArrayLike<T>) {
+        return arrayColumn<Schema.Aliased<T>>({ array, schema: Schema.Aliased(Schema.str) });
+    }
+
+    export function ofStringListArray<T extends string>(array: ArrayLike<T[]>, separator = ',') {
+        return arrayColumn<Schema.List<T>>({ array, schema: Schema.List<T>(separator, x => x as T) });
+    }
+
     export function ofIntTokens(tokens: Tokens) {
         const { count, data, indices } = tokens
         return lambdaColumn({

+ 27 - 8
src/mol-data/db/table.ts

@@ -9,7 +9,7 @@ import { sortArray } from '../util/sort'
 import { StringBuilder } from '../../mol-util';
 
 /** A collection of columns */
-type Table<Schema extends Table.Schema> = {
+type Table<Schema extends Table.Schema = any> = {
     readonly _rowCount: number,
     readonly _columns: ReadonlyArray<string>,
     readonly _schema: Schema
@@ -21,7 +21,8 @@ namespace Table {
     export type Columns<S extends Schema> = { [C in keyof S]: Column<S[C]['T']> }
     export type Row<S extends Schema> = { [C in keyof S]: S[C]['T'] }
     export type Arrays<S extends Schema> = { [C in keyof S]: ArrayLike<S[C]['T']> }
-    export type PartialTable<S extends Table.Schema> = { readonly _rowCount: number, readonly _columns: ReadonlyArray<string> } & { [C in keyof S]?: Column<S[C]['T']> }
+    export type PartialColumns<S extends Schema> = { [C in keyof S]?: Column<S[C]['T']> }
+    export type PartialTable<S extends Table.Schema> = { readonly _rowCount: number, readonly _columns: ReadonlyArray<string> } & PartialColumns<S>
 
     export function is(t: any): t is Table<any> {
         return t && typeof t._rowCount === 'number' && !!t._columns && !!t._schema;
@@ -47,6 +48,19 @@ namespace Table {
         return { _rowCount, _columns, _schema: schema, ...(columns as any) };
     }
 
+    export function ofPartialColumns<S extends Schema, R extends Table<S> = Table<S>>(schema: S, partialColumns: PartialColumns<S>, rowCount: number): R {
+        const ret = Object.create(null);
+        const columns = Object.keys(schema);
+        ret._rowCount = rowCount;
+        ret._columns = columns;
+        ret._schema = schema;
+        for (const k of columns) {
+            if (k in partialColumns) ret[k] = partialColumns[k]
+            else ret[k] = Column.Undefined(rowCount, schema[k])
+        }
+        return ret;
+    }
+
     export function ofUndefinedColumns<S extends Schema, R extends Table<S> = Table<S>>(schema: S, rowCount: number): R {
         const ret = Object.create(null);
         const columns = Object.keys(schema);
@@ -59,7 +73,7 @@ namespace Table {
         return ret;
     }
 
-    export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, rows: ArrayLike<Partial<Row<S>>>): R {
+    export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: S, rows: ArrayLike<Partial<Row<S>>>): R {
         const ret = Object.create(null);
         const rowCount = rows.length;
         const columns = Object.keys(schema);
@@ -77,14 +91,19 @@ namespace Table {
         return ret as R;
     }
 
-    export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, arrays: Arrays<S>): R {
+    export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: S, arrays: Partial<Arrays<S>>): R {
         const ret = Object.create(null);
         const columns = Object.keys(schema);
-        ret._rowCount = arrays[columns[0]].length;
+        ret._rowCount = 0;
         ret._columns = columns;
         ret._schema = schema;
         for (const k of columns) {
-            (ret as any)[k] = typeof arrays[k] !== 'undefined' ? Column.ofArray({ array: arrays[k], schema: schema[k] }) : Column.Undefined(ret._rowCount, schema[k]);
+            if (typeof arrays[k] !== 'undefined') {
+                (ret as any)[k] = Column.ofArray({ array: arrays[k]!, schema: schema[k] });
+                ret._rowCount = arrays[k]?.length;
+            } else {
+                (ret as any)[k] = Column.Undefined(ret._rowCount, schema[k]);
+            }
         }
         return ret as R;
     }
@@ -153,7 +172,7 @@ namespace Table {
     }
 
     /** Sort and return a new table */
-    export function sort<T extends Table<S>, S extends Schema>(table: T, cmp: (i: number, j: number) => number) {
+    export function sort<T extends Table>(table: T, cmp: (i: number, j: number) => number) {
         const indices = new Int32Array(table._rowCount);
         for (let i = 0, _i = indices.length; i < _i; i++) indices[i] = i;
         sortArray(indices, (_, i, j) => cmp(i, j));
@@ -177,7 +196,7 @@ namespace Table {
         return ret;
     }
 
-    export function areEqual<T extends Table<Schema>>(a: T, b: T) {
+    export function areEqual<T extends Table<any>>(a: T, b: T) {
         if (a._rowCount !== b._rowCount) return false;
         if (a._columns.length !== b._columns.length) return false;
         for (const c of a._columns) {

+ 2 - 2
src/mol-gl/_spec/renderer.spec.ts

@@ -121,7 +121,7 @@ describe('renderer', () => {
         const points = createPoints()
 
         scene.add(points)
-        await scene.commit().run()
+        scene.commit()
         expect(ctx.stats.resourceCounts.attribute).toBe(4);
         expect(ctx.stats.resourceCounts.texture).toBe(5);
         expect(ctx.stats.resourceCounts.vertexArray).toBe(5);
@@ -129,7 +129,7 @@ describe('renderer', () => {
         expect(ctx.stats.resourceCounts.shader).toBe(10);
 
         scene.remove(points)
-        await scene.commit().run()
+        scene.commit()
         expect(ctx.stats.resourceCounts.attribute).toBe(0);
         expect(ctx.stats.resourceCounts.texture).toBe(0);
         expect(ctx.stats.resourceCounts.vertexArray).toBe(0);

+ 55 - 0
src/mol-gl/commit-queue.ts

@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { LinkedList } from '../mol-data/generic'
+import { GraphicsRenderObject } from './render-object'
+
+type N = LinkedList.Node<GraphicsRenderObject>
+
+export class CommitQueue {
+    private removeList = LinkedList<GraphicsRenderObject>();
+    private removeMap = new Map<GraphicsRenderObject, N>();
+    private addList = LinkedList<GraphicsRenderObject>();
+    private addMap = new Map<GraphicsRenderObject, N>();
+
+    get isEmpty() {
+        return this.removeList.count === 0 && this.addList.count === 0;
+    }
+
+    add(o: GraphicsRenderObject) {
+        if (this.removeMap.has(o)) {
+            const a = this.removeMap.get(o)!;
+            this.removeMap.delete(o);
+            this.removeList.remove(a);
+        }
+        if (this.addMap.has(o)) return;
+        const b = this.addList.addLast(o);
+        this.addMap.set(o, b);
+    }
+
+    remove(o: GraphicsRenderObject) {
+        if (this.addMap.has(o)) {
+            const a = this.addMap.get(o)!;
+            this.addMap.delete(o);
+            this.addList.remove(a);
+        }
+        if (this.removeMap.has(o)) return;
+        const b = this.removeList.addLast(o);
+        this.removeMap.set(o, b);
+    }
+
+    tryGetRemove() {
+        const o = this.removeList.removeFirst();
+        if (o) this.removeMap.delete(o);
+        return o;
+    }
+
+    tryGetAdd() {
+        const o = this.addList.removeFirst();
+        if (o) this.addMap.delete(o);
+        return o;
+    }
+}

+ 37 - 60
src/mol-gl/scene.ts

@@ -1,7 +1,8 @@
 /**
- * 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 Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import { Renderable } from './renderable'
@@ -12,8 +13,9 @@ import { Object3D } from './object3d';
 import { Sphere3D } from '../mol-math/geometry';
 import { Vec3 } from '../mol-math/linear-algebra';
 import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
-import { RuntimeContext, Task } from '../mol-task';
-import { AsyncQueue } from '../mol-util/async-queue';
+import { CommitQueue } from './commit-queue';
+import { now } from '../mol-util/now';
+import { arraySetRemove } from '../mol-util/array';
 
 const boundaryHelper = new BoundaryHelper();
 function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
@@ -56,13 +58,12 @@ interface Scene extends Object3D {
     readonly count: number
     readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
     readonly boundingSphere: Sphere3D
-    readonly isCommiting: boolean
 
-    update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean) => void
+    update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean, isRemoving?: boolean) => void
     add: (o: GraphicsRenderObject) => void // Renderable<any>
     remove: (o: GraphicsRenderObject) => void
-    syncCommit: () => void
-    commit: () => Task<void>
+    commit: (maxTimeMs?: number) => boolean
+    readonly needsCommit: boolean
     has: (o: GraphicsRenderObject) => boolean
     clear: () => void
     forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void
@@ -78,7 +79,7 @@ namespace Scene {
 
         const object3d = Object3D.create()
 
-        const add = (o: GraphicsRenderObject) => {
+        function add(o: GraphicsRenderObject) {
             if (!renderableMap.has(o)) {
                 const renderable = createRenderable(ctx, o)
                 renderables.push(renderable)
@@ -91,47 +92,50 @@ namespace Scene {
             }
         }
 
-        const remove = (o: GraphicsRenderObject) => {
+        function remove(o: GraphicsRenderObject) {
             const renderable = renderableMap.get(o)
             if (renderable) {
                 renderable.dispose()
-                renderables.splice(renderables.indexOf(renderable), 1)
+                arraySetRemove(renderables, renderable);
                 renderableMap.delete(o)
                 boundingSphereDirty = true
             }
         }
 
-        const commitQueue = new AsyncQueue<any>();
-        const toAdd: GraphicsRenderObject[] = []
-        const toRemove: GraphicsRenderObject[] = []
+        const commitBulkSize = 100;
+        function commit(maxTimeMs: number) {
+            const start = now();
 
-        type CommitParams = { toAdd: GraphicsRenderObject[], toRemove: GraphicsRenderObject[] }
+            let i = 0;
 
-        const step = 100
-        const handle = async (ctx: RuntimeContext, arr: GraphicsRenderObject[], fn: (o: GraphicsRenderObject) => void, message: string) => {
-            for (let i = 0, il = arr.length; i < il; i += step) {
-                if (ctx.shouldUpdate) await ctx.update({ message, current: i, max: il })
-                for (let j = i, jl = Math.min(i + step, il); j < jl; ++j) {
-                    fn(arr[j])
-                }
+            while (true) {
+                const o = commitQueue.tryGetRemove();
+                if (!o) break;
+                remove(o);
+                if (++i % commitBulkSize === 0 && now() - start > maxTimeMs) return false;
+            }
+
+            while (true) {
+                const o = commitQueue.tryGetAdd();
+                if (!o) break;
+                add(o);
+                if (++i % commitBulkSize === 0 && now() - start > maxTimeMs) return false;
             }
-        }
 
-        const commit = async (ctx: RuntimeContext, p: CommitParams) => {
-            await handle(ctx, p.toRemove, remove, 'Removing GraphicsRenderObjects')
-            await handle(ctx, p.toAdd, add, 'Adding GraphicsRenderObjects')
-            if (ctx.shouldUpdate) await ctx.update({ message: 'Sorting GraphicsRenderObjects' })
             renderables.sort(renderableSort)
+            return true;
         }
 
+        const commitQueue = new CommitQueue();
+
         return {
             get view () { return object3d.view },
             get position () { return object3d.position },
             get direction () { return object3d.direction },
             get up () { return object3d.up },
-            get isCommiting () { return commitQueue.length > 0 },
+            // get isCommiting () { return commitQueue.length > 0 },
 
-            update(objects, keepBoundingSphere) {
+            update(objects, keepBoundingSphere, isRemoving) {
                 Object3D.update(object3d)
                 if (objects) {
                     for (let i = 0, il = objects.length; i < il; ++i) {
@@ -139,44 +143,17 @@ namespace Scene {
                         if (!o) continue;
                         o.update();
                     }
-                } else {
+                } else if (!isRemoving) {
                     for (let i = 0, il = renderables.length; i < il; ++i) {
                         renderables[i].update()
                     }
                 }
                 if (!keepBoundingSphere) boundingSphereDirty = true
             },
-            add: (o: GraphicsRenderObject) => {
-                toAdd.push(o)
-            },
-            remove: (o: GraphicsRenderObject) => {
-                toRemove.push(o)
-            },
-            syncCommit: () => {
-                for (let i = 0, il = toRemove.length; i < il; ++i) remove(toRemove[i])
-                toRemove.length = 0
-                for (let i = 0, il = toAdd.length; i < il; ++i) add(toAdd[i])
-                toAdd.length = 0
-                renderables.sort(renderableSort)
-            },
-            commit: () => {
-                const params = { toAdd: [ ...toAdd ], toRemove: [ ...toRemove ] }
-                toAdd.length = 0
-                toRemove.length = 0
-
-                return Task.create('Commiting GraphicsRenderObjects', async ctx => {
-                    const removed = await commitQueue.enqueue(params);
-                    if (!removed) return;
-
-                    try {
-                        await commit(ctx, params);
-                    } finally {
-                        commitQueue.handled(params);
-                    }
-                }, () => {
-                    commitQueue.remove(params);
-                })
-            },
+            add: (o: GraphicsRenderObject) => commitQueue.add(o),
+            remove: (o: GraphicsRenderObject) => commitQueue.remove(o),
+            commit: (maxTime = Number.MAX_VALUE) => commit(maxTime),
+            get needsCommit() { return !commitQueue.isEmpty; },
             has: (o: GraphicsRenderObject) => {
                 return renderableMap.has(o)
             },

+ 19 - 0
src/mol-io/reader/_spec/cif.spec.ts

@@ -8,6 +8,7 @@
 import * as Data from '../cif/data-model'
 import * as Schema from '../cif/schema'
 import { Column } from '../../../mol-data/db'
+import parse from '../cif/text/parser';
 
 const columnData = `123abc d,e,f '4 5 6'`;
 // 123abc d,e,f '4 5 6'
@@ -36,6 +37,24 @@ namespace TestSchema {
     export const schema = { test }
 }
 
+test('cif triple quote', async () => {
+    const data = `data_test
+_test.field1 '''123 " '' 1'''
+_test.field2 ''' c glide reflection through the plane (x,1/4,z)
+chosen as one of the generators of the space group'''`;
+
+    const result = await parse(data).run();
+    if (result.isError) {
+        expect(false).toBe(true);
+        return;
+    }
+
+    const cat = result.result.blocks[0].categories['test'];
+    expect(cat.getField('field1')!.str(0)).toBe(`123 " '' 1`);
+    expect(cat.getField('field2')!.str(0)).toBe(` c glide reflection through the plane (x,1/4,z)
+chosen as one of the generators of the space group`);
+});
+
 describe('schema', () => {
     const db = Schema.toDatabase(TestSchema.schema, testBlock);
     it('property access', () => {

+ 2 - 2
src/mol-io/reader/cif.ts

@@ -14,7 +14,7 @@ import { CCD_Schema, CCD_Database } from './cif/schema/ccd'
 import { BIRD_Schema, BIRD_Database } from './cif/schema/bird'
 import { dic_Schema, dic_Database } from './cif/schema/dic'
 import { DensityServer_Data_Schema, DensityServer_Data_Database } from './cif/schema/density-server'
-import { cifCore_Database, cifCore_Schema, cifCore_Aliases } from './cif/schema/cif-core'
+import { CifCore_Database, CifCore_Schema, CifCore_Aliases } from './cif/schema/cif-core'
 
 export const CIF = {
     parse: (data: string|Uint8Array) => typeof data === 'string' ? parseText(data) : parseBinary(data),
@@ -27,7 +27,7 @@ export const CIF = {
         CCD: (frame: CifFrame) => toDatabase<CCD_Schema, CCD_Database>(CCD_Schema, frame),
         BIRD: (frame: CifFrame) => toDatabase<BIRD_Schema, BIRD_Database>(BIRD_Schema, frame),
         dic: (frame: CifFrame) => toDatabase<dic_Schema, dic_Database>(dic_Schema, frame),
-        cifCore: (frame: CifFrame) => toDatabase<cifCore_Schema, cifCore_Database>(cifCore_Schema, frame, cifCore_Aliases),
+        cifCore: (frame: CifFrame) => toDatabase<CifCore_Schema, CifCore_Database>(CifCore_Schema, frame, CifCore_Aliases),
         densityServer: (frame: CifFrame) => toDatabase<DensityServer_Data_Schema, DensityServer_Data_Database>(DensityServer_Data_Schema, frame),
     }
 }

+ 3 - 3
src/mol-io/reader/cif/schema/bird.ts

@@ -1,7 +1,7 @@
 /**
- * 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.
  *
- * Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.320, IHM 1.05, CARB draft.
+ * Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
  *
  * @author molstar/ciftools package
  */
@@ -78,7 +78,7 @@ export const BIRD_Schema = {
         /**
          * Defines how this entity is represented in PDB data files.
          */
-        represent_as: Aliased<'polymer' | 'single molecule'>(str),
+        represent_as: Aliased<'polymer' | 'single molecule' | 'branched'>(str),
         /**
          * For entities represented as single molecules, the identifier
          * corresponding to the chemical definition for the molecule.

+ 2 - 2
src/mol-io/reader/cif/schema/ccd.ts

@@ -1,7 +1,7 @@
 /**
- * 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.
  *
- * Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.320, IHM 1.05, CARB draft.
+ * Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
  *
  * @author molstar/ciftools package
  */

+ 6 - 6
src/mol-io/reader/cif/schema/cif-core.ts

@@ -1,7 +1,7 @@
 /**
- * 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.
  *
- * Code-generated 'cifCore' schema file. Dictionary versions: CifCore 3.0.11.
+ * Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.0.11.
  *
  * @author molstar/ciftools package
  */
@@ -14,7 +14,7 @@ const int = Schema.int;
 const float = Schema.float;
 const str = Schema.str;
 
-export const cifCore_Schema = {
+export const CifCore_Schema = {
     /**
      * The CATEGORY of data items used to describe the parameters of
      * the crystal unit cell and their measurement.
@@ -585,7 +585,7 @@ export const cifCore_Schema = {
     },
 }
 
-export const cifCore_Aliases = {
+export const CifCore_Aliases = {
     'space_group.name_H-M_full': [
         'symmetry_space_group_name_H-M',
     ],
@@ -633,5 +633,5 @@ export const cifCore_Aliases = {
     ],
 }
 
-export type cifCore_Schema = typeof cifCore_Schema;
-export interface cifCore_Database extends Database<cifCore_Schema> {}
+export type CifCore_Schema = typeof CifCore_Schema;
+export interface CifCore_Database extends Database<CifCore_Schema> {}

+ 20 - 20
src/mol-io/reader/cif/schema/mmcif.ts

@@ -1,7 +1,7 @@
 /**
- * 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.
  *
- * Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.320, IHM 1.05, CARB draft.
+ * Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
  *
  * @author molstar/ciftools package
  */
@@ -1880,6 +1880,23 @@ export const mmCIF_Schema = {
          */
         name: str,
     },
+    /**
+     * PDBX_CHEM_COMP_SYNONYMS holds chemical name and synonym correspondences.
+     */
+    pdbx_chem_comp_synonyms: {
+        /**
+         * The synonym of this particular chemical component.
+         */
+        name: str,
+        /**
+         * The chemical component for which this synonym applies.
+         */
+        comp_id: str,
+        /**
+         * The provenance of this synonym.
+         */
+        provenance: Aliased<'AUTHOR' | 'DRUGBANK' | 'CHEBI' | 'CHEMBL' | 'PDB' | 'PUBCHEM'>(str),
+    },
     /**
      * Data items in the CHEM_COMP_IDENTIFIER category provide
      * identifiers for chemical components.
@@ -3665,7 +3682,7 @@ export const mmCIF_Schema = {
         /**
          * The type of crosslinker used.
          */
-        linker_type: Aliased<'EDC' | 'DSS' | 'EGS' | 'BS3' | 'BS2G' | 'DST' | 'sulfo-SDA' | 'sulfo-SMCC' | 'DSSO' | 'DSG' | 'BSP' | 'BMSO' | 'DHSO' | 'Other'>(str),
+        linker_type: Aliased<'EDC' | 'DSS' | 'EGS' | 'BS3' | 'BS2G' | 'DST' | 'sulfo-SDA' | 'sulfo-SMCC' | 'DSSO' | 'DSG' | 'BSP' | 'BMSO' | 'DHSO' | 'CYS' | 'Other'>(str),
         /**
          * Identifier to the crosslinking dataset.
          * This data item is a pointer to the _ihm_dataset_list.id in the
@@ -4584,23 +4601,6 @@ export const mmCIF_Schema = {
          */
         auth_mon_id: str,
     },
-    /**
-     * PDBX_CHEM_COMP_SYNONYMS holds chemical name and synonym correspondences.
-     */
-    pdbx_chem_comp_synonyms: {
-        /**
-         * The synonym of this particular chemical component.
-         */
-        name: str,
-        /**
-         * The chemical component for which this synonym applies.
-         */
-        comp_id: str,
-        /**
-         * The provenance of this synonym.
-         */
-        provenance: Aliased<'AUTHOR' | 'DRUGBANK' | 'CHEBI' | 'CHEMBL' | 'PDB' | 'PUBCHEM'>(str),
-    },
     /**
      * PDBX_CHEM_COMP_RELATED describes the relationship between two chemical components.
      */

+ 40 - 1
src/mol-io/reader/cif/text/parser.ts

@@ -131,6 +131,28 @@ function eatEscaped(state: TokenizerState, esc: number) {
     state.tokenEnd = state.position;
 }
 
+/**
+ * Eats an escaped value "triple quote" (''') value.
+ */
+function eatTripleQuote(state: TokenizerState) {
+    // skip the '''
+    state.position += 3;
+    while (state.position < state.length) {
+        if (state.data.charCodeAt(state.position) === 39 /* ' */ && isTripleQuoteAtPosition(state)) {
+            // get rid of the quotes.
+            state.tokenStart += 3;
+            state.tokenEnd = state.position;
+            state.isEscaped = true;
+            state.position += 3;
+            return;
+        }
+
+        ++state.position;
+    }
+
+    state.tokenEnd = state.position;
+}
+
 /**
  * Eats a multiline token of the form NL;....NL;
  */
@@ -235,6 +257,18 @@ function skipWhitespace(state: TokenizerState): number {
     return prev;
 }
 
+
+/**
+ * Returns true if there are two consecutive ' in +1 and +2 positions.
+ */
+function isTripleQuoteAtPosition(state: TokenizerState): boolean {
+    if (state.length - state.position < 2) return false;
+    if (state.data.charCodeAt(state.position + 1) !== 39) return false; // '
+    if (state.data.charCodeAt(state.position + 2) !== 39) return false; // '
+
+    return true;
+}
+
 function isData(state: TokenizerState): boolean {
     // here we already assume the 5th char is _ and that the length >= 5
 
@@ -393,8 +427,13 @@ function moveNextInternal(state: TokenizerState) {
             skipCommentLine(state);
             state.tokenType = CifTokenType.Comment;
             break;
-        case 34: // ", escaped value
         case 39: // ', escaped value
+            if (isTripleQuoteAtPosition(state)) {
+                eatTripleQuote(state);
+                state.tokenType = CifTokenType.Value;
+                break;
+            }
+        case 34: // ", escaped value
             eatEscaped(state, c);
             state.tokenType = CifTokenType.Value;
             break;

+ 1 - 1
src/mol-io/writer/cif/encoder.ts

@@ -199,7 +199,7 @@ export namespace Category {
         getFormat(cat, field) { return void 0; }
     }
 
-    export function ofTable(table: Table<Table.Schema>, indices?: ArrayLike<number>): Category.Instance {
+    export function ofTable(table: Table, indices?: ArrayLike<number>): Category.Instance {
         if (indices) {
             return {
                 fields: cifFieldsFromTableSchema(table._schema),

+ 42 - 38
src/mol-model-formats/structure/3dg.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>
  */
@@ -7,16 +7,15 @@
 import { Model } from '../../mol-model/structure/model';
 import { Task } from '../../mol-task';
 import { ModelFormat } from './format';
-import { _parse_mmCif } from './mmcif/parser';
-import { CifCategory, CifField } from '../../mol-io/reader/cif';
-import { Column } from '../../mol-data/db';
-import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
+import { Column, Table } from '../../mol-data/db';
 import { EntityBuilder } from './common/entity';
 import { File3DG } from '../../mol-io/reader/3dg/parser';
 import { fillSerial } from '../../mol-util/array';
 import { MoleculeType } from '../../mol-model/structure/model/types';
+import { BasicSchema, createBasic } from './basic/schema';
+import { createModels } from './basic/parser';
 
-function getCategories(table: File3DG['table']) {
+function getBasic(table: File3DG['table']) {
     const entityIds = new Array<string>(table._rowCount)
     const entityBuilder = new EntityBuilder()
 
@@ -33,47 +32,52 @@ function getCategories(table: File3DG['table']) {
         seqIdEnds[i] = seqIdStarts[i] + stride - 1
     }
 
-    const ihm_sphere_obj_site: CifCategory.SomeFields<mmCIF_Schema['ihm_sphere_obj_site']> = {
-        id: CifField.ofNumbers(fillSerial(new Uint32Array(table._rowCount))),
-        entity_id: CifField.ofStrings(entityIds),
-        seq_id_begin: CifField.ofNumbers(seqIdStarts),
-        seq_id_end: CifField.ofNumbers(seqIdEnds),
-        asym_id: CifField.ofColumn(table.chromosome),
+    const ihm_sphere_obj_site = Table.ofPartialColumns(BasicSchema.ihm_sphere_obj_site, {
+        id: Column.ofIntArray(fillSerial(new Uint32Array(table._rowCount))),
+        entity_id: Column.ofStringArray(entityIds),
+        seq_id_begin: Column.ofIntArray(seqIdStarts),
+        seq_id_end: Column.ofIntArray(seqIdEnds),
+        asym_id: table.chromosome,
 
-        Cartn_x: CifField.ofNumbers(Column.mapToArray(table.x, x => x * 10, Float32Array)),
-        Cartn_y: CifField.ofNumbers(Column.mapToArray(table.y, y => y * 10, Float32Array)),
-        Cartn_z: CifField.ofNumbers(Column.mapToArray(table.z, z => z * 10, Float32Array)),
+        Cartn_x: Column.ofFloatArray(Column.mapToArray(table.x, x => x * 10, Float32Array)),
+        Cartn_y: Column.ofFloatArray(Column.mapToArray(table.y, y => y * 10, Float32Array)),
+        Cartn_z: Column.ofFloatArray(Column.mapToArray(table.z, z => z * 10, Float32Array)),
 
-        object_radius: CifField.ofColumn(Column.ofConst(objectRadius, table._rowCount, Column.Schema.float)),
-        rmsf: CifField.ofColumn(Column.ofConst(0, table._rowCount, Column.Schema.float)),
-        model_id: CifField.ofColumn(Column.ofConst(1, table._rowCount, Column.Schema.int)),
-    }
+        object_radius: Column.ofConst(objectRadius, table._rowCount, Column.Schema.float),
+        rmsf: Column.ofConst(0, table._rowCount, Column.Schema.float),
+        model_id: Column.ofConst(1, table._rowCount, Column.Schema.int),
+    }, table._rowCount)
 
-    return {
-        entity: entityBuilder.getEntityCategory(),
-        ihm_model_list: CifCategory.ofFields('ihm_model_list', {
-            model_id: CifField.ofNumbers([1]),
-            model_name: CifField.ofStrings(['3DG Model']),
-        }),
-        ihm_sphere_obj_site: CifCategory.ofFields('ihm_sphere_obj_site', ihm_sphere_obj_site)
-    }
+    return createBasic({
+        entity: entityBuilder.getEntityTable(),
+        ihm_model_list: Table.ofPartialColumns(BasicSchema.ihm_model_list, {
+            model_id: Column.ofIntArray([1]),
+            model_name: Column.ofStringArray(['3DG Model']),
+        }, 1),
+        ihm_sphere_obj_site
+    })
 }
 
-async function mmCifFrom3dg(file3dg: File3DG) {
-    const categories = getCategories(file3dg.table)
+//
+
+export { Format3dg }
 
-    return {
-        header: '3DG',
-        categoryNames: Object.keys(categories),
-        categories
-    };
+type Format3dg = ModelFormat<File3DG>
+
+namespace Format3dg {
+    export function is(x: ModelFormat): x is Format3dg {
+        return x.kind === '3dg'
+    }
+
+    export function from3dg(file3dg: File3DG): Format3dg {
+        return { kind: '3dg', name: '3DG', data: file3dg };
+    }
 }
 
 export function trajectoryFrom3DG(file3dg: File3DG): Task<Model.Trajectory> {
     return Task.create('Parse 3DG', async ctx => {
-        await ctx.update('Converting to mmCIF');
-        const cif = await mmCifFrom3dg(file3dg);
-        const format = ModelFormat.mmCIF(cif);
-        return _parse_mmCif(format, ctx);
+        const format = Format3dg.from3dg(file3dg);
+        const basic = getBasic(file3dg.table)
+        return createModels(basic, format, ctx);
     })
 }

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -7,7 +7,6 @@
 
 import { Column, Table } from '../../../mol-data/db';
 import { Interval, Segmentation } from '../../../mol-data/int';
-import { mmCIF_Database } from '../../../mol-io/reader/cif/schema/mmcif';
 import UUID from '../../../mol-util/uuid';
 import { ElementIndex } from '../../../mol-model/structure';
 import { Model } from '../../../mol-model/structure/model/model';
@@ -16,9 +15,7 @@ import { getAtomicIndex } from '../../../mol-model/structure/model/properties/ut
 import { ElementSymbol } from '../../../mol-model/structure/model/types';
 import { Entities } from '../../../mol-model/structure/model/properties/common';
 import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
-import { FormatData } from './parser';
-
-type AtomSite = mmCIF_Database['atom_site']
+import { AtomSite } from './schema';
 
 function findHierarchyOffsets(atom_site: AtomSite) {
     if (atom_site._rowCount === 0) return { residues: [], chains: [] };
@@ -93,12 +90,12 @@ function getConformation(atom_site: AtomSite): AtomicConformation {
 
 function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
     // TODO need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300
-    return Table.areEqual(a.chains as Table<ChainsSchema>, b.chains as Table<ChainsSchema>)
-        && Table.areEqual(a.residues as Table<ResiduesSchema>, b.residues as Table<ResiduesSchema>)
-        && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>)
+    return Table.areEqual(a.chains, b.chains)
+        && Table.areEqual(a.residues, b.residues)
+        && Table.areEqual(a.atoms, b.atoms)
 }
 
-function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model) {
+function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
     const hierarchyOffsets = findHierarchyOffsets(atom_site);
     const hierarchyData = createHierarchyData(atom_site, sourceIndex, hierarchyOffsets);
 
@@ -115,13 +112,13 @@ function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, en
     }
 
     const index = getAtomicIndex(hierarchyData, entities, hierarchySegments);
-    const derived = getAtomicDerivedData(hierarchyData, index, formatData.chemicalComponentMap);
+    const derived = getAtomicDerivedData(hierarchyData, index, chemicalComponentMap);
     const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived };
     return { sameAsPrevious: false, hierarchy };
 }
 
-export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model) {
-    const { sameAsPrevious, hierarchy } = getAtomicHierarchy(atom_site, sourceIndex, entities, formatData, previous)
+export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
+    const { sameAsPrevious, hierarchy } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, previous)
     const conformation = getConformation(atom_site)
     return { sameAsPrevious, hierarchy, conformation };
 }

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

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

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

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

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

@@ -0,0 +1,210 @@
+/**
+ * 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 createModels(data: BasicData, format: ModelFormat, ctx: RuntimeContext) {
+    const properties = getProperties(data)
+    return data.ihm_model_list._rowCount > 0
+        ? 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 (data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(data.struct.title.value(0))
+    if (ihm.model_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)
+    }
+}

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

@@ -0,0 +1,81 @@
+/**
+ * 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';
+
+// TODO split into conformation and hierarchy parts
+// TODO extract `pdbx_struct_mod_residue` as property?
+
+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
+}
+
+export function createBasic(data: Partial<BasicData>): BasicData {
+    const basic = Object.create(null)
+    for (const name of Object.keys(BasicSchema)) {
+        if (name in data) {
+            basic[name] = data[name as keyof typeof BasicSchema]
+        } else {
+            basic[name] = Table.ofUndefinedColumns(BasicSchema[name as keyof typeof BasicSchema], 0)
+        }
+    }
+    return basic
+}

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

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

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

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

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

@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { BasicData } from './schema';
+import { Table } from '../../../mol-data/db';
+
+export function getModelGroupName(model_id: number, data: BasicData) {
+    const { ihm_model_group, ihm_model_group_link } = data;
+
+    const link = Table.pickRow(ihm_model_group_link, i => ihm_model_group_link.model_id.value(i) === model_id)
+    if (link) {
+        const group = Table.pickRow(ihm_model_group, i => ihm_model_group.id.value(i) === link.group_id)
+        if (group) return group.name
+    }
+    return ''
+}

+ 8 - 10
src/mol-model-formats/structure/common/component.ts

@@ -1,15 +1,14 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { _parse_mmCif } from '../mmcif/parser';
-import { CifCategory, CifField } from '../../../mol-io/reader/cif';
 import { Table, Column } from '../../../mol-data/db';
 import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
 import { WaterNames } from '../../../mol-model/structure/model/types';
 import { SetUtils } from '../../../mol-util/set';
+import { BasicSchema } from '../basic/schema';
 
 type Component = Table.Row<Pick<mmCIF_Schema['chem_comp'], 'id' | 'name' | 'type'>>
 
@@ -137,13 +136,12 @@ export class ComponentBuilder {
         return this.get(compId)!
     }
 
-    getChemCompCategory() {
-        const chemComp: CifCategory.SomeFields<mmCIF_Schema['chem_comp']> = {
-            id: CifField.ofStrings(this.ids),
-            name: CifField.ofStrings(this.names),
-            type: CifField.ofStrings(this.types),
-        }
-        return CifCategory.ofFields('chem_comp', chemComp)
+    getChemCompTable() {
+        return Table.ofPartialColumns(BasicSchema.chem_comp, {
+            id: Column.ofStringArray(this.ids),
+            name: Column.ofStringArray(this.names),
+            type: Column.ofStringAliasArray(this.types),
+        }, this.ids.length)
     }
 
     setNames(names: [string, string][]) {

+ 16 - 14
src/mol-model-formats/structure/common/entity.ts

@@ -1,20 +1,23 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { CifCategory, CifField } from '../../../mol-io/reader/cif';
 import { MoleculeType, isPolymer } from '../../../mol-model/structure/model/types';
-import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
+import { Column, Table } from '../../../mol-data/db';
+import { BasicSchema } from '../basic/schema';
 
 export type EntityCompound = { chains: string[], description: string }
 
+// TODO add support for `branched`
+type EntityType = 'water' | 'polymer' | 'non-polymer'
+
 export class EntityBuilder {
     private count = 0
     private ids: string[] = []
-    private types: string[] = []
-    private descriptions: string[] = []
+    private types: EntityType[] = []
+    private descriptions: string[][] = []
 
     private compoundsMap = new Map<string, string>()
     private namesMap = new Map<string, string>()
@@ -22,11 +25,11 @@ export class EntityBuilder {
     private chainMap = new Map<string, string>()
     private waterId?: string
 
-    private set(type: string, description: string) {
+    private set(type: EntityType, description: string) {
         this.count += 1
         this.ids.push(`${this.count}`)
         this.types.push(type)
-        this.descriptions.push(description)
+        this.descriptions.push([description])
     }
 
     getEntityId(compId: string, moleculeType: MoleculeType, chainId: string): string {
@@ -55,13 +58,12 @@ export class EntityBuilder {
         }
     }
 
-    getEntityCategory() {
-        const entity: CifCategory.SomeFields<mmCIF_Schema['entity']> = {
-            id: CifField.ofStrings(this.ids),
-            type: CifField.ofStrings(this.types),
-            pdbx_description: CifField.ofStrings(this.descriptions),
-        }
-        return CifCategory.ofFields('entity', entity)
+    getEntityTable() {
+        return Table.ofPartialColumns(BasicSchema.entity, {
+            id: Column.ofStringArray(this.ids),
+            type: Column.ofStringAliasArray(this.types),
+            pdbx_description: Column.ofStringListArray(this.descriptions),
+        }, this.count)
     }
 
     setCompounds(compounds: EntityCompound[]) {

+ 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
+            }
+        }
+    }
+}

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

@@ -1,18 +1,8 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { mmCIF_Database } from '../../mol-io/reader/cif/schema/mmcif';
-import { CIF, CifFrame } from '../../mol-io/reader/cif';
-
-type ModelFormat =
-    | ModelFormat.mmCIF
-
-namespace ModelFormat {
-    export interface mmCIF { kind: 'mmCIF', data: mmCIF_Database, frame: CifFrame }
-    export function mmCIF(frame: CifFrame, data?: mmCIF_Database): mmCIF { return { kind: 'mmCIF', data: data || CIF.schema.mmCIF(frame), frame }; }
-}
-
-export { ModelFormat }
+export interface ModelFormat<T = unknown> { readonly kind: string, name: string, data: T }

+ 41 - 42
src/mol-model-formats/structure/gro.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>
  */
@@ -7,22 +7,21 @@
 import { Model } from '../../mol-model/structure/model';
 import { Task } from '../../mol-task';
 import { ModelFormat } from './format';
-import { _parse_mmCif } from './mmcif/parser';
 import { GroFile, GroAtoms } from '../../mol-io/reader/gro/schema';
-import { CifCategory, CifField } from '../../mol-io/reader/cif';
-import { Column } from '../../mol-data/db';
-import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
+import { Column, Table } from '../../mol-data/db';
 import { guessElementSymbolString } from './util';
 import { MoleculeType, getMoleculeType } from '../../mol-model/structure/model/types';
 import { ComponentBuilder } from './common/component';
 import { getChainId } from './common/util';
 import { EntityBuilder } from './common/entity';
+import { BasicData, BasicSchema, createBasic } from './basic/schema';
+import { createModels } from './basic/parser';
 
 // TODO multi model files
 
-function getCategories(atoms: GroAtoms) {
-    const auth_atom_id = CifField.ofColumn(atoms.atomName)
-    const auth_comp_id = CifField.ofColumn(atoms.residueName)
+function getBasic(atoms: GroAtoms): BasicData {
+    const auth_atom_id = atoms.atomName
+    const auth_comp_id = atoms.residueName
 
     const entityIds = new Array<string>(atoms.count)
     const asymIds = new Array<string>(atoms.count)
@@ -69,57 +68,57 @@ function getCategories(atoms: GroAtoms) {
         ids[i] = i
     }
 
-    const auth_asym_id = CifField.ofColumn(Column.ofStringArray(asymIds))
+    const auth_asym_id = Column.ofStringArray(asymIds)
 
-    const atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
+    const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
         auth_asym_id,
         auth_atom_id,
         auth_comp_id,
-        auth_seq_id: CifField.ofColumn(atoms.residueNumber),
-        B_iso_or_equiv: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)),
-        Cartn_x: CifField.ofNumbers(Column.mapToArray(atoms.x, x => x * 10, Float32Array)),
-        Cartn_y: CifField.ofNumbers(Column.mapToArray(atoms.y, y => y * 10, Float32Array)),
-        Cartn_z: CifField.ofNumbers(Column.mapToArray(atoms.z, z => z * 10, Float32Array)),
-        group_PDB: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
-        id: CifField.ofColumn(Column.ofIntArray(ids)),
-
-        label_alt_id: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
+        auth_seq_id: atoms.residueNumber,
+        Cartn_x: Column.ofFloatArray(Column.mapToArray(atoms.x, x => x * 10, Float32Array)),
+        Cartn_y: Column.ofFloatArray(Column.mapToArray(atoms.y, y => y * 10, Float32Array)),
+        Cartn_z: Column.ofFloatArray(Column.mapToArray(atoms.z, z => z * 10, Float32Array)),
+        id: Column.ofIntArray(ids),
 
         label_asym_id: auth_asym_id,
         label_atom_id: auth_atom_id,
         label_comp_id: auth_comp_id,
-        label_seq_id: CifField.ofColumn(Column.ofIntArray(seqIds)),
-        label_entity_id: CifField.ofColumn(Column.ofStringArray(entityIds)),
+        label_seq_id: Column.ofIntArray(seqIds),
+        label_entity_id: Column.ofStringArray(entityIds),
 
-        occupancy: CifField.ofColumn(Column.ofConst(1, atoms.count, Column.Schema.float)),
-        type_symbol: CifField.ofStrings(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))),
+        occupancy: Column.ofConst(1, atoms.count, Column.Schema.float),
+        type_symbol: Column.ofStringArray(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))),
 
-        pdbx_PDB_ins_code: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
-        pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst('1', atoms.count, Column.Schema.str)),
-    }
+        pdbx_PDB_model_num: Column.ofConst(1, atoms.count, Column.Schema.int),
+    }, atoms.count)
 
-    return {
-        entity: entityBuilder.getEntityCategory(),
-        chem_comp: componentBuilder.getChemCompCategory(),
-        atom_site: CifCategory.ofFields('atom_site', atom_site)
-    }
+    return createBasic({
+        entity: entityBuilder.getEntityTable(),
+        chem_comp: componentBuilder.getChemCompTable(),
+        atom_site
+    })
 }
 
-function groToMmCif(gro: GroFile) {
-    const categories = getCategories(gro.structures[0].atoms)
+//
+
+export { GroFormat }
 
-    return {
-        header: gro.structures[0].header.title,
-        categoryNames: Object.keys(categories),
-        categories
-    };
+type GroFormat = ModelFormat<GroFile>
+
+namespace GroFormat {
+    export function is(x: ModelFormat): x is GroFormat {
+        return x.kind === 'gro'
+    }
+
+    export function fromGro(gro: GroFile): GroFormat {
+        return { kind: 'gro', name: gro.structures[0].header.title, data: gro };
+    }
 }
 
 export function trajectoryFromGRO(gro: GroFile): Task<Model.Trajectory> {
     return Task.create('Parse GRO', async ctx => {
-        await ctx.update('Converting to mmCIF');
-        const cif = groToMmCif(gro);
-        const format = ModelFormat.mmCIF(cif);
-        return _parse_mmCif(format, ctx);
+        const format = GroFormat.fromGro(gro);
+        const basic = getBasic(gro.structures[0].atoms)
+        return createModels(basic, format, ctx);
     })
 }

+ 85 - 4
src/mol-model-formats/structure/mmcif.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -8,9 +8,90 @@
 import { Model } from '../../mol-model/structure/model/model';
 import { Task } from '../../mol-task';
 import { ModelFormat } from './format';
-import { _parse_mmCif } from './mmcif/parser';
-import { CifFrame } from '../../mol-io/reader/cif';
+import { CifFrame, CIF } from '../../mol-io/reader/cif';
+import { mmCIF_Database, mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
+import { createModels } from './basic/parser';
+import { ModelSymmetry } from './property/symmetry';
+import { ModelSecondaryStructure } from './property/secondary-structure';
+import { Table } from '../../mol-data/db';
+import { AtomSiteAnisotrop } from './property/anisotropic';
+import { ComponentBond } from './property/bonds/comp';
+import { StructConn } from './property/bonds/struct_conn';
+import { ModelCrossLinkRestraint } from './property/pair-restraints/cross-links';
+
+function modelSymmetryFromMmcif(model: Model) {
+    if (!MmcifFormat.is(model.sourceData)) return;
+    return ModelSymmetry.fromData(model.sourceData.data.db)
+}
+ModelSymmetry.Provider.formatRegistry.add('mmCIF', modelSymmetryFromMmcif)
+
+function secondaryStructureFromMmcif(model: Model) {
+    if (!MmcifFormat.is(model.sourceData)) return;
+    const { struct_conf, struct_sheet_range } = model.sourceData.data.db
+    return ModelSecondaryStructure.fromStruct(struct_conf, struct_sheet_range, model.atomicHierarchy)
+}
+ModelSecondaryStructure.Provider.formatRegistry.add('mmCIF', secondaryStructureFromMmcif)
+
+function atomSiteAnisotropFromMmcif(model: Model) {
+    if (!MmcifFormat.is(model.sourceData)) return;
+    const { atom_site_anisotrop } = model.sourceData.data.db
+    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)
+
+function componentBondFromMmcif(model: Model) {
+    if (!MmcifFormat.is(model.sourceData)) return;
+    const { chem_comp_bond } = model.sourceData.data.db;
+    if (chem_comp_bond._rowCount === 0) return;
+    return {
+        data: chem_comp_bond,
+        entries: ComponentBond.getEntriesFromChemCompBond(chem_comp_bond)
+    }
+}
+ComponentBond.Provider.formatRegistry.add('mmCIF', componentBondFromMmcif)
+
+function structConnFromMmcif(model: Model) {
+    if (!MmcifFormat.is(model.sourceData)) return;
+    const { struct_conn } = model.sourceData.data.db;
+    if (struct_conn._rowCount === 0) return;
+    const entries = StructConn.getEntriesFromStructConn(struct_conn, model)
+    return {
+        data: struct_conn,
+        byAtomIndex: StructConn.getAtomIndexFromEntries(entries),
+        entries,
+    }
+}
+StructConn.Provider.formatRegistry.add('mmCIF', structConnFromMmcif)
+
+function crossLinkRestraintFromMmcif(model: Model) {
+    if (!MmcifFormat.is(model.sourceData)) return;
+    const { ihm_cross_link_restraint } = model.sourceData.data.db;
+    if (ihm_cross_link_restraint._rowCount === 0) return;
+    return ModelCrossLinkRestraint.fromTable(ihm_cross_link_restraint, model)
+}
+ModelCrossLinkRestraint.Provider.formatRegistry.add('mmCIF', crossLinkRestraintFromMmcif)
+
+//
+
+export { MmcifFormat }
+
+type MmcifFormat = ModelFormat<MmcifFormat.Data>
+
+namespace MmcifFormat {
+    export type Data = { db: mmCIF_Database, frame: CifFrame }
+    export function is(x: ModelFormat): x is MmcifFormat {
+        return x.kind === 'mmCIF'
+    }
+
+    export function fromFrame(frame: CifFrame, db?: mmCIF_Database): MmcifFormat {
+        if (!db) db = CIF.schema.mmCIF(frame)
+        return { kind: 'mmCIF', name: db._name, data: { db, frame } };
+    }
+}
 
 export function trajectoryFromMmCIF(frame: CifFrame): Task<Model.Trajectory> {
-    return Task.create('Create mmCIF Model', ctx => _parse_mmCif(ModelFormat.mmCIF(frame), ctx));
+    const format = MmcifFormat.fromFrame(frame)
+    return Task.create('Create mmCIF Model', ctx => createModels(format.data.db, format, ctx));
 }

+ 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 - 250
src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts

@@ -1,250 +0,0 @@
-/**
- * Copyright (c) 2017-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { Model } from '../../../../mol-model/structure/model/model'
-import { Structure } from '../../../../mol-model/structure'
-import { BondType } from '../../../../mol-model/structure/model/types'
-import { findEntityIdByAsymId, findAtomIndexByLabelName } from '../util'
-import { Column } from '../../../../mol-data/db'
-import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
-import { mmCIF_Database } from '../../../../mol-io/reader/cif/schema/mmcif';
-import { SortedArray } from '../../../../mol-data/int';
-import { CifWriter } from '../../../../mol-io/writer/cif'
-import { ElementIndex, ResidueIndex } from '../../../../mol-model/structure/model/indexing';
-import { getInterBondOrderFromTable } from '../../../../mol-model/structure/model/properties/atomic/bonds';
-
-export interface StructConn {
-    getResidueEntries(residueAIndex: ResidueIndex, residueBIndex: ResidueIndex): ReadonlyArray<StructConn.Entry>,
-    getAtomEntries(atomIndex: ElementIndex): ReadonlyArray<StructConn.Entry>,
-    readonly entries: ReadonlyArray<StructConn.Entry>
-}
-
-export namespace StructConn {
-    export const Descriptor: CustomPropertyDescriptor = {
-        name: 'struct_conn',
-        cifExport: {
-            prefix: '',
-            categories: [{
-                name: 'struct_conn',
-                instance(ctx) {
-                    const structure = ctx.structures[0], model = structure.model;
-                    const struct_conn = getStructConn(model);
-                    if (!struct_conn) return CifWriter.Category.Empty;
-
-                    const strConn = get(model);
-                    if (!strConn || strConn.entries.length === 0) return CifWriter.Category.Empty;
-
-                    const foundAtoms = new Set<ElementIndex>();
-                    const indices: number[] = [];
-                    for (const entry of strConn.entries) {
-                        const { partners } = entry;
-                        let hasAll = true;
-                        for (let i = 0, _i = partners.length; i < _i; i++) {
-                            const atom = partners[i].atomIndex;
-                            if (foundAtoms.has(atom)) continue;
-                            if (hasAtom(structure, atom)) {
-                                foundAtoms.add(atom);
-                            } else {
-                                hasAll = false;
-                                break;
-                            }
-                        }
-                        if (hasAll) {
-                            indices[indices.length] = entry.rowIndex;
-                        }
-                    }
-
-                    return CifWriter.Category.ofTable(struct_conn, indices);
-                }
-            }]
-        }
-    }
-
-    function hasAtom({ units }: Structure, element: ElementIndex) {
-        for (let i = 0, _i = units.length; i < _i; i++) {
-            if (SortedArray.indexOf(units[i].elements, element) >= 0) return true;
-        }
-        return false;
-    }
-
-    function _resKey(rA: number, rB: number) {
-        if (rA < rB) return `${rA}-${rB}`;
-        return `${rB}-${rA}`;
-    }
-    const _emptyEntry: Entry[] = [];
-
-    class StructConnImpl implements StructConn {
-        private _residuePairIndex: Map<string, StructConn.Entry[]> | undefined = void 0;
-        private _atomIndex: Map<number, StructConn.Entry[]> | undefined = void 0;
-
-        private getResiduePairIndex() {
-            if (this._residuePairIndex) return this._residuePairIndex;
-            this._residuePairIndex = new Map();
-            for (const e of this.entries) {
-                const ps = e.partners;
-                const l = ps.length;
-                for (let i = 0; i < l - 1; i++) {
-                    for (let j = i + i; j < l; j++) {
-                        const key = _resKey(ps[i].residueIndex, ps[j].residueIndex);
-                        if (this._residuePairIndex.has(key)) {
-                            this._residuePairIndex.get(key)!.push(e);
-                        } else {
-                            this._residuePairIndex.set(key, [e]);
-                        }
-                    }
-                }
-            }
-            return this._residuePairIndex;
-        }
-
-        private getAtomIndex() {
-            if (this._atomIndex) return this._atomIndex;
-            this._atomIndex = new Map();
-            for (const e of this.entries) {
-                for (const p of e.partners) {
-                    const key = p.atomIndex;
-                    if (this._atomIndex.has(key)) {
-                        this._atomIndex.get(key)!.push(e);
-                    } else {
-                        this._atomIndex.set(key, [e]);
-                    }
-                }
-            }
-            return this._atomIndex;
-        }
-
-        getResidueEntries(residueAIndex: ResidueIndex, residueBIndex: ResidueIndex): ReadonlyArray<StructConn.Entry> {
-            return this.getResiduePairIndex().get(_resKey(residueAIndex, residueBIndex)) || _emptyEntry;
-        }
-
-        getAtomEntries(atomIndex: ElementIndex): ReadonlyArray<StructConn.Entry> {
-            return this.getAtomIndex().get(atomIndex) || _emptyEntry;
-        }
-
-        constructor(public entries: StructConn.Entry[]) {
-        }
-    }
-
-    export interface Entry {
-        rowIndex: number,
-        distance: number,
-        order: number,
-        flags: number,
-        partners: { 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);
-
-        const { conn_type_id, pdbx_dist_value, pdbx_value_order } = struct_conn;
-        const p1 = {
-            label_asym_id: struct_conn.ptnr1_label_asym_id,
-            label_seq_id: struct_conn.ptnr1_label_seq_id,
-            auth_seq_id: struct_conn.ptnr1_auth_seq_id,
-            label_atom_id: struct_conn.ptnr1_label_atom_id,
-            label_alt_id: struct_conn.pdbx_ptnr1_label_alt_id,
-            ins_code: struct_conn.pdbx_ptnr1_PDB_ins_code,
-            symmetry: struct_conn.ptnr1_symmetry
-        };
-        const p2: typeof p1 = {
-            label_asym_id: struct_conn.ptnr2_label_asym_id,
-            label_seq_id: struct_conn.ptnr2_label_seq_id,
-            auth_seq_id: struct_conn.ptnr2_auth_seq_id,
-            label_atom_id: struct_conn.ptnr2_label_atom_id,
-            label_alt_id: struct_conn.pdbx_ptnr2_label_alt_id,
-            ins_code: struct_conn.pdbx_ptnr2_PDB_ins_code,
-            symmetry: struct_conn.ptnr2_symmetry
-        };
-
-        const _p = (row: number, ps: typeof p1) => {
-            if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0;
-            const asymId = ps.label_asym_id.value(row);
-            const residueIndex = model.atomicHierarchy.index.findResidue(
-                findEntityIdByAsymId(model, asymId),
-                asymId,
-                ps.auth_seq_id.value(row),
-                ps.ins_code.value(row)
-            );
-            if (residueIndex < 0) return void 0;
-            const atomName = ps.label_atom_id.value(row);
-            // turns out "mismat" records might not have atom name value
-            if (!atomName) return void 0;
-            const atomIndex = findAtomIndexByLabelName(model, residueIndex, atomName, ps.label_alt_id.value(row));
-            if (atomIndex < 0) return void 0;
-            return { residueIndex, atomIndex, symmetry: ps.symmetry.value(row) || '1_555' };
-        }
-
-        const _ps = (row: number) => {
-            const ret = [];
-            let p = _p(row, p1);
-            if (p) ret.push(p);
-            p = _p(row, p2);
-            if (p) ret.push(p);
-            return ret;
-        }
-
-        const entries: StructConn.Entry[] = [];
-        for (let i = 0; i < struct_conn._rowCount; i++) {
-            const partners = _ps(i);
-            if (partners.length < 2) continue;
-
-            const type = conn_type_id.value(i)
-            const orderType = (pdbx_value_order.value(i) || '').toLowerCase();
-            let flags = BondType.Flag.None;
-            let order = 1;
-
-            switch (orderType) {
-                case 'sing': order = 1; break;
-                case 'doub': order = 2; break;
-                case 'trip': order = 3; break;
-                case 'quad': order = 4; break;
-                default:
-                    order = getInterBondOrderFromTable(
-                        struct_conn.ptnr1_label_comp_id.value(i),
-                        struct_conn.ptnr1_label_atom_id.value(i),
-                        struct_conn.ptnr2_label_comp_id.value(i),
-                        struct_conn.ptnr2_label_atom_id.value(i)
-                    )
-            }
-
-            switch (type) {
-                case 'covale':
-                    flags = BondType.Flag.Covalent;
-                    break;
-                case 'disulf': flags = BondType.Flag.Covalent | BondType.Flag.Disulfide; break;
-                case 'hydrog':
-                    flags = BondType.Flag.HydrogenBond;
-                    break;
-                case 'metalc': flags = BondType.Flag.MetallicCoordination; break;
-            }
-
-            entries.push({ rowIndex: i, flags, order, distance: pdbx_dist_value.value(i), partners });
-        }
-
-        const ret = new StructConnImpl(entries);
-        model._staticPropertyData[PropName] = ret;
-        return ret;
-    }
-}

+ 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'

+ 0 - 529
src/mol-model-formats/structure/mmcif/parser.ts

@@ -1,529 +0,0 @@
-/**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-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 { RuntimeContext } from '../../../mol-task';
-import UUID from '../../../mol-util/uuid';
-import { Model } from '../../../mol-model/structure/model/model';
-import { Entities, ChemicalComponent, MissingResidue, EntitySubtype } from '../../../mol-model/structure/model/properties/common';
-import { CustomProperties } from '../../../mol-model/structure';
-import { ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry';
-import { createAssemblies } from './assembly';
-import { getAtomicHierarchyAndConformation } from './atomic';
-import { ComponentBond } from './bonds';
-import { getIHMCoarse, EmptyIHMCoarse, IHMData } from './ihm';
-import { getSecondaryStructure } from './secondary-structure';
-import { getSequence } from './sequence';
-import { sortAtomSite } from './sort';
-import { StructConn } from './bonds/struct_conn';
-import { getMoleculeType, MoleculeType, getEntityType, getEntitySubtype, getDefaultChemicalComponent } from '../../../mol-model/structure/model/types';
-import { ModelFormat } from '../format';
-import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
-import mmCIF_Format = ModelFormat.mmCIF
-import { memoize1 } from '../../../mol-util/memoize';
-import { ElementIndex, EntityIndex } from '../../../mol-model/structure/model';
-import { AtomSiteAnisotrop } from './anisotropic';
-import { getAtomicRanges } from '../../../mol-model/structure/model/properties/utils/atomic-ranges';
-
-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);
-}
-
-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 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 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
-}
-
-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 {
-    return {
-        modifiedResidues: getModifiedResidueNameMap(format),
-        missingResidues: getMissingResidues(format),
-        chemicalComponentMap: getChemicalComponentMap(format),
-        saccharideComponentMap: getSaccharideComponentMap(format)
-    }
-}
-
-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)
-
-    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;
-    }
-    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;
-}
-
-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;
-}

+ 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;
-}

+ 6 - 5
src/mol-model-formats/structure/pdb.ts

@@ -1,21 +1,22 @@
 /**
- * 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 David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { PdbFile } from '../../mol-io/reader/pdb/schema';
 import { pdbToMmCif } from './pdb/to-cif';
 import { Model } from '../../mol-model/structure/model';
 import { Task } from '../../mol-task';
-import { ModelFormat } from './format';
-import { _parse_mmCif } from './mmcif/parser';
+import { MmcifFormat } from './mmcif';
+import { createModels } from './basic/parser';
 
 export function trajectoryFromPDB(pdb: PdbFile): Task<Model.Trajectory> {
     return Task.create('Parse PDB', async ctx => {
         await ctx.update('Converting to mmCIF');
         const cif = await pdbToMmCif(pdb);
-        const format = ModelFormat.mmCIF(cif);
-        return _parse_mmCif(format, ctx);
+        const format = MmcifFormat.fromFrame(cif)
+        return createModels(format.data.db, format, ctx)
     })
 }

+ 2 - 2
src/mol-model-formats/structure/pdb/to-cif.ts

@@ -162,8 +162,8 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
     }
 
     const categories = {
-        entity: entityBuilder.getEntityCategory(),
-        chem_comp: componentBuilder.getChemCompCategory(),
+        entity: entityBuilder.getEntityTable(),
+        chem_comp: componentBuilder.getChemCompTable(),
         atom_site: CifCategory.ofFields('atom_site', getAtomSite(atomSite)),
         atom_site_anisotrop: CifCategory.ofFields('atom_site_anisotrop', getAnisotropic(anisotropic))
     } as any;

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

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

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

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

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

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

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

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

+ 168 - 0
src/mol-model-formats/structure/property/bonds/struct_conn.ts

@@ -0,0 +1,168 @@
+/**
+ * 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 { Structure } from '../../../../mol-model/structure'
+import { BondType } from '../../../../mol-model/structure/model/types'
+import { Column, Table } from '../../../../mol-data/db'
+import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
+import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
+import { SortedArray } from '../../../../mol-data/int';
+import { CifWriter } from '../../../../mol-io/writer/cif'
+import { ElementIndex, ResidueIndex } from '../../../../mol-model/structure/model/indexing';
+import { getInterBondOrderFromTable } from '../../../../mol-model/structure/model/properties/atomic/bonds';
+import { FormatPropertyProvider } from '../../common/property';
+
+export interface StructConn {
+    readonly data: Table<mmCIF_Schema['struct_conn']>
+    readonly byAtomIndex: Map<ElementIndex, ReadonlyArray<StructConn.Entry>>
+    readonly entries: ReadonlyArray<StructConn.Entry>
+}
+
+export namespace StructConn {
+    export const Descriptor: CustomPropertyDescriptor = {
+        name: 'struct_conn',
+        cifExport: {
+            prefix: '',
+            categories: [{
+                name: 'struct_conn',
+                instance(ctx) {
+                    const p = Provider.get(ctx.firstModel);
+                    if (!p || p.entries.length === 0) return CifWriter.Category.Empty;
+
+                    const structure = ctx.structures[0]
+
+                    const indices: number[] = [];
+                    for (const e of p.entries) {
+                        if (hasAtom(structure, e.partnerA.atomIndex) &&
+                                hasAtom(structure, e.partnerB.atomIndex)) {
+                            indices[indices.length] = e.rowIndex;
+                        }
+                    }
+
+                    return CifWriter.Category.ofTable(p.data, indices);
+                }
+            }]
+        }
+    }
+
+    export const Provider = FormatPropertyProvider.create<StructConn>(Descriptor)
+
+    function hasAtom({ units }: Structure, element: ElementIndex) {
+        for (let i = 0, _i = units.length; i < _i; i++) {
+            if (SortedArray.indexOf(units[i].elements, element) >= 0) return true;
+        }
+        return false;
+    }
+
+    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]);
+
+            if (m.has(iB)) m.get(iB)!.push(e);
+            else m.set(iB, [e]);
+        }
+        return m;
+    }
+
+    export interface Entry {
+        rowIndex: number,
+        distance: number,
+        order: number,
+        flags: number,
+        partnerA: { residueIndex: ResidueIndex, atomIndex: ElementIndex, symmetry: string },
+        partnerB: { residueIndex: ResidueIndex, atomIndex: ElementIndex, symmetry: string }
+    }
+
+    export function getEntriesFromStructConn(struct_conn: Table<mmCIF_Schema['struct_conn']>, model: Model): StructConn['entries'] {
+        const { conn_type_id, pdbx_dist_value, pdbx_value_order } = struct_conn;
+        const p1 = {
+            label_asym_id: struct_conn.ptnr1_label_asym_id,
+            label_seq_id: struct_conn.ptnr1_label_seq_id,
+            auth_seq_id: struct_conn.ptnr1_auth_seq_id,
+            label_atom_id: struct_conn.ptnr1_label_atom_id,
+            label_alt_id: struct_conn.pdbx_ptnr1_label_alt_id,
+            ins_code: struct_conn.pdbx_ptnr1_PDB_ins_code,
+            symmetry: struct_conn.ptnr1_symmetry
+        };
+        const p2: typeof p1 = {
+            label_asym_id: struct_conn.ptnr2_label_asym_id,
+            label_seq_id: struct_conn.ptnr2_label_seq_id,
+            auth_seq_id: struct_conn.ptnr2_auth_seq_id,
+            label_atom_id: struct_conn.ptnr2_label_atom_id,
+            label_alt_id: struct_conn.pdbx_ptnr2_label_alt_id,
+            ins_code: struct_conn.pdbx_ptnr2_PDB_ins_code,
+            symmetry: struct_conn.ptnr2_symmetry
+        };
+
+        const _p = (row: number, ps: typeof p1) => {
+            if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0;
+            const asymId = ps.label_asym_id.value(row);
+            const entityIndex = model.atomicHierarchy.index.findEntity(asymId);
+            if (entityIndex < 0) return void 0;
+            const residueIndex = model.atomicHierarchy.index.findResidue(
+                model.entities.data.id.value(entityIndex),
+                asymId,
+                ps.auth_seq_id.value(row),
+                ps.ins_code.value(row)
+            );
+            if (residueIndex < 0) return void 0;
+            const atomName = ps.label_atom_id.value(row);
+            // turns out "mismat" records might not have atom name value
+            if (!atomName) return void 0;
+            const atomIndex = model.atomicHierarchy.index.findAtomOnResidue(residueIndex, atomName, ps.label_alt_id.value(row));
+            if (atomIndex < 0) return void 0;
+            return { residueIndex, atomIndex, symmetry: ps.symmetry.value(row) };
+        }
+
+        const entries: StructConn.Entry[] = [];
+        for (let i = 0; i < struct_conn._rowCount; i++) {
+            const partnerA = _p(i, p1)
+            const partnerB = _p(i, p2)
+            if (partnerA === undefined || partnerB === undefined) continue;
+
+            const type = conn_type_id.value(i)
+            const orderType = (pdbx_value_order.value(i) || '').toLowerCase();
+            let flags = BondType.Flag.None;
+            let order = 1;
+
+            switch (orderType) {
+                case 'sing': order = 1; break;
+                case 'doub': order = 2; break;
+                case 'trip': order = 3; break;
+                case 'quad': order = 4; break;
+                default:
+                    order = getInterBondOrderFromTable(
+                        struct_conn.ptnr1_label_comp_id.value(i),
+                        struct_conn.ptnr1_label_atom_id.value(i),
+                        struct_conn.ptnr2_label_comp_id.value(i),
+                        struct_conn.ptnr2_label_atom_id.value(i)
+                    )
+            }
+
+            switch (type) {
+                case 'covale':
+                    flags = BondType.Flag.Covalent;
+                    break;
+                case 'disulf': flags = BondType.Flag.Covalent | BondType.Flag.Disulfide; break;
+                case 'hydrog':
+                    flags = BondType.Flag.HydrogenBond;
+                    break;
+                case 'metalc': flags = BondType.Flag.MetallicCoordination; break;
+            }
+
+            entries.push({
+                rowIndex: i, flags, order, distance: pdbx_dist_value.value(i), partnerA, partnerB
+            });
+        }
+
+        return entries;
+    }
+}

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

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

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


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

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

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

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

+ 37 - 42
src/mol-model-formats/structure/psf.ts

@@ -1,26 +1,24 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { PsfFile } from '../../mol-io/reader/psf/parser';
-import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
-import { Column } from '../../mol-data/db';
+import { Column, Table } from '../../mol-data/db';
 import { EntityBuilder } from './common/entity';
 import { ComponentBuilder } from './common/component';
-import { CifCategory, CifField } from '../../mol-io/reader/cif';
 import { guessElementSymbolString } from './util';
 import { MoleculeType, getMoleculeType } from '../../mol-model/structure/model/types';
 import { getChainId } from './common/util';
 import { Task } from '../../mol-task';
 import { ModelFormat } from './format';
 import { Topology } from '../../mol-model/structure/topology/topology';
+import { createBasic, BasicSchema } from './basic/schema';
 
-// TODO: shares most of the code with ./gro.ts#getCategories
-function getCategories(atoms: PsfFile['atoms']) {
-    const auth_atom_id = CifField.ofColumn(atoms.atomName)
-    const auth_comp_id = CifField.ofColumn(atoms.residueName)
+function getBasic(atoms: PsfFile['atoms']) {
+    const auth_atom_id = atoms.atomName
+    const auth_comp_id = atoms.residueName
 
     const entityIds = new Array<string>(atoms.count)
     const asymIds = new Array<string>(atoms.count)
@@ -62,57 +60,54 @@ function getCategories(atoms: PsfFile['atoms']) {
         ids[i] = i
     }
 
-    const auth_asym_id = CifField.ofColumn(Column.ofStringArray(asymIds))
+    const auth_asym_id = Column.ofStringArray(asymIds)
 
-    const atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
+    const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
         auth_asym_id,
         auth_atom_id,
         auth_comp_id,
-        auth_seq_id: CifField.ofColumn(atoms.residueId),
-        B_iso_or_equiv: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)),
-        Cartn_x: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)),
-        Cartn_y: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)),
-        Cartn_z: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)),
-        group_PDB: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
-        id: CifField.ofColumn(Column.ofIntArray(ids)),
-
-        label_alt_id: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
+        auth_seq_id: atoms.residueId,
+        id: Column.ofIntArray(ids),
 
         label_asym_id: auth_asym_id,
         label_atom_id: auth_atom_id,
         label_comp_id: auth_comp_id,
-        label_seq_id: CifField.ofColumn(Column.ofIntArray(seqIds)),
-        label_entity_id: CifField.ofColumn(Column.ofStringArray(entityIds)),
+        label_seq_id: Column.ofIntArray(seqIds),
+        label_entity_id: Column.ofStringArray(entityIds),
 
-        occupancy: CifField.ofColumn(Column.ofConst(1, atoms.count, Column.Schema.float)),
-        type_symbol: CifField.ofStrings(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))),
+        occupancy: Column.ofConst(1, atoms.count, Column.Schema.float),
+        type_symbol: Column.ofStringArray(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))),
 
-        pdbx_PDB_ins_code: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
-        pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst('1', atoms.count, Column.Schema.str)),
-    }
+        pdbx_PDB_model_num: Column.ofConst(1, atoms.count, Column.Schema.int),
+    }, atoms.count)
 
-    return {
-        entity: entityBuilder.getEntityCategory(),
-        chem_comp: componentBuilder.getChemCompCategory(),
-        atom_site: CifCategory.ofFields('atom_site', atom_site)
-    }
+    return createBasic({
+        entity: entityBuilder.getEntityTable(),
+        chem_comp: componentBuilder.getChemCompTable(),
+        atom_site
+    })
 }
 
-function psfToMmCif(psf: PsfFile) {
-    const categories = getCategories(psf.atoms)
+//
+
+export { PsfFormat }
 
-    return {
-        header: psf.id,
-        categoryNames: Object.keys(categories),
-        categories
-    };
+type PsfFormat = ModelFormat<PsfFile>
+
+namespace PsfFormat {
+    export function is(x: ModelFormat): x is PsfFormat {
+        return x.kind === 'psf'
+    }
+
+    export function fromPsf(psf: PsfFile): PsfFormat {
+        return { kind: 'psf', name: psf.id, data: psf };
+    }
 }
 
 export function topologyFromPsf(psf: PsfFile): Task<Topology> {
     return Task.create('Parse PSF', async ctx => {
-        const label = psf.id
-        const cif = psfToMmCif(psf);
-        const format = ModelFormat.mmCIF(cif);
+        const format = PsfFormat.fromPsf(psf);
+        const basic = getBasic(psf.atoms)
 
         const { atomIdA, atomIdB } = psf.bonds
 
@@ -130,6 +125,6 @@ export function topologyFromPsf(psf: PsfFile): Task<Topology> {
             order: Column.ofConst(1, psf.bonds.count, Column.Schema.int)
         }
 
-        return Topology.create(label, format, bonds)
+        return Topology.create(psf.id, basic, bonds, format)
     })
 }

+ 18 - 7
src/mol-model-props/common/custom-property.ts

@@ -41,21 +41,32 @@ namespace CustomProperty {
 
         /** Get params for all applicable property providers */
         getParams(data?: Data) {
-            const params: PD.Params = {}
+            const propertiesParams: PD.Params = {}
+            const autoAttachOptions: [string, string][] = []
+            const autoAttachDefault: string[] = []
             if (data) {
-            const values = this.providers.values();
+                const values = this.providers.values();
                 while (true) {
                     const v = values.next()
                     if (v.done) break
+
                     const provider = v.value
                     if (!provider.isApplicable(data)) continue
-                    params[provider.descriptor.name] = PD.Group({
-                        autoAttach: PD.Boolean(this.defaultAutoAttachValues.get(provider.descriptor.name)!),
-                        ...provider.getParams(data),
-                    }, { label: v.value.label })
+
+                    autoAttachOptions.push([provider.descriptor.name, provider.label])
+                    if (this.defaultAutoAttachValues.get(provider.descriptor.name)) {
+                        autoAttachDefault.push(provider.descriptor.name)
+                    }
+
+                    propertiesParams[provider.descriptor.name] = PD.Group({
+                        ...provider.getParams(data)
+                    }, { label: provider.label })
                 }
             }
-            return params
+            return {
+                autoAttach: PD.MultiSelect(autoAttachDefault, autoAttachOptions),
+                properties: PD.Group(propertiesParams, { isFlat: true })
+            }
         }
 
         setDefaultAutoAttach(name: string, value: boolean) {

+ 3 - 2
src/mol-model-props/common/wrapper.ts

@@ -7,6 +7,7 @@
 import { CifWriter } from '../../mol-io/writer/cif';
 import { Model } from '../../mol-model/structure';
 import { dateToUtcString } from '../../mol-util/date';
+import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
 
 interface PropertyWrapper<Data> {
     info: PropertyWrapper.Info,
@@ -40,11 +41,11 @@ namespace PropertyWrapper {
     ];
 
     export function tryGetInfoFromCif(categoryName: string, model: Model): Info | undefined {
-        if (model.sourceData.kind !== 'mmCIF' || !model.sourceData.frame.categoryNames.includes(categoryName)) {
+        if (!MmcifFormat.is(model.sourceData) || !model.sourceData.data.frame.categoryNames.includes(categoryName)) {
             return;
         }
 
-        const timestampField = model.sourceData.frame.categories[categoryName].getField('updated_datetime_utc');
+        const timestampField = model.sourceData.data.frame.categories[categoryName].getField('updated_datetime_utc');
         if (!timestampField || timestampField.rowCount === 0) return;
 
         return { timestamp_utc: timestampField.str(0) || dateToUtcString(new Date()) };

+ 4 - 3
src/mol-model-props/computed/accessible-surface-area/shrake-rupley/area.ts

@@ -31,10 +31,11 @@ export async function computeArea(runtime: RuntimeContext, ctx: ShrakeRupleyCont
 const aPos = Vec3();
 const bPos = Vec3();
 const testPoint = Vec3();
-const aLoc = StructureElement.Location.create()
-const bLoc = StructureElement.Location.create()
+const aLoc = StructureElement.Location.create(void 0 as any)
+const bLoc = StructureElement.Location.create(void 0 as any)
 
 function setLocation(l: StructureElement.Location, structure: Structure, serialIndex: number) {
+    l.structure = structure;
     l.unit = structure.units[structure.serialMapping.unitIndices[serialIndex]]
     l.element = structure.serialMapping.elementIndices[serialIndex]
     return l
@@ -63,7 +64,7 @@ function computeRange(ctx: ShrakeRupleyContext, begin: number, end: number) {
         const neighbors = []; // TODO reuse
         for (let iI = 0; iI < count; ++iI) {
             const bUnit = units[iI]
-            StructureElement.Location.set(bLoc, bUnit, bUnit.elements[indices[iI]])
+            StructureElement.Location.set(bLoc, ctx.structure, bUnit, bUnit.elements[indices[iI]])
             const bI = cumulativeUnitElementCount[unitIndexMap.get(bUnit.id)] + indices[iI]
 
             const radius2 = VdWLookup[atomRadiusType[bI]];

+ 2 - 1
src/mol-model-props/computed/accessible-surface-area/shrake-rupley/radii.ts

@@ -12,7 +12,7 @@ import { VdwRadius } from '../../../../mol-model/structure/model/properties/atom
 import { StructureElement, StructureProperties } from '../../../../mol-model/structure/structure';
 import { getElementMoleculeType } from '../../../../mol-model/structure/util';
 
-const l = StructureElement.Location.create()
+const l = StructureElement.Location.create(void 0)
 
 export function assignRadiusForHeavyAtoms(ctx: ShrakeRupleyContext) {
     const { label_comp_id, key } = StructureProperties.residue
@@ -23,6 +23,7 @@ export function assignRadiusForHeavyAtoms(ctx: ShrakeRupleyContext) {
     let residueIdx = 0
     let serialResidueIdx = -1
 
+    l.structure = structure;
     for (let i = 0, m = 0, il = structure.units.length; i < il; ++i) {
         const unit = structure.units[i]
         const { elements } = unit

+ 9 - 5
src/mol-model-props/computed/interactions/interactions.ts

@@ -39,6 +39,7 @@ interface Interactions {
 
 namespace Interactions {
     export interface Element {
+        structure: Structure,
         unitA: Unit
         /** Index into features of unitA */
         indexA: Features.FeatureIndex
@@ -48,8 +49,9 @@ namespace Interactions {
     }
     export interface Location extends DataLocation<Interactions, Element> {}
 
-    export function Location(interactions: Interactions, unitA?: Unit, indexA?: Features.FeatureIndex, unitB?: Unit, indexB?: Features.FeatureIndex): Location {
-        return DataLocation('interactions', interactions, { unitA: unitA as any, indexA: indexA as any, unitB: unitB as any, indexB: indexB as any });
+    export function Location(interactions: Interactions, structure: Structure, unitA?: Unit, indexA?: Features.FeatureIndex, unitB?: Unit, indexB?: Features.FeatureIndex): Location {
+        return DataLocation('interactions', interactions, 
+            { structure: structure as any, unitA: unitA as any, indexA: indexA as any, unitB: unitB as any, indexB: indexB as any });
     }
 
     export function isLocation(x: any): x is Location {
@@ -87,7 +89,9 @@ namespace Interactions {
     export interface Loci extends DataLoci<StructureInteractions, Element> { }
 
     export function Loci(structure: Structure, interactions: Interactions, elements: ReadonlyArray<Element>): Loci {
-        return DataLoci('interactions', { structure, interactions }, elements, (boundingSphere) => getBoundingSphere(interactions, elements, boundingSphere), () => getLabel(interactions, elements));
+        return DataLoci('interactions', { structure, interactions }, elements,
+            (boundingSphere) => getBoundingSphere(interactions, elements, boundingSphere),
+            () => getLabel(structure, interactions, elements));
     }
 
     export function isLoci(x: any): x is Loci {
@@ -103,7 +107,7 @@ namespace Interactions {
         }, boundingSphere)
     }
 
-    export function getLabel(interactions: Interactions, elements: ReadonlyArray<Element>) {
+    export function getLabel(structure: Structure, interactions: Interactions, elements: ReadonlyArray<Element>) {
         const element = elements[0]
         if (element === undefined) return ''
         const { unitA, indexA, unitB, indexB } = element
@@ -116,7 +120,7 @@ namespace Interactions {
         }
         return [
             _label(interactions, element),
-            bondLabel(Bond.Location(unitA, mA[oA[indexA]], unitB, mB[oB[indexB]]), options)
+            bondLabel(Bond.Location(structure, unitA, mA[oA[indexA]], structure, unitB, mB[oB[indexB]]), options)
         ].join('</br>')
     }
 }

+ 5 - 4
src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts

@@ -21,7 +21,7 @@ import { InteractionsProvider } from '../interactions';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
 import { InteractionFlag } from '../interactions/common';
 
-const tmpLoc = StructureElement.Location.create()
+const tmpLoc = StructureElement.Location.create(void 0)
 
 function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InteractionsInterUnitParams>, mesh?: Mesh) {
     if (!structure.hasAtomic) return Mesh.createEmpty(mesh)
@@ -49,6 +49,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
         radius: (edgeIndex: number) => {
             const b = edges[edgeIndex]
             const fA = unitsFeatures.get(b.unitA.id)
+            tmpLoc.structure = structure;
             tmpLoc.unit = b.unitA
             tmpLoc.element = b.unitA.elements[fA.members[fA.offsets[b.indexA]]]
             const sizeA = theme.size.size(tmpLoc)
@@ -100,8 +101,8 @@ function getInteractionLoci(pickingId: PickingId, structure: Structure, id: numb
         const interactions = InteractionsProvider.get(structure).value!
         const c = interactions.contacts.edges[groupId]
         return Interactions.Loci(structure, interactions, [
-            { unitA: c.unitA, indexA: c.indexA, unitB: c.unitB, indexB: c.indexB },
-            { unitA: c.unitB, indexA: c.indexB, unitB: c.unitA, indexB: c.indexA },
+            { structure, unitA: c.unitA, indexA: c.indexA, unitB: c.unitB, indexB: c.indexB },
+            { structure, unitA: c.unitB, indexA: c.indexB, unitB: c.unitA, indexB: c.indexA },
         ])
     }
     return EmptyLoci
@@ -130,7 +131,7 @@ function createInteractionsIterator(structure: Structure): LocationIterator {
     const { contacts } = interactions
     const groupCount = contacts.edgeCount
     const instanceCount = 1
-    const location = Interactions.Location(interactions)
+    const location = Interactions.Location(interactions, structure)
     const { element } = location
     const getLocation = (groupIndex: number) => {
         const c = contacts.edges[groupIndex]

+ 4 - 4
src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts

@@ -24,7 +24,7 @@ import { InteractionFlag } from '../interactions/common';
 async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
-    const location = StructureElement.Location.create(unit)
+    const location = StructureElement.Location.create(structure, unit)
 
     const interactions = InteractionsProvider.get(structure).value!
     const features = interactions.unitsFeatures.get(unit.id)
@@ -97,8 +97,8 @@ function getInteractionLoci(pickingId: PickingId, structureGroup: StructureGroup
         const interactions = InteractionsProvider.get(structure).value!
         const { a, b } = interactions.unitsContacts.get(unit.id)
         return Interactions.Loci(structure, interactions, [
-            { unitA: unit, indexA: a[groupId], unitB: unit, indexB: b[groupId] },
-            { unitA: unit, indexA: b[groupId], unitB: unit, indexB: a[groupId] },
+            { structure, unitA: unit, indexA: a[groupId], unitB: unit, indexB: b[groupId] },
+            { structure, unitA: unit, indexA: b[groupId], unitB: unit, indexB: a[groupId] },
         ])
     }
     return EmptyLoci
@@ -134,7 +134,7 @@ function createInteractionsIterator(structureGroup: StructureGroup): LocationIte
     const contacts = interactions.unitsContacts.get(unit.id)
     const groupCount = contacts.edgeCount * 2
     const instanceCount = group.units.length
-    const location = Interactions.Location(interactions)
+    const location = Interactions.Location(interactions, structure)
     const { element } = location
     const getLocation = (groupIndex: number, instanceIndex: number) => {
         const instanceUnit = group.units[instanceIndex]

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

@@ -11,6 +11,8 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { Unit } from '../../mol-model/structure/structure';
 import { CustomStructureProperty } from '../common/custom-structure-property';
 import { CustomProperty } from '../common/custom-property';
+import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure';
+import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
 
 function getSecondaryStructureParams(data?: Structure) {
     let defaultType = 'mmcif' as 'mmcif' | 'dssp'
@@ -18,10 +20,10 @@ function getSecondaryStructureParams(data?: Structure) {
         defaultType = 'dssp'
         for (let i = 0, il = data.models.length; i < il; ++i) {
             const m = data.models[i]
-            if (m.sourceData.kind === 'mmCIF') {
-                if (data.model.sourceData.data.struct_conf.id.isDefined ||
-                    data.model.sourceData.data.struct_sheet_range.id.isDefined ||
-                    data.model.sourceData.data.database_2.database_id.isDefined
+            if (MmcifFormat.is(m.sourceData)) {
+                if (m.sourceData.data.db.struct_conf.id.isDefined ||
+                    m.sourceData.data.db.struct_sheet_range.id.isDefined ||
+                    m.sourceData.data.db.database_2.database_id.isDefined
                 ) {
                     // if there is any secondary structure definition given or if there is
                     // an archival model, don't calculate dssp by default
@@ -83,7 +85,10 @@ async function computeMmcif(structure: Structure): Promise<SecondaryStructureVal
     for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
         const u = structure.unitSymmetryGroups[i].units[0]
         if (Unit.isAtomic(u)) {
-            map.set(u.invariantId, u.model.properties.secondaryStructure)
+            const secondaryStructure = ModelSecondaryStructure.Provider.get(u.model)
+            if (secondaryStructure) {
+                map.set(u.invariantId, secondaryStructure)
+            }
         }
     }
     return map

+ 6 - 4
src/mol-model-props/pdbe/preferred-assembly.ts

@@ -8,13 +8,15 @@ import { Column, Table } from '../../mol-data/db';
 import { toTable } from '../../mol-io/reader/cif/schema';
 import { CifWriter } from '../../mol-io/writer/cif';
 import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
+import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
+import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
 
 export namespace PDBePreferredAssembly {
     export type Property = string
 
     export function getFirstFromModel(model: Model): Property {
-        const asm = model.symmetry.assemblies;
-        return asm.length ? asm[0].id : '';
+        const symmetry = ModelSymmetry.Provider.get(model)
+        return symmetry?.assemblies.length ? symmetry.assemblies[0].id : '';
     }
 
     export function get(model: Model): Property {
@@ -46,8 +48,8 @@ export namespace PDBePreferredAssembly {
     });
 
     function fromCifData(model: Model): string | undefined {
-        if (model.sourceData.kind !== 'mmCIF') return void 0;
-        const cat = model.sourceData.frame.categories.pdbe_preferred_assembly;
+        if (!MmcifFormat.is(model.sourceData)) return void 0;
+        const cat = model.sourceData.data.frame.categories.pdbe_preferred_assembly;
         if (!cat) return void 0;
         return toTable(Schema.pdbe_preferred_assembly, cat).assembly_id.value(0) || getFirstFromModel(model);
     }

+ 3 - 2
src/mol-model-props/pdbe/struct-ref-domain.ts

@@ -9,6 +9,7 @@ import { toTable } from '../../mol-io/reader/cif/schema';
 import { CifWriter } from '../../mol-io/writer/cif';
 import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
 import { PropertyWrapper } from '../common/wrapper';
+import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
 
 export namespace PDBeStructRefDomain {
     export type Property = PropertyWrapper<Table<Schema['pdbe_struct_ref_domain']> | undefined>
@@ -57,8 +58,8 @@ export namespace PDBeStructRefDomain {
     });
 
     function fromCifData(model: Model): Property['data'] {
-        if (model.sourceData.kind !== 'mmCIF') return void 0;
-        const cat = model.sourceData.frame.categories.pdbe_struct_ref_domain;
+        if (!MmcifFormat.is(model.sourceData)) return void 0;
+        const cat = model.sourceData.data.frame.categories.pdbe_struct_ref_domain;
         if (!cat) return void 0;
         return toTable(Schema.pdbe_struct_ref_domain, cat);
     }

+ 42 - 11
src/mol-model-props/pdbe/structure-quality-report.ts

@@ -11,7 +11,7 @@ import { mmCIF_residueId_schema } from '../../mol-io/reader/cif/schema/mmcif-ext
 import { CifWriter } from '../../mol-io/writer/cif';
 import { Model, CustomPropertyDescriptor, ResidueIndex, Unit, IndexedCustomProperty } from '../../mol-model/structure';
 import { residueIdFields } from '../../mol-model/structure/export/categories/atom_site';
-import { StructureElement, CifExportContext } from '../../mol-model/structure/structure';
+import { StructureElement, CifExportContext, Structure } from '../../mol-model/structure/structure';
 import { CustomPropSymbol } from '../../mol-script/language/symbol';
 import Type from '../../mol-script/language/type';
 import { QuerySymbolRuntime } from '../../mol-script/runtime/query/compiler';
@@ -19,10 +19,15 @@ import { PropertyWrapper } from '../common/wrapper';
 import { CustomModelProperty } from '../common/custom-model-property';
 import { ParamDefinition as PD } from '../../mol-util/param-definition'
 import { CustomProperty } from '../common/custom-property';
+import { arraySetAdd } from '../../mol-util/array';
+import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
 
 export { StructureQualityReport }
 
-type StructureQualityReport = PropertyWrapper<IndexedCustomProperty.Residue<string[]> | undefined>
+type StructureQualityReport = PropertyWrapper<{
+    issues: IndexedCustomProperty.Residue<string[]>,
+    issueTypes: string[]
+}| undefined>
 
 namespace StructureQualityReport {
     export const DefaultServerUrl = 'https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/'
@@ -33,8 +38,8 @@ namespace StructureQualityReport {
     export function isApplicable(model?: Model): boolean {
         return (
             !!model &&
-            model.sourceData.kind === 'mmCIF' &&
-            (model.sourceData.data.database_2.database_id.isDefined ||
+            MmcifFormat.is(model.sourceData) &&
+            (model.sourceData.data.db.database_2.database_id.isDefined ||
                 model.entryId.length === 4)
         )
     }
@@ -88,14 +93,21 @@ namespace StructureQualityReport {
         const prop = StructureQualityReportProvider.get(e.unit.model).value;
         if (!prop || !prop.data) return _emptyArray;
         const rI = e.unit.residueIndex[e.element];
-        return prop.data.has(rI) ? prop.data.get(rI)! : _emptyArray;
+        return prop.data.issues.has(rI) ? prop.data.issues.get(rI)! : _emptyArray;
+    }
+
+    export function getIssueTypes(structure?: Structure) {
+        if (!structure) return _emptyArray;
+        const prop = StructureQualityReportProvider.get(structure.models[0]).value;
+        if (!prop || !prop.data) return _emptyArray;
+        return prop.data.issueTypes;
     }
 
     function getCifData(model: Model) {
-        if (model.sourceData.kind !== 'mmCIF') throw new Error('Data format must be mmCIF.');
+        if (!MmcifFormat.is(model.sourceData)) throw new Error('Data format must be mmCIF.');
         return {
-            residues: toTable(Schema.pdbe_structure_quality_report_issues, model.sourceData.frame.categories.pdbe_structure_quality_report_issues),
-            groups: toTable(Schema.pdbe_structure_quality_report_issue_types, model.sourceData.frame.categories.pdbe_structure_quality_report_issue_types),
+            residues: toTable(Schema.pdbe_structure_quality_report_issues, model.sourceData.data.frame.categories.pdbe_structure_quality_report_issues),
+            groups: toTable(Schema.pdbe_structure_quality_report_issue_types, model.sourceData.data.frame.categories.pdbe_structure_quality_report_issue_types),
         }
     }
 }
@@ -174,7 +186,7 @@ function createExportContext(ctx: CifExportContext): ReportExportContext {
         if (prop) info = prop.info;
         if (!prop || !prop.data) continue;
 
-        const { elements, property } = prop.data.getElements(s);
+        const { elements, property } = prop.data.issues.getElements(s);
         if (elements.length === 0) continue;
 
         const elementGroupId: number[] = [];
@@ -205,6 +217,8 @@ function createIssueMapFromJson(modelData: Model, data: any): StructureQualityRe
     const ret = new Map<ResidueIndex, string[]>();
     if (!data.molecules) return;
 
+    const issueTypes: string[] = [];
+
     for (const entity of data.molecules) {
         const entity_id = entity.entity_id.toString();
         for (const chain of entity.chains) {
@@ -217,12 +231,19 @@ function createIssueMapFromJson(modelData: Model, data: any): StructureQualityRe
                     const auth_seq_id = residue.author_residue_number, ins_code = residue.author_insertion_code || '';
                     const idx = modelData.atomicHierarchy.index.findResidue(entity_id, asym_id, auth_seq_id, ins_code);
                     ret.set(idx, residue.outlier_types);
+
+                    for (const t of residue.outlier_types) {
+                        arraySetAdd(issueTypes, t);
+                    }
                 }
             }
         }
     }
 
-    return IndexedCustomProperty.fromResidueMap(ret);
+    return {
+        issues: IndexedCustomProperty.fromResidueMap(ret),
+        issueTypes
+    };
 }
 
 function createIssueMapFromCif(modelData: Model,
@@ -240,7 +261,17 @@ function createIssueMapFromCif(modelData: Model,
         ret.set(idx, groups.get(issue_type_group_id.value(i))!);
     }
 
-    return IndexedCustomProperty.fromResidueMap(ret);
+    const issueTypes: string[] = [];
+    groups.forEach(issues => {
+        for (const t of issues) {
+            arraySetAdd(issueTypes, t);
+        }
+    })
+
+    return {
+        issues: IndexedCustomProperty.fromResidueMap(ret),
+        issueTypes
+    };
 }
 
 function parseIssueTypes(groupData: Table<typeof StructureQualityReport.Schema.pdbe_structure_quality_report_issue_types>): Map<number, string[]> {

+ 59 - 6
src/mol-model-props/pdbe/themes/structure-quality-report.ts

@@ -11,6 +11,8 @@ import { ColorTheme, LocationColor } from '../../../mol-theme/color';
 import { ThemeDataContext } from '../../../mol-theme/theme';
 import { Color } from '../../../mol-util/color';
 import { TableLegend } from '../../../mol-util/legend';
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+import { CustomProperty } from '../../common/custom-property';
 
 const ValidationColors = [
     Color.fromRgb(170, 170, 170), // not applicable
@@ -28,16 +30,38 @@ const ValidationColorTable: [string, Color][] = [
     ['Not Applicable', ValidationColors[9]]
 ]
 
-export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: {}): ColorTheme<{}> {
+export const StructureQualityReportColorThemeParams = {
+    type: PD.MappedStatic('issue-count', {
+        'issue-count': PD.Group({}),
+        'specific-issue': PD.Group({
+            kind: PD.Text()
+        })
+    })
+};
+
+type Params = typeof StructureQualityReportColorThemeParams
+
+export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: PD.Values<Params>): ColorTheme<Params> {
     let color: LocationColor
 
     if (ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(StructureQualityReportProvider.descriptor)) {
         const getIssues = StructureQualityReport.getIssues;
-        color = (location: Location) => {
-            if (StructureElement.Location.is(location)) {
-                return ValidationColors[Math.min(3, getIssues(location).length) + 1];
+
+        if (props.type.name === 'issue-count') {
+            color = (location: Location) => {
+                if (StructureElement.Location.is(location)) {
+                    return ValidationColors[Math.min(3, getIssues(location).length) + 1];
+                }
+                return ValidationColors[0];
+            }
+        } else {
+            const issue = props.type.params.kind;
+            color = (location: Location) => {
+                if (StructureElement.Location.is(location) && getIssues(location).indexOf(issue) >= 0) {
+                    return ValidationColors[4];
+                }
+                return ValidationColors[0];
             }
-            return ValidationColors[0];
         }
     } else {
         color = () => ValidationColors[0];
@@ -48,7 +72,36 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: {
         granularity: 'group',
         color: color,
         props: props,
-        description: 'Assigns residue colors according to the number of issues in the PDBe Validation Report.',
+        description: 'Assigns residue colors according to the number of issues or a specific issue in the PDBe Validation Report.',
         legend: TableLegend(ValidationColorTable)
     }
+}
+
+export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params> =  {
+    label: 'PDBe Structure Quality Report',
+    factory: StructureQualityReportColorTheme,
+    getParams: ctx => {
+        const issueTypes = StructureQualityReport.getIssueTypes(ctx.structure);
+        if (issueTypes.length === 0) {
+            return {
+                type: PD.MappedStatic('issue-count', {
+                    'issue-count': PD.Group({})
+                })
+            };
+        }
+
+        return {
+            type: PD.MappedStatic('issue-count', {
+                'issue-count': PD.Group({}),
+                'specific-issue': PD.Group({
+                    kind: PD.Select(issueTypes[0], PD.arrayToOptions(issueTypes))
+                }, { isFlat: true })
+            })
+        };
+    },
+    defaultValues: PD.getDefaultValues(StructureQualityReportColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]),
+    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
+        return data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
+    }
 }

+ 4 - 3
src/mol-model-props/rcsb/assembly-symmetry.ts

@@ -14,6 +14,7 @@ import { GraphQLClient } from '../../mol-util/graphql-client';
 import { CustomProperty } from '../common/custom-property';
 import { NonNullableArray } from '../../mol-util/type-helpers';
 import { CustomStructureProperty } from '../common/custom-structure-property';
+import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
 
 const BiologicalAssemblyNames = new Set([
     'author_and_software_defined_assembly',
@@ -25,15 +26,15 @@ const BiologicalAssemblyNames = new Set([
 ])
 
 export namespace AssemblySymmetry {
-    export const DefaultServerUrl = 'http://data-beta.rcsb.org/graphql'
+    export const DefaultServerUrl = 'https://data-beta.rcsb.org/graphql'
 
     export function isApplicable(structure?: Structure): boolean {
         // check if structure is from pdb entry
-        if (!structure || structure.models.length !== 1 || structure.models[0].sourceData.kind !== 'mmCIF' || (!structure.models[0].sourceData.data.database_2.database_id.isDefined &&
+        if (!structure || structure.models.length !== 1 || !MmcifFormat.is(structure.models[0].sourceData) || (!structure.models[0].sourceData.data.db.database_2.database_id.isDefined &&
         structure.models[0].entryId.length !== 4)) return false
 
         // check if assembly is 'biological'
-        const mmcif = structure.models[0].sourceData.data
+        const mmcif = structure.models[0].sourceData.data.db
         if (!mmcif.pdbx_struct_assembly.details.isDefined) return false
         const id = structure.units[0].conformation.operator.assembly.id
         const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id)

+ 3 - 3
src/mol-model-props/rcsb/graphql/types.d.ts → src/mol-model-props/rcsb/graphql/types.ts

@@ -1,7 +1,7 @@
 /* eslint-disable */
 export type Maybe<T> = T | null;
 
-// Generated in 2020-02-07T10:59:45-08:00
+// Generated in 2020-02-21T15:58:06-08:00
 
 /** All built-in and custom scalars, mapped to their actual values */
 export type Scalars = {
@@ -14,7 +14,6 @@ export type Scalars = {
   UNREPRESENTABLE: any,
 };
 
-
 export type AuditAuthor = {
   readonly identifier_ORCID?: Maybe<Scalars['String']>,
   readonly name?: Maybe<Scalars['String']>,
@@ -1750,6 +1749,7 @@ export type RcsbEntryContainerIdentifiers = {
   readonly emdb_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
   readonly entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
   readonly entry_id: Scalars['String'],
+  readonly model_ids?: Maybe<ReadonlyArray<Maybe<Scalars['Int']>>>,
   readonly non_polymer_entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
   readonly polymer_entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
   readonly pubmed_id?: Maybe<Scalars['Int']>,
@@ -1921,7 +1921,6 @@ export type RcsbNonpolymerInstanceAnnotationAnnotationLineage = {
 
 export type RcsbNonpolymerInstanceFeature = {
   readonly assignment_version?: Maybe<Scalars['String']>,
-  readonly auth_seq_id?: Maybe<Scalars['String']>,
   readonly comp_id?: Maybe<Scalars['String']>,
   readonly description?: Maybe<Scalars['String']>,
   readonly feature_id?: Maybe<Scalars['String']>,
@@ -1942,6 +1941,7 @@ export type RcsbNonpolymerInstanceFeatureFeatureValue = {
 };
 
 export type RcsbNonpolymerInstanceFeatureSummary = {
+  readonly comp_id?: Maybe<Scalars['String']>,
   readonly count?: Maybe<Scalars['Int']>,
   readonly maximum_length?: Maybe<Scalars['Int']>,
   readonly maximum_value?: Maybe<Scalars['Float']>,

+ 16 - 12
src/mol-model-props/rcsb/representations/validation-report-clashes.ts

@@ -91,7 +91,7 @@ function getIntraClashBoundingSphere(unit: Unit.Atomic, clashes: IntraUnitClashe
     }, boundingSphere)
 }
 
-function getIntraClashLabel(unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
+function getIntraClashLabel(structure: Structure, unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
     const idx = elements[0]
     if (idx === undefined) return ''
     const { edgeProps: { id, magnitude, distance } } = clashes
@@ -100,12 +100,14 @@ function getIntraClashLabel(unit: Unit.Atomic, clashes: IntraUnitClashes, elemen
 
     return [
         `Clash id: ${id[idx]} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`,
-        bondLabel(Bond.Location(unit, clashes.a[idx], unit, clashes.b[idx]))
+        bondLabel(Bond.Location(structure, unit, clashes.a[idx], structure, unit, clashes.b[idx]))
     ].join('</br>')
 }
 
-function IntraClashLoci(unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
-    return DataLoci('intra-clashes', { unit, clashes }, elements, (boundingSphere: Sphere3D) =>  getIntraClashBoundingSphere(unit, clashes, elements, boundingSphere), () => getIntraClashLabel(unit, clashes, elements))
+function IntraClashLoci(structure: Structure, unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
+    return DataLoci('intra-clashes', { unit, clashes }, elements,
+        (boundingSphere: Sphere3D) =>  getIntraClashBoundingSphere(unit, clashes, elements, boundingSphere),
+        () => getIntraClashLabel(structure, unit, clashes, elements))
 }
 
 function getIntraClashLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
@@ -115,7 +117,7 @@ function getIntraClashLoci(pickingId: PickingId, structureGroup: StructureGroup,
         const unit = group.units[instanceId]
         if (Unit.isAtomic(unit)) {
             const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id)
-            return IntraClashLoci(unit, clashes, [groupId])
+            return IntraClashLoci(structure, unit, clashes, [groupId])
         }
     }
     return EmptyLoci
@@ -134,7 +136,7 @@ function createIntraClashIterator(structureGroup: StructureGroup): LocationItera
     const { a } = clashes
     const groupCount = clashes.edgeCount * 2
     const instanceCount = group.units.length
-    const location = StructureElement.Location.create()
+    const location = StructureElement.Location.create(structure)
     const getLocation = (groupIndex: number, instanceIndex: number) => {
         const unit = group.units[instanceIndex]
         location.unit = unit
@@ -203,7 +205,7 @@ function getInterClashBoundingSphere(clashes: InterUnitClashes, elements: number
     }, boundingSphere)
 }
 
-function getInterClashLabel(clashes: InterUnitClashes, elements: number[]) {
+function getInterClashLabel(structure: Structure, clashes: InterUnitClashes, elements: number[]) {
     const idx = elements[0]
     if (idx === undefined) return ''
     const c = clashes.edges[idx]
@@ -212,19 +214,21 @@ function getInterClashLabel(clashes: InterUnitClashes, elements: number[]) {
 
     return [
         `Clash id: ${c.props.id} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`,
-        bondLabel(Bond.Location(c.unitA, c.indexA, c.unitB, c.indexB))
+        bondLabel(Bond.Location(structure, c.unitA, c.indexA, structure, c.unitB, c.indexB))
     ].join('</br>')
 }
 
-function InterClashLoci(clashes: InterUnitClashes, elements: number[]) {
-    return DataLoci('inter-clashes', clashes, elements, (boundingSphere: Sphere3D) =>  getInterClashBoundingSphere(clashes, elements, boundingSphere), () => getInterClashLabel(clashes, elements))
+function InterClashLoci(structure: Structure, clashes: InterUnitClashes, elements: number[]) {
+    return DataLoci('inter-clashes', clashes, elements,
+        (boundingSphere: Sphere3D) =>  getInterClashBoundingSphere(clashes, elements, boundingSphere),
+        () => getInterClashLabel(structure, clashes, elements))
 }
 
 function getInterClashLoci(pickingId: PickingId, structure: Structure, id: number) {
     const { objectId, groupId } = pickingId
     if (id === objectId) {
         const clashes = ClashesProvider.get(structure).value!.interUnit
-        return InterClashLoci(clashes, [groupId])
+        return InterClashLoci(structure, clashes, [groupId])
     }
     return EmptyLoci
 }
@@ -239,7 +243,7 @@ function createInterClashIterator(structure: Structure): LocationIterator {
     const clashes = ClashesProvider.get(structure).value!.interUnit
     const groupCount = clashes.edgeCount
     const instanceCount = 1
-    const location = StructureElement.Location.create()
+    const location = StructureElement.Location.create(structure)
     const getLocation = (groupIndex: number) => {
         const clash = clashes.edges[groupIndex]
         location.unit = clash.unitA

+ 36 - 13
src/mol-model-props/rcsb/themes/geometry-quality.ts

@@ -14,6 +14,7 @@ import { CustomProperty } from '../../common/custom-property';
 import { ValidationReportProvider, ValidationReport } from '../validation-report';
 import { TableLegend } from '../../../mol-util/legend';
 import { PolymerType } from '../../../mol-model/structure/model/types';
+import { SetUtils } from '../../../mol-util/set';
 
 const DefaultColor = Color(0x909090)
 
@@ -23,13 +24,28 @@ const TwoIssuesColor = Color(0xf46d43)
 const ThreeOrMoreIssuesColor = Color(0xa50026)
 
 const ColorLegend = TableLegend([
+    ['Data unavailable', DefaultColor],
     ['No issues', NoIssuesColor],
     ['One issue', OneIssueColor],
     ['Two issues', TwoIssuesColor],
-    ['Three or more issues', ThreeOrMoreIssuesColor]
+    ['Three or more issues', ThreeOrMoreIssuesColor],
 ])
 
-export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: {}): ColorTheme<{}> {
+export function getGeometricQualityColorThemeParams(ctx: ThemeDataContext) {
+    const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]).value
+    const options: [string, string][] = []
+    if (validationReport) {
+        const kinds = new Set<string>()
+        validationReport.geometryIssues.forEach(v => v.forEach(k => kinds.add(k)))
+        kinds.forEach(k => options.push([k, k]))
+    }
+    return {
+        ignore: PD.MultiSelect([] as string[], options)
+    }
+}
+export type GeometricQualityColorThemeParams = ReturnType<typeof getGeometricQualityColorThemeParams>
+
+export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Values<GeometricQualityColorThemeParams>): ColorTheme<GeometricQualityColorThemeParams> {
     let color: LocationColor = () => DefaultColor
 
     const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0])
@@ -42,19 +58,26 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: {}): Col
         const { geometryIssues, clashes, bondOutliers, angleOutliers } = value
         const residueIndex = model.atomicHierarchy.residueAtomSegments.index
         const { polymerType } = model.atomicHierarchy.derived.residue
+        const ignore = new Set(props.ignore)
+
         color = (location: Location): Color => {
             if (StructureElement.Location.is(location) && location.unit.model === model) {
                 const { element } = location
                 const rI = residueIndex[element]
-                let value = geometryIssues.get(rI)?.size
-                if (value !== undefined && polymerType[rI] === PolymerType.NA) {
-                    value = 0
-                    if (clashes.getVertexEdgeCount(element) > 0) value += 1
-                    if (bondOutliers.index.has(element)) value += 1
-                    if (angleOutliers.index.has(element)) value += 1
+
+                const value = geometryIssues.get(rI)
+                if (value === undefined) return DefaultColor
+
+                let count = SetUtils.differenceSize(value, ignore)
+
+                if (count > 0 && polymerType[rI] === PolymerType.NA) {
+                    count = 0
+                    if (!ignore.has('clash') && clashes.getVertexEdgeCount(element) > 0) count += 1
+                    if (!ignore.has('mog-bond-outlier') && bondOutliers.index.has(element)) count += 1
+                    if (!ignore.has('mog-angle-outlier') && angleOutliers.index.has(element)) count += 1
                 }
 
-                switch (value) {
+                switch (count) {
                     case undefined: return DefaultColor
                     case 0: return NoIssuesColor
                     case 1: return OneIssueColor
@@ -72,16 +95,16 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: {}): Col
         color,
         props,
         contextHash,
-        description: 'Assigns residue colors according to the number of geometry issues.',
+        description: 'Assigns residue colors according to the number of (filtered) geometry issues.',
         legend: ColorLegend
     }
 }
 
-export const GeometryQualityColorThemeProvider: ColorTheme.Provider<{}> = {
+export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQualityColorThemeParams> = {
     label: 'RCSB Geometry Quality',
     factory: GeometryQualityColorTheme,
-    getParams: () => ({}),
-    defaultValues: PD.getDefaultValues({}),
+    getParams: getGeometricQualityColorThemeParams,
+    defaultValues: PD.getDefaultValues(getGeometricQualityColorThemeParams({})),
     isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
     ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
         return data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()

+ 3 - 2
src/mol-model-props/rcsb/validation-report.ts

@@ -18,6 +18,7 @@ import { IntMap, SortedArray } from '../../mol-data/int';
 import { arrayMax } from '../../mol-util/array';
 import { equalEps } from '../../mol-math/linear-algebra/3d/common';
 import { Vec3 } from '../../mol-math/linear-algebra';
+import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
 
 export { ValidationReport }
 
@@ -90,8 +91,8 @@ namespace ValidationReport {
     export function isApplicable(model?: Model): boolean {
         return (
             !!model &&
-            model.sourceData.kind === 'mmCIF' &&
-            (model.sourceData.data.database_2.database_id.isDefined ||
+            MmcifFormat.is(model.sourceData) &&
+            (model.sourceData.data.db.database_2.database_id.isDefined ||
                 model.entryId.length === 4)
         )
     }

+ 2 - 2
src/mol-model/structure/export/categories/modified-residues.ts

@@ -35,7 +35,7 @@ function getModifiedResidues({ structures }: CifExportContext): StructureElement
 
     const ret = [];
     const prop = P.residue.label_comp_id;
-    const loc = StructureElement.Location.create();
+    const loc = StructureElement.Location.create(structure);
     for (const unit of structure.units) {
         if (!Unit.isAtomic(unit) || !unit.conformation.operator.isIdentity) continue;
         const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
@@ -45,7 +45,7 @@ function getModifiedResidues({ structures }: CifExportContext): StructureElement
             loc.element = unit.elements[seg.start];
             const name = prop(loc);
             if (map.has(name)) {
-                ret[ret.length] = StructureElement.Location.create(loc.unit, loc.element);
+                ret[ret.length] = StructureElement.Location.clone(loc);
             }
         }
     }

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

@@ -13,6 +13,7 @@ import CifField = CifWriter.Field
 import CifCategory = CifWriter.Category
 import { Column } from '../../../../mol-data/db';
 import { residueIdFields } from './atom_site';
+import { ModelSecondaryStructure } from '../../../../mol-model-formats/structure/property/secondary-structure';
 
 export const _struct_conf: CifCategory<CifExportContext> = {
     name: 'struct_conf',
@@ -70,11 +71,14 @@ interface SSElement<T extends SecondaryStructure.Element> {
 
 function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContext, kind: SecondaryStructure.Element['kind']) {
     // TODO: encode secondary structure for different models?
-    const { key, elements } = ctx.structures[0].model.properties.secondaryStructure;
+    const secondaryStructure = ModelSecondaryStructure.Provider.get(ctx.firstModel)
+    if (!secondaryStructure) return [] as SSElement<T>[]
 
+    const { key, elements } = secondaryStructure;
     const ssElements: SSElement<any>[] = [];
 
-    for (const unit of ctx.structures[0].units) {
+    const structure = ctx.structures[0];
+    for (const unit of structure.units) {
         // currently can only support this for "identity" operators.
         if (!Unit.isAtomic(unit) || !unit.conformation.operator.isIdentity) continue;
 
@@ -100,8 +104,8 @@ function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContex
                 if (startIdx !== key[current.index]) {
                     move = false;
                     ssElements[ssElements.length] = {
-                        start: StructureElement.Location.create(unit, segs.offsets[start]),
-                        end: StructureElement.Location.create(unit, segs.offsets[prev]),
+                        start: StructureElement.Location.create(structure, unit, segs.offsets[start]),
+                        end: StructureElement.Location.create(structure, unit, segs.offsets[prev]),
                         length: prev - start + 1,
                         element
                     }

+ 5 - 4
src/mol-model/structure/export/categories/utils.ts

@@ -13,10 +13,11 @@ import { UniqueArray } from '../../../../mol-data/generic';
 import { sortArray } from '../../../../mol-data/util';
 import { CifWriter } from '../../../../mol-io/writer/cif';
 import { CifExportContext } from '../mmcif';
+import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
 
 export function getModelMmCifCategory<K extends keyof mmCIF_Schema>(model: Model, name: K): mmCIF_Database[K] | undefined {
-    if (model.sourceData.kind !== 'mmCIF') return;
-    return model.sourceData.data[name];
+    if (!MmcifFormat.is(model.sourceData)) return;
+    return model.sourceData.data.db[name];
 }
 
 export function getUniqueResidueNamesFromStructures(structures: Structure[]) {
@@ -50,9 +51,9 @@ export function copy_mmCif_category(name: keyof mmCIF_Schema, condition?: (struc
             if (condition && !condition(structures[0])) return CifWriter.Category.Empty;
 
             const model = structures[0].model;
-            if (model.sourceData.kind !== 'mmCIF') return CifWriter.Category.Empty;
+            if (!MmcifFormat.is(model.sourceData)) return CifWriter.Category.Empty;
 
-            const table = model.sourceData.data[name];
+            const table = model.sourceData.data.db[name];
             if (!table || !table._rowCount) return CifWriter.Category.Empty;
             return CifWriter.Category.ofTable(table);
         }

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

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

+ 6 - 11
src/mol-model/structure/model/model.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -8,11 +8,9 @@
 import UUID from '../../../mol-util/uuid';
 import StructureSequence from './properties/sequence';
 import { AtomicHierarchy, AtomicConformation, AtomicRanges } from './properties/atomic';
-import { ModelSymmetry } from './properties/symmetry';
 import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
 import { Entities, ChemicalComponentMap, MissingResidues } from './properties/common';
 import { CustomProperties } from '../common/custom-property';
-import { SecondaryStructure } from './properties/seconday-structure';
 import { SaccharideComponentMap } from '../structure/carbohydrates/constants';
 import { ModelFormat } from '../../../mol-model-formats/structure/format';
 import { calcModelCenter } from './util';
@@ -20,9 +18,9 @@ import { Vec3 } from '../../../mol-math/linear-algebra';
 import { Mutable } from '../../../mol-util/type-helpers';
 import { Coordinates } from '../coordinates';
 import { Topology } from '../topology';
-import { _parse_mmCif } from '../../../mol-model-formats/structure/mmcif/parser';
 import { Task } from '../../../mol-task';
-import { IndexPairBonds } from '../../../mol-model-formats/structure/mmcif/bonds/index-pair';
+import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
+import { createModels } from '../../../mol-model-formats/structure/basic/parser';
 
 /**
  * Interface to the "source data" of the molecule.
@@ -47,7 +45,6 @@ export interface Model extends Readonly<{
 
     sourceData: ModelFormat,
 
-    symmetry: ModelSymmetry,
     entities: Entities,
     sequence: StructureSequence,
 
@@ -56,8 +53,6 @@ export interface Model extends Readonly<{
     atomicRanges: AtomicRanges,
 
     properties: {
-        /** secondary structure provided by the input file */
-        readonly secondaryStructure: SecondaryStructure,
         /** maps modified residue name to its parent */
         readonly modifiedResidues: Readonly<{
             parentId: ReadonlyMap<string, string>,
@@ -87,7 +82,6 @@ export interface Model extends Readonly<{
 } { }
 
 export namespace Model {
-    // TODO: is this enough?
     export type Trajectory = ReadonlyArray<Model>
 
     export function trajectoryFromModelAndCoordinates(model: Model, coordinates: Coordinates): Trajectory {
@@ -113,12 +107,13 @@ export namespace Model {
 
     export function trajectoryFromTopologyAndCoordinates(topology: Topology, coordinates: Coordinates): Task<Trajectory> {
         return Task.create('Create Trajectory', async ctx => {
-            const model = (await _parse_mmCif(topology.format, ctx))[0];
+            const model = (await createModels(topology.basic, topology.sourceData, ctx))[0];
             if (!model) throw new Error('found no model')
             const trajectory = trajectoryFromModelAndCoordinates(model, coordinates)
             const bondData = { pairs: topology.bonds, count: model.atomicHierarchy.atoms._rowCount }
+            const indexPairBonds = IndexPairBonds.fromData(bondData)
             for (const m of trajectory) {
-                IndexPairBonds.attachFromData(m, bondData)
+                IndexPairBonds.Provider.set(m, indexPairBonds)
             }
             return trajectory
         })

+ 5 - 3
src/mol-model/structure/model/properties/atomic/hierarchy.ts

@@ -50,7 +50,7 @@ export const AtomsSchema = {
 };
 
 export type AtomsSchema = typeof AtomsSchema
-export interface Atoms extends Table<AtomsSchema> { }
+export type Atoms = Table<AtomsSchema>
 
 export const ResiduesSchema = {
     /**
@@ -83,7 +83,7 @@ export const ResiduesSchema = {
     pdbx_PDB_ins_code: mmCIF.atom_site.pdbx_PDB_ins_code,
 };
 export type ResiduesSchema = typeof ResiduesSchema
-export interface Residues extends Table<ResiduesSchema> { }
+export type Residues = Table<ResiduesSchema>
 
 export const ChainsSchema = {
     /**
@@ -102,7 +102,7 @@ export const ChainsSchema = {
     label_entity_id: mmCIF.atom_site.label_entity_id
 }
 export type ChainsSchema = typeof ChainsSchema
-export interface Chains extends Table<ChainsSchema> { }
+export type Chains = Table<ChainsSchema>
 
 export interface AtomicData {
     atoms: Atoms,
@@ -139,6 +139,8 @@ export interface AtomicSegments {
 export interface AtomicIndex {
     /** @returns index or -1 if not present. */
     getEntityFromChain(cI: ChainIndex): EntityIndex,
+    /** @returns index or -1 if not present. */
+    findEntity(label_asym_id: string): EntityIndex
 
     /**
      * Find chain using label_ mmCIF properties

+ 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
-// }

+ 3 - 3
src/mol-model/structure/model/properties/custom/indexed.ts

@@ -109,7 +109,7 @@ class SegmentedMappedIndexedCustomProperty<Idx extends IndexedCustomProperty.Ind
                 const seg = chains.move();
                 if (!this.has(seg.index) || seenIndices.has(seg.index)) continue;
                 seenIndices.add(seg.index);
-                loci[loci.length] = StructureElement.Location.create(unit, unit.elements[seg.start]);
+                loci[loci.length] = StructureElement.Location.create(structure, unit, unit.elements[seg.start]);
             }
         }
 
@@ -154,7 +154,7 @@ class ElementMappedCustomProperty<T = any> implements IndexedCustomProperty<Elem
                 const e = elements[i];
                 if (!this.has(e) || seenIndices.has(e)) continue;
                 seenIndices.add(elements[i]);
-                loci[loci.length] = StructureElement.Location.create(unit, e);
+                loci[loci.length] = StructureElement.Location.create(structure, unit, e);
             }
         }
 
@@ -202,7 +202,7 @@ class EntityMappedCustomProperty<T = any> implements IndexedCustomProperty<Entit
                 const eI = index.getEntityFromChain(seg.index);
                 if (!this.has(eI) || seenIndices.has(eI)) continue;
                 seenIndices.add(eI);
-                loci[loci.length] = StructureElement.Location.create(unit, unit.elements[seg.start]);
+                loci[loci.length] = StructureElement.Location.create(structure, unit, unit.elements[seg.start]);
             }
         }
 

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

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

+ 12 - 2
src/mol-model/structure/model/properties/utils/atomic-index.ts

@@ -51,7 +51,9 @@ interface Mapping {
     chain_index_label_seq_id: Map<ChainIndex, Map<string | number, ResidueIndex>>,
 
     auth_asym_id_auth_seq_id: Map<string, Map<number, ChainIndex>>,
-    chain_index_auth_seq_id: Map<ChainIndex, Map<string | number, ResidueIndex>>
+    chain_index_auth_seq_id: Map<ChainIndex, Map<string | number, ResidueIndex>>,
+
+    label_asym_id: Map<string, EntityIndex>,
 }
 
 function createMapping(entities: Entities, data: AtomicData, segments: AtomicSegments): Mapping {
@@ -67,6 +69,7 @@ function createMapping(entities: Entities, data: AtomicData, segments: AtomicSeg
         chain_index_label_seq_id: new Map(),
         auth_asym_id_auth_seq_id: new Map(),
         chain_index_auth_seq_id: new Map(),
+        label_asym_id: new Map(),
     };
 }
 
@@ -79,6 +82,11 @@ class Index implements AtomicIndex {
         return this.map.chain_index_entity_index[cI];
     }
 
+    findEntity(label_asym_id: string): EntityIndex {
+        const entityIndex = this.map.label_asym_id.get(label_asym_id)
+        return entityIndex !== undefined ? entityIndex : -1 as EntityIndex
+    }
+
     findChainLabel(key: AtomicIndex.ChainLabelKey): ChainIndex {
         const eI = this.entityIndex(key.label_entity_id);
         if (eI < 0 || !this.map.entity_index_label_asym_id.has(eI)) return -1 as ChainIndex;
@@ -216,7 +224,9 @@ export function getAtomicIndex(data: AtomicData, entities: Entities, segments: A
             map.auth_asym_id_auth_seq_id.set(authAsymId, auth_asym_id_auth_seq_id)
         }
 
-        updateMapMapIndex(map.entity_index_label_asym_id, entityIndex, label_asym_id.value(chainIndex), chainIndex);
+        const labelAsymId = label_asym_id.value(chainIndex)
+        if (!map.label_asym_id.has(labelAsymId)) map.label_asym_id.set(labelAsymId, entityIndex);
+        updateMapMapIndex(map.entity_index_label_asym_id, entityIndex, labelAsymId, chainIndex);
 
         const chain_index_label_seq_id = new Map<string | number, ResidueIndex>();
         const chain_index_auth_seq_id = new Map<string | number, ResidueIndex>();

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

@@ -221,6 +221,7 @@ export const WaterNames = new Set([
 export const AminoAcidNamesL = new Set([
     'HIS', 'ARG', 'LYS', 'ILE', 'PHE', 'LEU', 'TRP', 'ALA', 'MET', 'PRO', 'CYS',
     'ASN', 'VAL', 'GLY', 'SER', 'GLN', 'TYR', 'ASP', 'GLU', 'THR', 'SEC', 'PYL',
+    'UNK' // unknown amino acid from CCD
 ])
 export const AminoAcidNamesD = new Set([
     'DAL', // D-ALANINE
@@ -247,8 +248,14 @@ export const AminoAcidNamesD = new Set([
 ])
 export const AminoAcidNames = SetUtils.unionMany(AminoAcidNamesL, AminoAcidNamesD)
 
-export const RnaBaseNames = new Set([ 'A', 'C', 'T', 'G', 'I', 'U' ])
-export const DnaBaseNames = new Set([ 'DA', 'DC', 'DT', 'DG', 'DI', 'DU' ])
+export const RnaBaseNames = new Set([
+    'A', 'C', 'T', 'G', 'I', 'U',
+    'N' // unknown RNA base from CCD
+])
+export const DnaBaseNames = new Set([
+    'DA', 'DC', 'DT', 'DG', 'DI', 'DU',
+    'DN' // unknown DNA base from CCD
+])
 export const PeptideBaseNames = new Set([ 'APN', 'CPN', 'TPN', 'GPN' ])
 export const PurineBaseNames = new Set([ 'A', 'G', 'DA', 'DG', 'DI', 'APN', 'GPN' ])
 export const PyrimidineBaseNames = new Set([ 'C', 'T', 'U', 'DC', 'DT', 'DU', 'CPN', 'TPN' ])

+ 15 - 12
src/mol-model/structure/query/context.ts

@@ -6,13 +6,12 @@
 
 import { Structure, StructureElement, Unit } from '../structure';
 import { now } from '../../../mol-util/now';
-import { ElementIndex } from '../model';
 import { BondType } from '../model/types';
 import { StructureSelection } from './selection';
 import { defaultBondTest } from './queries/internal';
 
 export interface QueryContextView {
-    readonly element: StructureElement.Location;
+    readonly element: Readonly<StructureElement.Location>;
     readonly currentStructure: Structure;
 }
 
@@ -28,7 +27,7 @@ export class QueryContext implements QueryContextView {
     readonly inputStructure: Structure;
 
     /** Current element */
-    readonly element = StructureElement.Location.create();
+    readonly element: StructureElement.Location = StructureElement.Location.create(void 0);
     currentStructure: Structure = void 0 as any;
 
     /** Current bond between atoms */
@@ -37,14 +36,9 @@ export class QueryContext implements QueryContextView {
     /** Supply this from the outside. Used by the internal.generator.current symbol */
     currentSelection: StructureSelection | undefined = void 0;
 
-    setElement(unit: Unit, e: ElementIndex) {
-        this.element.unit = unit;
-        this.element.element = e;
-    }
-
     pushCurrentElement(): StructureElement.Location {
         this.currentElementStack[this.currentElementStack.length] = this.element;
-        (this.element as StructureElement.Location) = StructureElement.Location.create();
+        (this.element as StructureElement.Location) = StructureElement.Location.create(void 0);
         return this.element;
     }
 
@@ -112,16 +106,21 @@ export interface QueryContextOptions {
 export interface QueryPredicate { (ctx: QueryContext): boolean }
 export interface QueryFn<T = any> { (ctx: QueryContext): T }
 
-export class QueryContextBondInfo<U extends Unit = Unit> {
-    a: StructureElement.Location<U> = StructureElement.Location.create();
+class QueryContextBondInfo<U extends Unit = Unit> {
+    a: StructureElement.Location<U> = StructureElement.Location.create(void 0);
     aIndex: StructureElement.UnitIndex = 0 as StructureElement.UnitIndex;
-    b: StructureElement.Location<U> = StructureElement.Location.create();
+    b: StructureElement.Location<U> = StructureElement.Location.create(void 0);
     bIndex: StructureElement.UnitIndex = 0 as StructureElement.UnitIndex;
     type: BondType = BondType.Flag.None;
     order: number = 0;
 
     private testFn: QueryPredicate = defaultBondTest;
 
+    setStructure(s: Structure) {
+        this.a.structure = s;
+        this.b.structure = s;
+    }
+
     setTestFn(fn?: QueryPredicate) {
         this.testFn = fn || defaultBondTest;
     }
@@ -136,6 +135,10 @@ export class QueryContextBondInfo<U extends Unit = Unit> {
     }
 
     private swap() {
+        // const sA = this.a.structure;
+        // this.a.structure = this.b.structure;
+        // this.b.structure = sA;
+
         const idxA = this.aIndex;
         this.aIndex = this.bIndex;
         this.bIndex = idxA;

+ 4 - 2
src/mol-model/structure/query/queries/filters.ts

@@ -56,6 +56,7 @@ export function getCurrentStructureProperties(ctx: QueryContext, props: UnitType
     const { units } = ctx.currentStructure;
     const l = ctx.pushCurrentElement();
 
+    l.structure = ctx.currentStructure;
     for (const unit of units) {
         l.unit = unit;
         const elements = unit.elements;
@@ -239,6 +240,9 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
     const atomicBond = queryCtx.atomicBond;
 
     const interBonds = input.interUnitBonds;
+
+    atomicBond.setStructure(input);
+
     for (const unit of structure.units) {
         if (!Unit.isAtomic(unit)) continue;
 
@@ -248,8 +252,6 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
         const bondedUnits = interBonds.getConnectedUnits(unit);
         const buCount = bondedUnits.length;
 
-        atomicBond.a.unit = inputUnit;
-
         const srcElements = unit.elements;
         const inputElements = inputUnit.elements;
 

+ 5 - 0
src/mol-model/structure/query/queries/generators.ts

@@ -64,6 +64,7 @@ function atomGroupsLinear(atomTest: QueryPredicate): StructureQuery {
         const l = ctx.pushCurrentElement();
         const builder = inputStructure.subsetBuilder(true);
 
+        l.structure = inputStructure;
         for (const unit of units) {
             l.unit = unit;
             const elements = unit.elements;
@@ -89,6 +90,7 @@ function atomGroupsSegmented({ unitTest, entityTest, chainTest, residueTest, ato
         const l = ctx.pushCurrentElement();
         const builder = inputStructure.subsetBuilder(true);
 
+        l.structure = inputStructure;
         for (const unit of units) {
             l.unit = unit;
             if (!unitTest(ctx)) continue;
@@ -158,6 +160,7 @@ function atomGroupsGrouped({ unitTest, entityTest, chainTest, residueTest, atomT
         const l = ctx.pushCurrentElement();
         const builder = new LinearGroupingBuilder(inputStructure);
 
+        l.structure = inputStructure;
         for (const unit of units) {
             l.unit = unit;
             if (!unitTest(ctx)) continue;
@@ -295,6 +298,8 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery {
         const atomicBond = ctx.atomicBond;
         atomicBond.setTestFn(bondTest);
 
+        atomicBond.setStructure(structure);
+
         // Process intra unit bonds
         for (const unit of structure.units) {
             if (unit.kind !== Unit.Kind.Atomic) continue;

+ 3 - 3
src/mol-model/structure/query/queries/internal.ts

@@ -22,7 +22,7 @@ export function defaultBondTest(ctx: QueryContext) {
 export function atomicSequence(): StructureQuery {
     return function query_atomicSequence(ctx) {
         const { inputStructure } = ctx;
-        const l = StructureElement.Location.create();
+        const l = StructureElement.Location.create(inputStructure);
 
         const units: Unit[] = [];
         for (const unit of inputStructure.units) {
@@ -50,7 +50,7 @@ export function atomicSequence(): StructureQuery {
 export function water(): StructureQuery {
     return function query_water(ctx) {
         const { inputStructure } = ctx;
-        const l = StructureElement.Location.create();
+        const l = StructureElement.Location.create(inputStructure);
 
         const units: Unit[] = [];
         for (const unit of inputStructure.units) {
@@ -69,7 +69,7 @@ export function water(): StructureQuery {
 export function atomicHet(): StructureQuery {
     return function query_atomicHet(ctx) {
         const { inputStructure } = ctx;
-        const l = StructureElement.Location.create();
+        const l = StructureElement.Location.create(inputStructure);
 
         const units: Unit[] = [];
         for (const unit of inputStructure.units) {

+ 7 - 0
src/mol-model/structure/query/queries/modifiers.ts

@@ -94,6 +94,7 @@ function getIncludeSurroundingsWithRadius(ctx: QueryContext, source: Structure,
     const { elementRadius, elementRadiusClosure, sourceMaxRadius, radius } = params;
 
     ctx.pushCurrentElement();
+    ctx.element.structure = structure;
     for (const unit of structure.units) {
         ctx.element.unit = unit;
         const { x, y, z } = unit.conformation;
@@ -115,6 +116,7 @@ function getIncludeSurroundingsWithRadius(ctx: QueryContext, source: Structure,
 
 function createElementRadiusFn(ctx: QueryContext, eRadius: QueryFn<number>): StructureElement.Property<number> {
     return e => {
+        ctx.element.structure = e.structure;
         ctx.element.unit = e.unit;
         ctx.element.element = e.element;
         return eRadius(ctx);
@@ -123,6 +125,7 @@ function createElementRadiusFn(ctx: QueryContext, eRadius: QueryFn<number>): Str
 
 function findStructureRadius(ctx: QueryContext, eRadius: QueryFn<number>) {
     let r = 0;
+    ctx.element.structure = ctx.inputStructure;
     for (const unit of ctx.inputStructure.units) {
         ctx.element.unit = unit;
         const elements = unit.elements;
@@ -252,6 +255,7 @@ export function expandProperty(query: StructureQuery, property: QueryFn): Struct
         const builders: StructureSubsetBuilder[] = [];
         ctx.pushCurrentElement();
         StructureSelection.forEach(src, (s, sI) => {
+            ctx.element.structure = s;
             for (const unit of s.units) {
                 ctx.element.unit = unit;
                 const elements = unit.elements;
@@ -272,6 +276,7 @@ export function expandProperty(query: StructureQuery, property: QueryFn): Struct
             if (sI % 10 === 0) ctx.throwIfTimedOut();
         });
 
+        ctx.element.structure = ctx.inputStructure;
         for (const unit of ctx.inputStructure.units) {
             ctx.element.unit = unit;
             const elements = unit.elements;
@@ -355,6 +360,8 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
         const inputUnitA = inputStructure.unitMap.get(unit.id) as Unit.Atomic;
         const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order } } = inputUnitA.bonds;
 
+        atomicBond.setStructure(inputStructure);
+
         // Process intra unit bonds
         atomicBond.a.unit = inputUnitA;
         atomicBond.b.unit = inputUnitA;

+ 1 - 1
src/mol-model/structure/query/utils/builders.ts

@@ -56,7 +56,7 @@ export class LinearGroupingBuilder {
 
     private singletonSelection(): StructureSelection {
         const builder = this.source.subsetBuilder(true);
-        const loc = StructureElement.Location.create();
+        const loc = StructureElement.Location.create(this.source);
         for (let i = 0, _i = this.builders.length; i < _i; i++) {
             this.builders[i].setSingletonLocation(loc);
             builder.addToUnit(loc.unit.id, loc.element);

+ 6 - 2
src/mol-model/structure/query/utils/structure-distance.ts

@@ -61,12 +61,14 @@ namespace MinMaxDist {
 
         const { units } = a;
         let withinRange = false;
+        ctx.element.structure = a;
         for (let i = 0, _i = units.length; i < _i; i++) {
             const unit = units[i];
             const { elements, conformation: { position } } = unit;
+            ctx.element.unit = unit;
             for (let i = 0, _i = elements.length; i < _i; i++) {
                 const e = elements[i];
-                ctx.setElement(unit, e);
+                ctx.element.element = e;
                 const tp = toPoint(ctx, b, position(e, distPivot), elementRadius(ctx), minDist, maxDist, elementRadius);
                 if (tp === Result.BelowMin) return Result.BelowMin;
                 if (tp === Result.WithinMax) withinRange = true;
@@ -102,12 +104,14 @@ namespace MaxRadiusDist {
         if (a.elementCount === 0 || b.elementCount === 0) return 0;
 
         const { units } = a;
+        ctx.element.structure = a;
         for (let i = 0, _i = units.length; i < _i; i++) {
             const unit = units[i];
+            ctx.element.unit = unit;
             const { elements, conformation: { position } } = unit;
             for (let i = 0, _i = elements.length; i < _i; i++) {
                 const e = elements[i];
-                ctx.setElement(unit, e);
+                ctx.element.element = e;
                 if (toPoint(ctx, b, position(e, distPivot), elementRadius(ctx), maxDist, elementRadius)) return true;
             }
         }

+ 15 - 3
src/mol-model/structure/structure/element/location.ts

@@ -8,22 +8,34 @@
 import { ElementIndex } from '../../model';
 import Unit from '../unit';
 import { Vec3 } from '../../../../mol-math/linear-algebra';
+import Structure from '../structure';
 
 export { Location }
 
 interface Location<U = Unit> {
     readonly kind: 'element-location',
+    structure: Structure,
     unit: U,
     /** Index into element (atomic/coarse) properties of unit.model */
     element: ElementIndex
 }
 
 namespace Location {
-    export function create<U extends Unit>(unit?: U, element?: ElementIndex): Location<U> {
-        return { kind: 'element-location', unit: unit!, element: element || (0 as ElementIndex) };
+    export function create<U extends Unit>(structure: Structure | undefined, unit?: U, element?: ElementIndex): Location<U> {
+        return {
+            kind: 'element-location', 
+            structure: structure as any,
+            unit: unit as any,
+            element: element || (0 as ElementIndex)
+        };
     }
 
-    export function set(a: Location, unit?: Unit, element?: ElementIndex): Location {
+    export function clone<U extends Unit>(l: Location<U>): Location<U> {
+        return create(l.structure, l.unit, l.element);
+    }
+
+    export function set(a: Location, structure?: Structure, unit?: Unit, element?: ElementIndex): Location {
+        if (structure) a.structure = structure;
         if (unit) a.unit = unit
         if (element !== undefined) a.element = element
         return a;

+ 3 - 2
src/mol-model/structure/structure/element/loci.ts

@@ -90,11 +90,12 @@ export namespace Loci {
         const unit = loci.elements[0].unit;
         const element = unit.elements[OrderedSet.getAt(loci.elements[0].indices, 0)];
         if (e) {
+            e.structure = loci.structure;
             e.unit = loci.elements[0].unit;
             e.element = element;
             return e;
         }
-        return Location.create(unit, element);
+        return Location.create(loci.structure, unit, element);
     }
 
     export function toStructure(loci: Loci): Structure {
@@ -365,7 +366,7 @@ export namespace Loci {
 
     export function extendToWholeEntities(loci: Loci): Loci {
         const elements: Loci['elements'][0][] = []
-        const l = Location.create()
+        const l = Location.create(loci.structure)
         const entities = new Set<string>()
         const { units } = loci.structure
 

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません