From c1d54facc6d37c341bd752cae71156016c519606 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Tue, 26 Jun 2018 11:30:14 +0200
Subject: [PATCH] moved Queries.props to StructureProperies, wip custom props

---
 src/apps/structure-info/model.ts              |   4 +-
 .../ui/visualization/sequence-view.tsx        |   6 +-
 src/mol-geo/representation/structure/index.ts |   6 +-
 src/mol-geo/theme/structure/color/chain-id.ts |  10 +-
 src/mol-geo/theme/structure/size/physical.ts  |   6 +-
 .../structure/export/categories/atom_site.ts  |  54 ++++
 .../export/categories/chem_comp_bond.ts       |   7 +
 src/mol-model/structure/export/mmcif.ts       |  60 +---
 .../structure/model/formats/mmcif/assembly.ts |   5 +-
 .../structure/model/formats/mmcif/bonds.ts    | 273 +-----------------
 .../model/formats/mmcif/bonds/comp.ts         |  93 ++++++
 .../model/formats/mmcif/bonds/struct_conn.ts  | 192 ++++++++++++
 src/mol-model/structure/query.ts              |   2 -
 src/mol-model/structure/query/generators.ts   |   3 +-
 src/mol-model/structure/query/predicates.ts   |   3 +-
 src/mol-model/structure/query/properties.ts   | 118 +-------
 src/mol-model/structure/structure.ts          |   3 +-
 .../structure/structure/properties.ts         | 126 ++++++++
 .../structure/structure/structure.ts          |  14 +-
 src/mol-view/label.ts                         |  18 +-
 src/perf-tests/structure.ts                   |  24 +-
 src/servers/model/server/api.ts               |  20 +-
 22 files changed, 543 insertions(+), 504 deletions(-)
 create mode 100644 src/mol-model/structure/export/categories/atom_site.ts
 create mode 100644 src/mol-model/structure/export/categories/chem_comp_bond.ts
 create mode 100644 src/mol-model/structure/model/formats/mmcif/bonds/comp.ts
 create mode 100644 src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts
 create mode 100644 src/mol-model/structure/structure/properties.ts

diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts
index d63913dca..d1252937c 100644
--- a/src/apps/structure-info/model.ts
+++ b/src/apps/structure-info/model.ts
@@ -10,7 +10,7 @@ require('util.promisify').shim();
 
 // import { Table } from 'mol-data/db'
 import { CifFrame } from 'mol-io/reader/cif'
-import { Model, Structure, Element, Unit, Queries, Format } from 'mol-model/structure'
+import { Model, Structure, Element, Unit, Format, StructureProperties } from 'mol-model/structure'
 // import { Run, Progress } from 'mol-task'
 import { OrderedSet } from 'mol-data/int';
 import { Table } from 'mol-data/db';
@@ -166,7 +166,7 @@ export function printUnits(structure: Structure) {
         } else if (Unit.isCoarse(l.unit)) {
             console.log(`Coarse unit ${unit.id} ${unit.conformation.operator.name} (${Unit.isSpheres(l.unit) ? 'spheres' : 'gaussians'}): ${size} elements.`);
 
-            const props = Queries.props.coarse;
+            const props = StructureProperties.coarse;
             const seq = l.unit.model.sequence;
 
             for (let j = 0, _j = Math.min(size, 3); j < _j; j++) {
diff --git a/src/mol-app/ui/visualization/sequence-view.tsx b/src/mol-app/ui/visualization/sequence-view.tsx
index 520505ca2..6025eea22 100644
--- a/src/mol-app/ui/visualization/sequence-view.tsx
+++ b/src/mol-app/ui/visualization/sequence-view.tsx
@@ -7,7 +7,7 @@
 import * as React from 'react'
 import { View } from '../view';
 import { SequenceViewController } from '../../controller/visualization/sequence-view';
-import { Structure, StructureSequence, Queries, Selection } from 'mol-model/structure';
+import { Structure, StructureSequence, Queries, Selection, StructureProperties } from 'mol-model/structure';
 import { Context } from '../../context/context';
 import { InteractivityEvents } from '../../event/basic';
 import { SyncRuntimeContext } from 'mol-task/execution/synchronous';
@@ -27,8 +27,8 @@ export class SequenceView extends View<SequenceViewController, {}, {}> {
 
 function createQuery(entityId: string, label_seq_id: number) {
     return Queries.generators.atoms({
-        entityTest: l => Queries.props.entity.id(l) === entityId,
-        residueTest: l => Queries.props.residue.label_seq_id(l) === label_seq_id
+        entityTest: l => StructureProperties.entity.id(l) === entityId,
+        residueTest: l => StructureProperties.residue.label_seq_id(l) === label_seq_id
     });
 }
 
diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts
index 9d46deebc..fd8340667 100644
--- a/src/mol-geo/representation/structure/index.ts
+++ b/src/mol-geo/representation/structure/index.ts
@@ -5,7 +5,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Structure, StructureSymmetry, Unit } from 'mol-model/structure';
+import { Structure, Unit } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object';
 import { Representation, RepresentationProps, Visual } from '..';
@@ -43,7 +43,7 @@ export function StructureRepresentation<P extends StructureProps>(unitsVisualCto
 
         return Task.create('Creating StructureRepresentation', async ctx => {
             if (!_structure) {
-                _groups = structure.symmetryGroups;
+                _groups = structure.unitSymmetryGroups;
                 for (let i = 0; i < _groups.length; i++) {
                     const group = _groups[i];
                     const visual = unitsVisualCtor()
@@ -59,7 +59,7 @@ export function StructureRepresentation<P extends StructureProps>(unitsVisualCto
                 if (_structure.hashCode === structure.hashCode) {
                     await update(_props)
                 } else {
-                    _groups = structure.symmetryGroups;
+                    _groups = structure.unitSymmetryGroups;
                     const newGroups: Unit.SymmetryGroup[] = []
                     const oldUnitsVisuals = unitsVisuals
                     unitsVisuals = new Map()
diff --git a/src/mol-geo/theme/structure/color/chain-id.ts b/src/mol-geo/theme/structure/color/chain-id.ts
index 4c4b9687f..7afe56c46 100644
--- a/src/mol-geo/theme/structure/color/chain-id.ts
+++ b/src/mol-geo/theme/structure/color/chain-id.ts
@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit, Queries, Element } from 'mol-model/structure';
+import { Unit, StructureProperties, Element } from 'mol-model/structure';
 
 import { StructureColorDataProps } from '.';
 import { ColorData, createElementColor } from '../../../util/color-data';
@@ -49,9 +49,9 @@ export function chainIdColorData(props: StructureColorDataProps, locationFn: (l:
 
     let asym_id: Element.Property<string>
     if (Unit.isAtomic(unit)) {
-        asym_id = Queries.props.chain.label_asym_id
+        asym_id = StructureProperties.chain.label_asym_id
     } else if (Unit.isCoarse(unit)) {
-        asym_id = Queries.props.coarse.asym_id
+        asym_id = StructureProperties.coarse.asym_id
     } else {
         throw new Error('unhandled unit kind')
     }
@@ -92,9 +92,9 @@ export function chainIdElementColorData(props: StructureColorDataProps, colorDat
 
     // let asym_id: Element.Property<string>
     // if (Unit.isAtomic(unit)) {
-    //     asym_id = Queries.props.chain.label_asym_id
+    //     asym_id = StructureProperties.chain.label_asym_id
     // } else if (Unit.isCoarse(unit)) {
-    //     asym_id = Queries.props.coarse.asym_id
+    //     asym_id = StructureProperties.coarse.asym_id
     // }
 
     // const l = Element.Location()
diff --git a/src/mol-geo/theme/structure/size/physical.ts b/src/mol-geo/theme/structure/size/physical.ts
index 84be4d339..34ac86d0d 100644
--- a/src/mol-geo/theme/structure/size/physical.ts
+++ b/src/mol-geo/theme/structure/size/physical.ts
@@ -4,15 +4,15 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Element, Unit, Queries } from 'mol-model/structure';
+import { Element, Unit, StructureProperties } from 'mol-model/structure';
 import { StructureSizeDataProps } from '.';
 import { createAttributeSize } from '../../../util/size-data';
 
 export function getPhysicalRadius(unit: Unit): Element.Property<number> {
     if (Unit.isAtomic(unit)) {
-        return Queries.props.atom.vdw_radius
+        return StructureProperties.atom.vdw_radius
     } else if (Unit.isSpheres(unit)) {
-        return Queries.props.coarse.sphere_radius
+        return StructureProperties.coarse.sphere_radius
     } else {
         return () => 0
     }
diff --git a/src/mol-model/structure/export/categories/atom_site.ts b/src/mol-model/structure/export/categories/atom_site.ts
new file mode 100644
index 000000000..7ee771645
--- /dev/null
+++ b/src/mol-model/structure/export/categories/atom_site.ts
@@ -0,0 +1,54 @@
+/**
+ * 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>
+ */
+
+import { CifWriter } from 'mol-io/writer/cif';
+import { Element, Structure, StructureProperties as P } from '../../structure';
+import { CifExportContext } from '../mmcif';
+import CifField = CifWriter.Field
+import CifCategory = CifWriter.Category
+import E = CifWriter.Encodings
+
+const atom_site_fields: CifField<Element.Location>[] = [
+    CifField.str('group_PDB', P.residue.group_PDB),
+    CifField.int('id', P.atom.id, { encoder: E.deltaRLE }),
+    CifField.str('type_symbol', P.atom.type_symbol as any),
+    CifField.str('label_atom_id', P.atom.label_atom_id),
+    CifField.str('label_alt_id', P.atom.label_alt_id),
+
+    CifField.str('label_comp_id', P.residue.label_comp_id),
+    CifField.int('label_seq_id', P.residue.label_seq_id, { encoder: E.deltaRLE }),
+    CifField.str('pdbx_PDB_ins_code', P.residue.pdbx_PDB_ins_code),
+
+    CifField.str('label_asym_id', P.chain.label_asym_id),
+    CifField.str('label_entity_id', P.chain.label_entity_id),
+
+    CifField.float('Cartn_x', P.atom.x, { digitCount: 3, encoder: E.fixedPoint3 }),
+    CifField.float('Cartn_y', P.atom.y, { digitCount: 3, encoder: E.fixedPoint3 }),
+    CifField.float('Cartn_z', P.atom.z, { digitCount: 3, encoder: E.fixedPoint3 }),
+    CifField.float('occupancy', P.atom.occupancy, { digitCount: 2, encoder: E.fixedPoint2 }),
+    CifField.int('pdbx_formal_charge', P.atom.pdbx_formal_charge, { encoder: E.deltaRLE }),
+
+    CifField.str('auth_atom_id', P.atom.auth_atom_id),
+    CifField.str('auth_comp_id', P.residue.auth_comp_id),
+    CifField.int('auth_seq_id', P.residue.auth_seq_id, { encoder: E.deltaRLE }),
+    CifField.str('auth_asym_id', P.chain.auth_asym_id),
+
+    CifField.int('pdbx_PDB_model_num', P.unit.model_num, { encoder: E.deltaRLE }),
+    CifField.str<Element.Location, Structure>('operator_name', P.unit.operator_name, {
+        shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity)
+    })
+];
+
+export function _atom_site({ structure }: CifExportContext): CifCategory {
+    return {
+        data: structure,
+        name: 'atom_site',
+        fields: atom_site_fields,
+        rowCount: structure.elementCount,
+        keys: () => structure.elementLocations()
+    }
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/export/categories/chem_comp_bond.ts b/src/mol-model/structure/export/categories/chem_comp_bond.ts
new file mode 100644
index 000000000..66298c566
--- /dev/null
+++ b/src/mol-model/structure/export/categories/chem_comp_bond.ts
@@ -0,0 +1,7 @@
+/**
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+// TODO
\ No newline at end of file
diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts
index bf93893f0..4458ef154 100644
--- a/src/mol-model/structure/export/mmcif.ts
+++ b/src/mol-model/structure/export/mmcif.ts
@@ -7,52 +7,18 @@
 
 import { CifWriter } from 'mol-io/writer/cif'
 import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'
-import { Structure, Element } from '../structure'
+import { Structure } from '../structure'
 import { Model } from '../model'
-import P from '../query/properties'
+import { _atom_site } from './categories/atom_site';
 
 export interface CifExportContext {
     structure: Structure,
     model: Model
 }
 
-import CifField = CifWriter.Field
 import CifCategory = CifWriter.Category
 
-import E = CifWriter.Encodings
-
-const atom_site_fields: CifField<Element.Location>[] = [
-    CifField.str('group_PDB', P.residue.group_PDB),
-    CifField.int('id', P.atom.id, { encoder: E.deltaRLE }),
-    CifField.str('type_symbol', P.atom.type_symbol as any),
-    CifField.str('label_atom_id', P.atom.label_atom_id),
-    CifField.str('label_alt_id', P.atom.label_alt_id),
-
-    CifField.str('label_comp_id', P.residue.label_comp_id),
-    CifField.int('label_seq_id', P.residue.label_seq_id, { encoder: E.deltaRLE }),
-    CifField.str('pdbx_PDB_ins_code', P.residue.pdbx_PDB_ins_code),
-
-    CifField.str('label_asym_id', P.chain.label_asym_id),
-    CifField.str('label_entity_id', P.chain.label_entity_id),
-
-    CifField.float('Cartn_x', P.atom.x, { digitCount: 3, encoder: E.fixedPoint3 }),
-    CifField.float('Cartn_y', P.atom.y, { digitCount: 3, encoder: E.fixedPoint3 }),
-    CifField.float('Cartn_z', P.atom.z, { digitCount: 3, encoder: E.fixedPoint3 }),
-    CifField.float('occupancy', P.atom.occupancy, { digitCount: 2, encoder: E.fixedPoint2 }),
-    CifField.int('pdbx_formal_charge', P.atom.pdbx_formal_charge, { encoder: E.deltaRLE }),
-
-    CifField.str('auth_atom_id', P.atom.auth_atom_id),
-    CifField.str('auth_comp_id', P.residue.auth_comp_id),
-    CifField.int('auth_seq_id', P.residue.auth_seq_id, { encoder: E.deltaRLE }),
-    CifField.str('auth_asym_id', P.chain.auth_asym_id),
-
-    CifField.int('pdbx_PDB_model_num', P.unit.model_num, { encoder: E.deltaRLE }),
-    CifField.str<Element.Location, Structure>('operator_name', P.unit.operator_name, {
-        shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity)
-    })
-];
-
-function copy_mmCif_cat(name: keyof mmCIF_Schema) {
+function copy_mmCif_category(name: keyof mmCIF_Schema) {
     return ({ model }: CifExportContext) => {
         if (model.sourceData.kind !== 'mmCIF') return CifCategory.Empty;
         const table = model.sourceData.data[name];
@@ -66,27 +32,15 @@ function _entity({ model, structure }: CifExportContext): CifCategory {
     return CifCategory.ofTable('entity', model.entities.data, keys);
 }
 
-function _atom_site({ structure }: CifExportContext): CifCategory {
-    return {
-        data: structure,
-        name: 'atom_site',
-        fields: atom_site_fields,
-        rowCount: structure.elementCount,
-        keys: () => structure.elementLocations()
-    }
-}
-
 const Categories = [
-    copy_mmCif_cat('entry'),
-    copy_mmCif_cat('exptl'),
-    copy_mmCif_cat('cell'),
-    copy_mmCif_cat('symmetry'),
+    copy_mmCif_category('entry'),
+    copy_mmCif_category('exptl'),
+    copy_mmCif_category('cell'),
+    copy_mmCif_category('symmetry'),
     _entity,
     _atom_site
 ];
 
-mmCIF_Schema
-
 namespace _Filters {
     export const AtomSitePositionsFieldNames = new Set<string>(<(keyof typeof mmCIF_Schema.atom_site)[]>['id', 'Cartn_x', 'Cartn_y', 'Cartn_z']);
 }
diff --git a/src/mol-model/structure/model/formats/mmcif/assembly.ts b/src/mol-model/structure/model/formats/mmcif/assembly.ts
index 48720596c..c86abd27c 100644
--- a/src/mol-model/structure/model/formats/mmcif/assembly.ts
+++ b/src/mol-model/structure/model/formats/mmcif/assembly.ts
@@ -11,6 +11,7 @@ import { Assembly, OperatorGroup, OperatorGroups } from '../../properties/symmet
 import { Queries as Q, Query } from '../../../query'
 
 import mmCIF_Format = Format.mmCIF
+import { StructureProperties } from '../../../structure';
 
 export function createAssemblies(format: mmCIF_Format): ReadonlyArray<Assembly> {
     const { pdbx_struct_assembly } = format.data;
@@ -58,8 +59,8 @@ function operatorGroupsProvider(generators: Generator[], matrices: Matrices): ()
             const operatorNames = expandOperators(operatorList);
             const operators = getAssemblyOperators(matrices, operatorNames, operatorOffset);
             const selector = Query(Q.generators.atoms({ chainTest: Q.pred.and(
-                Q.pred.eq(Q.props.unit.operator_name, SymmetryOperator.DefaultName),
-                Q.pred.inSet(Q.props.chain.label_asym_id, gen.asymIds)
+                Q.pred.eq(StructureProperties.unit.operator_name, SymmetryOperator.DefaultName),
+                Q.pred.inSet(StructureProperties.chain.label_asym_id, gen.asymIds)
             )}));
             groups[groups.length] = { selector, operators };
             operatorOffset += operators.length;
diff --git a/src/mol-model/structure/model/formats/mmcif/bonds.ts b/src/mol-model/structure/model/formats/mmcif/bonds.ts
index 873ccda96..0f6997023 100644
--- a/src/mol-model/structure/model/formats/mmcif/bonds.ts
+++ b/src/mol-model/structure/model/formats/mmcif/bonds.ts
@@ -5,274 +5,5 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import Model from '../../model'
-import { Element } from '../../../structure'
-import { LinkType } from '../../types'
-import { findEntityIdByAsymId, findAtomIndexByLabelName } from './util'
-import { Column } from 'mol-data/db'
-
-// TODO: add dynamic property descriptor for this?
-
-export interface StructConn {
-    getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry>
-    getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry>
-}
-
-export interface ComponentBond {
-    entries: Map<string, ComponentBond.Entry>
-}
-
-export namespace StructConn {
-    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: number, residueBIndex: number): ReadonlyArray<StructConn.Entry> {
-            return this.getResiduePairIndex().get(_resKey(residueAIndex, residueBIndex)) || _emptyEntry;
-        }
-
-        getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry> {
-            return this.getAtomIndex().get(atomIndex) || _emptyEntry;
-        }
-
-        constructor(public entries: StructConn.Entry[]) {
-        }
-    }
-
-    export interface Entry {
-        distance: number,
-        order: number,
-        flags: number,
-        partners: { residueIndex: number, atomIndex: Element, symmetry: string }[]
-    }
-
-    type StructConnType =
-        | 'covale'
-        | 'covale_base'
-        | 'covale_phosphate'
-        | 'covale_sugar'
-        | 'disulf'
-        | 'hydrog'
-        | 'metalc'
-        | 'mismat'
-        | 'modres'
-        | 'saltbr'
-
-    export const PropName = '__StructConn__';
-    export function fromModel(model: Model): StructConn | undefined {
-        if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName];
-
-        if (model.sourceData.kind !== 'mmCIF') return;
-        const { struct_conn } = model.sourceData.data;
-        if (!struct_conn._rowCount) return void 0;
-
-        const { conn_type_id, pdbx_dist_value, pdbx_value_order } = struct_conn;
-        const p1 = {
-            label_asym_id: struct_conn.ptnr1_label_asym_id,
-            label_comp_id: struct_conn.ptnr1_label_comp_id,
-            label_seq_id: struct_conn.ptnr1_label_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_comp_id: struct_conn.ptnr2_label_comp_id,
-            label_seq_id: struct_conn.ptnr2_label_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.findResidueKey(
-                findEntityIdByAsymId(model, asymId),
-                ps.label_comp_id.value(row),
-                asymId,
-                ps.label_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)! as StructConnType;
-            const orderType = (pdbx_value_order.value(i) || '').toLowerCase();
-            let flags = LinkType.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;
-            }
-
-            switch (type) {
-                case 'covale':
-                case 'covale_base':
-                case 'covale_phosphate':
-                case 'covale_sugar':
-                case 'modres':
-                    flags = LinkType.Flag.Covalent;
-                    break;
-                case 'disulf': flags = LinkType.Flag.Covalent | LinkType.Flag.Sulfide; break;
-                case 'hydrog': flags = LinkType.Flag.Hydrogen; break;
-                case 'metalc': flags = LinkType.Flag.MetallicCoordination; break;
-                case 'saltbr': flags = LinkType.Flag.Ion; break;
-            }
-
-            entries.push({ flags, order, distance: pdbx_dist_value.value(i), partners });
-        }
-
-        const ret = new StructConnImpl(entries);
-        model._staticPropertyData[PropName] = ret;
-        return ret;
-    }
-}
-
-export namespace ComponentBond {
-    export class ComponentBondImpl implements ComponentBond {
-        entries: Map<string, ComponentBond.Entry> = new Map();
-
-        addEntry(id: string) {
-            let e = new Entry(id);
-            this.entries.set(id, e);
-            return e;
-        }
-    }
-
-    export class Entry implements 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);
-            }
-
-            if (swap) this.add(b, a, order, flags, false);
-        }
-
-        constructor(public id: string) {
-        }
-    }
-
-    export const PropName = '__ComponentBond__';
-    export function fromModel(model: Model): ComponentBond | undefined {
-        if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName];
-
-        if (model.sourceData.kind !== 'mmCIF') return
-        const { chem_comp_bond } = model.sourceData.data;
-        if (!chem_comp_bond._rowCount) return void 0;
-
-        let compBond = new ComponentBondImpl();
-
-        const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount: rowCount } = chem_comp_bond;
-
-        let entry = compBond.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)!;
-            const order = value_order.value(i)!;
-            const aromatic = pdbx_aromatic_flag.value(i) === 'Y';
-
-            if (entry.id !== id) {
-                entry = compBond.addEntry(id);
-            }
-
-            let flags: number = LinkType.Flag.Covalent;
-            let ord = 1;
-            if (aromatic) flags |= LinkType.Flag.Aromatic;
-            switch (order.toLowerCase()) {
-                case 'doub':
-                case 'delo':
-                    ord = 2;
-                    break;
-                case 'trip': ord = 3; break;
-                case 'quad': ord = 4; break;
-            }
-
-            entry.add(nameA, nameB, ord, flags);
-        }
-
-        model._staticPropertyData[PropName] = compBond;
-        return compBond;
-    }
-}
\ No newline at end of file
+export * from './bonds/comp'
+export * from './bonds/struct_conn'
\ No newline at end of file
diff --git a/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts b/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts
new file mode 100644
index 000000000..4b31456ab
--- /dev/null
+++ b/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts
@@ -0,0 +1,93 @@
+/**
+ * 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>
+ */
+
+import Model from '../../../model'
+import { LinkType } from '../../../types'
+
+export interface ComponentBond {
+    entries: Map<string, ComponentBond.Entry>
+}
+
+export namespace ComponentBond {
+    export class ComponentBondImpl implements ComponentBond {
+        entries: Map<string, ComponentBond.Entry> = new Map();
+
+        addEntry(id: string) {
+            let e = new Entry(id);
+            this.entries.set(id, e);
+            return e;
+        }
+    }
+
+    export class Entry implements 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);
+            }
+
+            if (swap) this.add(b, a, order, flags, false);
+        }
+
+        constructor(public id: string) {
+        }
+    }
+
+    export const PropName = '__ComponentBond__';
+    export function fromModel(model: Model): ComponentBond | undefined {
+        if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName];
+
+        if (model.sourceData.kind !== 'mmCIF') return
+        const { chem_comp_bond } = model.sourceData.data;
+        if (!chem_comp_bond._rowCount) return void 0;
+
+        let compBond = new ComponentBondImpl();
+
+        const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount: rowCount } = chem_comp_bond;
+
+        let entry = compBond.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)!;
+            const order = value_order.value(i)!;
+            const aromatic = pdbx_aromatic_flag.value(i) === 'Y';
+
+            if (entry.id !== id) {
+                entry = compBond.addEntry(id);
+            }
+
+            let flags: number = LinkType.Flag.Covalent;
+            let ord = 1;
+            if (aromatic) flags |= LinkType.Flag.Aromatic;
+            switch (order.toLowerCase()) {
+                case 'doub':
+                case 'delo':
+                    ord = 2;
+                    break;
+                case 'trip': ord = 3; break;
+                case 'quad': ord = 4; break;
+            }
+
+            entry.add(nameA, nameB, ord, flags);
+        }
+
+        model._staticPropertyData[PropName] = compBond;
+        return compBond;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts b/src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts
new file mode 100644
index 000000000..d0c0e5f07
--- /dev/null
+++ b/src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts
@@ -0,0 +1,192 @@
+/**
+ * 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>
+ */
+
+import Model from '../../../model'
+import { Element } from '../../../../structure'
+import { LinkType } from '../../../types'
+import { findEntityIdByAsymId, findAtomIndexByLabelName } from '../util'
+import { Column } from 'mol-data/db'
+
+export interface StructConn {
+    getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry>
+    getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry>
+}
+
+export namespace StructConn {
+    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: number, residueBIndex: number): ReadonlyArray<StructConn.Entry> {
+            return this.getResiduePairIndex().get(_resKey(residueAIndex, residueBIndex)) || _emptyEntry;
+        }
+
+        getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry> {
+            return this.getAtomIndex().get(atomIndex) || _emptyEntry;
+        }
+
+        constructor(public entries: StructConn.Entry[]) {
+        }
+    }
+
+    export interface Entry {
+        distance: number,
+        order: number,
+        flags: number,
+        partners: { residueIndex: number, atomIndex: Element, symmetry: string }[]
+    }
+
+    type StructConnType =
+        | 'covale'
+        | 'covale_base'
+        | 'covale_phosphate'
+        | 'covale_sugar'
+        | 'disulf'
+        | 'hydrog'
+        | 'metalc'
+        | 'mismat'
+        | 'modres'
+        | 'saltbr'
+
+    export const PropName = '__StructConn__';
+    export function fromModel(model: Model): StructConn | undefined {
+        if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName];
+
+        if (model.sourceData.kind !== 'mmCIF') return;
+        const { struct_conn } = model.sourceData.data;
+        if (!struct_conn._rowCount) return void 0;
+
+        const { conn_type_id, pdbx_dist_value, pdbx_value_order } = struct_conn;
+        const p1 = {
+            label_asym_id: struct_conn.ptnr1_label_asym_id,
+            label_comp_id: struct_conn.ptnr1_label_comp_id,
+            label_seq_id: struct_conn.ptnr1_label_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_comp_id: struct_conn.ptnr2_label_comp_id,
+            label_seq_id: struct_conn.ptnr2_label_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.findResidueKey(
+                findEntityIdByAsymId(model, asymId),
+                ps.label_comp_id.value(row),
+                asymId,
+                ps.label_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)! as StructConnType;
+            const orderType = (pdbx_value_order.value(i) || '').toLowerCase();
+            let flags = LinkType.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;
+            }
+
+            switch (type) {
+                case 'covale':
+                case 'covale_base':
+                case 'covale_phosphate':
+                case 'covale_sugar':
+                case 'modres':
+                    flags = LinkType.Flag.Covalent;
+                    break;
+                case 'disulf': flags = LinkType.Flag.Covalent | LinkType.Flag.Sulfide; break;
+                case 'hydrog': flags = LinkType.Flag.Hydrogen; break;
+                case 'metalc': flags = LinkType.Flag.MetallicCoordination; break;
+                case 'saltbr': flags = LinkType.Flag.Ion; break;
+            }
+
+            entries.push({ 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/structure/query.ts b/src/mol-model/structure/query.ts
index b5886cbda..428d976d8 100644
--- a/src/mol-model/structure/query.ts
+++ b/src/mol-model/structure/query.ts
@@ -8,13 +8,11 @@ import Selection from './query/selection'
 import Query from './query/query'
 import * as generators from './query/generators'
 import * as modifiers from './query/modifiers'
-import props from './query/properties'
 import pred from './query/predicates'
 
 export const Queries = {
     generators,
     modifiers,
-    props,
     pred
 }
 
diff --git a/src/mol-model/structure/query/generators.ts b/src/mol-model/structure/query/generators.ts
index 0650a17ae..290a84341 100644
--- a/src/mol-model/structure/query/generators.ts
+++ b/src/mol-model/structure/query/generators.ts
@@ -6,8 +6,7 @@
 
 import Query from './query'
 import Selection from './selection'
-import P from './properties'
-import { Element, Unit } from '../structure'
+import { Element, Unit, StructureProperties as P } from '../structure'
 import { OrderedSet, Segmentation } from 'mol-data/int'
 import { LinearGroupingBuilder } from './utils/builders';
 
diff --git a/src/mol-model/structure/query/predicates.ts b/src/mol-model/structure/query/predicates.ts
index f6e6dfc32..99ef95389 100644
--- a/src/mol-model/structure/query/predicates.ts
+++ b/src/mol-model/structure/query/predicates.ts
@@ -4,8 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Element } from '../structure'
-import P from './properties'
+import { Element, StructureProperties as P } from '../structure'
 
 namespace Predicates {
     export interface SetLike<A> { has(v: A): boolean }
diff --git a/src/mol-model/structure/query/properties.ts b/src/mol-model/structure/query/properties.ts
index 3299acaae..66298c566 100644
--- a/src/mol-model/structure/query/properties.ts
+++ b/src/mol-model/structure/query/properties.ts
@@ -4,120 +4,4 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Element, Unit } from '../structure'
-import { VdwRadius } from '../model/properties/atomic';
-
-const constant = {
-    true: Element.property(l => true),
-    false: Element.property(l => false),
-    zero: Element.property(l => 0)
-}
-
-function notAtomic(): never {
-    throw 'Property only available for atomic models.';
-}
-
-function notCoarse(kind?: string): never {
-    if (!!kind) throw `Property only available for coarse models (${kind}).`;
-    throw `Property only available for coarse models.`;
-}
-
-const atom = {
-    key: Element.property(l => l.element),
-
-    // Conformation
-    x: Element.property(l => l.unit.conformation.x(l.element)),
-    y: Element.property(l => l.unit.conformation.y(l.element)),
-    z: Element.property(l => l.unit.conformation.z(l.element)),
-    id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.atomId.value(l.element)),
-    occupancy: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.occupancy.value(l.element)),
-    B_iso_or_equiv: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.B_iso_or_equiv.value(l.element)),
-
-    // Hierarchy
-    type_symbol: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element)),
-    label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_atom_id.value(l.element)),
-    auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_atom_id.value(l.element)),
-    label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_alt_id.value(l.element)),
-    pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.value(l.element)),
-
-    // Derived
-    vdw_radius: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element))),
-}
-
-const residue = {
-    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residueKey[l.unit.residueIndex[l.element]]),
-
-    group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
-    label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
-    auth_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
-    label_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
-    auth_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
-    pdbx_PDB_ins_code: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])),
-
-    // Properties
-    secondary_structure_type: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.type[l.unit.residueIndex[l.element]]),
-    secondary_structure_key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.key[l.unit.residueIndex[l.element]]),
-}
-
-const chain = {
-    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chainKey[l.unit.chainIndex[l.element]]),
-
-    label_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
-    auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
-    label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
-}
-
-const coarse = {
-    key: atom.key,
-    modelKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.modelKey[l.element]),
-    entityKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.entityKey[l.element]),
-
-    x: atom.x,
-    y: atom.y,
-    z: atom.z,
-
-    asym_id: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.asym_id.value(l.element)),
-    seq_id_begin: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_begin.value(l.element)),
-    seq_id_end: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_end.value(l.element)),
-
-    sphere_radius: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.radius[l.element]),
-    sphere_rmsf: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.rmsf[l.element]),
-
-    gaussian_weight: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.weight[l.element]),
-    gaussian_covariance_matrix: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[l.element])
-}
-
-function eK(l: Element.Location) { return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.entityKey[l.unit.chainIndex[l.element]]; }
-
-const entity = {
-    key: eK,
-
-    id: Element.property(l => l.unit.model.entities.data.id.value(eK(l))),
-    type: Element.property(l => l.unit.model.entities.data.type.value(eK(l))),
-    src_method: Element.property(l => l.unit.model.entities.data.src_method.value(eK(l))),
-    pdbx_description: Element.property(l => l.unit.model.entities.data.pdbx_description.value(eK(l))),
-    formula_weight: Element.property(l => l.unit.model.entities.data.formula_weight.value(eK(l))),
-    pdbx_number_of_molecules: Element.property(l => l.unit.model.entities.data.pdbx_number_of_molecules.value(eK(l))),
-    details: Element.property(l => l.unit.model.entities.data.details.value(eK(l))),
-    pdbx_mutation: Element.property(l => l.unit.model.entities.data.pdbx_mutation.value(eK(l))),
-    pdbx_fragment: Element.property(l => l.unit.model.entities.data.pdbx_fragment.value(eK(l))),
-    pdbx_ec: Element.property(l => l.unit.model.entities.data.pdbx_ec.value(eK(l)))
-}
-
-const unit = {
-    operator_name: Element.property(l => l.unit.conformation.operator.name),
-    model_num: Element.property(l => l.unit.model.modelNum)
-}
-
-const Properties = {
-    constant,
-    atom,
-    residue,
-    chain,
-    entity,
-    unit,
-    coarse
-}
-
-type Properties = typeof Properties
-export default Properties
\ No newline at end of file
+// TODO
\ No newline at end of file
diff --git a/src/mol-model/structure/structure.ts b/src/mol-model/structure/structure.ts
index bed8ae1fd..e64227851 100644
--- a/src/mol-model/structure/structure.ts
+++ b/src/mol-model/structure/structure.ts
@@ -9,5 +9,6 @@ import Structure from './structure/structure'
 import Unit from './structure/unit'
 import StructureSymmetry from './structure/symmetry'
 import { Link } from './structure/unit/links'
+import StructureProperties from './structure/properties'
 
-export { Element, Link, Structure, Unit, StructureSymmetry }
\ No newline at end of file
+export { Element, Link, Structure, Unit, StructureSymmetry, StructureProperties }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/properties.ts b/src/mol-model/structure/structure/properties.ts
new file mode 100644
index 000000000..943c219c2
--- /dev/null
+++ b/src/mol-model/structure/structure/properties.ts
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import Element from './element'
+import Unit from './unit'
+import { VdwRadius } from '../model/properties/atomic';
+
+const constant = {
+    true: Element.property(l => true),
+    false: Element.property(l => false),
+    zero: Element.property(l => 0)
+}
+
+function notAtomic(): never {
+    throw 'Property only available for atomic models.';
+}
+
+function notCoarse(kind?: string): never {
+    if (!!kind) throw `Property only available for coarse models (${kind}).`;
+    throw `Property only available for coarse models.`;
+}
+
+// TODO: remove the type checks?
+
+const atom = {
+    key: Element.property(l => l.element),
+
+    // Conformation
+    x: Element.property(l => l.unit.conformation.x(l.element)),
+    y: Element.property(l => l.unit.conformation.y(l.element)),
+    z: Element.property(l => l.unit.conformation.z(l.element)),
+    id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.atomId.value(l.element)),
+    occupancy: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.occupancy.value(l.element)),
+    B_iso_or_equiv: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.B_iso_or_equiv.value(l.element)),
+
+    // Hierarchy
+    type_symbol: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element)),
+    label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_atom_id.value(l.element)),
+    auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_atom_id.value(l.element)),
+    label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_alt_id.value(l.element)),
+    pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.value(l.element)),
+
+    // Derived
+    vdw_radius: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element))),
+}
+
+const residue = {
+    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residueKey[l.unit.residueIndex[l.element]]),
+
+    group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
+    label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
+    auth_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
+    label_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
+    auth_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
+    pdbx_PDB_ins_code: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])),
+
+    // Properties
+    secondary_structure_type: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.type[l.unit.residueIndex[l.element]]),
+    secondary_structure_key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.key[l.unit.residueIndex[l.element]]),
+}
+
+const chain = {
+    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chainKey[l.unit.chainIndex[l.element]]),
+
+    label_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
+    auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
+    label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
+}
+
+const coarse = {
+    key: atom.key,
+    modelKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.modelKey[l.element]),
+    entityKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.entityKey[l.element]),
+
+    x: atom.x,
+    y: atom.y,
+    z: atom.z,
+
+    asym_id: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.asym_id.value(l.element)),
+    seq_id_begin: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_begin.value(l.element)),
+    seq_id_end: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_end.value(l.element)),
+
+    sphere_radius: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.radius[l.element]),
+    sphere_rmsf: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.rmsf[l.element]),
+
+    gaussian_weight: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.weight[l.element]),
+    gaussian_covariance_matrix: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[l.element])
+}
+
+function eK(l: Element.Location) { return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.entityKey[l.unit.chainIndex[l.element]]; }
+
+const entity = {
+    key: eK,
+
+    id: Element.property(l => l.unit.model.entities.data.id.value(eK(l))),
+    type: Element.property(l => l.unit.model.entities.data.type.value(eK(l))),
+    src_method: Element.property(l => l.unit.model.entities.data.src_method.value(eK(l))),
+    pdbx_description: Element.property(l => l.unit.model.entities.data.pdbx_description.value(eK(l))),
+    formula_weight: Element.property(l => l.unit.model.entities.data.formula_weight.value(eK(l))),
+    pdbx_number_of_molecules: Element.property(l => l.unit.model.entities.data.pdbx_number_of_molecules.value(eK(l))),
+    details: Element.property(l => l.unit.model.entities.data.details.value(eK(l))),
+    pdbx_mutation: Element.property(l => l.unit.model.entities.data.pdbx_mutation.value(eK(l))),
+    pdbx_fragment: Element.property(l => l.unit.model.entities.data.pdbx_fragment.value(eK(l))),
+    pdbx_ec: Element.property(l => l.unit.model.entities.data.pdbx_ec.value(eK(l)))
+}
+
+const unit = {
+    operator_name: Element.property(l => l.unit.conformation.operator.name),
+    model_num: Element.property(l => l.unit.model.modelNum)
+}
+
+const StructureProperties = {
+    constant,
+    atom,
+    residue,
+    chain,
+    entity,
+    unit,
+    coarse
+}
+
+type StructureProperties = typeof StructureProperties
+export default StructureProperties
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts
index d36ea0a2b..5bca93fff 100644
--- a/src/mol-model/structure/structure/structure.ts
+++ b/src/mol-model/structure/structure/structure.ts
@@ -14,9 +14,9 @@ import Unit from './unit'
 import { StructureLookup3D } from './util/lookup3d';
 import { CoarseElements } from '../model/properties/coarse';
 import { StructureSubsetBuilder } from './util/subset-builder';
-import { Queries } from '../query';
 import { InterUnitBonds, computeInterUnitBonds } from './unit/links';
 import StructureSymmetry from './symmetry';
+import StructureProperties from './properties';
 
 class Structure {
     readonly unitMap: IntMap<Unit>;
@@ -69,11 +69,11 @@ class Structure {
         return this._links;
     }
 
-    private _symmetryGroups?: ReadonlyArray<Unit.SymmetryGroup> = void 0;
-    get symmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> {
-        if (this._symmetryGroups) return this._symmetryGroups;
-        this._symmetryGroups = StructureSymmetry.computeTransformGroups(this);
-        return this._symmetryGroups;
+    private _unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup> = void 0;
+    get unitSymmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> {
+        if (this._unitSymmetryGroups) return this._unitSymmetryGroups;
+        this._unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
+        return this._unitSymmetryGroups;
     }
 
     constructor(units: ArrayLike<Unit>) {
@@ -252,7 +252,7 @@ namespace Structure {
         const keys = UniqueArray.create<number, number>();
 
         for (const unit of units) {
-            const prop = unit.kind === Unit.Kind.Atomic ? Queries.props.entity.key : Queries.props.coarse.entityKey;
+            const prop = unit.kind === Unit.Kind.Atomic ? StructureProperties.entity.key : StructureProperties.coarse.entityKey;
 
             l.unit = unit;
             const elements = unit.elements;
diff --git a/src/mol-view/label.ts b/src/mol-view/label.ts
index 3107afeb8..1623b27a9 100644
--- a/src/mol-view/label.ts
+++ b/src/mol-view/label.ts
@@ -5,7 +5,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Unit, Element, Queries } from 'mol-model/structure';
+import { Unit, Element, StructureProperties as Props } from 'mol-model/structure';
 import { Loci } from 'mol-model/loci';
 import { OrderedSet } from 'mol-data/int';
 
@@ -49,17 +49,17 @@ export function elementLabel(loc: Element.Location) {
     let element = ''
 
     if (Unit.isAtomic(loc.unit)) {
-        const asym_id = Queries.props.chain.auth_asym_id(loc)
-        const seq_id = Queries.props.residue.auth_seq_id(loc)
-        const comp_id = Queries.props.residue.auth_comp_id(loc)
-        const atom_id = Queries.props.atom.auth_atom_id(loc)
+        const asym_id = Props.chain.auth_asym_id(loc)
+        const seq_id = Props.residue.auth_seq_id(loc)
+        const comp_id = Props.residue.auth_comp_id(loc)
+        const atom_id = Props.atom.auth_atom_id(loc)
         element = `[${comp_id}]${seq_id}:${asym_id}.${atom_id}`
     } else if (Unit.isCoarse(loc.unit)) {
-        const asym_id = Queries.props.coarse.asym_id(loc)
-        const seq_id_begin = Queries.props.coarse.seq_id_begin(loc)
-        const seq_id_end = Queries.props.coarse.seq_id_end(loc)
+        const asym_id = Props.coarse.asym_id(loc)
+        const seq_id_begin = Props.coarse.seq_id_begin(loc)
+        const seq_id_end = Props.coarse.seq_id_end(loc)
         if (seq_id_begin === seq_id_end) {
-            const entityKey = Queries.props.coarse.entityKey(loc)
+            const entityKey = Props.coarse.entityKey(loc)
             const seq = loc.unit.model.sequence.byEntityKey[entityKey]
             const comp_id = seq.compId.value(seq_id_begin)
             element = `[${comp_id}]${seq_id_begin}:${asym_id}`
diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts
index 22914398f..ee8cae29c 100644
--- a/src/perf-tests/structure.ts
+++ b/src/perf-tests/structure.ts
@@ -11,7 +11,7 @@ import * as fs from 'fs'
 import fetch from 'node-fetch'
 import CIF from 'mol-io/reader/cif'
 
-import { Structure, Model, Queries as Q, Element, Selection, StructureSymmetry, Query, Format } from 'mol-model/structure'
+import { Structure, Model, Queries as Q, Element, Selection, StructureSymmetry, Query, Format, StructureProperties as SP } from 'mol-model/structure'
 //import { Segmentation, OrderedSet } from 'mol-data/int'
 
 import to_mmCIF from 'mol-model/structure/export/mmcif'
@@ -293,7 +293,7 @@ export namespace PropertyAccess {
     export async function testAssembly(id: string, s: Structure) {
         console.time('assembly')
         const a = await StructureSymmetry.buildAssembly(s, '1').run();
-        //const auth_comp_id = Q.props.residue.auth_comp_id;
+        //const auth_comp_id = SP.residue.auth_comp_id;
         //const q1 = Query(Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'ALA' }));
         //const alas = await query(q1, a);
 
@@ -306,7 +306,7 @@ export namespace PropertyAccess {
     export async function testSymmetry(id: string, s: Structure) {
         console.time('symmetry')
         const a = await StructureSymmetry.buildSymmetryRange(s, Vec3.create(-1, -1, -1), Vec3.create(1, 1, 1)).run();
-        //const auth_comp_id = Q.props.residue.auth_comp_id;
+        //const auth_comp_id = SP.residue.auth_comp_id;
         //const q1 = Query(Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'ALA' }));
         //const alas = await query(q1, a);
 
@@ -322,7 +322,7 @@ export namespace PropertyAccess {
         const a = await StructureSymmetry.buildSymmetryRange(s, Vec3.create(-2, -2, -2), Vec3.create(2, 2, 2)).run();
         //console.log(printUnits(a));
 
-        const auth_comp_id = Q.props.residue.auth_comp_id, op = Q.props.unit.operator_name;
+        const auth_comp_id = SP.residue.auth_comp_id, op = SP.unit.operator_name;
         //const q1 = Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'REA' });
         const q1 = Q.modifiers.includeSurroundings(Q.generators.atoms({
             chainTest: l => op(l) === '1_555',
@@ -345,7 +345,7 @@ export namespace PropertyAccess {
         // const it = surr.elementLocations();
         // while (it.hasNext) {
         //     const e = it.move();
-        //     console.log(`${Q.props.unit.operator_name(e)} ${Q.props.atom.id(e)}`);
+        //     console.log(`${SP.unit.operator_name(e)} ${SP.atom.id(e)}`);
         // }
     //fs.writeFileSync(`${DATA_DIR}/${id}_surr.bcif`, to_mmCIF(id, a, true));
         fs.writeFileSync(`${DATA_DIR}/${id}_surr.cif`, to_mmCIF(id, surr, false));
@@ -418,7 +418,7 @@ export namespace PropertyAccess {
         //console.log('r', sumPropertyResidue(structures[0], l => l.unit.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.atom])));
 
         console.time('atom.x');
-        console.log('atom.x', sumProperty(structures[0], Q.props.atom.x));
+        console.log('atom.x', sumProperty(structures[0], SP.atom.x));
         console.timeEnd('atom.x');
         console.time('__x')
         //console.log('__x', sumProperty(structures[0], l => l.unit.conformation.x[l.atom]));
@@ -426,16 +426,16 @@ export namespace PropertyAccess {
 
         //const authSeqId = Element.property(l => l.unit.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.atom]));
 
-        //const auth_seq_id = Q.props.residue.auth_seq_id;
-        const auth_comp_id = Q.props.residue.auth_comp_id;
-        //const auth_asym_id = Q.props.chain.auth_asym_id;
+        //const auth_seq_id = SP.residue.auth_seq_id;
+        const auth_comp_id = SP.residue.auth_comp_id;
+        //const auth_asym_id = SP.chain.auth_asym_id;
         //const set =  new Set(['A', 'B', 'C', 'D']);
         //const q = Q.generators.atomGroups({ atomTest: l => auth_seq_id(l) < 3 });
-        const q = Query(Q.generators.atoms({ atomTest: Q.pred.eq(Q.props.residue.auth_comp_id, 'ALA') }));
-        const P = Q.props
+        const q = Query(Q.generators.atoms({ atomTest: Q.pred.eq(SP.residue.auth_comp_id, 'ALA') }));
+        const P = SP
         //const q0 = Q.generators.atoms({ atomTest: l => auth_comp_id(l) === 'ALA' });
         const q1 = Query(Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'ALA' }));
-        const q2 = Query(Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'ALA', groupBy: Q.props.residue.key }));
+        const q2 = Query(Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'ALA', groupBy: SP.residue.key }));
         const q3 = Query(Q.generators.atoms({
             chainTest: Q.pred.inSet(P.chain.auth_asym_id, ['A', 'B', 'C', 'D']),
             residueTest: Q.pred.eq(P.residue.auth_comp_id, 'ALA')
diff --git a/src/servers/model/server/api.ts b/src/servers/model/server/api.ts
index 96820b82d..88830f6cd 100644
--- a/src/servers/model/server/api.ts
+++ b/src/servers/model/server/api.ts
@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Query, Queries, Structure, Element, StructureSymmetry } from 'mol-model/structure';
+import { Query, Queries, Structure, Element, StructureSymmetry, StructureProperties as Props } from 'mol-model/structure';
 
 export enum QueryParamType {
     String,
@@ -47,23 +47,23 @@ const AtomSiteParameters = {
 
 // function entityTest(params: any): Element.Predicate | undefined {
 //     if (typeof params.entity_id === 'undefined') return void 0;
-//     const p = Queries.props.entity.id, id = '' + params.entityId;
+//     const p = Props.entity.id, id = '' + params.entityId;
 //     return Element.property(l => p(l) === id);
 // }
 
 function entityTest1_555(params: any): Element.Predicate | undefined {
     if (typeof params.entity_id === 'undefined') return Element.property(l => l.unit.conformation.operator.isIdentity);
-    const p = Queries.props.entity.id, id = '' + params.entityId;
+    const p = Props.entity.id, id = '' + params.entityId;
     return Element.property(l => l.unit.conformation.operator.isIdentity && p(l) === id);
 }
 
 function chainTest(params: any): Element.Predicate | undefined {
     if (typeof params.label_asym_id !== 'undefined') {
-        const p = Queries.props.chain.label_asym_id, id = '' + params.label_asym_id;
+        const p = Props.chain.label_asym_id, id = '' + params.label_asym_id;
         return Element.property(l => p(l) === id);
     }
     if (typeof params.auth_asym_id !== 'undefined') {
-        const p = Queries.props.chain.auth_asym_id, id = '' + params.auth_asym_id;
+        const p = Props.chain.auth_asym_id, id = '' + params.auth_asym_id;
         return Element.property(l => p(l) === id);
     }
     return void 0;
@@ -73,27 +73,27 @@ function residueTest(params: any): Element.Predicate | undefined {
     const props: Element.Property<any>[] = [], values: any[] = [];
 
     if (typeof params.label_seq_id !== 'undefined') {
-        props.push(Queries.props.residue.label_seq_id);
+        props.push(Props.residue.label_seq_id);
         values.push(+params.label_seq_id);
     }
 
     if (typeof params.auth_seq_id !== 'undefined') {
-        props.push(Queries.props.residue.auth_seq_id);
+        props.push(Props.residue.auth_seq_id);
         values.push(+params.auth_seq_id);
     }
 
     if (typeof params.label_comp_id !== 'undefined') {
-        props.push(Queries.props.residue.label_comp_id);
+        props.push(Props.residue.label_comp_id);
         values.push(params.label_comp_id);
     }
 
     if (typeof params.auth_comp_id !== 'undefined') {
-        props.push(Queries.props.residue.auth_comp_id);
+        props.push(Props.residue.auth_comp_id);
         values.push(params.auth_comp_id);
     }
 
     if (typeof params.pdbx_PDB_ins_code !== 'undefined') {
-        props.push(Queries.props.residue.pdbx_PDB_ins_code);
+        props.push(Props.residue.pdbx_PDB_ins_code);
         values.push(params.pdbx_PDB_ins_code);
     }
 
-- 
GitLab