diff --git a/README.md b/README.md
index 91d48e5e50e9899cfa1270ff2c5522ed1e40ed4f..966b03ca726183d87db8f3ce6599949094a40ccf 100644
--- a/README.md
+++ b/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**
 
diff --git a/data/cif-core.csv b/data/cif-core.csv
deleted file mode 100644
index 25ce0320c9e0e7c60a833309094d6a27814b7a4c..0000000000000000000000000000000000000000
--- a/data/cif-core.csv
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/data/rcsb-graphql/codegen.yml b/data/rcsb-graphql/codegen.yml
index d470dd1734e115746ca3d64fd343534388165706..726a47193be3b15ac8085a40842d210863f722c0 100644
--- a/data/rcsb-graphql/codegen.yml
+++ b/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
diff --git a/package-lock.json b/package-lock.json
index 68aca65ce3021d1fc472ecb3836f900f775eb067..5cd5ce9e9f5b8b0c6b8e3a51e9cbd0b25dd54a4d 100644
Binary files a/package-lock.json and b/package-lock.json differ
diff --git a/package.json b/package.json
index 094894bad700aceefd16acaa1e26d35d6a78add6..a376d66ec660ecd0a6d414d448da20af92c989c0 100644
--- a/package.json
+++ b/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",
diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts
index 3f8f15056b1e12358d4562042e570cdb8c45aa3b..79c65460b2a7fb765db156c3f5e0804b2feeb805 100644
--- a/src/apps/basic-wrapper/index.ts
+++ b/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' });
     }
 
diff --git a/src/apps/chem-comp-bond/create-table.ts b/src/apps/chem-comp-bond/create-table.ts
index 84091a6d6fc1fe54c9d80585a8a78b80c9a46681..1e26c4935c4e44adf886ccfaee13c736e08ff9f4 100644
--- a/src/apps/chem-comp-bond/create-table.ts
+++ b/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) {
diff --git a/src/apps/state-docs/pd-to-md.ts b/src/apps/state-docs/pd-to-md.ts
index 578753df00e954937af3058c043e397aff650d43..f0df30519f9e4e18dfd3cd60509d6b45baf244f0 100644
--- a/src/apps/state-docs/pd-to-md.ts
+++ b/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]`;
diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts
index 980d525551d478941f88f7101e3158e94f55df51..507ef7a19c151e9e7dfbb02c55540c4fbcfd25b5 100644
--- a/src/apps/structure-info/model.ts
+++ b/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(', ')}`);
diff --git a/src/apps/viewer/extensions/cellpack/model.ts b/src/apps/viewer/extensions/cellpack/model.ts
index 352bd8a7ea68b7739d38bedbfb660f3438ae5a9d..bae20822d53b91a0b15532b84b2d34809b01da64 100644
--- a/src/apps/viewer/extensions/cellpack/model.ts
+++ b/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]
     })
 
diff --git a/src/examples/proteopedia-wrapper/coloring.ts b/src/examples/proteopedia-wrapper/coloring.ts
index b694129e1dcc3e088dfd65416fa023472f361056..0f7bc4b3a5f423f5ccabcdb4f5cb38ca521508fb 100644
--- a/src/examples/proteopedia-wrapper/coloring.ts
+++ b/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) {
diff --git a/src/examples/proteopedia-wrapper/helpers.ts b/src/examples/proteopedia-wrapper/helpers.ts
index d376596d3157a6b9bda160bf5bb9346443dc69ae..8611b4ba02d43c5e12770e9935159e4a7520e08f 100644
--- a/src/examples/proteopedia-wrapper/helpers.ts
+++ b/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
         };
     }
diff --git a/src/examples/proteopedia-wrapper/index.ts b/src/examples/proteopedia-wrapper/index.ts
index c83e4d66bb6546817600de648eb6428de0efd004..7254ea8b8727546c2c030bd517a0879cec262d2d 100644
--- a/src/examples/proteopedia-wrapper/index.ts
+++ b/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 });
diff --git a/src/mol-canvas3d/camera.ts b/src/mol-canvas3d/camera.ts
index dc64ccf73d9b178d0cca0bd5a9af3fdcc042a537..37ac7d866989e92b87663c2b3466a63313ab4b76 100644
--- a/src/mol-canvas3d/camera.ts
+++ b/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') {
diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts
index cf08c02c979b3ad50860a19b98679de956b62b31..a00217f4b7b7aed0e509e252a7fd5ffcd6483c66 100644
--- a/src/mol-canvas3d/canvas3d.ts
+++ b/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,
diff --git a/src/mol-canvas3d/helper/bounding-sphere-helper.ts b/src/mol-canvas3d/helper/bounding-sphere-helper.ts
index 4d0d18e2c026da2e68c7490c944144ba005974ea..69f69194144483bc2560bfe4d9bafbb56e3eddd6 100644
--- a/src/mol-canvas3d/helper/bounding-sphere-helper.ts
+++ b/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() {
diff --git a/src/mol-data/db/column.ts b/src/mol-data/db/column.ts
index 1f25b531ea611511f567c059028320ce2ce7b227..8f242202ca93a747e465b0587c6fa6d8d7f1fee8 100644
--- a/src/mol-data/db/column.ts
+++ b/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({
diff --git a/src/mol-data/db/table.ts b/src/mol-data/db/table.ts
index 693fe3cd077b9c74434a450e259891ca75cd019c..b202468232b649e0591a39a496fc178bdf5d2f5a 100644
--- a/src/mol-data/db/table.ts
+++ b/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) {
diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts
index 4f85148b16f86c502ecfe73c3d91b4592aa82b9c..c9fdde6ddda907376edb2dfab6f54cf894516b1d 100644
--- a/src/mol-gl/_spec/renderer.spec.ts
+++ b/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);
diff --git a/src/mol-gl/commit-queue.ts b/src/mol-gl/commit-queue.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2cfd37d6206dcdde6c600c8d0fe7e074be551d5b
--- /dev/null
+++ b/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;
+    }
+}
diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts
index e47ae7f194970625aa738054fa9569dd7069caa6..ecfbbad30634189da8dcba6c39c2564c37937d4f 100644
--- a/src/mol-gl/scene.ts
+++ b/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)
             },
diff --git a/src/mol-io/reader/_spec/cif.spec.ts b/src/mol-io/reader/_spec/cif.spec.ts
index f8823d1309adb2141bbedbe99089c3cdd054aa90..8b25cf7b17b3aef5dd5708b24ad913316c01e8d1 100644
--- a/src/mol-io/reader/_spec/cif.spec.ts
+++ b/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', () => {
diff --git a/src/mol-io/reader/cif.ts b/src/mol-io/reader/cif.ts
index 436e3137c4e258f1ead1438cc41690af5168784d..63883a2fdf4a219d3f125dff0ece117db244264f 100644
--- a/src/mol-io/reader/cif.ts
+++ b/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),
     }
 }
diff --git a/src/mol-io/reader/cif/schema/bird.ts b/src/mol-io/reader/cif/schema/bird.ts
index 6a1912e5f3e55814706997eb11807912dc65abc0..6e87afbd818f490f10cbd99bfa696df40ff45a33 100644
--- a/src/mol-io/reader/cif/schema/bird.ts
+++ b/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.
diff --git a/src/mol-io/reader/cif/schema/ccd.ts b/src/mol-io/reader/cif/schema/ccd.ts
index ba71621291228682e276d0c02d2add2de407f71d..ae08d364df297f9d7e6bacc05f670c8a2f8ea926 100644
--- a/src/mol-io/reader/cif/schema/ccd.ts
+++ b/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
  */
diff --git a/src/mol-io/reader/cif/schema/cif-core.ts b/src/mol-io/reader/cif/schema/cif-core.ts
index ddde84aaa47f344030ebe90a6d3750ab80acd1ef..e3de62df551ace8ad31174e095b522150402058f 100644
--- a/src/mol-io/reader/cif/schema/cif-core.ts
+++ b/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> {}
\ No newline at end of file
+export type CifCore_Schema = typeof CifCore_Schema;
+export interface CifCore_Database extends Database<CifCore_Schema> {}
\ No newline at end of file
diff --git a/src/mol-io/reader/cif/schema/mmcif.ts b/src/mol-io/reader/cif/schema/mmcif.ts
index d52d5763ed69b64273f3cbe77d6ded6601f50a88..484c694f06e0a44e735b072a1db878ba761f31de 100644
--- a/src/mol-io/reader/cif/schema/mmcif.ts
+++ b/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.
      */
diff --git a/src/mol-io/reader/cif/text/parser.ts b/src/mol-io/reader/cif/text/parser.ts
index c593e25ddf4d176b39f2fc289da025058a34687c..693754053e9743d122d4b07f94ef6ffca1067883 100644
--- a/src/mol-io/reader/cif/text/parser.ts
+++ b/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;
diff --git a/src/mol-io/writer/cif/encoder.ts b/src/mol-io/writer/cif/encoder.ts
index 9c0b8a743f3377edd90527b13977b95165fbfe03..74079dcfa2a64b80cdb19ca1461643ea8233bfb8 100644
--- a/src/mol-io/writer/cif/encoder.ts
+++ b/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),
diff --git a/src/mol-model-formats/structure/3dg.ts b/src/mol-model-formats/structure/3dg.ts
index f72aff712cfc7d96f4bd3d7a33bd2d30d7942f17..74d5eba815f6560164c043e521319fb44401f6f8 100644
--- a/src/mol-model-formats/structure/3dg.ts
+++ b/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);
     })
 }
diff --git a/src/mol-model-formats/structure/mmcif/atomic.ts b/src/mol-model-formats/structure/basic/atomic.ts
similarity index 86%
rename from src/mol-model-formats/structure/mmcif/atomic.ts
rename to src/mol-model-formats/structure/basic/atomic.ts
index 81346a2ce981d0643e8d60ed04ed9f729dba9bae..277026770b18b7f084bf6df2c6ef9da23a2234de 100644
--- a/src/mol-model-formats/structure/mmcif/atomic.ts
+++ b/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 };
 }
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/ihm.ts b/src/mol-model-formats/structure/basic/coarse.ts
similarity index 74%
rename from src/mol-model-formats/structure/mmcif/ihm.ts
rename to src/mol-model-formats/structure/basic/coarse.ts
index 7ce1eb9fd8b03eb2f6709a9cafc8297951c64a26..7cb248ec5f9cdd75753331e3072363e4deb572f4 100644
--- a/src/mol-model-formats/structure/mmcif/ihm.ts
+++ b/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) };
 }
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/basic/entities.ts b/src/mol-model-formats/structure/basic/entities.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77d1468297bc84d448c8f22e7ca112fe8cb5be7b
--- /dev/null
+++ b/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 };
+}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/basic/parser.ts b/src/mol-model-formats/structure/basic/parser.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cf41cd1866c7c82aef5f6b339cdf6d5f28a8e402
--- /dev/null
+++ b/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;
+}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/basic/properties.ts b/src/mol-model-formats/structure/basic/properties.ts
new file mode 100644
index 0000000000000000000000000000000000000000..45ecd6e5d17ee6f0d4e13803c91981b6cf0369b0
--- /dev/null
+++ b/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)
+    }
+}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/basic/schema.ts b/src/mol-model-formats/structure/basic/schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dea7e328e8a9db387542f49ee8c057e7fb9f73aa
--- /dev/null
+++ b/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
+}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/sequence.ts b/src/mol-model-formats/structure/basic/sequence.ts
similarity index 77%
rename from src/mol-model-formats/structure/mmcif/sequence.ts
rename to src/mol-model-formats/structure/basic/sequence.ts
index 2b455a2a74c9103ed984be005c0b93a6886c41ec..d73a6f84a50bf48ff02504a0002813012f49d417 100644
--- a/src/mol-model-formats/structure/mmcif/sequence.ts
+++ b/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[] = [];
diff --git a/src/mol-model-formats/structure/mmcif/sort.ts b/src/mol-model-formats/structure/basic/sort.ts
similarity index 86%
rename from src/mol-model-formats/structure/mmcif/sort.ts
rename to src/mol-model-formats/structure/basic/sort.ts
index a6e450b40076403d40a2fac796ceb0ab7b6233c9..51b939afdad6718564068c2c38047ae8f4c6c9f0 100644
--- a/src/mol-model-formats/structure/mmcif/sort.ts
+++ b/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)
     };
 }
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/basic/util.ts b/src/mol-model-formats/structure/basic/util.ts
new file mode 100644
index 0000000000000000000000000000000000000000..daf0d3a8a3ebeaddf5e9c373843e602cc46bb2d7
--- /dev/null
+++ b/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 ''
+}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/common/component.ts b/src/mol-model-formats/structure/common/component.ts
index 30f201b86c2f344560749e26fdd71ecdbb34f46a..483ac6e95369b33506deee7132a663104a8a7b91 100644
--- a/src/mol-model-formats/structure/common/component.ts
+++ b/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][]) {
diff --git a/src/mol-model-formats/structure/common/entity.ts b/src/mol-model-formats/structure/common/entity.ts
index d44fefb2f7f9e7d5117333b80731f81d7181c604..3de2bfefd7c42951082f68b0f654d059d2dba716 100644
--- a/src/mol-model-formats/structure/common/entity.ts
+++ b/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[]) {
diff --git a/src/mol-model-formats/structure/common/property.ts b/src/mol-model-formats/structure/common/property.ts
new file mode 100644
index 0000000000000000000000000000000000000000..44bae5f2f3e0e1642a01a9fc10b3a302dde39f69
--- /dev/null
+++ b/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
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/format.ts b/src/mol-model-formats/structure/format.ts
index 08d0d3b3ffdb70973b46e3a9fd22c4a75cde6161..a3cb77eb5327320f34972b02f43fc71995851534 100644
--- a/src/mol-model-formats/structure/format.ts
+++ b/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 }
\ No newline at end of file
+export interface ModelFormat<T = unknown> { readonly kind: string, name: string, data: T }
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/gro.ts b/src/mol-model-formats/structure/gro.ts
index 8f141ea1c98305df18e3b54a5dd6ecf64a5b027a..c38beca49164cb7d98d322036b10db9d83a2a316 100644
--- a/src/mol-model-formats/structure/gro.ts
+++ b/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);
     })
 }
diff --git a/src/mol-model-formats/structure/mmcif.ts b/src/mol-model-formats/structure/mmcif.ts
index ea8c6de0653c21bb9f0542f25e4fa69123be5462..d52f7898ce97b0431e80ea59a6e726efa58e3660 100644
--- a/src/mol-model-formats/structure/mmcif.ts
+++ b/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));
 }
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/anisotropic.ts b/src/mol-model-formats/structure/mmcif/anisotropic.ts
deleted file mode 100644
index b4ae035eb11be2951a3ad627edebb06c66964cf4..0000000000000000000000000000000000000000
--- a/src/mol-model-formats/structure/mmcif/anisotropic.ts
+++ /dev/null
@@ -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;
-    }
-}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/bonds.ts b/src/mol-model-formats/structure/mmcif/bonds.ts
deleted file mode 100644
index 0f69970236251826f5c529d481726b6d2c82b8fc..0000000000000000000000000000000000000000
--- a/src/mol-model-formats/structure/mmcif/bonds.ts
+++ /dev/null
@@ -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'
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts b/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts
deleted file mode 100644
index 8c8edd3857bd15ebaa93119853bcec9dc611fc69..0000000000000000000000000000000000000000
--- a/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts
+++ /dev/null
@@ -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;
-    }
-}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/pair-restraint.ts b/src/mol-model-formats/structure/mmcif/pair-restraint.ts
deleted file mode 100644
index 40cec49d41786cc3576f29bca18c357e87cb715e..0000000000000000000000000000000000000000
--- a/src/mol-model-formats/structure/mmcif/pair-restraint.ts
+++ /dev/null
@@ -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'
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/parser.ts b/src/mol-model-formats/structure/mmcif/parser.ts
deleted file mode 100644
index 8f19909479f858d24406f421f324863c1b10f291..0000000000000000000000000000000000000000
--- a/src/mol-model-formats/structure/mmcif/parser.ts
+++ /dev/null
@@ -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;
-}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/util.ts b/src/mol-model-formats/structure/mmcif/util.ts
deleted file mode 100644
index 71d7ac58cc5810b6fda966b26b4966e4a01ee93f..0000000000000000000000000000000000000000
--- a/src/mol-model-formats/structure/mmcif/util.ts
+++ /dev/null
@@ -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;
-}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/pdb.ts b/src/mol-model-formats/structure/pdb.ts
index 525ec39cccfa76c405fbb2d44b231b213ab0a946..c77392ec26435bf213bec83521a7f74f95836c17 100644
--- a/src/mol-model-formats/structure/pdb.ts
+++ b/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)
     })
 }
diff --git a/src/mol-model-formats/structure/pdb/to-cif.ts b/src/mol-model-formats/structure/pdb/to-cif.ts
index cf967d4359bbb2a6437f4973a3eb3a20069646b8..05e0f8592689d7a48a0d44b3cac74e06f83284d5 100644
--- a/src/mol-model-formats/structure/pdb/to-cif.ts
+++ b/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;
diff --git a/src/mol-model-formats/structure/property/anisotropic.ts b/src/mol-model-formats/structure/property/anisotropic.ts
new file mode 100644
index 0000000000000000000000000000000000000000..156fcb8926083d77d75dd277a22e48621530d972
--- /dev/null
+++ b/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
+    }
+}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/assembly.ts b/src/mol-model-formats/structure/property/assembly.ts
similarity index 84%
rename from src/mol-model-formats/structure/mmcif/assembly.ts
rename to src/mol-model-formats/structure/property/assembly.ts
index 46720eb26c5bd1560c93ef14b83e696fc359540e..f253cf1edfb65483a01a8ef7ec1733b589bc0644 100644
--- a/src/mol-model-formats/structure/mmcif/assembly.ts
+++ b/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>();
 
diff --git a/src/mol-model-formats/structure/mmcif/bonds/comp.ts b/src/mol-model-formats/structure/property/bonds/comp.ts
similarity index 51%
rename from src/mol-model-formats/structure/mmcif/bonds/comp.ts
rename to src/mol-model-formats/structure/property/bonds/comp.ts
index 4ae1bfc330dff8550ebaf441fc0d78ff351622c3..21eafaa7f9f979a8aee88031e8d66a047a201015 100644
--- a/src/mol-model-formats/structure/mmcif/bonds/comp.ts
+++ b/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) { }
     }
 }
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/bonds/index-pair.ts b/src/mol-model-formats/structure/property/bonds/index-pair.ts
similarity index 54%
rename from src/mol-model-formats/structure/mmcif/bonds/index-pair.ts
rename to src/mol-model-formats/structure/property/bonds/index-pair.ts
index 22af2913d7f403ee6a42fcf01b2408f933eff851..8cc29e892471b5d9847dffb075c82efbf1d52d9d 100644
--- a/src/mol-model-formats/structure/mmcif/bonds/index-pair.ts
+++ b/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);
     }
 }
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/property/bonds/struct_conn.ts b/src/mol-model-formats/structure/property/bonds/struct_conn.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4462ebe20da53b2081ef4ec4961a3e9f98832728
--- /dev/null
+++ b/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;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/pair-restraints/cross-links.ts b/src/mol-model-formats/structure/property/pair-restraints/cross-links.ts
similarity index 57%
rename from src/mol-model-formats/structure/mmcif/pair-restraints/cross-links.ts
rename to src/mol-model-formats/structure/property/pair-restraints/cross-links.ts
index 62305d3e942cefc6f3cbef76c598aba74de7caf4..484645a3a921c46bc2aa3f63aa26766043b3569d 100644
--- a/src/mol-model-formats/structure/mmcif/pair-restraints/cross-links.ts
+++ b/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
     }
 }
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/mmcif/pair-restraints/predicted-contacts.ts b/src/mol-model-formats/structure/property/pair-restraints/predicted-contacts.ts
similarity index 100%
rename from src/mol-model-formats/structure/mmcif/pair-restraints/predicted-contacts.ts
rename to src/mol-model-formats/structure/property/pair-restraints/predicted-contacts.ts
diff --git a/src/mol-model-formats/structure/mmcif/secondary-structure.ts b/src/mol-model-formats/structure/property/secondary-structure.ts
similarity index 77%
rename from src/mol-model-formats/structure/mmcif/secondary-structure.ts
rename to src/mol-model-formats/structure/property/secondary-structure.ts
index 73c454a8dea08a44966f700330153503568a1043..d1be73086b1cff12372e6ad2406aa78118fa593e 100644
--- a/src/mol-model-formats/structure/mmcif/secondary-structure.ts
+++ b/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;
diff --git a/src/mol-model-formats/structure/property/symmetry.ts b/src/mol-model-formats/structure/property/symmetry.ts
new file mode 100644
index 0000000000000000000000000000000000000000..487509249aefa9567915ca4b994aa715cf4e7f6d
--- /dev/null
+++ b/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;
+}
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/psf.ts b/src/mol-model-formats/structure/psf.ts
index ab4eae3e12a7c9c2a7d441f554fbe9d9b80dc59d..88e5a53d64a8e3f6dfdf798b15f8e61f580dccbf 100644
--- a/src/mol-model-formats/structure/psf.ts
+++ b/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)
     })
 }
\ No newline at end of file
diff --git a/src/mol-model-props/common/custom-property.ts b/src/mol-model-props/common/custom-property.ts
index f8bc99821856b6d095ca2f86d0dcc0aabeeeaf46..24dbbd7a9adb19634b9a79184ca97814aa152687 100644
--- a/src/mol-model-props/common/custom-property.ts
+++ b/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) {
diff --git a/src/mol-model-props/common/wrapper.ts b/src/mol-model-props/common/wrapper.ts
index 21c57e124e18ad5ab3193eb9a67774cc8a0abd51..7f3523515dbfc88602fe798e05c7a6c79328b8f0 100644
--- a/src/mol-model-props/common/wrapper.ts
+++ b/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()) };
diff --git a/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/area.ts b/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/area.ts
index 5097af98adc5d27078e239c9aa1cb1e580bd6aaf..fd93549b530d56b99679fc68644cab989513a5c6 100644
--- a/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/area.ts
+++ b/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]];
diff --git a/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/radii.ts b/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/radii.ts
index 32520eda0599b1296a6edab429419d020a05a9ab..cc9ac3f5a0ad31f4d8f69b7769c9622636bbf0c7 100644
--- a/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/radii.ts
+++ b/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
diff --git a/src/mol-model-props/computed/interactions/interactions.ts b/src/mol-model-props/computed/interactions/interactions.ts
index c7e1e409bdf03c41522bd470ca22bf5c163bf416..881163733e3549460322b933d2297b734621be20 100644
--- a/src/mol-model-props/computed/interactions/interactions.ts
+++ b/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>')
     }
 }
diff --git a/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts b/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts
index 8717cb985abc479070db186c5859edb34faca488..a13a95720688fd97ff2f042fa9ad144fb688971e 100644
--- a/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts
+++ b/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]
diff --git a/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts b/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts
index 6626f248950e8526023b5aaff2ef1bc10ae73d55..66a03f6ac7ee24d281c2251051e40bbf4e9a22ba 100644
--- a/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts
+++ b/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]
diff --git a/src/mol-model-props/computed/secondary-structure.ts b/src/mol-model-props/computed/secondary-structure.ts
index 7b875d34705a5c92c46020ec173b3ffdc526f5df..d80c58b383b9a8725daed90eb9632203986e2f17 100644
--- a/src/mol-model-props/computed/secondary-structure.ts
+++ b/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
diff --git a/src/mol-model-props/pdbe/preferred-assembly.ts b/src/mol-model-props/pdbe/preferred-assembly.ts
index 9be69ce401bd1bbc4fd2847f97f3af0e7cd321c4..ebc7fb081c24c9e50396450f84618f0a06a09b99 100644
--- a/src/mol-model-props/pdbe/preferred-assembly.ts
+++ b/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);
     }
diff --git a/src/mol-model-props/pdbe/struct-ref-domain.ts b/src/mol-model-props/pdbe/struct-ref-domain.ts
index 54a31131e8e6ecd75134ea49a17227dbda57b06b..db37fee7e8412b3d06e7f5e587aea9822483ebe9 100644
--- a/src/mol-model-props/pdbe/struct-ref-domain.ts
+++ b/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);
     }
diff --git a/src/mol-model-props/pdbe/structure-quality-report.ts b/src/mol-model-props/pdbe/structure-quality-report.ts
index b956250aadcfc613462e4e2b0ce67de22cefc3da..e153de014ba3749c37f0700edb55b9f0a01698b3 100644
--- a/src/mol-model-props/pdbe/structure-quality-report.ts
+++ b/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[]> {
diff --git a/src/mol-model-props/pdbe/themes/structure-quality-report.ts b/src/mol-model-props/pdbe/themes/structure-quality-report.ts
index f84a18744b05dc1ca7056b5a02d67999ef2ff035..e3296bab946d7dc478f50b5b20c3cfe05a533ef4 100644
--- a/src/mol-model-props/pdbe/themes/structure-quality-report.ts
+++ b/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()
+    }
 }
\ No newline at end of file
diff --git a/src/mol-model-props/rcsb/assembly-symmetry.ts b/src/mol-model-props/rcsb/assembly-symmetry.ts
index e4c8c07e2f00ac16de5477a40106ff5a921479ab..6dce3e871e3ebd414256ecc7fb2af5825fe524f6 100644
--- a/src/mol-model-props/rcsb/assembly-symmetry.ts
+++ b/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)
diff --git a/src/mol-model-props/rcsb/graphql/types.d.ts b/src/mol-model-props/rcsb/graphql/types.ts
similarity index 99%
rename from src/mol-model-props/rcsb/graphql/types.d.ts
rename to src/mol-model-props/rcsb/graphql/types.ts
index 0af39affaf13fc554cf4dfdcfe12c551a42902ad..ac02ff026d544af2de62eed61b9aa8a58a3f52e3 100644
--- a/src/mol-model-props/rcsb/graphql/types.d.ts
+++ b/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']>,
diff --git a/src/mol-model-props/rcsb/representations/validation-report-clashes.ts b/src/mol-model-props/rcsb/representations/validation-report-clashes.ts
index 136aea063f014e6d3debddb152a2e1baf0a5b6d0..be69dbcda1645c51fda6b31ec0768f2cc15460c4 100644
--- a/src/mol-model-props/rcsb/representations/validation-report-clashes.ts
+++ b/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
diff --git a/src/mol-model-props/rcsb/themes/geometry-quality.ts b/src/mol-model-props/rcsb/themes/geometry-quality.ts
index 201d8fabf73f9b997620431889d104c532cd39da..1f39b8de4b19e47b1765c0c85ae93cbe7ab9c64f 100644
--- a/src/mol-model-props/rcsb/themes/geometry-quality.ts
+++ b/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()
diff --git a/src/mol-model-props/rcsb/validation-report.ts b/src/mol-model-props/rcsb/validation-report.ts
index c2a2b054f8dab317d739a391fef2823b56658b0f..ada844656b5fd832507d19efae31e330cbe3d98c 100644
--- a/src/mol-model-props/rcsb/validation-report.ts
+++ b/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)
         )
     }
diff --git a/src/mol-model/structure/export/categories/modified-residues.ts b/src/mol-model/structure/export/categories/modified-residues.ts
index e19e33636c6c1b7531fbd22876517e517e18686a..51726e8c6b2a1df02b78335e1b9087f7c01b3a33 100644
--- a/src/mol-model/structure/export/categories/modified-residues.ts
+++ b/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);
             }
         }
     }
diff --git a/src/mol-model/structure/export/categories/secondary-structure.ts b/src/mol-model/structure/export/categories/secondary-structure.ts
index a5bc27ce4b6d9cf74350fd1a705eeef8bcc4c0ff..21d9c13488ac510ddcf6bb5cac5c0c39e378db9b 100644
--- a/src/mol-model/structure/export/categories/secondary-structure.ts
+++ b/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
                     }
diff --git a/src/mol-model/structure/export/categories/utils.ts b/src/mol-model/structure/export/categories/utils.ts
index 091e92172dcbfeefa10bb7e069c717103474a05a..4f500efbf56d9dfe93322614ca74b4b1d1958e47 100644
--- a/src/mol-model/structure/export/categories/utils.ts
+++ b/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);
         }
diff --git a/src/mol-model/structure/model.ts b/src/mol-model/structure/model.ts
index 8a1dccfdebe0bda698574a261203ab9741156036..a1cb4a8bd81a7af47e32eafbc8236632cc61c6d9 100644
--- a/src/mol-model/structure/model.ts
+++ b/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 }
\ No newline at end of file
+export { Model, Types, Symmetry, StructureSequence }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts
index 74cd85af6bf0d1b6652f4e390b654cbef7c67fc2..04dd2a0dd482c28713a5bb4c68686bf244752153 100644
--- a/src/mol-model/structure/model/model.ts
+++ b/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
         })
diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
index 5b17b8b4f2e137161191553075bf4b8c41d4ff6a..0f1bee9cd65d589251a2a74fa83f5f4266841d1a 100644
--- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts
+++ b/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
diff --git a/src/mol-model/structure/model/properties/computed.ts b/src/mol-model/structure/model/properties/computed.ts
deleted file mode 100644
index 39896495fbaa30bcf65f3575e6664b7517a9af68..0000000000000000000000000000000000000000
--- a/src/mol-model/structure/model/properties/computed.ts
+++ /dev/null
@@ -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
-// }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/custom/indexed.ts b/src/mol-model/structure/model/properties/custom/indexed.ts
index 4e8aa7ef39895112531941eee44bbab9b52cd24f..de239f33f5ed8a6708ce12a8d670e87ad45ce97e 100644
--- a/src/mol-model/structure/model/properties/custom/indexed.ts
+++ b/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]);
             }
         }
 
diff --git a/src/mol-model/structure/model/properties/symmetry.ts b/src/mol-model/structure/model/properties/symmetry.ts
index 38b9e5e12642ac80ad7c8e5ae1daa661bd6334a9..a25aa43a4d55541e6e9f40fffd990b4b71a938ea 100644
--- a/src/mol-model/structure/model/properties/symmetry.ts
+++ b/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 }
\ No newline at end of file
+export { Symmetry }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/utils/atomic-index.ts b/src/mol-model/structure/model/properties/utils/atomic-index.ts
index f35949c1cd71eb26c540f809e2e22ffc6219caec..b16cbb5289ee2b1dfead9b7a32b2fdb2453a0f6b 100644
--- a/src/mol-model/structure/model/properties/utils/atomic-index.ts
+++ b/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>();
diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts
index d9f083413db7634beb079693f418b50d18ebeaa9..95a1e8e41d6b9688c79b8316186748eb08501001 100644
--- a/src/mol-model/structure/model/types.ts
+++ b/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' ])
diff --git a/src/mol-model/structure/query/context.ts b/src/mol-model/structure/query/context.ts
index d73e9f1a4026f1a7062c87ab65a36f6c97ce8cc1..43b2538b98ba727032ab231948c203283e123f61 100644
--- a/src/mol-model/structure/query/context.ts
+++ b/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;
diff --git a/src/mol-model/structure/query/queries/filters.ts b/src/mol-model/structure/query/queries/filters.ts
index aff78800bc31595d98513243e04d6771a132d0c6..3b689246c33f2db5e22062ab22e426d75c4a7737 100644
--- a/src/mol-model/structure/query/queries/filters.ts
+++ b/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;
 
diff --git a/src/mol-model/structure/query/queries/generators.ts b/src/mol-model/structure/query/queries/generators.ts
index 646f67dfc7f60cba98cf1facc4314fa58a014c48..afac8b988674b8fe329eab0bf1fca5f2a5ea83e9 100644
--- a/src/mol-model/structure/query/queries/generators.ts
+++ b/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;
diff --git a/src/mol-model/structure/query/queries/internal.ts b/src/mol-model/structure/query/queries/internal.ts
index c0258e58d901e769fe51dd787c9b9625b5da0fd0..68ce9e555818ad41443e00864fef4dd21670f8ab 100644
--- a/src/mol-model/structure/query/queries/internal.ts
+++ b/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) {
diff --git a/src/mol-model/structure/query/queries/modifiers.ts b/src/mol-model/structure/query/queries/modifiers.ts
index 8f38b20430e72dc66118da04056af4639d2f69aa..6c7fd7319cbf17b7bbc3d34cb9cc0e7e532b4a24 100644
--- a/src/mol-model/structure/query/queries/modifiers.ts
+++ b/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;
diff --git a/src/mol-model/structure/query/utils/builders.ts b/src/mol-model/structure/query/utils/builders.ts
index c7d0c6ceeff8abcecbc2aa94e138a1df320e19c7..add4742dd95b13e7fbaeca60c12be03c8264a60c 100644
--- a/src/mol-model/structure/query/utils/builders.ts
+++ b/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);
diff --git a/src/mol-model/structure/query/utils/structure-distance.ts b/src/mol-model/structure/query/utils/structure-distance.ts
index f05ca7cb1fe2e87e464fa281ed68f97c5020ee0a..bdea7bfe1089c874d003b9786ae161b5673fafab 100644
--- a/src/mol-model/structure/query/utils/structure-distance.ts
+++ b/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;
             }
         }
diff --git a/src/mol-model/structure/structure/element/location.ts b/src/mol-model/structure/structure/element/location.ts
index c0d745b23633d308a425b7c8312ca9bfa541f6da..ec5f970e4a17f64c08a5d6775cb68505260b3c30 100644
--- a/src/mol-model/structure/structure/element/location.ts
+++ b/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;
diff --git a/src/mol-model/structure/structure/element/loci.ts b/src/mol-model/structure/structure/element/loci.ts
index 4523f631ae8f423215188430a64f006a2d1968dd..76746ddb8b49af4dcda61173166e9c1c6d6df908 100644
--- a/src/mol-model/structure/structure/element/loci.ts
+++ b/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
 
diff --git a/src/mol-model/structure/structure/element/stats.ts b/src/mol-model/structure/structure/element/stats.ts
index fea5bc4198831c24457a31530aa641b1d9b44f7c..ac9f6e2854b4946c60c278af1a5a93e45338108d 100644
--- a/src/mol-model/structure/structure/element/stats.ts
+++ b/src/mol-model/structure/structure/element/stats.ts
@@ -10,6 +10,7 @@ import Unit from '../unit';
 import { Loci } from './loci';
 import { Location } from './location';
 import { ChainIndex } from '../../model/indexing';
+import Structure from '../structure';
 
 export interface Stats {
     elementCount: number
@@ -37,12 +38,12 @@ export namespace Stats {
             unitCount: 0,
             structureCount: 0,
 
-            firstElementLoc: Location.create(),
-            firstConformationLoc: Location.create(),
-            firstResidueLoc: Location.create(),
-            firstChainLoc: Location.create(),
-            firstUnitLoc: Location.create(),
-            firstStructureLoc: Location.create(),
+            firstElementLoc: Location.create(void 0),
+            firstConformationLoc: Location.create(void 0),
+            firstResidueLoc: Location.create(void 0),
+            firstChainLoc: Location.create(void 0),
+            firstUnitLoc: Location.create(void 0),
+            firstStructureLoc: Location.create(void 0),
         }
     }
 
@@ -51,7 +52,7 @@ export namespace Stats {
         map.set(key, count + inc)
     }
 
-    function handleElement(stats: Stats, element: Loci['elements'][0]) {
+    function handleElement(stats: Stats, structure: Structure, element: Loci['elements'][0]) {
         const { indices, unit } = element
         const { elements } = unit
         const size = OrderedSet.size(indices)
@@ -60,19 +61,19 @@ export namespace Stats {
         const residueAltIdCounts = new Map<string, number>()
 
         if (size > 0) {
-            Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)])
+            Location.set(stats.firstElementLoc, structure, unit, elements[OrderedSet.start(indices)])
         }
 
         // count single element unit as unit not element
         if (size === elements.length) {
             stats.unitCount += 1
             if (stats.unitCount === 1) {
-                Location.set(stats.firstUnitLoc, unit, elements[OrderedSet.start(indices)])
+                Location.set(stats.firstUnitLoc, structure, unit, elements[OrderedSet.start(indices)])
             }
         } else if (size === 1) {
             stats.elementCount += 1
             if (stats.elementCount === 1) {
-                Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)])
+                Location.set(stats.firstElementLoc, structure, unit, elements[OrderedSet.start(indices)])
             }
         } else {
             if (Unit.isAtomic(unit)) {
@@ -99,7 +100,7 @@ export namespace Stats {
                         // full residue
                         stats.residueCount += 1
                         if (stats.residueCount === 1) {
-                            Location.set(stats.firstResidueLoc, unit, offsets[rI])
+                            Location.set(stats.firstResidueLoc, structure, unit, offsets[rI])
                         }
                     } else {
                         // partial residue
@@ -116,7 +117,7 @@ export namespace Stats {
                                     if (stats.conformationCount === 1) {
                                         for (let l = offsets[rI], _l = offsets[rI + 1]; l < _l; ++l) {
                                             if (k === label_alt_id.value(l)) {
-                                                Location.set(stats.firstConformationLoc, unit, l)
+                                                Location.set(stats.firstConformationLoc, structure, unit, l)
                                                 break
                                             }
                                         }
@@ -131,13 +132,13 @@ export namespace Stats {
             } else {
                 stats.elementCount += size
                 if (stats.elementCount === 1) {
-                    Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)])
+                    Location.set(stats.firstElementLoc, structure, unit, elements[OrderedSet.start(indices)])
                 }
             }
         }
     }
 
-    function handleUnitChainsSimple(stats: Stats, element: Loci['elements'][0]) {
+    function handleUnitChainsSimple(stats: Stats, structure: Structure, element: Loci['elements'][0]) {
         const { indices, unit } = element;
         const size = OrderedSet.size(indices);
         if (size === 0) return;
@@ -148,7 +149,7 @@ export namespace Stats {
             if (size === elements.length) {
                 stats.chainCount += 1;
                 if (stats.chainCount === 1) {
-                    Location.set(stats.firstChainLoc, unit, elements[OrderedSet.start(indices)]);
+                    Location.set(stats.firstChainLoc, structure, unit, elements[OrderedSet.start(indices)]);
                 }
             }
             return;
@@ -186,13 +187,13 @@ export namespace Stats {
                 // full chain
                 stats.chainCount += 1;
                 if (stats.chainCount === 1) {
-                    Location.set(stats.firstChainLoc, unit, offsets[cI]);
+                    Location.set(stats.firstChainLoc, structure, unit, offsets[cI]);
                 }
             }
         }
     }
 
-    function handleUnitChainsPartitioned(stats: Stats, lociElements: Loci['elements'], start: number, end: number) {
+    function handleUnitChainsPartitioned(stats: Stats, structure: Structure, lociElements: Loci['elements'], start: number, end: number) {
         let element = lociElements[start];
 
         // all the elements have the same model since they are part of the same group so this is ok.
@@ -273,7 +274,7 @@ export namespace Stats {
                 const eI = elements[OrderedSet.getAt(indices, i)];
                 const cI = index[eI];
                 if (cI === firstCI) {
-                    Location.set(stats.firstChainLoc, unit, eI);
+                    Location.set(stats.firstChainLoc, structure, unit, eI);
                     return;
                 }
             }
@@ -289,13 +290,13 @@ export namespace Stats {
             stats.structureCount += 1
             if (stats.structureCount === 1) {
                 const { unit, indices } = loci.elements[0]
-                Location.set(stats.firstStructureLoc, unit, unit.elements[OrderedSet.min(indices)])
+                Location.set(stats.firstStructureLoc, loci.structure, unit, unit.elements[OrderedSet.min(indices)])
             }
         } else {
             for (const e of loci.elements) {
-                handleElement(stats, e)
+                handleElement(stats, loci.structure, e)
                 if (!Unit.Traits.is(e.unit.traits, Unit.Trait.Partitioned)) {
-                    handleUnitChainsSimple(stats, e);
+                    handleUnitChainsSimple(stats, loci.structure, e);
                 } else {
                     hasPartitions = true;
                 }
@@ -314,9 +315,9 @@ export namespace Stats {
                 const end = i;
                 i--;
                 if (end - start === 1) {
-                    handleUnitChainsSimple(stats, e);
+                    handleUnitChainsSimple(stats, loci.structure, e);
                 } else {
-                    handleUnitChainsPartitioned(stats, loci.elements, start, end);
+                    handleUnitChainsPartitioned(stats, loci.structure, loci.elements, start, end);
                 }
             }
         }
diff --git a/src/mol-model/structure/structure/properties.ts b/src/mol-model/structure/structure/properties.ts
index 1b11bc22c9f8c627dd19c34f62f520a70eaade1d..48ab835a516d3c50613ad4e5004389db0f6d53a8 100644
--- a/src/mol-model/structure/structure/properties.ts
+++ b/src/mol-model/structure/structure/properties.ts
@@ -7,6 +7,8 @@
 import StructureElement from './element'
 import Unit from './unit'
 import { VdwRadius } from '../model/properties/atomic';
+import { ModelSecondaryStructure } from '../../../mol-model-formats/structure/property/secondary-structure';
+import { SecondaryStructureType } from '../model/types';
 
 function p<T>(p: StructureElement.Property<T>) { return p; }
 
@@ -60,7 +62,7 @@ function _compId(l: StructureElement.Location) {
 function compId(l: StructureElement.Location) {
     if (!Unit.isAtomic(l.unit)) notAtomic()
     if (!hasMicroheterogeneity(l)) return _compId(l)
-    return l.unit.model.sourceData.data.atom_site.label_comp_id.value(l.element)
+    return l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])
 }
 
 function seqId(l: StructureElement.Location) {
@@ -103,8 +105,20 @@ const residue = {
     isNonStandard: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.mon_nstd_flag[0] !== 'y'),
     hasMicroheterogeneity: p(hasMicroheterogeneity),
     microheterogeneityCompIds: p(microheterogeneityCompIds),
-    secondary_structure_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.type[l.unit.residueIndex[l.element]]),
-    secondary_structure_key: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.key[l.unit.residueIndex[l.element]]),
+    // TODO implement as symbol in SecondaryStructureProvider (not ModelSecondaryStructure.Provider)
+    secondary_structure_type: p(l => {
+        if (!Unit.isAtomic(l.unit)) notAtomic()
+        const secondaryStructure = ModelSecondaryStructure.Provider.get(l.unit.model)
+        if (secondaryStructure) return secondaryStructure.type[l.unit.residueIndex[l.element]]
+        else return SecondaryStructureType.Flag.NA
+    }),
+    // TODO implement as symbol in SecondaryStructureProvider (not ModelSecondaryStructure.Provider)
+    secondary_structure_key: p(l => {
+        if (!Unit.isAtomic(l.unit)) notAtomic()
+        const secondaryStructure = ModelSecondaryStructure.Provider.get(l.unit.model)
+        if (secondaryStructure) return secondaryStructure.key[l.unit.residueIndex[l.element]]
+        else return -1
+    }),
     chem_comp_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.type),
 }
 
diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts
index aae04aa3c2f8fb64156168329e1069e68a3a1af8..c03a2a97b212e350f5bcf609fc421b98bb2036d1 100644
--- a/src/mol-model/structure/structure/structure.ts
+++ b/src/mol-model/structure/structure/structure.ts
@@ -401,7 +401,7 @@ function getModels(s: Structure) {
 function getUniqueResidueNames(s: Structure) {
     const prop = StructureProperties.residue.label_comp_id;
     const names = new Set<string>();
-    const loc = StructureElement.Location.create();
+    const loc = StructureElement.Location.create(s);
     for (const unit of s.units) {
         // TODO: support coarse unit?
         if (!Unit.isAtomic(unit)) continue;
@@ -418,7 +418,7 @@ function getUniqueResidueNames(s: Structure) {
 
 function getEntityIndices(structure: Structure): ReadonlyArray<EntityIndex> {
     const { units } = structure;
-    const l = StructureElement.Location.create();
+    const l = StructureElement.Location.create(structure);
     const keys = UniqueArray.create<number, EntityIndex>();
 
     for (const unit of units) {
@@ -871,7 +871,7 @@ namespace Structure {
     }
 
     export class ElementLocationIterator implements Iterator<StructureElement.Location> {
-        private current = StructureElement.Location.create();
+        private current: StructureElement.Location;
         private unitIndex = 0;
         private elements: StructureElement.Set;
         private maxIdx = 0;
@@ -905,6 +905,7 @@ namespace Structure {
         }
 
         constructor(private structure: Structure) {
+            this.current = StructureElement.Location.create(structure);
             this.hasNext = structure.elementCount > 0;
             if (this.hasNext) {
                 this.elements = structure.units[0].elements;
diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts
index 6568f9cb4f41af97513ea1d51376dd958f85f628..e6c5bae4673f2379bfaeb1b2296f905b74613b70 100644
--- a/src/mol-model/structure/structure/symmetry.ts
+++ b/src/mol-model/structure/structure/symmetry.ts
@@ -10,10 +10,11 @@ import { EquivalenceClasses } from '../../../mol-data/util';
 import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/geometry';
 import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
 import { RuntimeContext, Task } from '../../../mol-task';
-import { ModelSymmetry, Model } from '../model';
+import { Symmetry, Model } from '../model';
 import { QueryContext, StructureSelection } from '../query';
 import Structure from './structure';
 import Unit from './unit';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 namespace StructureSymmetry {
     export function buildAssembly(structure: Structure, asmName: string) {
@@ -21,7 +22,7 @@ namespace StructureSymmetry {
             const models = structure.models;
             if (models.length !== 1) throw new Error('Can only build assemblies from structures based on 1 model.');
 
-            const assembly = ModelSymmetry.findAssembly(models[0], asmName);
+            const assembly = Symmetry.findAssembly(models[0], asmName);
             if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
 
             const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] })
@@ -95,7 +96,7 @@ namespace StructureSymmetry {
     }
 }
 
-function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) {
+function getOperators(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) {
     const { spacegroup, ncsOperators } = symmetry;
     const ncsCount = (ncsOperators && ncsOperators.length) || 0
     const operators: SymmetryOperator[] = [];
@@ -135,7 +136,7 @@ function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3, model
     return operators;
 }
 
-function getOperatorsCached333(symmetry: ModelSymmetry, ref: Vec3) {
+function getOperatorsCached333(symmetry: Symmetry, ref: Vec3) {
     if (symmetry._operators_333 && Vec3.equals(ref, symmetry._operators_333.ref)) {
         return symmetry._operators_333.operators;
     }
@@ -161,7 +162,10 @@ async function _buildNCS(ctx: RuntimeContext, structure: Structure) {
     const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build NCS from structures based on 1 model.');
 
-    const operators = models[0].symmetry.ncsOperators;
+    const symmetry = ModelSymmetry.Provider.get(models[0])
+    if (!symmetry) return structure
+
+    const operators = symmetry.ncsOperators;
     if (!operators || !operators.length) return structure;
     return assembleOperators(structure, operators);
 }
@@ -170,11 +174,14 @@ async function findSymmetryRange(ctx: RuntimeContext, structure: Structure, ijkM
     const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
 
-    const { spacegroup } = models[0].symmetry;
+    const symmetry = ModelSymmetry.Provider.get(models[0])
+    if (!symmetry) return structure
+
+    const { spacegroup } = symmetry;
     if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
 
     const modelCenter = Model.getCenter(models[0])
-    const operators = getOperators(models[0].symmetry, ijkMin, ijkMax, modelCenter);
+    const operators = getOperators(symmetry, ijkMin, ijkMax, modelCenter);
     return assembleOperators(structure, operators);
 }
 
@@ -182,7 +189,9 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius
     const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
 
-    const symmetry = models[0].symmetry;
+    const symmetry = ModelSymmetry.Provider.get(models[0])
+    if (!symmetry) return structure
+
     const { spacegroup } = symmetry;
     if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
 
diff --git a/src/mol-model/structure/structure/unit/bonds.ts b/src/mol-model/structure/structure/unit/bonds.ts
index 698677153f4ef231f917ec2d3627ccec99c1da3d..2cc0c7b298e04d449f22f65a800c6441a6dcd520 100644
--- a/src/mol-model/structure/structure/unit/bonds.ts
+++ b/src/mol-model/structure/structure/unit/bonds.ts
@@ -19,16 +19,28 @@ export * from './bonds/inter-compute'
 namespace Bond {
     export interface Location<U extends Unit = Unit> {
         readonly kind: 'bond-location',
+        
+        aStructure: Structure,
         aUnit: U,
         /** Index into aUnit.elements */
         aIndex: StructureElement.UnitIndex,
+
+        bStructure: Structure,
         bUnit: U,
         /** Index into bUnit.elements */
         bIndex: StructureElement.UnitIndex,
     }
 
-    export function Location(aUnit?: Unit, aIndex?: StructureElement.UnitIndex, bUnit?: Unit, bIndex?: StructureElement.UnitIndex): Location {
-        return { kind: 'bond-location', aUnit: aUnit as any, aIndex: aIndex as any, bUnit: bUnit as any, bIndex: bIndex as any };
+    export function Location(aStructure?: Structure, aUnit?: Unit, aIndex?: StructureElement.UnitIndex, bStructure?: Structure, bUnit?: Unit, bIndex?: StructureElement.UnitIndex): Location {
+        return {
+            kind: 'bond-location',
+            aStructure: aStructure as any,
+            aUnit: aUnit as any,
+            aIndex: aIndex as any,
+            bStructure: bStructure as any,
+            bUnit: bUnit as any,
+            bIndex: bIndex as any
+        };
     }
 
     export function isLocation(x: any): x is Location {
@@ -37,6 +49,7 @@ namespace Bond {
 
     export function areLocationsEqual(locA: Location, locB: Location) {
         return (
+            locA.aStructure.label === locB.aStructure.label && locA.bStructure.label === locB.bStructure.label &&
             locA.aIndex === locB.aIndex && locA.bIndex === locB.bIndex &&
             locA.aUnit.id === locB.aUnit.id && locA.bUnit.id === locB.bUnit.id
         )
@@ -86,7 +99,7 @@ namespace Bond {
             const indexB = SortedArray.indexOf(unitB.elements, elementB) as StructureElement.UnitIndex | -1
             if (indexB === -1) return
 
-            bonds.push(Location(unitA, indexA, unitB, indexB))
+            bonds.push(Location(loci.structure, unitA, indexA, loci.structure, unitB, indexB))
         });
 
         return Loci(structure, bonds);
diff --git a/src/mol-model/structure/structure/unit/bonds/inter-compute.ts b/src/mol-model/structure/structure/unit/bonds/inter-compute.ts
index 2b3d68e415233d0ef4177355f1ed9f2d07aa4f52..053bf322f1b7efa5a96cf9463ae25980555df551 100644
--- a/src/mol-model/structure/structure/unit/bonds/inter-compute.ts
+++ b/src/mol-model/structure/structure/unit/bonds/inter-compute.ts
@@ -13,11 +13,11 @@ import { InterUnitBonds, InterUnitEdgeProps } from './data';
 import { SortedArray } from '../../../../../mol-data/int';
 import { Vec3, Mat4 } from '../../../../../mol-math/linear-algebra';
 import StructureElement from '../../element';
-import { StructConn } from '../../../../../mol-model-formats/structure/mmcif/bonds';
 import { ElementIndex } from '../../../model/indexing';
 import { getInterBondOrderFromTable } from '../../../model/properties/atomic/bonds';
-import { IndexPairBonds } from '../../../../../mol-model-formats/structure/mmcif/bonds/index-pair';
+import { IndexPairBonds } from '../../../../../mol-model-formats/structure/property/bonds/index-pair';
 import { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph';
+import { StructConn } from '../../../../../mol-model-formats/structure/property/bonds/struct_conn';
 
 const MAX_RADIUS = 4;
 
@@ -46,8 +46,8 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
     const { occupancy: occupancyB } = unitB.model.atomicConformation;
 
     const { lookup3d } = unitB;
-    const structConn = unitA.model === unitB.model && unitA.model.sourceData.kind === 'mmCIF' ? StructConn.get(unitA.model) : void 0;
-    const indexPairs = unitA.model === unitB.model ? IndexPairBonds.get(unitA.model) : void 0;
+    const structConn = unitA.model === unitB.model && StructConn.Provider.get(unitA.model)
+    const indexPairs = unitA.model === unitB.model && IndexPairBonds.Provider.get(unitA.model)
 
     // the lookup queries need to happen in the "unitB space".
     // that means imageA = inverseOperB(operA(aI))
@@ -75,18 +75,20 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
             continue // assume `indexPairs` supplies all bonds
         }
 
-        const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
+        const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.byAtomIndex.get(aI);
         if (structConnEntries && structConnEntries.length) {
             let added = false;
             for (const se of structConnEntries) {
-                for (const p of se.partners) {
-                    const _bI = SortedArray.indexOf(unitB.elements, p.atomIndex) as StructureElement.UnitIndex;
-                    if (_bI < 0 || _aI === _bI) continue;
-                    // check if the bond is within MAX_RADIUS for this pair of units
-                    if (getDistance(unitA, aI, unitB, p.atomIndex) > MAX_RADIUS) continue;
-                    builder.add(_aI, _bI, { order: se.order, flag: se.flags });
-                    added = true;
-                }
+                const { partnerA, partnerB } = se
+                const p = partnerA.atomIndex === aI ? partnerB : partnerA
+                const _bI = SortedArray.indexOf(unitB.elements, p.atomIndex) as StructureElement.UnitIndex;
+                if (_bI < 0) continue;
+
+                // check if the bond is within MAX_RADIUS for this pair of units
+                if (getDistance(unitA, aI, unitB, p.atomIndex) > MAX_RADIUS) continue;
+
+                builder.add(_aI, _bI, { order: se.order, flag: se.flags });
+                added = true;
             }
             // assume, for an atom, that if any inter unit bond is given
             // all are given and thus we don't need to compute any other
diff --git a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts
index 10327a44a84dbe743128edecb7501666093dd5e4..1ba15206b2a9a0e6ddad536477760e9b792140ca 100644
--- a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts
+++ b/src/mol-model/structure/structure/unit/bonds/intra-compute.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>
@@ -11,10 +11,11 @@ import Unit from '../../unit'
 import { IntAdjacencyGraph } from '../../../../../mol-math/graph';
 import { BondComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold, DefaultBondComputationProps } from './common';
 import { SortedArray } from '../../../../../mol-data/int';
-import { StructConn, ComponentBond } from '../../../../../mol-model-formats/structure/mmcif/bonds';
 import { getIntraBondOrderFromTable } from '../../../model/properties/atomic/bonds';
 import StructureElement from '../../element';
-import { IndexPairBonds } from '../../../../../mol-model-formats/structure/mmcif/bonds/index-pair';
+import { IndexPairBonds } from '../../../../../mol-model-formats/structure/property/bonds/index-pair';
+import { ComponentBond } from '../../../../../mol-model-formats/structure/property/bonds/comp';
+import { StructConn } from '../../../../../mol-model-formats/structure/property/bonds/struct_conn';
 
 function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], atomCount: number): IntraUnitBonds {
     const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB);
@@ -29,6 +30,8 @@ function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.U
     return builder.createGraph({ flags, order });
 }
 
+const __structConnAdded = new Set<StructureElement.UnitIndex>();
+
 function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBonds {
     const MAX_RADIUS = 4;
 
@@ -39,9 +42,9 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
     const { label_comp_id } = unit.model.atomicHierarchy.residues;
     const query3d = unit.lookup3d;
 
-    const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.get(unit.model) : void 0;
-    const component = unit.model.sourceData.kind === 'mmCIF' ? ComponentBond.get(unit.model) : void 0;
-    const indexPairs = IndexPairBonds.get(unit.model)
+    const structConn = StructConn.Provider.get(unit.model)
+    const component = ComponentBond.Provider.get(unit.model)
+    const indexPairs = IndexPairBonds.Provider.get(unit.model)
 
     const atomA: StructureElement.UnitIndex[] = [];
     const atomB: StructureElement.UnitIndex[] = [];
@@ -51,6 +54,8 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
     let lastResidue = -1;
     let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0;
 
+    const structConnAdded = __structConnAdded;
+
     for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
         const aI =  atoms[_aI];
 
@@ -66,19 +71,26 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
             continue // assume `indexPairs` supplies all bonds
         }
 
-        const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
-        const structConnAdded = new Set<StructureElement.UnitIndex>()
+        const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.byAtomIndex.get(aI);
+        let hasStructConn = false;
         if (structConnEntries) {
             for (const se of structConnEntries) {
-                for (const p of se.partners) {
-                    const _bI = SortedArray.indexOf(unit.elements, p.atomIndex) as StructureElement.UnitIndex;
-                    if (_bI < 0 || _aI === _bI) continue;
-                    atomA[atomA.length] = _aI;
-                    atomB[atomB.length] = _bI;
-                    flags[flags.length] = se.flags;
-                    order[order.length] = se.order;
-                    structConnAdded.add(_bI)
-                }
+                const { partnerA, partnerB } = se
+                // symmetry must be the same for intra-unit bonds
+                if (partnerA.symmetry !== partnerB.symmetry) continue
+
+                const p = partnerA.atomIndex === aI ? partnerB : partnerA
+                const _bI = SortedArray.indexOf(unit.elements, p.atomIndex) as StructureElement.UnitIndex;
+                if (_bI < 0) continue;
+
+                atomA[atomA.length] = _aI;
+                atomB[atomB.length] = _bI;
+                flags[flags.length] = se.flags;
+                order[order.length] = se.order;
+
+                if (!hasStructConn) structConnAdded.clear();
+                hasStructConn = true;
+                structConnAdded.add(_bI);
             }
         }
 
@@ -106,7 +118,7 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
 
         for (let ni = 0; ni < count; ni++) {
             const _bI = indices[ni];
-            if (structConnAdded.has(_bI)) continue;
+            if (hasStructConn && structConnAdded.has(_bI)) continue;
 
             const bI = atoms[_bI];
             if (bI <= aI) continue;
diff --git a/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts b/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts
index 2cddb0e9c5cfd3ce0d972f1ba3f681c91b993d1e..0b98c288cd7e44f5409655d1919e232a726fdb0b 100644
--- a/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts
+++ b/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts
@@ -8,9 +8,9 @@ import Unit from '../../unit';
 import Structure from '../../structure';
 import { PairRestraints, CrossLinkRestraint } from './data';
 import { StructureElement } from '../../../structure';
-import { IHMCrossLinkRestraint } from '../../../../../mol-model-formats/structure/mmcif/pair-restraint';
+import { ModelCrossLinkRestraint } from '../../../../../mol-model-formats/structure/property/pair-restraints/cross-links';
 
-function _addRestraints(map: Map<number, number>, unit: Unit, restraints: IHMCrossLinkRestraint) {
+function _addRestraints(map: Map<number, number>, unit: Unit, restraints: ModelCrossLinkRestraint) {
     const { elements } = unit;
     const elementCount = elements.length;
     const kind = unit.kind
@@ -25,7 +25,7 @@ function extractInter(pairs: CrossLinkRestraint[], unitA: Unit, unitB: Unit) {
     if (unitA.model !== unitB.model) return
     if (unitA.model.sourceData.kind !== 'mmCIF') return
 
-    const restraints = IHMCrossLinkRestraint.fromModel(unitA.model)
+    const restraints = ModelCrossLinkRestraint.Provider.get(unitA.model)
     if (!restraints) return
 
     const rA = new Map<number, StructureElement.UnitIndex>();
@@ -47,7 +47,7 @@ function extractInter(pairs: CrossLinkRestraint[], unitA: Unit, unitB: Unit) {
 function extractIntra(pairs: CrossLinkRestraint[], unit: Unit) {
     if (unit.model.sourceData.kind !== 'mmCIF') return
 
-    const restraints = IHMCrossLinkRestraint.fromModel(unit.model)
+    const restraints = ModelCrossLinkRestraint.Provider.get(unit.model)
     if (!restraints) return
 
     const { elements } = unit;
@@ -75,7 +75,7 @@ function extractIntra(pairs: CrossLinkRestraint[], unit: Unit) {
     })
 }
 
-function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitIndex, unitB: Unit, indexB: StructureElement.UnitIndex, restraints: IHMCrossLinkRestraint, row: number): CrossLinkRestraint {
+function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitIndex, unitB: Unit, indexB: StructureElement.UnitIndex, restraints: ModelCrossLinkRestraint, row: number): CrossLinkRestraint {
     return {
         unitA, indexA, unitB, indexB,
 
@@ -89,7 +89,7 @@ function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitInde
 
 function extractCrossLinkRestraints(structure: Structure): PairRestraints<CrossLinkRestraint> {
     const pairs: CrossLinkRestraint[] = []
-    if (!structure.models.some(m => IHMCrossLinkRestraint.fromModel(m))) {
+    if (!structure.models.some(m => ModelCrossLinkRestraint.Provider.get(m))) {
         return new PairRestraints(pairs)
     }
 
diff --git a/src/mol-model/structure/structure/util/lookup3d.ts b/src/mol-model/structure/structure/util/lookup3d.ts
index c10e03095960cdbd6e8044158a7802c700419d6e..e3ac9d7dbc713d32db378d5e11ac223c350b28d4 100644
--- a/src/mol-model/structure/structure/util/lookup3d.ts
+++ b/src/mol-model/structure/structure/util/lookup3d.ts
@@ -100,7 +100,7 @@ export class StructureLookup3D {
         const closeUnits = this.unitLookup.find(x, y, z, radius);
         if (closeUnits.count === 0) return;
 
-        const se = StructureElement.Location.create();
+        const se = StructureElement.Location.create(this.structure);
         const queryRadius = pivotR + maxRadius + radius;
 
         for (let t = 0, _t = closeUnits.count; t < _t; t++) {
diff --git a/src/mol-model/structure/structure/util/unit-transforms.ts b/src/mol-model/structure/structure/util/unit-transforms.ts
index 9a7be99d81c87d0dd07069336db9b7cda6782416..f2de9309b2d0d65654b581b2d3e772dcc1d4c609 100644
--- a/src/mol-model/structure/structure/util/unit-transforms.ts
+++ b/src/mol-model/structure/structure/util/unit-transforms.ts
@@ -18,8 +18,10 @@ export class StructureUnitTransforms {
     private unitOffsetMap = IntMap.Mutable<number>();
     private groupIndexMap = IntMap.Mutable<number>();
     private size: number;
-
+    
     private _isIdentity: boolean | undefined = undefined;
+    
+    version = 0;
 
     constructor(readonly structure: Structure) {
         this.unitTransforms = new Float32Array(structure.units.length * 16)
@@ -39,6 +41,7 @@ export class StructureUnitTransforms {
     }
 
     reset() {
+        this.version = 0;
         fillIdentityTransform(this.unitTransforms, this.size);
         this._isIdentity = true
     }
@@ -58,6 +61,7 @@ export class StructureUnitTransforms {
     }
 
     setTransform(matrix: Mat4, unit: Unit) {
+        this.version = (this.version + 1) % 0x7fffffff;
         Mat4.toArray(matrix, this.unitTransforms, this.unitOffsetMap.get(unit.id))
         this._isIdentity = undefined
     }
diff --git a/src/mol-model/structure/topology/topology.ts b/src/mol-model/structure/topology/topology.ts
index d5e6dbac27cde544ce95dc6784bbb880a2eeab67..6ef843f72f937e518baaa489571ca361759bc377 100644
--- a/src/mol-model/structure/topology/topology.ts
+++ b/src/mol-model/structure/topology/topology.ts
@@ -1,11 +1,12 @@
 /**
- * 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 { UUID } from '../../../mol-util';
 import { Column } from '../../../mol-data/db';
+import { BasicData } from '../../../mol-model-formats/structure/basic/schema';
 import { ModelFormat } from '../../../mol-model-formats/structure/format';
 
 export { Topology }
@@ -14,7 +15,8 @@ interface Topology {
     readonly id: UUID
     readonly label: string
 
-    readonly format: ModelFormat
+    readonly basic: BasicData
+    readonly sourceData: ModelFormat
 
     readonly bonds: {
         readonly indexA: Column<number>,
@@ -47,11 +49,12 @@ interface Topology {
 }
 
 namespace Topology {
-    export function create(label: string, format: ModelFormat, bonds: Topology['bonds']): Topology {
+    export function create(label: string, basic: BasicData, bonds: Topology['bonds'], format: ModelFormat): Topology {
         return {
             id: UUID.create22(),
             label,
-            format,
+            basic,
+            sourceData: format,
             bonds
         }
     }
diff --git a/src/mol-plugin-ui/controls/parameters.tsx b/src/mol-plugin-ui/controls/parameters.tsx
index a942c12439df0d7fdcc595e26d000c8ea361e3fc..49363d5cd68a1fa21a03855f4ffe426dd887a1a4 100644
--- a/src/mol-plugin-ui/controls/parameters.tsx
+++ b/src/mol-plugin-ui/controls/parameters.tsx
@@ -59,6 +59,7 @@ function controlFor(param: PD.Any): ParamControl | undefined {
         case 'color-list': return ColorListControl;
         case 'vec3': return Vec3Control;
         case 'file': return FileControl;
+        case 'file-list': return FileListControl;
         case 'select': return SelectControl;
         case 'text': return TextControl;
         case 'interval': return typeof param.min !== 'undefined' && typeof param.max !== 'undefined'
@@ -436,7 +437,6 @@ export class Vec3Control extends React.PureComponent<ParamProps<PD.Vec3>, { isEx
     }
 }
 
-
 export class FileControl extends React.PureComponent<ParamProps<PD.FileParam>> {
     change(value: File) {
         this.props.onChange({ name: this.props.name, param: this.props.param, value });
@@ -449,13 +449,40 @@ export class FileControl extends React.PureComponent<ParamProps<PD.FileParam>> {
     render() {
         const value = this.props.value;
 
-        // return <input disabled={this.props.isDisabled} value={void 0} type='file' multiple={false} />
         return <div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file' style={{ marginTop: '1px' }}>
             {value ? value.name : 'Select a file...'} <input disabled={this.props.isDisabled} onChange={this.onChangeFile} type='file' multiple={false} accept={this.props.param.accept} />
         </div>
     }
 }
 
+export class FileListControl extends React.PureComponent<ParamProps<PD.FileListParam>> {
+    change(value: FileList) {
+        this.props.onChange({ name: this.props.name, param: this.props.param, value });
+    }
+
+    onChangeFileList = (e: React.ChangeEvent<HTMLInputElement>) => {
+        this.change(e.target.files!);
+    }
+
+    render() {
+        const value = this.props.value;
+
+        const names: string[] = []
+        if (value) {
+            for (let i = 0, il = value.length; i < il; ++i) {
+                names.push(value[i].name)
+            }
+        }
+        const label = names.length === 0
+            ? 'Select files...' : names.length === 1
+                ? names[0] : `${names.length} files selected`
+
+        return <div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file' style={{ marginTop: '1px' }}>
+            {label} <input disabled={this.props.isDisabled} onChange={this.onChangeFileList} type='file' multiple={true} accept={this.props.param.accept} />
+        </div>
+    }
+}
+
 export class MultiSelectControl extends React.PureComponent<ParamProps<PD.MultiSelect<any>>, { isExpanded: boolean }> {
     state = { isExpanded: false }
 
diff --git a/src/mol-plugin-ui/sequence.tsx b/src/mol-plugin-ui/sequence.tsx
index 1ab7f214fdab49c0aa8b6955fa40b0ba797c2fd9..f89f119ee02447590f1c133c4654f8fad497bba9 100644
--- a/src/mol-plugin-ui/sequence.tsx
+++ b/src/mol-plugin-ui/sequence.tsx
@@ -39,13 +39,13 @@ function splitModelEntityId(modelEntityId: string) {
 
 function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureElementSelectionManager): SequenceWrapper.Any | string {
     const { structure, modelEntityId, chainGroupId, operatorKey } = state
-    const l = StructureElement.Location.create()
+    const l = StructureElement.Location.create(structure)
     const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId)
 
     const units: Unit[] = []
 
     for (const unit of structure.units) {
-        StructureElement.Location.set(l, unit, unit.elements[0])
+        StructureElement.Location.set(l, structure, unit, unit.elements[0])
         if (structure.getModelIndex(unit.model) !== modelIdx) continue
         if (SP.entity.id(l) !== entityId) continue
         if (unit.chainGroupId !== chainGroupId) continue
@@ -60,7 +60,7 @@ function getSequenceWrapper(state: SequenceViewState, structureSelection: Struct
 
         let sw: SequenceWrapper<any>
         if (unit.polymerElements.length) {
-            const l = StructureElement.Location.create(unit, unit.elements[0])
+            const l = StructureElement.Location.create(structure, unit, unit.elements[0])
             const entitySeq = unit.model.sequence.byEntityKey[SP.entity.key(l)]
             // check if entity sequence is available
             if (entitySeq && entitySeq.sequence.length <= MaxDisplaySequenceLength) {
@@ -92,11 +92,11 @@ function getSequenceWrapper(state: SequenceViewState, structureSelection: Struct
 
 function getModelEntityOptions(structure: Structure) {
     const options: [string, string][] = []
-    const l = StructureElement.Location.create()
+    const l = StructureElement.Location.create(structure)
     const seen = new Set<string>()
 
     for (const unit of structure.units) {
-        StructureElement.Location.set(l, unit, unit.elements[0])
+        StructureElement.Location.set(l, structure, unit, unit.elements[0])
         const id = SP.entity.id(l)
         const modelIdx = structure.getModelIndex(unit.model)
         const key = `${modelIdx}|${id}`
@@ -121,12 +121,12 @@ function getModelEntityOptions(structure: Structure) {
 
 function getChainOptions(structure: Structure, modelEntityId: string) {
     const options: [number, string][] = []
-    const l = StructureElement.Location.create()
+    const l = StructureElement.Location.create(structure)
     const seen = new Set<number>()
     const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId)
 
     for (const unit of structure.units) {
-        StructureElement.Location.set(l, unit, unit.elements[0])
+        StructureElement.Location.set(l, structure, unit, unit.elements[0])
         if (structure.getModelIndex(unit.model) !== modelIdx) continue
         if (SP.entity.id(l) !== entityId) continue
 
@@ -147,12 +147,12 @@ function getChainOptions(structure: Structure, modelEntityId: string) {
 
 function getOperatorOptions(structure: Structure, modelEntityId: string, chainGroupId: number) {
     const options: [string, string][] = []
-    const l = StructureElement.Location.create()
+    const l = StructureElement.Location.create(structure)
     const seen = new Set<string>()
     const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId)
 
     for (const unit of structure.units) {
-        StructureElement.Location.set(l, unit, unit.elements[0])
+        StructureElement.Location.set(l, structure, unit, unit.elements[0])
         if (structure.getModelIndex(unit.model) !== modelIdx) continue
         if (SP.entity.id(l) !== entityId) continue
         if (unit.chainGroupId !== chainGroupId) continue
diff --git a/src/mol-plugin-ui/sequence/chain.ts b/src/mol-plugin-ui/sequence/chain.ts
index b3e53700e3263f3dd3ed84a57a46962300ba4999..e15f691ec47f4f00861965eaaa6816e579f1c1b5 100644
--- a/src/mol-plugin-ui/sequence/chain.ts
+++ b/src/mol-plugin-ui/sequence/chain.ts
@@ -54,14 +54,14 @@ export class ChainSequenceWrapper extends SequenceWrapper<StructureUnit> {
         let residueCount = 0
         let elementCount = 0
         const counts: string[] = []
-        const l = StructureElement.Location.create()
+        const l = StructureElement.Location.create(data.structure)
 
         const unitIndices = new Map<number, Interval<StructureElement.UnitIndex>>()
         const lociElements: StructureElement.Loci['elements'][0][] = []
 
         for (let i = 0, il = data.units.length; i < il; ++i) {
             const unit = data.units[i]
-            StructureElement.Location.set(l, unit, unit.elements[0])
+            StructureElement.Location.set(l, data.structure, unit, unit.elements[0])
             const entitySeq = unit.model.sequence.byEntityKey[StructureProperties.entity.key(l)]
             if (entitySeq) residueCount += entitySeq.sequence.length
             elementCount += unit.elements.length
diff --git a/src/mol-plugin-ui/sequence/polymer.ts b/src/mol-plugin-ui/sequence/polymer.ts
index 1402a8888735197aeb3f8d9b972a826a58142838..2073dd4bb284c9d726457bac65348a3adb86bb2c 100644
--- a/src/mol-plugin-ui/sequence/polymer.ts
+++ b/src/mol-plugin-ui/sequence/polymer.ts
@@ -66,7 +66,7 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
     }
 
     constructor(data: StructureUnit) {
-        const l = StructureElement.Location.create(data.units[0], data.units[0].elements[0])
+        const l = StructureElement.Location.create(data.structure, data.units[0], data.units[0].elements[0])
         const entitySeq = data.units[0].model.sequence.byEntityKey[SP.entity.key(l)]
 
         const length = entitySeq.sequence.length
diff --git a/src/mol-plugin-ui/sequence/sequence.tsx b/src/mol-plugin-ui/sequence/sequence.tsx
index f1ef4b5ffbf1ac949c9b014479c349e5ae725ba2..3ff18fdcefbbfa270195d2cc2cb1a4059e0ca239 100644
--- a/src/mol-plugin-ui/sequence/sequence.tsx
+++ b/src/mol-plugin-ui/sequence/sequence.tsx
@@ -176,7 +176,7 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
         return classList.join(' ')
     }
 
-    private location = StructureElement.Location.create();
+    private location = StructureElement.Location.create(void 0);
     private getSequenceNumber(seqIdx: number) {
         let seqNum = ''
         const loci = this.props.sequenceWrapper.getLoci(seqIdx)
diff --git a/src/mol-plugin-ui/viewport/simple-settings.tsx b/src/mol-plugin-ui/viewport/simple-settings.tsx
index 699c7c484d49c87f962ce8c3c57540b2c920ef65..387e0154319e1b650725ccdc27eeaecbc10c2596 100644
--- a/src/mol-plugin-ui/viewport/simple-settings.tsx
+++ b/src/mol-plugin-ui/viewport/simple-settings.tsx
@@ -21,7 +21,7 @@ const SimpleSettingsParams = {
         'transparent': PD.EmptyGroup(),
         'opaque': PD.Group({ color: PD.Color(Color(0xFCFBF9), { description: 'Custom background color' }) }, { isFlat: true })
     }, { description: 'Background of the 3D canvas' }),
-    renderStyle: PD.Select('glossy', [['toon', 'Toon'], ['matte', 'Matte'], ['glossy', 'Glossy'], ['metallic', 'Metallic']], { description: 'Style in which the 3D scene is rendered' }),
+    renderStyle: PD.Select('glossy', [['flat', 'Flat'], ['matte', 'Matte'], ['glossy', 'Glossy'], ['metallic', 'Metallic']], { description: 'Style in which the 3D scene is rendered' }),
     occlusion: PD.Boolean(false, { description: 'Darken occluded crevices with the ambient occlusion effect' }),
     outline: PD.Boolean(false, { description: 'Draw outline around 3D objects' }),
     fog: PD.Boolean(false, { description: 'Show fog in the distance' }),
@@ -49,7 +49,7 @@ export class SimpleSettingsControl extends PluginUIComponent {
             if (!this.plugin.canvas3d) return;
 
             const renderer = this.plugin.canvas3d.props.renderer;
-            if (p.value === 'toon') {
+            if (p.value === 'flat') {
                 PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
                     renderer: { ...renderer, lightIntensity: 0, ambientIntensity: 1, roughness: 0.4, metalness: 0 }
                 } });
@@ -80,7 +80,7 @@ export class SimpleSettingsControl extends PluginUIComponent {
             } });
         } else if (p.name === 'fog') {;
             PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
-                cameraFog: p.value ? 50 : 1,
+                cameraFog: p.value ? 50 : 0,
             } });
         } else if (p.name === 'clipFar') {;
             PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
@@ -97,7 +97,7 @@ export class SimpleSettingsControl extends PluginUIComponent {
 
         if (renderer) {
             if (renderer.lightIntensity === 0 && renderer.ambientIntensity === 1 && renderer.roughness === 0.4 && renderer.metalness === 0) {
-                renderStyle = 'toon'
+                renderStyle = 'flat'
             } else if (renderer.lightIntensity === 0.6 && renderer.ambientIntensity === 0.4) {
                 if (renderer.roughness === 1 && renderer.metalness === 0) {
                     renderStyle = 'matte'
diff --git a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts
index 2c35e078ea35a00d2b138ed09085be460b7ab257..69742b2992896625fd383526de1a7e8183c90520 100644
--- a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts
+++ b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts
@@ -6,13 +6,11 @@
 
 import { OrderedSet } from '../../../../../mol-data/int';
 import { StructureQualityReport, StructureQualityReportProvider } from '../../../../../mol-model-props/pdbe/structure-quality-report';
-import { StructureQualityReportColorTheme } from '../../../../../mol-model-props/pdbe/themes/structure-quality-report';
+import { StructureQualityReportColorThemeProvider } from '../../../../../mol-model-props/pdbe/themes/structure-quality-report';
 import { Loci } from '../../../../../mol-model/loci';
 import { StructureElement } from '../../../../../mol-model/structure';
 import { ParamDefinition as PD } from '../../../../../mol-util/param-definition';
 import { PluginBehavior } from '../../../behavior';
-import { ThemeDataContext } from '../../../../../mol-theme/theme';
-import { CustomProperty } from '../../../../../mol-model-props/common/custom-property';
 
 export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
     name: 'pdbe-structure-quality-report-prop',
@@ -32,7 +30,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
                     const u = e.unit;
                     if (!u.model.customProperties.has(StructureQualityReportProvider.descriptor)) return void 0;
 
-                    const se = StructureElement.Location.create(u, u.elements[OrderedSet.getAt(e.indices, 0)]);
+                    const se = StructureElement.Location.create(loci.structure, u, u.elements[OrderedSet.getAt(e.indices, 0)]);
                     const issues = StructureQualityReport.getIssues(se);
                     if (issues.length === 0) return 'PDBe Validation: No Issues';
                     return `PDBe Validation: ${issues.join(', ')}`;
@@ -45,16 +43,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
             this.ctx.customModelProperties.register(this.provider, false);
             this.ctx.lociLabels.addProvider(this.labelPDBeValidation);
 
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('pdbe-structure-quality-report', {
-                label: 'PDBe Structure Quality Report',
-                factory: StructureQualityReportColorTheme,
-                getParams: () => ({}),
-                defaultValues: {},
-                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()
-                }
-            })
+            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('pdbe-structure-quality-report', StructureQualityReportColorThemeProvider)
         }
 
         update(p: { autoAttach: boolean, showTooltip: boolean }) {
diff --git a/src/mol-plugin/behavior/dynamic/representation.ts b/src/mol-plugin/behavior/dynamic/representation.ts
index 9f3a48c3ae6a0795b89798f77d1685aca4d19c54..e945aeab6b78486381c78f4802c5f1a95e117b8e 100644
--- a/src/mol-plugin/behavior/dynamic/representation.ts
+++ b/src/mol-plugin/behavior/dynamic/representation.ts
@@ -45,7 +45,7 @@ export const HighlightLoci = PluginBehavior.create({
         }
         register() {
             this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, buttons, modifiers }) => {
-                if (!this.ctx.canvas3d) return
+                if (!this.ctx.canvas3d || this.ctx.isBusy) return
                 let matched = false
 
                 if (Binding.match(this.params.bindings.hoverHighlightOnly, buttons, modifiers)) {
@@ -132,7 +132,7 @@ export const SelectLoci = PluginBehavior.create({
             })
 
             this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => {
-                if (!this.ctx.canvas3d) return
+                if (!this.ctx.canvas3d || this.ctx.isBusy) return;
 
                 // only trigger the 1st action that matches
                 for (const [binding, action, condition] of actions) {
diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/util.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/util.ts
index ea703fe3691773819183ba7f215374de547d1af7..0f1d5ecca749050a9ef7e3f7e0aa388d53e21dd5 100644
--- a/src/mol-plugin/behavior/dynamic/volume-streaming/util.ts
+++ b/src/mol-plugin/behavior/dynamic/volume-streaming/util.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 David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -9,30 +9,31 @@ import { Structure, Model } from '../../../../mol-model/structure';
 import { VolumeServerInfo } from './model';
 import { PluginContext } from '../../../../mol-plugin/context';
 import { RuntimeContext } from '../../../../mol-task';
+import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
 
 export function getStreamingMethod(s?: Structure, defaultKind: VolumeServerInfo.Kind = 'x-ray'): VolumeServerInfo.Kind {
     if (!s) return defaultKind;
 
     const model = s.models[0];
-    if (model.sourceData.kind !== 'mmCIF') return defaultKind;
+    if (!MmcifFormat.is(model.sourceData)) return defaultKind;
 
-    const { data } = model.sourceData;
+    const { db } = model.sourceData.data;
 
     // prefer EMDB entries over structure-factors (SF) e.g. for 'ELECTRON CRYSTALLOGRAPHY' entries
     // like 6axz or 6kj3 for which EMDB entries are available but map calculation from SF is hard
-    for (let i = 0, il = data.pdbx_database_related._rowCount; i < il; ++i) {
-        if (data.pdbx_database_related.db_name.value(i).toUpperCase() === 'EMDB') {
+    for (let i = 0, il = db.pdbx_database_related._rowCount; i < il; ++i) {
+        if (db.pdbx_database_related.db_name.value(i).toUpperCase() === 'EMDB') {
             return 'em'
         }
     }
 
-    if (data.pdbx_database_status.status_code_sf.isDefined && data.pdbx_database_status.status_code_sf.value(0) === 'REL') {
+    if (db.pdbx_database_status.status_code_sf.isDefined && db.pdbx_database_status.status_code_sf.value(0) === 'REL') {
         return 'x-ray'
     }
 
     // fallbacks
-    for (let i = 0; i < data.exptl.method.rowCount; i++) {
-        const v = data.exptl.method.value(i).toUpperCase();
+    for (let i = 0; i < db.exptl.method.rowCount; i++) {
+        const v = db.exptl.method.value(i).toUpperCase();
         if (v.indexOf('MICROSCOPY') >= 0) return 'em';
     }
     return defaultKind;
@@ -41,9 +42,9 @@ export function getStreamingMethod(s?: Structure, defaultKind: VolumeServerInfo.
 /** Returns EMD ID when available, otherwise falls back to PDB ID */
 export function getEmIds(model: Model): string[] {
     const ids: string[] = []
-    if (model.sourceData.kind !== 'mmCIF') return [ model.entryId ]
+    if (!MmcifFormat.is(model.sourceData)) return [ model.entryId ]
 
-    const { db_id, db_name, content_type } = model.sourceData.data.pdbx_database_related
+    const { db_id, db_name, content_type } = model.sourceData.data.db.pdbx_database_related
     if (!db_name.isDefined) return [ model.entryId ]
 
     for (let i = 0, il = db_name.rowCount; i < il; ++i) {
diff --git a/src/mol-plugin/behavior/static/camera.ts b/src/mol-plugin/behavior/static/camera.ts
index 0fc627654b28bab00b8a4c7b1cfafdcc114c6f70..32971d8edc6ae922e406d40e21293f20c74290a0 100644
--- a/src/mol-plugin/behavior/static/camera.ts
+++ b/src/mol-plugin/behavior/static/camera.ts
@@ -16,7 +16,7 @@ export function registerDefault(ctx: PluginContext) {
 
 export function Reset(ctx: PluginContext) {
     PluginCommands.Camera.Reset.subscribe(ctx, () => {
-        ctx.canvas3d?.resetCamera();
+        ctx.canvas3d?.requestCameraReset();
     })
 }
 
diff --git a/src/mol-plugin/behavior/static/representation.ts b/src/mol-plugin/behavior/static/representation.ts
index 8cdbe6ad31c5e7fc7e2b02dc6060a070e0e81885..4bc083eda3a22c68e928bb84fa804efd53a08c81 100644
--- a/src/mol-plugin/behavior/static/representation.ts
+++ b/src/mol-plugin/behavior/static/representation.ts
@@ -20,7 +20,7 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) {
 
     ctx.events.canvas3d.initialized.subscribe(() => {
         ctx.canvas3d?.reprCount.subscribe(v => {
-            if (reprCount === 0) ctx.canvas3d?.resetCamera();
+            if (reprCount === 0) ctx.canvas3d?.requestCameraReset();
             reprCount = v;
         });
     })
diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts
index 591d9763e840dc14302bc7ed3bf9c1d0da2e16db..802a6ad2c8aa0697b026055c97056bdf04209d00 100644
--- a/src/mol-plugin/context.ts
+++ b/src/mol-plugin/context.ts
@@ -192,6 +192,11 @@ export class PluginContext {
      */
     readonly fetch = ajaxGet
 
+    /** return true is animating or updating */
+    get isBusy() {
+        return this.behaviors.state.isAnimating.value || this.behaviors.state.isUpdating.value;
+    }
+
     runTask<T>(task: Task<T>) {
         return this.tasks.run(task);
     }
diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts
index 51a4ee96fc85fecbf830c482b3d4c0cf108e71d9..9c43953cd18827be185aeac38a5988635e214bc9 100644
--- a/src/mol-plugin/index.ts
+++ b/src/mol-plugin/index.ts
@@ -25,7 +25,7 @@ export const DefaultPluginSpec: PluginSpec = {
         PluginSpec.Action(StateActions.Structure.DownloadStructure),
         PluginSpec.Action(StateActions.Structure.AddTrajectory),
         PluginSpec.Action(StateActions.Volume.DownloadDensity),
-        PluginSpec.Action(StateActions.DataFormat.OpenFile),
+        PluginSpec.Action(StateActions.DataFormat.OpenFiles),
         PluginSpec.Action(StateActions.Structure.Create3DRepresentationPreset),
         PluginSpec.Action(StateActions.Structure.Remove3DRepresentationPreset),
         PluginSpec.Action(StateActions.Structure.EnableModelCustomProps),
diff --git a/src/mol-plugin/state/actions/data-format.ts b/src/mol-plugin/state/actions/data-format.ts
index 75ae09650b465dcce5012b5bbac8cd058b7a3c5b..a759f94eb6f85cbffa670868f29e62318eb5ab2b 100644
--- a/src/mol-plugin/state/actions/data-format.ts
+++ b/src/mol-plugin/state/actions/data-format.ts
@@ -119,36 +119,35 @@ export interface DataFormatProvider<D extends PluginStateObject.Data.Binary | Pl
 
 //
 
-export const OpenFile = StateAction.build({
-    display: { name: 'Open File', description: 'Load a file and optionally create its default visuals' },
+export const OpenFiles = StateAction.build({
+    display: { name: 'Open Files', description: 'Load one or more files and optionally create default visuals' },
     from: PluginStateObject.Root,
     params: (a, ctx: PluginContext) => {
         const { extensions, options } = ctx.dataFormat.registry
         return {
-            file: PD.File({ accept: Array.from(extensions.values()).map(e => `.${e}`).join(',') + ',.gz,.zip' }),
+            files: PD.FileList({ accept: Array.from(extensions.values()).map(e => `.${e}`).join(',') + ',.gz,.zip', multiple: true }),
             format: PD.Select('auto', options),
             visuals: PD.Boolean(true, { description: 'Add default visuals' }),
         }
     }
-})(({ params, state }, ctx: PluginContext) => Task.create('Open File', async taskCtx => {
-    const info = getFileInfo(params.file)
-    const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: ctx.dataFormat.registry.binaryExtensions.has(info.ext) });
-    const dataStateObject = await state.updateTree(data).runInContext(taskCtx);
-
-    // Alternative for more complex states where the builder is not a simple StateBuilder.To<>:
-    /*
-    const dataRef = dataTree.ref;
-    await state.updateTree(dataTree).runInContext(taskCtx);
-    const dataCell = state.select(dataRef)[0];
-    */
-
-    // const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) });
-
-    const provider = params.format === 'auto' ? ctx.dataFormat.registry.auto(info, dataStateObject) : ctx.dataFormat.registry.get(params.format)
-    const b = state.build().to(data.ref);
-    const options = { visuals: params.visuals }
-    // need to await the 2nd update the so that the enclosing Task finishes after the update is done.
-    await provider.getDefaultBuilder(ctx, b, options, state).runInContext(taskCtx)
+})(({ params, state }, ctx: PluginContext) => Task.create('Open Files', async taskCtx => {
+    for (let i = 0, il = params.files.length; i < il; ++i) {
+        try {
+            const file = params.files[i]
+            const info = getFileInfo(file)
+            const isBinary = ctx.dataFormat.registry.binaryExtensions.has(info.ext)
+            const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file, isBinary });
+            const dataStateObject = await state.updateTree(data).runInContext(taskCtx);
+            const provider = params.format === 'auto'
+                ? ctx.dataFormat.registry.auto(info, dataStateObject)
+                : ctx.dataFormat.registry.get(params.format)
+            const b = state.build().to(data.ref);
+            // need to await so that the enclosing Task finishes after the update is done.
+            await provider.getDefaultBuilder(ctx, b, { visuals: params.visuals }, state).runInContext(taskCtx)
+        } catch (e) {
+            ctx.log.error(e)
+        }
+    }
 }));
 
 //
diff --git a/src/mol-plugin/state/representation/model.ts b/src/mol-plugin/state/representation/model.ts
index 143d8ce61c0504cbb6153ed239bd62f4ce5ed3e5..47abb3649f33ac19334f3edca6fddc52675692c9 100644
--- a/src/mol-plugin/state/representation/model.ts
+++ b/src/mol-plugin/state/representation/model.ts
@@ -11,13 +11,16 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 import { RuntimeContext } from '../../../mol-task';
 import { PluginContext } from '../../context';
-import { Assembly, ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry';
+import { Assembly, Symmetry } from '../../../mol-model/structure/model/properties/symmetry';
 import { PluginStateObject as SO } from '../objects';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 export namespace ModelStructureRepresentation {
     export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates') {
-        const assemblyIds = model ? model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]) : [];
-        const showSymm = !model ? true : !SpacegroupCell.isZero(model.symmetry.spacegroup.cell);
+        const symmetry = model && ModelSymmetry.Provider.get(model)
+
+        const assemblyIds = symmetry ? symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]) : [];
+        const showSymm = !symmetry ? true : !SpacegroupCell.isZero(symmetry.spacegroup.cell);
 
         const modes = {
             deposited: PD.EmptyGroup(),
@@ -58,17 +61,19 @@ export namespace ModelStructureRepresentation {
     async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string) {
         let asm: Assembly | undefined = void 0;
 
+        const symmetry = ModelSymmetry.Provider.get(model)
+
         // if no id is specified, use the 1st assembly.
-        if (!id && model.symmetry.assemblies.length !== 0) {
-            id = model.symmetry.assemblies[0].id;
+        if (!id && symmetry && symmetry.assemblies.length !== 0) {
+            id = symmetry.assemblies[0].id;
         }
 
-        if (model.symmetry.assemblies.length === 0) {
+        if (!symmetry || symmetry.assemblies.length === 0) {
             if (id !== 'deposited') {
                 plugin.log.warn(`Model '${model.entryId}' has no assembly, returning deposited structure.`);
             }
         } else {
-            asm = ModelSymmetry.findAssembly(model, id || '');
+            asm = Symmetry.findAssembly(model, id || '');
             if (!asm) {
                 plugin.log.warn(`Model '${model.entryId}' has no assembly called '${id}', returning deposited structure.`);
             }
diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts
index 77a364905ab46d5199b7455d6d0942c4477dd3d1..d593a97b01f8ae8f2aa79c5d5577ee468e0eee94 100644
--- a/src/mol-plugin/state/transforms/model.ts
+++ b/src/mol-plugin/state/transforms/model.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -32,6 +32,7 @@ import { parseDcd } from '../../../mol-io/reader/dcd/parser';
 import { coordinatesFromDcd } from '../../../mol-model-formats/structure/dcd';
 import { topologyFromPsf } from '../../../mol-model-formats/structure/psf';
 import { deepEqual } from '../../../mol-util';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 export { CoordinatesFromDcd };
 export { TopologyFromPsf };
@@ -300,8 +301,8 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({
         if (!a) {
             return { id: PD.Optional(PD.Text('', { label: 'Assembly Id', description: 'Assembly Id. Value \'deposited\' can be used to specify deposited asymmetric unit.' })) };
         }
-        const model = a.data;
-        const ids = model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]);
+        const assemblies = ModelSymmetry.Provider.get(a.data)?.assemblies || []
+        const ids = assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]);
         ids.push(['deposited', 'Deposited']);
         return {
             id: PD.Optional(PD.Select(ids[0][0], ids, { label: 'Asm Id', description: 'Assembly Id' }))
@@ -713,12 +714,13 @@ const CustomModelProperties = PluginStateTransform.BuiltIn({
         });
     }
 });
-async function attachModelProps(model: Model, ctx: PluginContext, taskCtx: RuntimeContext, params: PD.Values<PD.Params>) {
+async function attachModelProps(model: Model, ctx: PluginContext, taskCtx: RuntimeContext, params: ReturnType<CustomModelProperties['createDefaultParams']>) {
     const propertyCtx = { runtime: taskCtx, fetch: ctx.fetch }
-    for (const name of Object.keys(params)) {
+    const { autoAttach, properties } = params
+    for (const name of Object.keys(properties)) {
         const property = ctx.customModelProperties.get(name)
         const props = params[name as keyof typeof params]
-        if (props.autoAttach) {
+        if (autoAttach.includes(name)) {
             try {
                 await property.attach(propertyCtx, model, props)
             } catch (e) {
@@ -747,12 +749,13 @@ const CustomStructureProperties = PluginStateTransform.BuiltIn({
         });
     }
 });
-async function attachStructureProps(structure: Structure, ctx: PluginContext, taskCtx: RuntimeContext, params: PD.Values<PD.Params>) {
+async function attachStructureProps(structure: Structure, ctx: PluginContext, taskCtx: RuntimeContext, params: ReturnType<CustomStructureProperties['createDefaultParams']>) {
     const propertyCtx = { runtime: taskCtx, fetch: ctx.fetch }
-    for (const name of Object.keys(params)) {
+    const { autoAttach, properties } = params
+    for (const name of Object.keys(properties)) {
         const property = ctx.customStructureProperties.get(name)
         const props = params[name as keyof typeof params]
-        if (props.autoAttach) {
+        if (autoAttach.includes(name)) {
             try {
                 await property.attach(propertyCtx, structure, props)
             } catch (e) {
diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts
index f4c9956b90c2ce42c13e091a49f5ab83a3a73c2a..611d19c55790844b5bd6f68bb60c8121f857f134 100644
--- a/src/mol-plugin/state/transforms/representation.ts
+++ b/src/mol-plugin/state/transforms/representation.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -13,7 +13,7 @@ import { BuiltInStructureRepresentationsName } from '../../../mol-repr/structure
 import { StructureParams } from '../../../mol-repr/structure/representation';
 import { BuiltInVolumeRepresentationsName } from '../../../mol-repr/volume/registry';
 import { VolumeParams } from '../../../mol-repr/volume/representation';
-import { StateTransformer } from '../../../mol-state';
+import { StateTransformer, StateObject } from '../../../mol-state';
 import { Task } from '../../../mol-task';
 import { BuiltInColorThemeName, ColorTheme, BuiltInColorThemes } from '../../../mol-theme/color';
 import { BuiltInSizeThemeName, SizeTheme } from '../../../mol-theme/size';
@@ -36,6 +36,7 @@ import { LabelParams, LabelRepresentation } from '../../../mol-repr/shape/loci/l
 import { OrientationRepresentation, OrientationParams } from '../../../mol-repr/shape/loci/orientation';
 import { AngleParams, AngleRepresentation } from '../../../mol-repr/shape/loci/angle';
 import { DihedralParams, DihedralRepresentation } from '../../../mol-repr/shape/loci/dihedral';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 export { StructureRepresentation3D }
 export { StructureRepresentation3DHelpers }
@@ -649,13 +650,16 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
         ...UnitcellParams,
     }
 })({
+    isApplicable: a => !!ModelSymmetry.Provider.get(a.data),
     canAutoUpdate({ oldParams, newParams }) {
         return true;
     },
     apply({ a, params }) {
         return Task.create('Model Unitcell', async ctx => {
+            const symmetry = ModelSymmetry.Provider.get(a.data)
+            if (!symmetry) return StateObject.Null
             const repr = await getUnitcellRepresentation(ctx, a.data, params);
-            return new SO.Shape.Representation3D({ repr, source: a }, { label: `Unitcell`, description: a.data.symmetry.spacegroup.name });
+            return new SO.Shape.Representation3D({ repr, source: a }, { label: `Unitcell`, description: symmetry.spacegroup.name });
         });
     },
     update({ a, b, newParams }) {
diff --git a/src/mol-plugin/util/model-unitcell.ts b/src/mol-plugin/util/model-unitcell.ts
index def1a033b60a822e853d63967424ae78d81ff547..7ea961e9dbd12bd53edf1a804038ecc9e4ec99ba 100644
--- a/src/mol-plugin/util/model-unitcell.ts
+++ b/src/mol-plugin/util/model-unitcell.ts
@@ -1,10 +1,10 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Model, ModelSymmetry } from '../../mol-model/structure';
+import { Model, Symmetry } from '../../mol-model/structure';
 import { ShapeRepresentation } from '../../mol-repr/shape/representation';
 import { Shape } from '../../mol-model/shape';
 import { ColorNames } from '../../mol-util/color/names';
@@ -16,6 +16,7 @@ import { BoxCage } from '../../mol-geo/primitive/box';
 import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
 import { transformCage, cloneCage } from '../../mol-geo/primitive/cage';
 import { radToDeg } from '../../mol-math/misc';
+import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
 
 const translate05 = Mat4.fromTranslation(Mat4(), Vec3.create(0.5, 0.5, 0.5))
 const unitCage = transformCage(cloneCage(BoxCage()), translate05)
@@ -24,7 +25,7 @@ const tmpRef = Vec3()
 const tmpTranslate = Mat4()
 
 interface UnitcellData {
-    symmetry: ModelSymmetry
+    symmetry: Symmetry
     ref: Vec3
 }
 
@@ -54,11 +55,14 @@ function getUnitcellMesh(data: UnitcellData, props: UnitcellProps, mesh?: Mesh)
 
 export async function getUnitcellRepresentation(ctx: RuntimeContext, model: Model, params: UnitcellProps, prev?: ShapeRepresentation<UnitcellData, Mesh, Mesh.Params>) {
     const repr = prev || ShapeRepresentation(getUnitcellShape, Mesh.Utils);
-    const data = {
-        symmetry: model.symmetry,
-        ref: Vec3.transformMat4(Vec3(), Model.getCenter(model), model.symmetry.spacegroup.cell.toFractional)
+    const symmetry = ModelSymmetry.Provider.get(model)
+    if (symmetry) {
+        const data = {
+            symmetry,
+            ref: Vec3.transformMat4(Vec3(), Model.getCenter(model), symmetry.spacegroup.cell.toFractional)
+        }
+        await repr.createOrUpdate(params, data).runInContext(ctx);
     }
-    await repr.createOrUpdate(params, data).runInContext(ctx);
     return repr;
 }
 
diff --git a/src/mol-repr/structure/representation.ts b/src/mol-repr/structure/representation.ts
index a5cc1224e74b2591708ef7346c3502d4f6c162fb..a01d70ab45d288d9054a15a7832c37eae3170bb6 100644
--- a/src/mol-repr/structure/representation.ts
+++ b/src/mol-repr/structure/representation.ts
@@ -19,13 +19,15 @@ import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
 import { Text } from '../../mol-geo/geometry/text/text';
 
 export interface StructureRepresentationState extends Representation.State {
-    unitTransforms: StructureUnitTransforms | null
+    unitTransforms: StructureUnitTransforms | null,
+    unitTransformsVersion: number
 }
 export const StructureRepresentationStateBuilder: Representation.StateBuilder<StructureRepresentationState> = {
     create: () => {
         return {
             ...Representation.createState(),
-            unitTransforms: null
+            unitTransforms: null,
+            unitTransformsVersion: -1
         }
     },
     update: (state: StructureRepresentationState, update: Partial<StructureRepresentationState>) => {
diff --git a/src/mol-repr/structure/representation/ellipsoid.ts b/src/mol-repr/structure/representation/ellipsoid.ts
index 2fd4c05a3485e76df1baa23178c4cc5156ba8b70..9d8f9cf10bedc2f8764f4c40b33ad0520b3d9e2d 100644
--- a/src/mol-repr/structure/representation/ellipsoid.ts
+++ b/src/mol-repr/structure/representation/ellipsoid.ts
@@ -11,7 +11,7 @@ import { Structure } from '../../../mol-model/structure';
 import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
 import { EllipsoidMeshParams, EllipsoidMeshVisual } from '../visual/ellipsoid-mesh';
 import { UnitKind, UnitKindOptions } from '../../../mol-repr/structure/visual/util/common';
-import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/mmcif/anisotropic';
+import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/property/anisotropic';
 import { IntraUnitBondParams, IntraUnitBondVisual } from '../visual/bond-intra-unit-cylinder';
 import { InterUnitBondParams, InterUnitBondVisual } from '../visual/bond-inter-unit-cylinder';
 
diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts
index 478f1762aa1e76be703e10be9e9ebab241c30674..8ce3568ece2c05d3b2d1bd1aed2866229ae09fd7 100644
--- a/src/mol-repr/structure/units-representation.ts
+++ b/src/mol-repr/structure/units-representation.ts
@@ -18,10 +18,11 @@ import { Theme } from '../../mol-theme/theme';
 import { Task } from '../../mol-task';
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { Loci, EmptyLoci, isEmptyLoci, isEveryLoci, isDataLoci } from '../../mol-model/loci';
-import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
+import { MarkerAction, MarkerActions, applyMarkerAction } from '../../mol-util/marker-action';
 import { Overpaint } from '../../mol-theme/overpaint';
 import { Transparency } from '../../mol-theme/transparency';
 import { Mat4, EPSILON } from '../../mol-math/linear-algebra';
+import { Interval } from '../../mol-data/int';
 
 export const UnitsParams = {
     ...StructureParams,
@@ -88,6 +89,13 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R
                         if (promise) await promise
                         visuals.set(group.hashCode, { visual, group })
                         oldVisuals.delete(group.hashCode)
+
+                        // Remove highlight 
+                        // TODO: remove selection too??
+                        if (visual.renderObject) {
+                            const arr = visual.renderObject.values.tMarker.ref.value.array;
+                            applyMarkerAction(arr, Interval.ofBounds(0, arr.length), MarkerAction.RemoveHighlight);
+                        }
                     } else {
                         // console.log(label, 'not found visualGroup to reuse, creating new')
                         // newGroups.push(group)
@@ -223,9 +231,13 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R
         if (transparency !== undefined && !Transparency.areEqual(transparency, _state.transparency)) {
             newState.transparency = transparency
         }
-        if (transform !== undefined && !Mat4.areEqual(transform, _state.transform, EPSILON)) { newState.transform = transform
+        if (transform !== undefined && !Mat4.areEqual(transform, _state.transform, EPSILON)) {
+            newState.transform = transform
+        }
+        if (unitTransforms !== _state.unitTransforms || unitTransforms?.version !== state.unitTransformsVersion) {
+            newState.unitTransforms = unitTransforms
+            _state.unitTransformsVersion = unitTransforms ? unitTransforms?.version : -1
         }
-        if (unitTransforms !== _state.unitTransforms) newState.unitTransforms = unitTransforms
         if (syncManually !== _state.syncManually) newState.syncManually = syncManually
         if (markerActions !== _state.markerActions) newState.markerActions = markerActions
 
diff --git a/src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts b/src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts
index 4a998a09ae0661a1682ecd8e570546a25570d4aa..6ab12e6477d19023cef0e4d2351736dbbc8dae51 100644
--- a/src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts
+++ b/src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts
@@ -33,7 +33,7 @@ function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, inde
 }
 
 const tmpRef = Vec3()
-const tmpLoc = StructureElement.Location.create()
+const tmpLoc = StructureElement.Location.create(void 0)
 
 function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitBondParams>, mesh?: Mesh) {
     const bonds = structure.interUnitBonds
@@ -90,6 +90,7 @@ function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structur
         },
         radius: (edgeIndex: number) => {
             const b = edges[edgeIndex]
+            tmpLoc.structure = structure
             tmpLoc.unit = b.unitA
             tmpLoc.element = b.unitA.elements[b.indexA]
             const sizeA = theme.size.size(tmpLoc)
@@ -142,12 +143,12 @@ function getBondLoci(pickingId: PickingId, structure: Structure, id: number) {
         const bond = structure.interUnitBonds.edges[groupId]
         return Bond.Loci(structure, [
             Bond.Location(
-                bond.unitA, bond.indexA as StructureElement.UnitIndex,
-                bond.unitB, bond.indexB as StructureElement.UnitIndex
+                structure, bond.unitA, bond.indexA as StructureElement.UnitIndex,
+                structure, bond.unitB, bond.indexB as StructureElement.UnitIndex
             ),
             Bond.Location(
-                bond.unitB, bond.indexB as StructureElement.UnitIndex,
-                bond.unitA, bond.indexA as StructureElement.UnitIndex
+                structure, bond.unitB, bond.indexB as StructureElement.UnitIndex,
+                structure, bond.unitA, bond.indexA as StructureElement.UnitIndex
             )
         ])
     }
diff --git a/src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts b/src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts
index 622603ae1daf724817766cb85ded366dcf604898..7283e794ab2381f67cf335efab01a61af03dbf52 100644
--- a/src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts
+++ b/src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts
@@ -25,7 +25,7 @@ import { ignoreBondType, BondCylinderParams, BondIterator } from './util/bond';
 function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondParams>, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
-    const location = StructureElement.Location.create(unit)
+    const location = StructureElement.Location.create(structure, unit)
 
     const elements = unit.elements;
     const bonds = unit.bonds
@@ -135,12 +135,12 @@ function getBondLoci(pickingId: PickingId, structureGroup: StructureGroup, id: n
         if (Unit.isAtomic(unit)) {
             return Bond.Loci(structure, [
                 Bond.Location(
-                    unit, unit.bonds.a[groupId] as StructureElement.UnitIndex,
-                    unit, unit.bonds.b[groupId] as StructureElement.UnitIndex
+                    structure, unit, unit.bonds.a[groupId] as StructureElement.UnitIndex,
+                    structure, unit, unit.bonds.b[groupId] as StructureElement.UnitIndex
                 ),
                 Bond.Location(
-                    unit, unit.bonds.b[groupId] as StructureElement.UnitIndex,
-                    unit, unit.bonds.a[groupId] as StructureElement.UnitIndex
+                    structure, unit, unit.bonds.b[groupId] as StructureElement.UnitIndex,
+                    structure, unit, unit.bonds.a[groupId] as StructureElement.UnitIndex
                 )
             ])
         }
diff --git a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts
index 3fd7397e53e585a6a7b00196fe3c26c2a8b78e32..0379023e9fdd0dcab6580e3d723dda028ad17fea 100644
--- a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts
+++ b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts
@@ -24,7 +24,7 @@ function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: Struc
     const { links, elements } = structure.carbohydrates
     const { linkSizeFactor } = props
 
-    const location = StructureElement.Location.create()
+    const location = StructureElement.Location.create(structure)
 
     const builderProps = {
         linkCount: links.length,
@@ -74,7 +74,7 @@ function CarbohydrateLinkIterator(structure: Structure): LocationIterator {
     const { elements, links } = structure.carbohydrates
     const groupCount = links.length
     const instanceCount = 1
-    const location = StructureElement.Location.create()
+    const location = StructureElement.Location.create(structure)
     const getLocation = (groupIndex: number) => {
         const link = links[groupIndex]
         const carbA = elements[link.carbohydrateIndexA]
diff --git a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts
index a266989792f17ada44312b13b21dc0f0f107df60..39ab381b91a15002eb6187e22dafa319d0fc98b2 100644
--- a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts
+++ b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts
@@ -53,7 +53,7 @@ function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure,
 
     const carbohydrates = structure.carbohydrates
     const n = carbohydrates.elements.length
-    const l = StructureElement.Location.create()
+    const l = StructureElement.Location.create(structure)
 
     for (let i = 0; i < n; ++i) {
         const c = carbohydrates.elements[i];
@@ -187,7 +187,7 @@ function CarbohydrateElementIterator(structure: Structure): LocationIterator {
     const carbElements = structure.carbohydrates.elements
     const groupCount = carbElements.length * 2
     const instanceCount = 1
-    const location = StructureElement.Location.create()
+    const location = StructureElement.Location.create(structure)
     function getLocation (groupIndex: number, instanceIndex: number) {
         const carb = carbElements[Math.floor(groupIndex / 2)]
         const ring = carb.unit.rings.all[carb.ringIndex]
diff --git a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts
index bc661bde59560ffa46768ddb7d5888f57cf95071..1b176e4b3065c257d7b6760ada6e503fd2707837 100644
--- a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts
+++ b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts
@@ -25,7 +25,7 @@ function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, structur
     const { terminalLinks, elements } = structure.carbohydrates
     const { terminalLinkSizeFactor } = props
 
-    const location = StructureElement.Location.create()
+    const location = StructureElement.Location.create(structure)
 
     const builderProps = {
         linkCount: terminalLinks.length,
@@ -91,7 +91,7 @@ function CarbohydrateTerminalLinkIterator(structure: Structure): LocationIterato
     const { elements, terminalLinks } = structure.carbohydrates
     const groupCount = terminalLinks.length
     const instanceCount = 1
-    const location = StructureElement.Location.create()
+    const location = StructureElement.Location.create(structure)
     const getLocation = (groupIndex: number) => {
         const terminalLink = terminalLinks[groupIndex]
         if (terminalLink.fromCarbohydrate) {
diff --git a/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts b/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts
index 06e78634061a48264db601159b9d76e7d7f29c2f..db047214479a432ea8d0e84dc57c3f5d9b3fd0d1 100644
--- a/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts
+++ b/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts
@@ -24,7 +24,7 @@ function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Str
     if (!crossLinks.count) return Mesh.createEmpty(mesh)
     const { sizeFactor } = props
 
-    const location = StructureElement.Location.create()
+    const location = StructureElement.Location.create(structure)
 
     const builderProps = {
         linkCount: crossLinks.count,
@@ -76,8 +76,10 @@ function CrossLinkRestraintIterator(structure: Structure): LocationIterator {
     const location = Bond.Location()
     const getLocation = (groupIndex: number) => {
         const pair = pairs[groupIndex]
+        location.aStructure = structure
         location.aUnit = pair.unitA
         location.aIndex = pair.indexA
+        location.bStructure = structure
         location.bUnit = pair.unitB
         location.bIndex = pair.indexB
         return location
@@ -91,8 +93,8 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
         const pair = structure.crossLinkRestraints.pairs[groupId]
         if (pair) {
             return Bond.Loci(structure, [
-                Bond.Location(pair.unitA, pair.indexA, pair.unitB, pair.indexB),
-                Bond.Location(pair.unitB, pair.indexB, pair.unitA, pair.indexA)
+                Bond.Location(structure, pair.unitA, pair.indexA, structure, pair.unitB, pair.indexB),
+                Bond.Location(structure, pair.unitB, pair.indexB, structure, pair.unitA, pair.indexA)
             ])
         }
     }
diff --git a/src/mol-repr/structure/visual/ellipsoid-mesh.ts b/src/mol-repr/structure/visual/ellipsoid-mesh.ts
index e812c2098799de3123955c0afc733417532b7b22..1496651807b0186b284d40b29c466cc986db747a 100644
--- a/src/mol-repr/structure/visual/ellipsoid-mesh.ts
+++ b/src/mol-repr/structure/visual/ellipsoid-mesh.ts
@@ -17,7 +17,7 @@ import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
 import { Vec3, Mat3, Tensor, EPSILON } from '../../../mol-math/linear-algebra';
 import { isHydrogen } from '../../../mol-repr/structure/visual/util/common';
 import { addEllipsoid } from '../../../mol-geo/geometry/mesh/builder/ellipsoid';
-import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/mmcif/anisotropic'
+import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/property/anisotropic'
 import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
 import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
 
@@ -62,7 +62,7 @@ export function createEllipsoidMesh(ctx: VisualContext, unit: Unit, structure: S
     const vertexCount = elementCount * sphereVertexCount(detail)
     const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh)
 
-    const atomSiteAnisotrop = AtomSiteAnisotrop.get(model)
+    const atomSiteAnisotrop = AtomSiteAnisotrop.Provider.get(model)
     if (!atomSiteAnisotrop) return Mesh.createEmpty(mesh)
 
     const v = Vec3()
@@ -74,7 +74,7 @@ export function createEllipsoidMesh(ctx: VisualContext, unit: Unit, structure: S
     const { U } = data
     const space = data._schema.U.space
     const pos = unit.conformation.invariantPosition
-    const l = StructureElement.Location.create()
+    const l = StructureElement.Location.create(structure)
     l.unit = unit
 
     for (let i = 0; i < elementCount; i++) {
diff --git a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts
index 22c939e38816cee1b2c91518aa31ab56586be6b9..d52d00a8b31ab34fcec773514dc74f94bca0159c 100644
--- a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts
+++ b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts
@@ -38,7 +38,7 @@ export function getGaussianSurfaceVisual(webgl?: WebGLContext) {
 
 async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
     const { smoothness } = props
-    const { transform, field, idField } = await computeUnitGaussianDensity(unit, props, ctx.webgl).runInContext(ctx.runtime)
+    const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime)
 
     const params = {
         isoLevel: Math.exp(-smoothness),
@@ -119,7 +119,7 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
     if (!ctx.webgl) throw new Error('webgl context required to create gaussian surface texture-mesh')
     const isoLevel = Math.exp(-props.smoothness)
 
-    const densityTextureData = await computeUnitGaussianDensityTexture2d(unit, props, ctx.webgl).runInContext(ctx.runtime)
+    const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, props, ctx.webgl).runInContext(ctx.runtime)
     // console.log(densityTextureData)
     // console.log('vertexGroupTexture', readTexture(ctx.webgl, densityTextureData.texture))
     // ctx.webgl.waitForGpuCommandsCompleteSync()
diff --git a/src/mol-repr/structure/visual/gaussian-surface-wireframe.ts b/src/mol-repr/structure/visual/gaussian-surface-wireframe.ts
index 04bd8c7f54c4beba04875370728705120547926c..c0410641e47220cfb54ed4a9d584c0462c2a119d 100644
--- a/src/mol-repr/structure/visual/gaussian-surface-wireframe.ts
+++ b/src/mol-repr/structure/visual/gaussian-surface-wireframe.ts
@@ -17,7 +17,7 @@ import { VisualUpdateState } from '../../util';
 
 async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, lines?: Lines): Promise<Lines> {
     const { smoothness } = props
-    const { transform, field, idField } = await computeUnitGaussianDensity(unit, props, ctx.webgl).runInContext(ctx.runtime)
+    const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime)
 
     const params = {
         isoLevel: Math.exp(-smoothness),
diff --git a/src/mol-repr/structure/visual/label-text.ts b/src/mol-repr/structure/visual/label-text.ts
index a2511e993ed5c9a4cf9c2651c4d9c89d0ee8cdca..696f64fb3db59946bd23df3d226afe56eaf77ac8 100644
--- a/src/mol-repr/structure/visual/label-text.ts
+++ b/src/mol-repr/structure/visual/label-text.ts
@@ -67,8 +67,7 @@ const tmpVec = Vec3();
 const boundaryHelper = new BoundaryHelper();
 
 function createChainText(ctx: VisualContext, structure: Structure, theme: Theme, props: LabelTextProps, text?: Text): Text {
-
-    const l = StructureElement.Location.create();
+    const l = StructureElement.Location.create(structure);
     const { units, serialMapping } = structure;
     const { auth_asym_id, label_asym_id } = StructureProperties.chain;
     const { cumulativeUnitElementCount } = serialMapping
@@ -93,8 +92,7 @@ function createChainText(ctx: VisualContext, structure: Structure, theme: Theme,
 }
 
 function createResidueText(ctx: VisualContext, structure: Structure, theme: Theme, props: LabelTextProps, text?: Text): Text {
-
-    const l = StructureElement.Location.create();
+    const l = StructureElement.Location.create(structure);
     const { units, serialMapping } = structure;
     const { auth_seq_id, label_comp_id } = StructureProperties.residue;
     const { cumulativeUnitElementCount } = serialMapping
@@ -145,8 +143,7 @@ function createResidueText(ctx: VisualContext, structure: Structure, theme: Them
 }
 
 function createElementText(ctx: VisualContext, structure: Structure, theme: Theme, props: LabelTextProps, text?: Text): Text {
-
-    const l = StructureElement.Location.create();
+    const l = StructureElement.Location.create(structure);
     const { units, serialMapping } = structure;
     const { label_atom_id, label_alt_id } = StructureProperties.atom;
     const { cumulativeUnitElementCount } = serialMapping
diff --git a/src/mol-repr/structure/visual/molecular-surface-mesh.ts b/src/mol-repr/structure/visual/molecular-surface-mesh.ts
index 0971b4a11ff202d6e421abb96ba75c438de39271..ac17068a8d4371e7331f440719234ae3fbf3c4b3 100644
--- a/src/mol-repr/structure/visual/molecular-surface-mesh.ts
+++ b/src/mol-repr/structure/visual/molecular-surface-mesh.ts
@@ -27,7 +27,7 @@ export type MolecularSurfaceMeshParams = typeof MolecularSurfaceMeshParams
 
 async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceProps, mesh?: Mesh): Promise<Mesh> {
 
-    const { transform, field, idField } = await computeUnitMolecularSurface(unit, props).runInContext(ctx.runtime)
+    const { transform, field, idField } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime)
     const params = {
         isoLevel: props.probeRadius,
         scalarField: field,
diff --git a/src/mol-repr/structure/visual/molecular-surface-wireframe.ts b/src/mol-repr/structure/visual/molecular-surface-wireframe.ts
index b7b809b61b2a9c31154e8028a0ead2657e0d58c8..964857397e3458cdc7ec5196ffe7c07679157f24 100644
--- a/src/mol-repr/structure/visual/molecular-surface-wireframe.ts
+++ b/src/mol-repr/structure/visual/molecular-surface-wireframe.ts
@@ -28,7 +28,7 @@ export type MolecularSurfaceWireframeParams = typeof MolecularSurfaceWireframePa
 
 async function createMolecularSurfaceWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceProps, lines?: Lines): Promise<Lines> {
 
-    const { transform, field, idField } = await computeUnitMolecularSurface(unit, props).runInContext(ctx.runtime)
+    const { transform, field, idField } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime)
     const params = {
         isoLevel: props.probeRadius,
         scalarField: field,
diff --git a/src/mol-repr/structure/visual/orientation-ellipsoid-mesh.ts b/src/mol-repr/structure/visual/orientation-ellipsoid-mesh.ts
index df4ffead22d6a9600eace70d6a45b056f5564e94..fadec4c0f7b0b20c89da7429ce89ba536bc93285 100644
--- a/src/mol-repr/structure/visual/orientation-ellipsoid-mesh.ts
+++ b/src/mol-repr/structure/visual/orientation-ellipsoid-mesh.ts
@@ -88,10 +88,10 @@ export function createOrientationEllipsoidMesh(ctx: VisualContext, unit: Unit, s
 //
 
 function UnitIterator(structureGroup: StructureGroup): LocationIterator {
-    const { group } = structureGroup
+    const { group, structure } = structureGroup
     const groupCount = 1
     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
diff --git a/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts b/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts
index 73da6a17de41b4753927ac618c336d294b2cfd00..84d8ac7e3d6481e69357bb80f8106574351c0d86 100644
--- a/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts
+++ b/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts
@@ -42,7 +42,7 @@ function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, struc
     const pB = Vec3.zero()
     const cylinderProps: CylinderProps = { radiusTop: 1, radiusBottom: 1, radialSegments }
 
-    const polymerBackboneIt = PolymerBackboneIterator(unit)
+    const polymerBackboneIt = PolymerBackboneIterator(structure, unit)
     while (polymerBackboneIt.hasNext) {
         const { centerA, centerB } = polymerBackboneIt.move()
         pos(centerA.element, pA)
diff --git a/src/mol-repr/structure/visual/polymer-gap-cylinder.ts b/src/mol-repr/structure/visual/polymer-gap-cylinder.ts
index 074c190839214a03c04bcb88b18773a520c5304c..78b7aba9e0fe950b95b8226053a20767d1188b4d 100644
--- a/src/mol-repr/structure/visual/polymer-gap-cylinder.ts
+++ b/src/mol-repr/structure/visual/polymer-gap-cylinder.ts
@@ -48,7 +48,7 @@ function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, structure:
     }
 
     let i = 0
-    const polymerGapIt = PolymerGapIterator(unit)
+    const polymerGapIt = PolymerGapIterator(structure, unit)
     while (polymerGapIt.hasNext) {
         const { centerA, centerB } = polymerGapIt.move()
         if (centerA.element === centerB.element) {
diff --git a/src/mol-repr/structure/visual/util/bond.ts b/src/mol-repr/structure/visual/util/bond.ts
index b21b11ccb50d6b7824d58e26329ada568fd29a13..1b05d5e840b9fcd865fd146157d940e909e29828 100644
--- a/src/mol-repr/structure/visual/util/bond.ts
+++ b/src/mol-repr/structure/visual/util/bond.ts
@@ -25,11 +25,11 @@ export function ignoreBondType(include: BondType.Flag, exclude: BondType.Flag, f
 
 export namespace BondIterator {
     export function fromGroup(structureGroup: StructureGroup): LocationIterator {
-        const { group } = structureGroup
+        const { group, structure } = structureGroup
         const unit = group.units[0]
         const groupCount = Unit.isAtomic(unit) ? unit.bonds.edgeCount * 2 : 0
         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
@@ -42,7 +42,7 @@ export namespace BondIterator {
     export function fromStructure(structure: Structure): LocationIterator {
         const groupCount = structure.interUnitBonds.edgeCount
         const instanceCount = 1
-        const location = StructureElement.Location.create()
+        const location = StructureElement.Location.create(structure)
         const getLocation = (groupIndex: number) => {
             const bond = structure.interUnitBonds.edges[groupIndex]
             location.unit = bond.unitA
diff --git a/src/mol-repr/structure/visual/util/common.ts b/src/mol-repr/structure/visual/util/common.ts
index eeee2ceea5f5a6dca2dc20c2073f1430602b1c04..855b37efd01fff324aa46681098c72e0249028ea 100644
--- a/src/mol-repr/structure/visual/util/common.ts
+++ b/src/mol-repr/structure/visual/util/common.ts
@@ -105,7 +105,7 @@ export function getConformation(unit: Unit) {
     }
 }
 
-export function getUnitConformationAndRadius(unit: Unit, ignoreHydrogens = false) {
+export function getUnitConformationAndRadius(structure: Structure, unit: Unit, ignoreHydrogens = false) {
     const conformation = getConformation(unit)
     const { elements } = unit
 
@@ -135,7 +135,7 @@ export function getUnitConformationAndRadius(unit: Unit, ignoreHydrogens = false
         id
     }
 
-    const l = StructureElement.Location.create(unit)
+    const l = StructureElement.Location.create(structure, unit)
     const sizeTheme = PhysicalSizeTheme({}, {})
     const radius = (index: number) => {
         l.element = index as ElementIndex
@@ -146,7 +146,7 @@ export function getUnitConformationAndRadius(unit: Unit, ignoreHydrogens = false
 }
 
 export function getStructureConformationAndRadius(structure: Structure, ignoreHydrogens = false) {
-    const l = StructureElement.Location.create()
+    const l = StructureElement.Location.create(structure)
     const sizeTheme = PhysicalSizeTheme({}, {})
 
     let xs: ArrayLike<number>
diff --git a/src/mol-repr/structure/visual/util/element.ts b/src/mol-repr/structure/visual/util/element.ts
index 82bdedfb1a94ed641157765f537c7650c7efff44..daa69d87e0d1d8a8e51fa3fac80c0e9fff83308c 100644
--- a/src/mol-repr/structure/visual/util/element.ts
+++ b/src/mol-repr/structure/visual/util/element.ts
@@ -38,7 +38,7 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
 
     const v = Vec3.zero()
     const pos = unit.conformation.invariantPosition
-    const l = StructureElement.Location.create()
+    const l = StructureElement.Location.create(structure)
     l.unit = unit
 
     for (let i = 0; i < elementCount; i++) {
@@ -159,10 +159,10 @@ export function getSerialElementLoci(pickingId: PickingId, structure: Structure,
 
 export namespace ElementIterator {
     export function fromGroup(structureGroup: StructureGroup): LocationIterator {
-        const { group } = structureGroup
+        const { group, structure } = structureGroup
         const groupCount = group.elements.length
         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
@@ -177,7 +177,7 @@ export namespace ElementIterator {
         const groupCount = elementCount
         const instanceCount = 1
         const { unitIndices, elementIndices } = structure.serialMapping
-        const location = StructureElement.Location.create()
+        const location = StructureElement.Location.create(structure)
         const getLocation = (groupIndex: number) => {
             location.unit = units[unitIndices[groupIndex]]
             location.element = elementIndices[groupIndex]
diff --git a/src/mol-repr/structure/visual/util/gaussian.ts b/src/mol-repr/structure/visual/util/gaussian.ts
index fb850975919ee193613eb1527a5e5e324eb05d7b..b836de2c6f443a7dd974df803564ba842cbebe64 100644
--- a/src/mol-repr/structure/visual/util/gaussian.ts
+++ b/src/mol-repr/structure/visual/util/gaussian.ts
@@ -34,22 +34,22 @@ export type GaussianDensityTextureProps = typeof DefaultGaussianDensityTexturePr
 
 //
 
-export function computeUnitGaussianDensity(unit: Unit, props: GaussianDensityProps, webgl?: WebGLContext) {
-    const { position, radius } = getUnitConformationAndRadius(unit, props.ignoreHydrogens)
+export function computeUnitGaussianDensity(structure: Structure, unit: Unit, props: GaussianDensityProps, webgl?: WebGLContext) {
+    const { position, radius } = getUnitConformationAndRadius(structure, unit, props.ignoreHydrogens)
     return Task.create('Gaussian Density', async ctx => {
         return await GaussianDensity(ctx, position, unit.lookup3d.boundary.box, radius, props, webgl);
     });
 }
 
-export function computeUnitGaussianDensityTexture(unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) {
-    const { position, radius } = getUnitConformationAndRadius(unit, props.ignoreHydrogens)
+export function computeUnitGaussianDensityTexture(structure: Structure, unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) {
+    const { position, radius } = getUnitConformationAndRadius(structure, unit, props.ignoreHydrogens)
     return Task.create('Gaussian Density', async ctx => {
         return GaussianDensityTexture(webgl, position, unit.lookup3d.boundary.box, radius, props, texture);
     });
 }
 
-export function computeUnitGaussianDensityTexture2d(unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) {
-    const { position, radius } = getUnitConformationAndRadius(unit, props.ignoreHydrogens)
+export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) {
+    const { position, radius } = getUnitConformationAndRadius(structure, unit, props.ignoreHydrogens)
     return Task.create('Gaussian Density', async ctx => {
         return GaussianDensityTexture2d(webgl, position, unit.lookup3d.boundary.box, radius, props, texture);
     });
diff --git a/src/mol-repr/structure/visual/util/molecular-surface.ts b/src/mol-repr/structure/visual/util/molecular-surface.ts
index 5d3fd7491289151b590c1d4f8a8c681cc2926f27..550e99fadee2694046d461363a96e096d7dce2a0 100644
--- a/src/mol-repr/structure/visual/util/molecular-surface.ts
+++ b/src/mol-repr/structure/visual/util/molecular-surface.ts
@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit } from '../../../../mol-model/structure';
+import { Unit, Structure } from '../../../../mol-model/structure';
 import { Task, RuntimeContext } from '../../../../mol-task';
 import { getUnitConformationAndRadius } from './common';
 import { PositionData, DensityData } from '../../../../mol-math/geometry';
@@ -15,9 +15,9 @@ export type MolecularSurfaceProps = MolecularSurfaceCalculationProps & {
     ignoreHydrogens: boolean
 }
 
-function getPositionDataAndMaxRadius(unit: Unit, props: MolecularSurfaceProps) {
+function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, props: MolecularSurfaceProps) {
     const { probeRadius, ignoreHydrogens } = props
-    const { position, radius } = getUnitConformationAndRadius(unit, ignoreHydrogens)
+    const { position, radius } = getUnitConformationAndRadius(structure, unit, ignoreHydrogens)
     const { indices } = position
     const n = OrderedSet.size(indices)
     const radii = new Float32Array(OrderedSet.end(indices))
@@ -33,8 +33,8 @@ function getPositionDataAndMaxRadius(unit: Unit, props: MolecularSurfaceProps) {
     return { position: { ...position, radius: radii }, maxRadius }
 }
 
-export function computeUnitMolecularSurface(unit: Unit, props: MolecularSurfaceProps) {
-    const { position, maxRadius } = getPositionDataAndMaxRadius(unit, props)
+export function computeUnitMolecularSurface(structure: Structure, unit: Unit, props: MolecularSurfaceProps) {
+    const { position, maxRadius } = getPositionDataAndMaxRadius(structure, unit, props)
     return Task.create('Molecular Surface', async ctx => {
         return await MolecularSurface(ctx, position, maxRadius, props);
     });
diff --git a/src/mol-repr/structure/visual/util/nucleotide.ts b/src/mol-repr/structure/visual/util/nucleotide.ts
index 41c744a168f3667f074ec9154a8ff66ffd35f6c6..6b54f3bb1648f8ea6a004502689eed80ec00a373 100644
--- a/src/mol-repr/structure/visual/util/nucleotide.ts
+++ b/src/mol-repr/structure/visual/util/nucleotide.ts
@@ -15,12 +15,12 @@ import { eachAtomicUnitTracedElement } from './polymer';
 
 export namespace NucleotideLocationIterator {
     export function fromGroup(structureGroup: StructureGroup): LocationIterator {
-        const { group } = structureGroup
+        const { group, structure } = structureGroup
         const u = group.units[0]
         const nucleotideElementIndices = Unit.isAtomic(u) ? u.nucleotideElements : []
         const groupCount = nucleotideElementIndices.length
         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
diff --git a/src/mol-repr/structure/visual/util/polymer.ts b/src/mol-repr/structure/visual/util/polymer.ts
index d40bdbebb4d11ca1bcc521a3e9b1067520827c51..dc868b4587a62883b04b11d9c4c54e8b1c02da2d 100644
--- a/src/mol-repr/structure/visual/util/polymer.ts
+++ b/src/mol-repr/structure/visual/util/polymer.ts
@@ -43,11 +43,11 @@ export function getGapRanges(unit: Unit): SortedRanges<ElementIndex> {
 
 export namespace PolymerLocationIterator {
     export function fromGroup(structureGroup: StructureGroup): LocationIterator {
-        const { group } = structureGroup
+        const { group, structure } = structureGroup
         const polymerElements = group.units[0].polymerElements
         const groupCount = polymerElements.length
         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
@@ -60,11 +60,11 @@ export namespace PolymerLocationIterator {
 
 export namespace PolymerGapLocationIterator {
     export function fromGroup(structureGroup: StructureGroup): LocationIterator {
-        const { group } = structureGroup
+        const { group, structure } = structureGroup
         const gapElements = group.units[0].gapElements
         const groupCount = gapElements.length
         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
@@ -214,8 +214,8 @@ export function getPolymerGapElementLoci(pickingId: PickingId, structureGroup: S
         const unitIndexB = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId % 2 ? groupId - 1 : groupId + 1]) as StructureElement.UnitIndex
         if (unitIndexA !== -1 && unitIndexB !== -1) {
             return Bond.Loci(structure, [
-                Bond.Location(unit, unitIndexA, unit, unitIndexB),
-                Bond.Location(unit, unitIndexB, unit, unitIndexA)
+                Bond.Location(structure, unit, unitIndexA, structure, unit, unitIndexB),
+                Bond.Location(structure, unit, unitIndexB, structure, unit, unitIndexA)
             ])
         }
     }
diff --git a/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts b/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts
index 82155734d9d95f0cbe9ab739ddd7989e4a50a83a..c8bb7f68173506b6879a8a197e076833e08c88d4 100644
--- a/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts
+++ b/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts
@@ -4,19 +4,19 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit, StructureElement, ElementIndex, ResidueIndex } from '../../../../../mol-model/structure';
+import { Unit, Structure, StructureElement, ElementIndex, ResidueIndex } from '../../../../../mol-model/structure';
 import { Segmentation } from '../../../../../mol-data/int';
 import Iterator from '../../../../../mol-data/iterator';
 import SortedRanges from '../../../../../mol-data/int/sorted-ranges';
 import { getPolymerRanges } from '../polymer';
 
 /** Iterates over consecutive pairs of residues/coarse elements in polymers */
-export function PolymerBackboneIterator(unit: Unit): Iterator<PolymerBackbonePair> {
+export function PolymerBackboneIterator(structure: Structure, unit: Unit): Iterator<PolymerBackbonePair> {
     switch (unit.kind) {
-        case Unit.Kind.Atomic: return new AtomicPolymerBackboneIterator(unit)
+        case Unit.Kind.Atomic: return new AtomicPolymerBackboneIterator(structure, unit)
         case Unit.Kind.Spheres:
         case Unit.Kind.Gaussians:
-            return new CoarsePolymerBackboneIterator(unit)
+            return new CoarsePolymerBackboneIterator(structure, unit)
     }
 }
 
@@ -25,10 +25,10 @@ interface PolymerBackbonePair {
     centerB: StructureElement.Location
 }
 
-function createPolymerBackbonePair (unit: Unit) {
+function createPolymerBackbonePair (structure: Structure, unit: Unit) {
     return {
-        centerA: StructureElement.Location.create(unit),
-        centerB: StructureElement.Location.create(unit),
+        centerA: StructureElement.Location.create(structure, unit),
+        centerB: StructureElement.Location.create(structure, unit),
     }
 }
 
@@ -80,11 +80,11 @@ export class AtomicPolymerBackboneIterator implements Iterator<PolymerBackbonePa
         return this.value;
     }
 
-    constructor(private unit: Unit.Atomic) {
+    constructor(structure: Structure, private unit: Unit.Atomic) {
         this.traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex> // can assume it won't be -1 for polymer residues
         this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements)
         this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements)
-        this.value = createPolymerBackbonePair(unit)
+        this.value = createPolymerBackbonePair(structure, unit)
         this.hasNext = this.residueIt.hasNext && this.polymerIt.hasNext
     }
 }
@@ -126,9 +126,9 @@ export class CoarsePolymerBackboneIterator implements Iterator<PolymerBackbonePa
         return this.value;
     }
 
-    constructor(private unit: Unit.Spheres | Unit.Gaussians) {
+    constructor(structure: Structure, private unit: Unit.Spheres | Unit.Gaussians) {
         this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
-        this.value = createPolymerBackbonePair(unit)
+        this.value = createPolymerBackbonePair(structure, unit)
         this.hasNext = this.polymerIt.hasNext
     }
 }
\ No newline at end of file
diff --git a/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts b/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts
index 5b8208f57bc07ebae58b2ea0ab4610d89279ad2a..0da71df3904149025a867a7a0421dc3ffb2f7152 100644
--- a/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts
+++ b/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts
@@ -4,18 +4,18 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit, StructureElement, ElementIndex, ResidueIndex } from '../../../../../mol-model/structure';
+import { Unit, StructureElement, ElementIndex, ResidueIndex, Structure } from '../../../../../mol-model/structure';
 import Iterator from '../../../../../mol-data/iterator';
 import SortedRanges from '../../../../../mol-data/int/sorted-ranges';
 import { getGapRanges } from '../polymer';
 
 /** Iterates over gaps, i.e. the stem residues/coarse elements adjacent to gaps */
-export function PolymerGapIterator(unit: Unit): Iterator<PolymerGapPair> {
+export function PolymerGapIterator(structure: Structure, unit: Unit): Iterator<PolymerGapPair> {
     switch (unit.kind) {
-        case Unit.Kind.Atomic: return new AtomicPolymerGapIterator(unit)
+        case Unit.Kind.Atomic: return new AtomicPolymerGapIterator(structure, unit)
         case Unit.Kind.Spheres:
         case Unit.Kind.Gaussians:
-            return new CoarsePolymerGapIterator(unit)
+            return new CoarsePolymerGapIterator(structure, unit)
     }
 }
 
@@ -24,10 +24,10 @@ interface PolymerGapPair {
     centerB: StructureElement.Location
 }
 
-function createPolymerGapPair (unit: Unit) {
+function createPolymerGapPair (structure: Structure, unit: Unit) {
     return {
-        centerA: StructureElement.Location.create(unit),
-        centerB: StructureElement.Location.create(unit),
+        centerA: StructureElement.Location.create(structure, unit),
+        centerB: StructureElement.Location.create(structure, unit),
     }
 }
 
@@ -46,10 +46,10 @@ export class AtomicPolymerGapIterator implements Iterator<PolymerGapPair> {
         return this.value;
     }
 
-    constructor(private unit: Unit.Atomic) {
+    constructor(structure: Structure, private unit: Unit.Atomic) {
         this.traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex> // can assume it won't be -1 for polymer residues
         this.gapIt = SortedRanges.transientSegments(getGapRanges(unit), unit.elements);
-        this.value = createPolymerGapPair(unit)
+        this.value = createPolymerGapPair(structure, unit)
         this.hasNext = this.gapIt.hasNext
     }
 }
@@ -67,9 +67,9 @@ export class CoarsePolymerGapIterator implements Iterator<PolymerGapPair> {
         return this.value;
     }
 
-    constructor(private unit: Unit.Spheres | Unit.Gaussians) {
+    constructor(structure: Structure, private unit: Unit.Spheres | Unit.Gaussians) {
         this.gapIt = SortedRanges.transientSegments(getGapRanges(unit), unit.elements);
-        this.value = createPolymerGapPair(unit)
+        this.value = createPolymerGapPair(structure, unit)
         this.hasNext = this.gapIt.hasNext
     }
 }
diff --git a/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts b/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts
index f0b405a0fcb31bc15362cb015cb36f965606858a..2631d7588926625f383c0ade64cd016cbbc1f7af 100644
--- a/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts
+++ b/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts
@@ -47,11 +47,11 @@ interface PolymerTraceElement {
 
 const SecStrucTypeNA = SecondaryStructureType.create(SecondaryStructureType.Flag.NA)
 
-function createPolymerTraceElement (unit: Unit): PolymerTraceElement {
+function createPolymerTraceElement (structure: Structure, unit: Unit): PolymerTraceElement {
     return {
-        center: StructureElement.Location.create(unit),
-        centerPrev: StructureElement.Location.create(unit),
-        centerNext: StructureElement.Location.create(unit),
+        center: StructureElement.Location.create(structure, unit),
+        centerPrev: StructureElement.Location.create(structure, unit),
+        centerNext: StructureElement.Location.create(structure, unit),
         first: false, last: false,
         initial: false, final: false,
         secStrucFirst: false, secStrucLast: false,
@@ -302,7 +302,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
         this.cyclicPolymerMap = unit.model.atomicRanges.cyclicPolymerMap
         this.polymerIt = SortedRanges.transientSegments(this.polymerRanges, unit.elements)
         this.residueIt = Segmentation.transientSegments(this.residueAtomSegments, unit.elements);
-        this.value = createPolymerTraceElement(unit)
+        this.value = createPolymerTraceElement(structure, unit)
         this.hasNext = this.residueIt.hasNext && this.polymerIt.hasNext
 
         const secondaryStructure = SecondaryStructureProvider.get(structure).value?.get(unit.invariantId)
@@ -397,7 +397,7 @@ export class CoarsePolymerTraceIterator implements Iterator<PolymerTraceElement>
 
     constructor(private unit: Unit.Spheres | Unit.Gaussians, structure: Structure) {
         this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
-        this.value = createPolymerTraceElement(unit)
+        this.value = createPolymerTraceElement(structure, unit)
         Vec3.set(this.value.d12, 1, 0, 0)
         Vec3.set(this.value.d23, 1, 0, 0)
         switch (unit.kind) {
diff --git a/src/mol-theme/color/chain-id.ts b/src/mol-theme/color/chain-id.ts
index ef8b1f00daef96e0c5d5fbfe09587e178566f86b..16c072ff40491cae1f2c181df6d4eef3194fe1fa 100644
--- a/src/mol-theme/color/chain-id.ts
+++ b/src/mol-theme/color/chain-id.ts
@@ -81,7 +81,7 @@ export function ChainIdColorTheme(ctx: ThemeDataContext, props: PD.Values<ChainI
     let legend: ScaleLegend | TableLegend | undefined
 
     if (ctx.structure) {
-        const l = StructureElement.Location.create()
+        const l = StructureElement.Location.create(ctx.structure)
         const asymIdSerialMap = getAsymIdSerialMap(ctx.structure.root)
 
         const labelTable = Array.from(asymIdSerialMap.keys())
diff --git a/src/mol-theme/color/entity-source.ts b/src/mol-theme/color/entity-source.ts
index c38d9f14a2776eaf61953b61bc99816856bed500..a8ac312bfe1693d8be1637d780f1709702303b5d 100644
--- a/src/mol-theme/color/entity-source.ts
+++ b/src/mol-theme/color/entity-source.ts
@@ -16,6 +16,7 @@ import { getPaletteParams, getPalette } from '../../mol-util/color/palette';
 import { TableLegend, ScaleLegend } from '../../mol-util/legend';
 import { isInteger } from '../../mol-util/number';
 import { ColorLists } from '../../mol-util/color/lists';
+import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
 
 const DefaultList = 'dark-2'
 const DefaultColor = Color(0xFAFAFA)
@@ -100,8 +101,8 @@ function getMaps(models: ReadonlyArray<Model>) {
 
     for (let i = 0, il = models.length; i <il; ++i) {
         const m = models[i]
-        if (m.sourceData.kind !== 'mmCIF') continue
-        const { entity_src_gen, entity_src_nat, pdbx_entity_src_syn } = m.sourceData.data
+        if (!MmcifFormat.is(m.sourceData)) continue
+        const { entity_src_gen, entity_src_nat, pdbx_entity_src_syn } = m.sourceData.data.db
         addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, entity_src_gen, entity_src_gen.pdbx_gene_src_scientific_name, entity_src_gen.plasmid_name, entity_src_gen.pdbx_gene_src_gene)
         addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, entity_src_nat, entity_src_nat.pdbx_organism_scientific, entity_src_nat.pdbx_plasmid_name)
         addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, pdbx_entity_src_syn, pdbx_entity_src_syn.organism_scientific)
@@ -125,7 +126,7 @@ export function EntitySourceColorTheme(ctx: ThemeDataContext, props: PD.Values<E
     let legend: ScaleLegend | TableLegend | undefined
 
     if (ctx.structure) {
-        const l = StructureElement.Location.create()
+        const l = StructureElement.Location.create(ctx.structure)
         const { models } = ctx.structure.root
         const { seqToSrcByModelEntity, srcKeySerialMap } = getMaps(models)
 
diff --git a/src/mol-theme/color/polymer-id.ts b/src/mol-theme/color/polymer-id.ts
index 18f0e76541605b271f10de8c4bd6bd096ba69aa4..46955e905c48bad6d951683869e8c9f3fb5f94ee 100644
--- a/src/mol-theme/color/polymer-id.ts
+++ b/src/mol-theme/color/polymer-id.ts
@@ -90,7 +90,7 @@ export function PolymerIdColorTheme(ctx: ThemeDataContext, props: PD.Values<Poly
     let legend: ScaleLegend | TableLegend | undefined
 
     if (ctx.structure) {
-        const l = StructureElement.Location.create()
+        const l = StructureElement.Location.create(ctx.structure)
         const polymerAsymIdSerialMap = getPolymerAsymIdSerialMap(ctx.structure.root)
 
         const labelTable = Array.from(polymerAsymIdSerialMap.keys())
diff --git a/src/mol-theme/color/uncertainty.ts b/src/mol-theme/color/uncertainty.ts
index b715369f10fb8db503a5c89c3745a07b8382e44d..d7511cb0533f6554bf50d9d92797ef1b0df223f5 100644
--- a/src/mol-theme/color/uncertainty.ts
+++ b/src/mol-theme/color/uncertainty.ts
@@ -67,5 +67,5 @@ export const UncertaintyColorThemeProvider: ColorTheme.Provider<UncertaintyColor
     factory: UncertaintyColorTheme,
     getParams: getUncertaintyColorThemeParams,
     defaultValues: PD.getDefaultValues(UncertaintyColorThemeParams),
-    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => m.atomicConformation.B_iso_or_equiv.isDefined || m.sourceData.data.ihm_sphere_obj_site.rmsf.isDefined)
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => m.atomicConformation.B_iso_or_equiv.isDefined || m.coarseHierarchy.isDefined)
 }
\ No newline at end of file
diff --git a/src/mol-theme/label.ts b/src/mol-theme/label.ts
index cc58137b48249a7806c84a2d0ee367569d1e2fdd..a903862f28ade44b66a725717f671d1ff53a2f4c 100644
--- a/src/mol-theme/label.ts
+++ b/src/mol-theme/label.ts
@@ -112,8 +112,8 @@ function _structureElementStatsLabel(stats: StructureElement.Stats, countsOnly =
 
 export function bondLabel(bond: Bond.Location, options: Partial<LabelOptions> = {}): string {
     const o = { ...DefaultLabelOptions, ...options }
-    const locA = StructureElement.Location.create(bond.aUnit, bond.aUnit.elements[bond.aIndex])
-    const locB = StructureElement.Location.create(bond.bUnit, bond.bUnit.elements[bond.bIndex])
+    const locA = StructureElement.Location.create(bond.aStructure, bond.aUnit, bond.aUnit.elements[bond.aIndex])
+    const locB = StructureElement.Location.create(bond.bStructure, bond.bUnit, bond.bUnit.elements[bond.bIndex])
     const labelA = _elementLabel(locA, o.granularity, o.hidePrefix)
     const labelB = _elementLabel(locB, o.granularity, o.hidePrefix)
     let offset = 0
@@ -137,7 +137,7 @@ export function bundleLabel(bundle: Loci.Bundle<any>, options: Partial<LabelOpti
         const o = { ...DefaultLabelOptions, ...options }
         const locations = (bundle.loci as StructureElement.Loci[]).map(l => {
             const { unit, indices } = l.elements[0]
-            return StructureElement.Location.create(unit, unit.elements[OrderedSet.start(indices)])
+            return StructureElement.Location.create(l.structure, unit, unit.elements[OrderedSet.start(indices)])
         })
         const labels = locations.map(l => _elementLabel(l, o.granularity, o.hidePrefix))
 
diff --git a/src/mol-theme/size/uncertainty.ts b/src/mol-theme/size/uncertainty.ts
index 78e4dc41c8d915d8904b8c3c4d6bb47696dfe122..5f9da065b4b8331ac620db16e6e4b806446ac176 100644
--- a/src/mol-theme/size/uncertainty.ts
+++ b/src/mol-theme/size/uncertainty.ts
@@ -57,5 +57,5 @@ export const UncertaintySizeThemeProvider: SizeTheme.Provider<UncertaintySizeThe
     factory: UncertaintySizeTheme,
     getParams: getUncertaintySizeThemeParams,
     defaultValues: PD.getDefaultValues(UncertaintySizeThemeParams),
-    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => m.atomicConformation.B_iso_or_equiv.isDefined || m.sourceData.data.ihm_sphere_obj_site.rmsf.isDefined)
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => m.atomicConformation.B_iso_or_equiv.isDefined || m.coarseHierarchy.isDefined)
 }
\ No newline at end of file
diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts
index 4ba2901d7190e73786769c9d9e8de16e175e834e..edcf9300c4635fb1ac00869978d8f9457f58906c 100644
--- a/src/mol-util/param-definition.ts
+++ b/src/mol-util/param-definition.ts
@@ -119,15 +119,25 @@ export namespace ParamDefinition {
     }
 
     export interface FileParam extends Base<File> {
-        type: 'file',
+        type: 'file'
         accept?: string
     }
-    export function File(info?: Info & { accept?: string }): FileParam {
+    export function File(info?: Info & { accept?: string, multiple?: boolean }): FileParam {
         const ret = setInfo<FileParam>({ type: 'file', defaultValue: void 0 as any }, info);
         if (info && info.accept) ret.accept = info.accept;
         return ret;
     }
 
+    export interface FileListParam extends Base<FileList> {
+        type: 'file-list'
+        accept?: string
+    }
+    export function FileList(info?: Info & { accept?: string, multiple?: boolean }): FileListParam {
+        const ret = setInfo<FileListParam>({ type: 'file-list', defaultValue: void 0 as any }, info);
+        if (info && info.accept) ret.accept = info.accept;
+        return ret;
+    }
+
     export interface Range {
         /** If given treat as a range. */
         min?: number
@@ -256,7 +266,7 @@ export namespace ParamDefinition {
     }
 
     export type Any =
-        | Value<any> | Select<any> | MultiSelect<any> | BooleanParam | Text | Color | Vec3 | Numeric | FileParam | Interval | LineGraph
+        | Value<any> | Select<any> | MultiSelect<any> | BooleanParam | Text | Color | Vec3 | Numeric | FileParam | FileListParam | Interval | LineGraph
         | ColorList<any> | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | Script | ObjectList
 
     export type Params = { [k: string]: Any }
diff --git a/src/mol-util/set.ts b/src/mol-util/set.ts
index 25114f787f1df08d836f969726272cc95d4f13e0..af02d56a3cab789b73612da9c24a48d43c4c461f 100644
--- a/src/mol-util/set.ts
+++ b/src/mol-util/set.ts
@@ -67,6 +67,14 @@ export namespace SetUtils {
         return flag;
     }
 
+    export function intersectionSize<T>(setA: ReadonlySet<T>, setB: ReadonlySet<T>): number {
+        let count = 0
+        setB.forEach(elem => {
+            if (setA.has(elem)) count += 1;
+        })
+        return count;
+    }
+
     /** Create set containing elements of set a that are not in set b. */
     export function difference<T>(setA: ReadonlySet<T>, setB: ReadonlySet<T>): Set<T> {
         const difference = new Set(setA);
@@ -74,6 +82,15 @@ export namespace SetUtils {
         return difference;
     }
 
+    /** Number of elements that are in set a but not in set b. */
+    export function differenceSize<T>(setA: ReadonlySet<T>, setB: ReadonlySet<T>): number {
+        let count = setA.size;
+        setA.forEach(elem => {
+            if (setB.has(elem)) count -= 1
+        })
+        return count;
+    }
+
     /** Test if set a and b contain the same elements. */
     export function areEqual<T>(setA: ReadonlySet<T>, setB: ReadonlySet<T>) {
         if (setA.size !== setB.size) return false
diff --git a/src/mol-util/uuid.ts b/src/mol-util/uuid.ts
index c5f5b508dbe5f70aba21116b8e4ea6da1356fce8..541489a5f5f57d439bff87bdee88381c9eb5cc6e 100644
--- a/src/mol-util/uuid.ts
+++ b/src/mol-util/uuid.ts
@@ -6,13 +6,14 @@
 
 import { now } from '../mol-util/now';
 
+/** A UUID, either standard 36 characters or 22 characters base64 encoded. */
 type UUID = string & { '@type': 'uuid' }
 
 namespace UUID {
     const _btoa = typeof btoa !== 'undefined' ? btoa : (s: string) => Buffer.from(s).toString('base64')
 
     const chars: string[] = [];
-    /** Creates 22 characted "base64" UUID */
+    /** Creates a 22 characters 'base64' encoded UUID */
     export function create22(): UUID {
         let d = (+new Date()) + now();
         for (let i = 0; i < 16; i++) {
diff --git a/src/perf-tests/lookup3d.ts b/src/perf-tests/lookup3d.ts
index 8df122b3e8f708a35d22e50f4ddf8ce927e34a9d..c4f7c19b319e039d08a2220ef925fc05b1cab758 100644
--- a/src/perf-tests/lookup3d.ts
+++ b/src/perf-tests/lookup3d.ts
@@ -7,7 +7,7 @@ import { Structure } from '../mol-model/structure'
 import { GridLookup3D } from '../mol-math/geometry';
 // import { sortArray } from 'mol-data/util';
 import { OrderedSet } from '../mol-data/int';
-import { trajectoryFromMmCIF } from '../mol-model-formats/structure/mmcif';
+import { trajectoryFromMmCIF, MmcifFormat } from '../mol-model-formats/structure/mmcif';
 
 require('util.promisify').shim();
 const readFileAsync = util.promisify(fs.readFile);
@@ -35,14 +35,14 @@ export async function readCIF(path: string) {
     const models = await trajectoryFromMmCIF(parsed.result.blocks[0]).run();
     const structures = models.map(Structure.ofModel);
 
-    return { mmcif: models[0].sourceData.data, models, structures };
+    return { mmcif: models[0].sourceData.data as MmcifFormat.Data, models, structures };
 }
 
 export async function test() {
     const { mmcif, structures } = await readCIF('e:/test/quick/1tqn_updated.cif');
 
-    const lookup = GridLookup3D({ x: mmcif.atom_site.Cartn_x.toArray(), y: mmcif.atom_site.Cartn_y.toArray(), z: mmcif.atom_site.Cartn_z.toArray(),
-        indices: OrderedSet.ofBounds(0, mmcif.atom_site._rowCount),
+    const lookup = GridLookup3D({ x: mmcif.db.atom_site.Cartn_x.toArray(), y: mmcif.db.atom_site.Cartn_y.toArray(), z: mmcif.db.atom_site.Cartn_z.toArray(),
+        indices: OrderedSet.ofBounds(0, mmcif.db.atom_site._rowCount),
         // radius: [1, 1, 1, 1]
         // indices: [1]
     });
diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts
index 8f7d1ac4e6cd3db684566a39004a2e485c5ed0c5..989e29c7f4df5bb9cef0ab3567fafd6d5390c99c 100644
--- a/src/perf-tests/structure.ts
+++ b/src/perf-tests/structure.ts
@@ -16,7 +16,7 @@ import { Structure, Model, Queries as Q, StructureElement, StructureSelection, S
 
 import to_mmCIF from '../mol-model/structure/export/mmcif'
 import { Vec3 } from '../mol-math/linear-algebra';
-import { trajectoryFromMmCIF } from '../mol-model-formats/structure/mmcif';
+import { trajectoryFromMmCIF, MmcifFormat } from '../mol-model-formats/structure/mmcif';
 // import { printUnits } from '../apps/structure-info/model';
 // import { EquivalenceClasses } from '../mol-data/util';
 
@@ -106,8 +106,8 @@ export async function getBcif(pdbId: string) {
 
 export namespace PropertyAccess {
     function baseline(model: Model) {
-        if (model.sourceData.kind !== 'mmCIF') throw new Error('Model must be mmCIF');
-        const atom_site = model.sourceData.data.atom_site;
+        if (!MmcifFormat.is(model.sourceData)) throw new Error('Model must be mmCIF');
+        const atom_site = model.sourceData.data.db.atom_site;
         const id = atom_site.id.value;
         let s = 0;
         for (let i = 0, _i = atom_site._rowCount; i < _i; i++) {
@@ -117,7 +117,7 @@ export namespace PropertyAccess {
     }
 
     function sumProperty(structure: Structure, p: StructureElement.Property<number>) {
-        const l = StructureElement.Location.create();
+        const l = StructureElement.Location.create(structure);
         let s = 0;
 
         for (const unit of structure.units) {
diff --git a/src/servers/model/properties/providers/wwpdb.ts b/src/servers/model/properties/providers/wwpdb.ts
index 773bbbff39ca5f6df1ba867b8680e31b8bbf4152..c35f991be4998bd07dfcabaee8434f8b2ae3d483 100644
--- a/src/servers/model/properties/providers/wwpdb.ts
+++ b/src/servers/model/properties/providers/wwpdb.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -9,15 +9,17 @@ import * as util from 'util'
 import { AttachModelProperty } from '../../property-provider';
 import { CIF } from '../../../../mol-io/reader/cif';
 import { getParam } from '../../../common/util';
-import { ComponentBond } from '../../../../mol-model-formats/structure/mmcif/bonds';
 import { mmCIF_Database, mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
+import { ComponentBond } from '../../../../mol-model-formats/structure/property/bonds/comp';
 
 require('util.promisify').shim()
 const readFile = util.promisify(fs.readFile)
 
 export const wwPDB_chemCompBond: AttachModelProperty = async ({ model, params }) => {
     const table = await getChemCompBondTable(getTablePath(params))
-    return ComponentBond.attachFromExternalData(model, table, true)
+    const data = ComponentBond.chemCompBondFromTable(model, table)
+    const entries = ComponentBond.getEntriesFromChemCompBond(data)
+    return ComponentBond.Provider.set(model, { entries, data })
 }
 
 async function read(path: string) {
diff --git a/src/tests/browser/marching-cubes.ts b/src/tests/browser/marching-cubes.ts
index 4f8e8335b34dfd2ae04c720e74c64a49e43b667c..66fa5d3fe59d5d021fc6cacee278e8507d783288 100644
--- a/src/tests/browser/marching-cubes.ts
+++ b/src/tests/browser/marching-cubes.ts
@@ -114,7 +114,7 @@ async function init() {
     const mcIsoSurfaceRepr = Representation.fromRenderObject('texture-mesh', mcIsoSurfaceRenderObject)
 
     canvas3d.add(mcIsoSurfaceRepr)
-    canvas3d.resetCamera()
+    canvas3d.requestCameraReset()
 
     //
 
@@ -141,7 +141,7 @@ async function init() {
     const meshRepr = Representation.fromRenderObject('mesh', meshRenderObject)
 
     canvas3d.add(meshRepr)
-    canvas3d.resetCamera()
+    canvas3d.requestCameraReset()
 }
 
 init()
\ No newline at end of file
diff --git a/src/tests/browser/render-lines.ts b/src/tests/browser/render-lines.ts
index 4cecd427cb4e85e4b061bafd95ab948ad6c99408..d782b92aa7540d53169f57741fcebf8b7767ca3a 100644
--- a/src/tests/browser/render-lines.ts
+++ b/src/tests/browser/render-lines.ts
@@ -41,4 +41,4 @@ function linesRepr() {
 }
 
 canvas3d.add(linesRepr())
-canvas3d.resetCamera()
\ No newline at end of file
+canvas3d.requestCameraReset()
\ No newline at end of file
diff --git a/src/tests/browser/render-mesh.ts b/src/tests/browser/render-mesh.ts
index ca5ee131a4db0b7c7875d1a419bea8a099420bb2..f5358bf9f6d54ff5d208a854a8b5dd34a8939cd2 100644
--- a/src/tests/browser/render-mesh.ts
+++ b/src/tests/browser/render-mesh.ts
@@ -47,4 +47,4 @@ function meshRepr() {
 }
 
 canvas3d.add(meshRepr())
-canvas3d.resetCamera()
\ No newline at end of file
+canvas3d.requestCameraReset()
\ No newline at end of file
diff --git a/src/tests/browser/render-shape.ts b/src/tests/browser/render-shape.ts
index ac2153ce5d55f3069be39ccb5cb08700833dc2d0..cf7b748b6eeaa7d18605fc207aed8c578611909e 100644
--- a/src/tests/browser/render-shape.ts
+++ b/src/tests/browser/render-shape.ts
@@ -112,7 +112,7 @@ export async function init() {
     // Create shape from myData and add to canvas3d
     await repr.createOrUpdate({}, myData).run((p: Progress) => console.log(Progress.format(p)))
     canvas3d.add(repr)
-    canvas3d.resetCamera()
+    canvas3d.requestCameraReset()
 
     // Change color after 1s
     setTimeout(async () => {
diff --git a/src/tests/browser/render-spheres.ts b/src/tests/browser/render-spheres.ts
index 020b5795906cc42f4c06b8790aebc4571dabe337..094e48585b429ddcee036a1ad64fe65b1239532b 100644
--- a/src/tests/browser/render-spheres.ts
+++ b/src/tests/browser/render-spheres.ts
@@ -40,4 +40,4 @@ function spheresRepr() {
 }
 
 canvas3d.add(spheresRepr())
-canvas3d.resetCamera()
\ No newline at end of file
+canvas3d.requestCameraReset()
\ No newline at end of file
diff --git a/src/tests/browser/render-structure.ts b/src/tests/browser/render-structure.ts
index 36e284c5448ed8f3743d3d182ba2ababade6fc24..e19dbb44e12c0505c21816c5173bd05893396f3c 100644
--- a/src/tests/browser/render-structure.ts
+++ b/src/tests/browser/render-structure.ts
@@ -195,7 +195,7 @@ async function init() {
     if (show.ballAndStick) canvas3d.add(ballAndStickRepr)
     if (show.molecularSurface) canvas3d.add(molecularSurfaceRepr)
     if (show.gaussianSurface) canvas3d.add(gaussianSurfaceRepr)
-    canvas3d.resetCamera()
+    canvas3d.requestCameraReset()
     // canvas3d.setProps({ trackball: { ...canvas3d.props.trackball, spin: true } })
 }
 
diff --git a/src/tests/browser/render-text.ts b/src/tests/browser/render-text.ts
index 692e1e4a28a299bfbf5143df4ccbd22448106936..0c11067311cc7733651a443cf00cb2921df955ed 100644
--- a/src/tests/browser/render-text.ts
+++ b/src/tests/browser/render-text.ts
@@ -74,4 +74,4 @@ function spheresRepr() {
 
 canvas3d.add(textRepr())
 canvas3d.add(spheresRepr())
-canvas3d.resetCamera()
\ No newline at end of file
+canvas3d.requestCameraReset()
\ No newline at end of file