Browse Source

Merge branch 'master' into cartoon-repr

Alexander Rose 6 years ago
parent
commit
270d1b91da

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

@@ -13,8 +13,6 @@ import { Model, Structure, Element, Unit, Format, StructureProperties } from 'mo
 // import { Run, Progress } from 'mol-task'
 import { OrderedSet } from 'mol-data/int';
 import { openCif, downloadCif } from './helpers';
-import { BitFlags } from 'mol-util';
-import { SecondaryStructureType } from 'mol-model/structure/model/types';
 import { UnitRings } from 'mol-model/structure/structure/unit/rings';
 import { Vec3 } from 'mol-math/linear-algebra';
 
@@ -51,7 +49,7 @@ export function residueLabel(model: Model, rI: number) {
 export function printSecStructure(model: Model) {
     console.log('\nSecondary Structure\n=============');
     const { residues } = model.atomicHierarchy;
-    const { type, key } = model.properties.secondaryStructure;
+    const { key, elements } = model.properties.secondaryStructure;
 
     const count = residues._rowCount;
     let rI = 0;
@@ -60,12 +58,8 @@ export function printSecStructure(model: Model) {
         while (rI < count && key[start] === key[rI]) rI++;
         rI--;
 
-        if (BitFlags.has(type[start], SecondaryStructureType.Flag.Beta)) {
-            console.log(`Sheet: ${residueLabel(model, start)} - ${residueLabel(model, rI)} (key ${key[start]})`);
-        } else if (BitFlags.has(type[start], SecondaryStructureType.Flag.Helix)) {
-            console.log(`Helix: ${residueLabel(model, start)} - ${residueLabel(model, rI)} (key ${key[start]})`);
-        }
-
+        const e = elements[key[start]];
+        if (e.kind !== 'none') console.log(`${e.kind}: ${residueLabel(model, start)} - ${residueLabel(model, rI)}`);
         rI++;
     }
 }

+ 8 - 4
src/mol-io/writer/cif/encoder.ts

@@ -21,7 +21,7 @@ import { ArrayEncoder, ArrayEncoding } from '../../common/binary-cif';
 export interface Field<Key = any, Data = any> {
     name: string,
     type: Field.Type,
-    value(key: Key, data: Data): string | number
+    value(key: Key, data: Data, index: number): string | number
     valueKind?: (key: Key, data: Data) => Column.ValueKind,
     defaultFormat?: Field.Format,
     shouldInclude?: (data: Data) => boolean
@@ -38,11 +38,11 @@ export namespace Field {
 
     export type ParamsBase<K, D> = { valueKind?: (k: K, d: D) => Column.ValueKind, encoder?: ArrayEncoder, shouldInclude?: (data: D) => boolean }
 
-    export function str<K, D = any>(name: string, value: (k: K, d: D) => string, params?: ParamsBase<K, D>): Field<K, D> {
+    export function str<K, D = any>(name: string, value: (k: K, d: D, index: number) => string, params?: ParamsBase<K, D>): Field<K, D> {
         return { name, type: Type.Str, value, valueKind: params && params.valueKind, defaultFormat: params && params.encoder ? { encoder: params.encoder } : void 0, shouldInclude: params && params.shouldInclude };
     }
 
-    export function int<K, D = any>(name: string, value: (k: K, d: D) => number, params?:  ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }): Field<K, D> {
+    export function int<K, D = any>(name: string, value: (k: K, d: D, index: number) => number, params?:  ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }): Field<K, D> {
         return {
             name,
             type: Type.Int,
@@ -53,7 +53,7 @@ export namespace Field {
         };
     }
 
-    export function float<K, D = any>(name: string, value: (k: K, d: D) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }): Field<K, D> {
+    export function float<K, D = any>(name: string, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }): Field<K, D> {
         return {
             name,
             type: Type.Float,
@@ -63,6 +63,10 @@ export namespace Field {
             shouldInclude: params && params.shouldInclude
         };
     }
+
+    export function index(name: string) {
+        return int(name, (e, d, i) => i + 1, { typedArray: Int32Array, encoder: ArrayEncoding.by(ArrayEncoding.delta).and(ArrayEncoding.runLength).and(ArrayEncoding.integerPacking) })
+    }
 }
 
 export interface Category<Key = any, Data = any> {

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

@@ -141,7 +141,7 @@ function encodeField(field: Field, data: { data: any, keys: () => Iterator<any>
                 allPresent = false;
             } else {
                 mask[offset] = Column.ValueKind.Present;
-                array[offset] = getter(key, d);
+                array[offset] = getter(key, d, offset);
             }
             offset++;
         }

+ 6 - 4
src/mol-io/writer/cif/encoder/text.ts

@@ -73,14 +73,14 @@ export default class TextEncoder implements Encoder<string> {
     }
 }
 
-function writeValue(builder: StringBuilder, data: any, key: any, f: Field<any, any>, floatPrecision: number): boolean {
+function writeValue(builder: StringBuilder, data: any, key: any, f: Field<any, any>, floatPrecision: number, index: number): boolean {
     const kind = f.valueKind;
     const p = kind ? kind(key, data) : Column.ValueKind.Present;
     if (p !== Column.ValueKind.Present) {
         if (p === Column.ValueKind.NotPresent) writeNotPresent(builder);
         else writeUnknown(builder);
     } else {
-        const val = f.value(key, data);
+        const val = f.value(key, data, index);
         const t = f.type;
         if (t === Field.Type.Str) {
             if (isMultiline(val as string)) {
@@ -127,7 +127,7 @@ function writeCifSingleRecord(category: Category<any>, builder: StringBuilder, f
         if (!filter.includeField(category.name, f.name)) continue;
 
         StringBuilder.writePadRight(builder, `_${category.name}.${f.name}`, width);
-        const multiline = writeValue(builder, data, key, f, precisions[_f]);
+        const multiline = writeValue(builder, data, key, f, precisions[_f], 0);
         if (!multiline) StringBuilder.newline(builder);
     }
     StringBuilder.write(builder, '#\n');
@@ -147,6 +147,7 @@ function writeCifLoop(categories: Category[], builder: StringBuilder, filter: Ca
         writeLine(builder, `_${first.name}.${fields[i].name}`);
     }
 
+    let index = 0;
     for (let _c = 0; _c < categories.length; _c++) {
         const category = categories[_c];
         const data = category.data;
@@ -159,9 +160,10 @@ function writeCifLoop(categories: Category[], builder: StringBuilder, filter: Ca
 
             let multiline = false;
             for (let _f = 0; _f < fieldCount; _f++) {
-                multiline = writeValue(builder, data, key, fields[_f], precisions[_f]);
+                multiline = writeValue(builder, data, key, fields[_f], precisions[_f], index);
             }
             if (!multiline) StringBuilder.newline(builder);
+            index++;
         }
     }
     StringBuilder.write(builder, '#\n');

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

@@ -14,7 +14,7 @@ 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.index('id'),
     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),

+ 120 - 0
src/mol-model/structure/export/categories/secondary-structure.ts

@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Segmentation } from 'mol-data/int';
+import { CifWriter } from 'mol-io/writer/cif';
+import { SecondaryStructure } from '../../model/properties/seconday-structure';
+import { Element, Unit, StructureProperties as P } from '../../structure';
+import { CifExportContext } from '../mmcif';
+
+import CifField = CifWriter.Field
+import CifCategory = CifWriter.Category
+import { Column } from 'mol-data/db';
+
+export function _struct_conf(ctx: CifExportContext): CifCategory {
+    const elements = findElements(ctx, 'helix');
+    return {
+        data: elements,
+        name: 'struct_conf',
+        fields: struct_conf_fields,
+        rowCount: elements.length
+    };
+}
+
+export function _struct_sheet_range(ctx: CifExportContext): CifCategory {
+    const elements = findElements(ctx, 'sheet');
+    return {
+        data: elements,
+        name: 'struct_sheet_range',
+        fields: struct_sheet_range_fields,
+        rowCount: elements.length
+    };
+}
+
+const struct_conf_fields: CifField[] = [
+    CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('conf_type_id', (i, data) => data[i].element.type_id),
+    CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('conf_type_id', (i, data, idx) => `${data[i].element.type_id}${idx + 1}`),
+    ...residueIdFields('beg_', e => e.start),
+    ...residueIdFields('end_', e => e.end),
+    CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('pdbx_PDB_helix_class', (i, data) => data[i].element.helix_class),
+    CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('details', (i, data) => data[i].element.details || '', {
+        valueKind: (i, d) => !!d[i].element.details ? Column.ValueKind.Present : Column.ValueKind.Unknown
+    }),
+    CifField.int<number, SSElement<SecondaryStructure.Helix>[]>('pdbx_PDB_helix_class', (i, data) => data[i].length)
+];
+
+const struct_sheet_range_fields: CifField[] = [
+    CifField.index('id'),
+    CifField.str<number, SSElement<SecondaryStructure.Sheet>[]>('sheet_id', (i, data) => data[i].element.sheet_id),
+    ...residueIdFields('beg_', e => e.start),
+    ...residueIdFields('end_', e => e.end),
+    CifField.str('symmetry', (i, data) => '', { valueKind: (i, d) => Column.ValueKind.Unknown })
+];
+
+function residueIdFields(prefix: string, loc: (e: SSElement<any>) => Element.Location): CifField<number, SSElement<SecondaryStructure.Helix>[]>[] {
+    return [
+        CifField.str(`${prefix}label_comp_id`, (i, d) => P.residue.label_comp_id(loc(d[i]))),
+        CifField.int(`${prefix}label_seq_id`, (i, d) => P.residue.label_seq_id(loc(d[i]))),
+        CifField.str(`pdbx_${prefix}PDB_ins_code`, (i, d) => P.residue.pdbx_PDB_ins_code(loc(d[i]))),
+        CifField.str(`${prefix}label_asym_id`, (i, d) => P.chain.label_asym_id(loc(d[i]))),
+        CifField.str(`${prefix}_entity_id`, (i, d) => P.chain.label_entity_id(loc(d[i]))),
+        CifField.str(`${prefix}auth_comp_id`, (i, d) => P.residue.auth_comp_id(loc(d[i]))),
+        CifField.int(`${prefix}auth_seq_id`, (i, d) => P.residue.auth_seq_id(loc(d[i]))),
+        CifField.str(`${prefix}auth_asym_id`, (i, d) => P.chain.auth_asym_id(loc(d[i])))
+    ];
+}
+
+interface SSElement<T extends SecondaryStructure.Element> {
+    start: Element.Location,
+    end: Element.Location,
+    length: number,
+    element: T
+}
+
+function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContext, kind: SecondaryStructure.Element['kind']) {
+    const { key, elements } = ctx.model.properties.secondaryStructure;
+
+    const ssElements: SSElement<any>[] = [];
+
+    for (const unit of ctx.structure.units) {
+        // currently can only support this for "identity" operators.
+        if (!Unit.isAtomic(unit) || !unit.conformation.operator.isIdentity) continue;
+
+        const segs = unit.model.atomicHierarchy.residueSegments;
+        const residues = Segmentation.transientSegments(segs, unit.elements);
+
+        let current: Segmentation.Segment<Element>, move = true;
+        while (residues.hasNext) {
+            if (move) current = residues.move();
+
+            const start = current!.index;
+            const startIdx = key[start];
+            const element = elements[startIdx];
+            if (element.kind !== kind) {
+                move = true;
+                continue;
+            }
+
+            let prev = start;
+            while (residues.hasNext) {
+                prev = current!.index;
+                current = residues.move();
+                if (startIdx !== key[current.index]) {
+                    move = false;
+                    ssElements[ssElements.length] = {
+                        start: Element.Location(unit, segs.segments[start]),
+                        end: Element.Location(unit, segs.segments[prev]),
+                        length: prev - start + 1,
+                        element
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    return ssElements as SSElement<T>[];
+}

+ 20 - 1
src/mol-model/structure/export/mmcif.ts

@@ -17,6 +17,7 @@ export interface CifExportContext {
 }
 
 import CifCategory = CifWriter.Category
+import { _struct_conf, _struct_sheet_range } from './categories/secondary-structure';
 
 function copy_mmCif_category(name: keyof mmCIF_Schema) {
     return ({ model }: CifExportContext) => {
@@ -33,12 +34,30 @@ function _entity({ model, structure }: CifExportContext): CifCategory {
 }
 
 const Categories = [
+    // Basics
     copy_mmCif_category('entry'),
     copy_mmCif_category('exptl'),
+    _entity,
+
+    // Symmetry
     copy_mmCif_category('cell'),
     copy_mmCif_category('symmetry'),
+
+    // Assemblies
+    copy_mmCif_category('pdbx_struct_assembly'),
+    copy_mmCif_category('pdbx_struct_assembly_gen'),
+    copy_mmCif_category('pdbx_struct_oper_list'),
+
+    // Secondary structure
+    _struct_conf,
+    _struct_sheet_range,
+
+    // Misc
+    // TODO: filter for actual present residues?
     copy_mmCif_category('chem_comp'),
-    _entity,
+    copy_mmCif_category('atom_sites'),
+
+    // Atoms
     _atom_site
 ];
 

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

@@ -22,11 +22,12 @@ import { getIHMCoarse, EmptyIHMCoarse, IHMData } from './mmcif/ihm';
 import { getSecondaryStructureMmCif } from './mmcif/secondary-structure';
 import { getSequence } from './mmcif/sequence';
 import { sortAtomSite } from './mmcif/sort';
-
-import mmCIF_Format = Format.mmCIF
+import { StructConn } from './mmcif/bonds/struct_conn';
 import { ChemicalComponent } from '../properties/chemical-component';
 import { ComponentType, getMoleculeType } from '../types';
 
+import mmCIF_Format = Format.mmCIF
+
 type AtomSite = mmCIF_Database['atom_site']
 
 function getSymmetry(format: mmCIF_Format): ModelSymmetry {
@@ -192,6 +193,7 @@ function createModelIHM(format: mmCIF_Format, data: IHMData): Model {
 
 function attachProps(model: Model) {
     ComponentBond.attachFromMmCif(model);
+    StructConn.attachFromMmCif(model);
 }
 
 function findModelEnd(num: Column<number>, startIndex: number) {

+ 2 - 3
src/mol-model/structure/model/formats/mmcif/bonds/comp.ts

@@ -24,11 +24,10 @@ export namespace ComponentBond {
         cifExport: {
             categoryNames: ['chem_comp_bond'],
             categoryProvider(ctx) {
-                const comp_names = getUniqueResidueNames(ctx.structure);
                 const chem_comp_bond = getChemCompBond(ctx.model);
-
                 if (!chem_comp_bond) return [];
 
+                const comp_names = getUniqueResidueNames(ctx.structure);
                 const { comp_id, _rowCount } = chem_comp_bond;
                 const indices: number[] = [];
                 for (let i = 0; i < _rowCount; i++) {
@@ -95,7 +94,7 @@ export namespace ComponentBond {
         if (!model.customProperties.has(Descriptor)) return void 0;
         const chem_comp_bond = getChemCompBond(model);
 
-        let compBond = new ComponentBondImpl();
+        const compBond = new ComponentBondImpl();
 
         const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount: rowCount } = chem_comp_bond;
 

+ 77 - 10
src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts

@@ -6,17 +6,68 @@
  */
 
 import Model from '../../../model'
-import { Element } from '../../../../structure'
+import { Element, Structure } from '../../../../structure'
 import { LinkType } from '../../../types'
 import { findEntityIdByAsymId, findAtomIndexByLabelName } from '../util'
 import { Column } from 'mol-data/db'
+import { ModelPropertyDescriptor } from '../../../properties/custom';
+import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
+import { SortedArray } from 'mol-data/int';
+import { CifWriter } from 'mol-io/writer/cif'
 
 export interface StructConn {
-    getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry>
-    getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry>
+    getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry>,
+    getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry>,
+    readonly entries: ReadonlyArray<StructConn.Entry>
 }
 
 export namespace StructConn {
+    export const Descriptor: ModelPropertyDescriptor = {
+        isStatic: true,
+        name: 'struct_conn',
+        cifExport: {
+            categoryNames: ['struct_conn'],
+            categoryProvider(ctx) {
+                const struct_conn = getStructConn(ctx.model);
+                if (!struct_conn) return [];
+
+                const strConn = get(ctx.model);
+                if (!strConn || strConn.entries.length === 0) return [];
+
+                const foundAtoms = new Set<Element>();
+                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(ctx.structure, atom)) {
+                            foundAtoms.add(atom);
+                        } else {
+                            hasAll = false;
+                            break;
+                        }
+                    }
+                    if (hasAll) {
+                        indices[indices.length] = entry.rowIndex;
+                    }
+                }
+
+                return [
+                    () => CifWriter.Category.ofTable('struct_conn', struct_conn, indices)
+                ];
+            }
+        }
+    }
+
+    function hasAtom({ units }: Structure, element: Element) {
+        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}`;
@@ -77,6 +128,7 @@ export namespace StructConn {
     }
 
     export interface Entry {
+        rowIndex: number,
         distance: number,
         order: number,
         flags: number,
@@ -95,19 +147,33 @@ export namespace StructConn {
         | 'modres'
         | 'saltbr'
 
+    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 fromModel(model: Model): StructConn | undefined {
+    export function get(model: Model): StructConn | undefined {
         if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName];
+        if (!model.customProperties.has(Descriptor)) return void 0;
 
-        if (model.sourceData.kind !== 'mmCIF') return;
-        const { struct_conn } = model.sourceData.data;
-        if (!struct_conn._rowCount) 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_comp_id: struct_conn.ptnr1_label_comp_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,
@@ -117,6 +183,7 @@ export namespace StructConn {
             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,
+            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,
@@ -128,9 +195,9 @@ export namespace StructConn {
             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.label_comp_id.value(row),
+                ps.auth_seq_id.value(row),
                 ps.ins_code.value(row)
             );
             if (residueIndex < 0) return void 0;
@@ -182,7 +249,7 @@ export namespace StructConn {
                 case 'saltbr': flags = LinkType.Flag.Ion; break;
             }
 
-            entries.push({ flags, order, distance: pdbx_dist_value.value(i), partners });
+            entries.push({ rowIndex: i, flags, order, distance: pdbx_dist_value.value(i), partners });
         }
 
         const ret = new StructConnImpl(entries);

+ 36 - 16
src/mol-model/structure/model/formats/mmcif/secondary-structure.ts

@@ -13,13 +13,15 @@ import { Column } from 'mol-data/db';
 
 export function getSecondaryStructureMmCif(data: mmCIF_Database, hierarchy: AtomicHierarchy): SecondaryStructure {
     const map: SecondaryStructureMap = new Map();
-    addHelices(data.struct_conf, 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);
+    addSheets(data.struct_sheet_range, map, data.struct_conf._rowCount, elements);
 
     const secStruct: SecondaryStructureData = {
         type: new Int32Array(hierarchy.residues._rowCount) as any,
-        key: new Int32Array(hierarchy.residues._rowCount) as any
+        key: new Int32Array(hierarchy.residues._rowCount) as any,
+        elements
     };
 
     if (map.size > 0) assignSecondaryStructureRanges(hierarchy, map, secStruct);
@@ -35,31 +37,40 @@ type SecondaryStructureEntry = {
     key: number
 }
 type SecondaryStructureMap = Map<string, Map<number, SecondaryStructureEntry>>
-type SecondaryStructureData = { type: SecondaryStructureType[], key: number[] }
+type SecondaryStructureData = { type: SecondaryStructureType[], key: number[], elements: SecondaryStructure.Element[]  }
 
-function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap) {
+function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap, elements: SecondaryStructure.Element[]) {
     if (!cat._rowCount) return;
 
     const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat;
     const { end_label_seq_id, pdbx_end_PDB_ins_code } = cat;
-    const { pdbx_PDB_helix_class, conf_type_id } = cat;
+    const { pdbx_PDB_helix_class, conf_type_id, details } = cat;
 
     for (let i = 0, _i = cat._rowCount; i < _i; i++) {
-        const type =  pdbx_PDB_helix_class.valueKind(i) === Column.ValueKind.Present
+        const type =  SecondaryStructureType.create(pdbx_PDB_helix_class.valueKind(i) === Column.ValueKind.Present
             ? SecondaryStructureType.SecondaryStructurePdb[pdbx_PDB_helix_class.value(i)]
             : conf_type_id.valueKind(i) === Column.ValueKind.Present
             ? SecondaryStructureType.SecondaryStructureMmcif[conf_type_id.value(i)]
-            : SecondaryStructureType.Flag.NA
-
+            : SecondaryStructureType.Flag.NA);
+
+        const element: SecondaryStructure.Helix = {
+            kind: 'helix',
+            flags: type,
+            type_id: conf_type_id.valueKind(i) === Column.ValueKind.Present ? conf_type_id.value(i) : 'HELIX_P',
+            helix_class: pdbx_PDB_helix_class.value(i),
+            details: details.valueKind(i) === Column.ValueKind.Present ? details.value(i) : void 0
+        };
         const entry: SecondaryStructureEntry = {
             startSeqNumber: beg_label_seq_id.value(i),
             startInsCode: pdbx_beg_PDB_ins_code.value(i),
             endSeqNumber: end_label_seq_id.value(i),
             endInsCode: pdbx_end_PDB_ins_code.value(i),
-            type: SecondaryStructureType.create(type),
-            key: i + 1
+            type,
+            key: elements.length
         };
 
+        elements[elements.length] = element;
+
         const asymId = beg_label_asym_id.value(i)!;
         if (map.has(asymId)) {
             map.get(asymId)!.set(entry.startSeqNumber, entry);
@@ -69,7 +80,7 @@ function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap) {
     }
 }
 
-function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap, sheetCount: number) {
+function addSheets(cat: mmCIF['struct_sheet_range'], 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;
@@ -88,15 +99,25 @@ function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap,
             sheet_id_key.set(id, key);
         }
 
+        const type = SecondaryStructureType.create(SecondaryStructureType.Flag.Beta | SecondaryStructureType.Flag.BetaSheet);
+        const element: SecondaryStructure.Sheet = {
+            kind: 'sheet',
+            flags: type,
+            sheet_id: id,
+            symmetry: void 0
+        }
         const entry: SecondaryStructureEntry = {
             startSeqNumber: beg_label_seq_id.value(i),
             startInsCode: pdbx_beg_PDB_ins_code.value(i),
             endSeqNumber: end_label_seq_id.value(i),
             endInsCode: pdbx_end_PDB_ins_code.value(i),
-            type: SecondaryStructureType.create(SecondaryStructureType.Flag.Beta | SecondaryStructureType.Flag.BetaSheet),
-            key
+            type,
+            key: elements.length
         };
 
+        elements[elements.length] = element;
+
+
         const asymId = beg_label_asym_id.value(i)!;
         if (map.has(asymId)) {
             map.get(asymId)!.set(entry.startSeqNumber, entry);
@@ -110,12 +131,11 @@ function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap,
 
 function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: SecondaryStructureEntry, resStart: number, resEnd: number, data: SecondaryStructureData) {
     const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
-    const { endSeqNumber, endInsCode, type, key } = entry;
+    const { endSeqNumber, endInsCode, key, type } = entry;
 
     let rI = resStart;
     while (rI < resEnd) {
         const seqNumber = label_seq_id.value(rI);
-
         data.type[rI] = type;
         data.key[rI] = key;
 

+ 2 - 3
src/mol-model/structure/model/formats/mmcif/util.ts

@@ -17,10 +17,9 @@ export function findEntityIdByAsymId(model: Model, asymId: string) {
 }
 
 export function findAtomIndexByLabelName(model: Model, residueIndex: number, atomName: string, altLoc: string | null): Element {
-    const { segmentMap, segments } = model.atomicHierarchy.residueSegments
-    const idx = segmentMap[residueIndex]
+    const { segments } = model.atomicHierarchy.residueSegments;
     const { label_atom_id, label_alt_id } = model.atomicHierarchy.atoms;
-    for (let i = segments[idx], n = segments[idx + 1]; i <= n; ++i) {
+    for (let i = segments[residueIndex], n = segments[residueIndex + 1]; i < n; ++i) {
         if (label_atom_id.value(i) === atomName && (!altLoc || label_alt_id.value(i) === altLoc)) return i as Element;
     }
     return -1 as Element;

+ 2 - 0
src/mol-model/structure/model/properties/atomic/hierarchy.ts

@@ -48,7 +48,9 @@ export interface AtomicData {
 }
 
 export interface AtomicSegments {
+    /** Maps residueIndex to a range of atoms [segments[rI], segments[rI + 1]) */
     residueSegments: Segmentation<Element>,
+    /** Maps chainIndex to a range of atoms [segments[cI], segments[cI + 1]) */
     chainSegments: Segmentation<Element>,
     /**
      * bonded/connected stretches of polymer chains, i.e. a chain will be

+ 28 - 3
src/mol-model/structure/model/properties/seconday-structure.ts

@@ -8,10 +8,35 @@ import { SecondaryStructureType } from '../types';
 
 /** Secondary structure "indexed" by residues. */
 interface SecondaryStructure {
-    // assign flags to each residue
     readonly type: ArrayLike<SecondaryStructureType>,
-    /** unique value for each "element". This is because single sheet is speficied by multiple records. */
-    readonly key: ArrayLike<number>
+
+    /** index into the elements array */
+    readonly key: ArrayLike<number>,
+    /** indexed by key */
+    readonly elements: ReadonlyArray<SecondaryStructure.Element>
+}
+
+namespace SecondaryStructure {
+    export type Element = None | Helix | Sheet
+
+    export interface None {
+        kind: 'none'
+    }
+
+    export interface Helix {
+        kind: 'helix',
+        flags: SecondaryStructureType,
+        type_id: string, // TODO: use aliased type?
+        helix_class: string,
+        details?: string
+    }
+
+    export interface Sheet {
+        kind: 'sheet',
+        flags: SecondaryStructureType,
+        sheet_id: string,
+        symmetry?: string
+    }
 }
 
 export { SecondaryStructure }

+ 1 - 1
src/mol-model/structure/structure/unit/links/inter-compute.ts

@@ -49,7 +49,7 @@ function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkCompu
     const { type_symbol: type_symbolA, label_alt_id: label_alt_idA } = unitA.model.atomicHierarchy.atoms;
     const { type_symbol: type_symbolB, label_alt_id: label_alt_idB } = unitB.model.atomicHierarchy.atoms;
     const { lookup3d } = unitB;
-    const structConn = unitA.model === unitB.model && unitA.model.sourceData.kind === 'mmCIF' ? StructConn.fromModel(unitA.model) : void 0;
+    const structConn = unitA.model === unitB.model && unitA.model.sourceData.kind === 'mmCIF' ? StructConn.get(unitA.model) : void 0;
 
     // the lookup queries need to happen in the "unitB space".
     // that means imageA = inverseOperB(operA(aI))

+ 1 - 1
src/mol-model/structure/structure/unit/links/intra-compute.ts

@@ -35,7 +35,7 @@ function _computeBonds(unit: Unit.Atomic, params: LinkComputationParameters): In
     const { label_comp_id } = unit.model.atomicHierarchy.residues;
     const query3d = unit.lookup3d;
 
-    const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.fromModel(unit.model) : void 0;
+    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 atomA: number[] = [];