Browse Source

added support for cyclic atomic polymers

Alexander Rose 6 years ago
parent
commit
40c82387e4

+ 3 - 4
src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts

@@ -25,8 +25,7 @@ import { getElementLoci, markElement } from './util/element';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { Segmentation, SortedArray } from 'mol-data/int';
 import { MoleculeType, isNucleic, isPurinBase, isPyrimidineBase } from 'mol-model/structure/model/types';
-import { getElementIndexForAtomId } from 'mol-model/structure/util';
-import { getElementIndexForResidueTypeAtomId } from './util/polymer';
+import { getElementIndexForAtomId, getElementIndexForAtomRole } from 'mol-model/structure/util';
 
 async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
@@ -77,7 +76,7 @@ async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?:
                     idx3 = getElementIndexForAtomId(model, residueIndex, 'C6')
                     idx4 = getElementIndexForAtomId(model, residueIndex, 'C2')
                     idx5 = getElementIndexForAtomId(model, residueIndex, 'N9')
-                    idx6 = getElementIndexForResidueTypeAtomId(model, residueIndex, 'trace')
+                    idx6 = getElementIndexForAtomRole(model, residueIndex, 'trace')
                 } else if (isPyrimidineBase(compId)) {
                     height = 3.0
                     idx1 = getElementIndexForAtomId(model, residueIndex, 'N3')
@@ -85,7 +84,7 @@ async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?:
                     idx3 = getElementIndexForAtomId(model, residueIndex, 'C4')
                     idx4 = getElementIndexForAtomId(model, residueIndex, 'C2')
                     idx5 = getElementIndexForAtomId(model, residueIndex, 'N1')
-                    idx6 = getElementIndexForResidueTypeAtomId(model, residueIndex, 'trace')
+                    idx6 = getElementIndexForAtomRole(model, residueIndex, 'trace')
                 }
 
                 if (idx1 !== -1 && idx2 !== -1 && idx3 !== -1 && idx4 !== -1 && idx5 !== -1 && idx6 !== -1) {

+ 65 - 75
src/mol-geo/representation/structure/visual/util/polymer.ts

@@ -4,14 +4,14 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit, StructureElement, Model, ElementIndex, ResidueIndex } from 'mol-model/structure';
+import { Unit, StructureElement, ElementIndex, ResidueIndex } from 'mol-model/structure';
 import { Segmentation, OrderedSet, Interval, SortedArray } from 'mol-data/int';
-import { MoleculeType, SecondaryStructureType } from 'mol-model/structure/model/types';
+import { MoleculeType, SecondaryStructureType, AtomRole } from 'mol-model/structure/model/types';
 import Iterator from 'mol-data/iterator';
 import { Vec3 } from 'mol-math/linear-algebra';
 import SortedRanges from 'mol-data/int/sorted-ranges';
 import { CoarseSphereConformation, CoarseGaussianConformation } from 'mol-model/structure/model/properties/coarse';
-import { getElementIndexForAtomId, getMoleculeType } from 'mol-model/structure/util';
+import { getMoleculeType, getElementIndexForAtomRole } from 'mol-model/structure/util';
 
 export function getPolymerRanges(unit: Unit): SortedRanges<ElementIndex> {
     switch (unit.kind) {
@@ -68,34 +68,7 @@ export function getPolymerGapCount(unit: Unit) {
     return count
 }
 
-export function getResidueTypeAtomId(moleculeType: MoleculeType, atomType: 'trace' | 'direction') {
-    switch (moleculeType) {
-        case MoleculeType.protein:
-            switch (atomType) {
-                case 'trace': return 'CA'
-                case 'direction': return 'O'
-            }
-            break
-        case MoleculeType.RNA:
-            switch (atomType) {
-                case 'trace': return 'C4\''
-                case 'direction': return 'C3\''
-            }
-            break
-        case MoleculeType.DNA:
-            switch (atomType) {
-                case 'trace': return 'C3\''
-                case 'direction': return 'C1\''
-            }
-            break
-    }
-    return ''
-}
 
-export function getElementIndexForResidueTypeAtomId(model: Model, rI: ResidueIndex, atomType: 'trace' | 'direction') {
-    const atomId = getResidueTypeAtomId(getMoleculeType(model, rI), atomType)
-    return getElementIndexForAtomId(model, rI, atomId)
-}
 
 /** Iterates over consecutive pairs of residues/coarse elements in polymers */
 export function PolymerBackboneIterator(unit: Unit): Iterator<PolymerBackbonePair> {
@@ -119,21 +92,22 @@ function createPolymerBackbonePair (unit: Unit) {
     }
 }
 
-const enum AtomicPolymerBackboneIteratorState { nextPolymer, firstResidue, nextResidue }
+const enum AtomicPolymerBackboneIteratorState { nextPolymer, firstResidue, nextResidue, cycle }
 
 export class AtomicPolymerBackboneIterator implements Iterator<PolymerBackbonePair> {
     private value: PolymerBackbonePair
     private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex>
     private residueIt: Segmentation.SegmentIterator<ResidueIndex>
     private state: AtomicPolymerBackboneIteratorState = AtomicPolymerBackboneIteratorState.nextPolymer
+    private residueSegment: Segmentation.Segment<ResidueIndex>
     hasNext: boolean = false;
 
-    private getElementIndex(residueIndex: ResidueIndex, atomType: 'trace' | 'direction') {
-        const index = getElementIndexForResidueTypeAtomId(this.unit.model, residueIndex, atomType)
+    private getElementIndex(residueIndex: ResidueIndex, atomRole: AtomRole) {
+        const index = getElementIndexForAtomRole(this.unit.model, residueIndex, atomRole)
         // TODO handle case when it returns -1
         const elementIndex = SortedArray.indexOf(this.unit.elements, index) as ElementIndex
         if (elementIndex === -1) {
-            console.log('-1', residueIndex, atomType, index)
+            console.log('-1', residueIndex, atomRole, index)
         }
         return elementIndex === -1 ? 0 as ElementIndex : elementIndex
     }
@@ -141,10 +115,10 @@ export class AtomicPolymerBackboneIterator implements Iterator<PolymerBackbonePa
     move() {
         if (this.state === AtomicPolymerBackboneIteratorState.nextPolymer) {
             while (this.polymerIt.hasNext) {
-                const residueSegment = this.polymerIt.move()
-                this.residueIt.setSegment(residueSegment);
+                this.residueIt.setSegment(this.polymerIt.move());
                 if (this.residueIt.hasNext) {
-                    this.value.centerB.element = this.getElementIndex(this.residueIt.move().index, 'trace')
+                    this.residueSegment = this.residueIt.move()
+                    this.value.centerB.element = this.getElementIndex(this.residueSegment.index, 'trace')
                     this.state = AtomicPolymerBackboneIteratorState.nextResidue
                     break
                 }
@@ -152,15 +126,26 @@ export class AtomicPolymerBackboneIterator implements Iterator<PolymerBackbonePa
         }
 
         if (this.state === AtomicPolymerBackboneIteratorState.nextResidue) {
+            this.residueSegment = this.residueIt.move()
             this.value.centerA.element = this.value.centerB.element
-            this.value.centerB.element = this.getElementIndex(this.residueIt.move().index, 'trace')
+            this.value.centerB.element = this.getElementIndex(this.residueSegment.index, 'trace')
             if (!this.residueIt.hasNext) {
-                // TODO need to advance to a polymer that has two or more residues (can't assume it has)
-                this.state = AtomicPolymerBackboneIteratorState.nextPolymer
+                if (this.unit.model.atomicHierarchy.cyclicPolymerMap.has(this.residueSegment.index)) {
+                    this.state = AtomicPolymerBackboneIteratorState.cycle
+                } else {
+                    // TODO need to advance to a polymer that has two or more residues (can't assume it has)
+                    this.state = AtomicPolymerBackboneIteratorState.nextPolymer
+                }
             }
+        } else if (this.state === AtomicPolymerBackboneIteratorState.cycle) {
+            const { cyclicPolymerMap } = this.unit.model.atomicHierarchy
+            this.value.centerA.element = this.value.centerB.element
+            this.value.centerB.element = this.getElementIndex(cyclicPolymerMap.get(this.residueSegment.index)!, 'trace')
+            // TODO need to advance to a polymer that has two or more residues (can't assume it has)
+            this.state = AtomicPolymerBackboneIteratorState.nextPolymer
         }
 
-        this.hasNext = this.residueIt.hasNext || this.polymerIt.hasNext
+        this.hasNext = this.residueIt.hasNext || this.polymerIt.hasNext || this.state === AtomicPolymerBackboneIteratorState.cycle
         return this.value;
     }
 
@@ -243,12 +228,12 @@ export class AtomicPolymerGapIterator implements Iterator<PolymerGapPair> {
     private gapIt: SortedRanges.Iterator<ElementIndex, ResidueIndex>
     hasNext: boolean = false;
 
-    private getElementIndex(residueIndex: ResidueIndex, atomType: 'trace' | 'direction') {
-        const index = getElementIndexForResidueTypeAtomId(this.unit.model, residueIndex, atomType)
+    private getElementIndex(residueIndex: ResidueIndex, atomRole: AtomRole) {
+        const index = getElementIndexForAtomRole(this.unit.model, residueIndex, atomRole)
         // TODO handle case when it returns -1
         const elementIndex = SortedArray.indexOf(this.unit.elements, index) as ElementIndex
         if (elementIndex === -1) {
-            console.log('-1', residueIndex, atomType, index)
+            console.log('-1', residueIndex, atomRole, index)
         }
         return elementIndex === -1 ? 0 as ElementIndex : elementIndex
     }
@@ -363,22 +348,38 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
         this.residueSegmentMax = index[this.unit.elements[polymerSegment.end - 1]]
     }
 
-    private getAtomIndex(residueIndex: number, atomType: 'trace' | 'direction') {
-        const index = Math.min(Math.max(this.residueSegmentMin, residueIndex), this.residueSegmentMax)
-        return getElementIndexForResidueTypeAtomId(this.unit.model, index as ResidueIndex, atomType)
+    private getAtomIndex(residueIndex: ResidueIndex, atomRole: AtomRole) {
+        const { cyclicPolymerMap } = this.unit.model.atomicHierarchy
+        if (residueIndex < this.residueSegmentMin) {
+            const cyclicIndex = cyclicPolymerMap.get(this.residueSegmentMin)
+            if (cyclicIndex !== undefined) {
+
+                residueIndex = cyclicIndex - (this.residueSegmentMin - residueIndex - 1) as ResidueIndex
+            } else {
+                residueIndex = this.residueSegmentMin
+            }
+        } else if (residueIndex > this.residueSegmentMax) {
+            const cyclicIndex = cyclicPolymerMap.get(this.residueSegmentMax)
+            if (cyclicIndex !== undefined) {
+                residueIndex = cyclicIndex + (residueIndex - this.residueSegmentMax - 1) as ResidueIndex
+            } else {
+                residueIndex = this.residueSegmentMax
+            }
+        }
+        return getElementIndexForAtomRole(this.unit.model, residueIndex as ResidueIndex, atomRole)
     }
 
-    private getElementIndex(residueIndex: number, atomType: 'trace' | 'direction') {
-        const index = this.getAtomIndex(residueIndex, atomType)
+    private getElementIndex(residueIndex: ResidueIndex, atomRole: AtomRole) {
+        const index = this.getAtomIndex(residueIndex, atomRole)
         // TODO handle case when it returns -1
         const elementIndex = SortedArray.indexOf(this.unit.elements, index) as ElementIndex
         if (elementIndex === -1) {
-            console.log('-1', residueIndex, atomType, index)
+            console.log('-1', residueIndex, atomRole, index)
         }
         return elementIndex === -1 ? 0 as ElementIndex : elementIndex
     }
 
-    private setControlPoint(out: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, residueIndex: number) {
+    private setControlPoint(out: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, residueIndex: ResidueIndex) {
         const ss = this.unit.model.properties.secondaryStructure.type[residueIndex]
         if (SecondaryStructureType.is(ss, SecondaryStructureType.Flag.Beta)) {
             Vec3.scale(out, Vec3.add(out, p1, Vec3.add(out, p3, Vec3.add(out, p2, p2))), 1/4)
@@ -387,15 +388,6 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
         }
     }
 
-    // private setDirectionVector(out: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, residueIndex: number) {
-    //     const ss = this.unit.model.properties.secondaryStructure.type[residueIndex]
-    //     if (SecondaryStructureType.is(ss, SecondaryStructureType.Flag.Beta)) {
-    //         Vec3.scale(out, Vec3.add(out, p1, Vec3.add(out, p2, p3)), 1/3)
-    //     } else {
-    //         Vec3.copy(out, p2)
-    //     }
-    // }
-
     move() {
         const { residueIt, polymerIt, value } = this
 
@@ -415,29 +407,27 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
             const { index: residueIndex } = residueIt.move();
             value.center.element = this.getElementIndex(residueIndex, 'trace')
 
-            this.pos(this.p0, this.getAtomIndex(residueIndex - 3, 'trace'))
-            this.pos(this.p1, this.getAtomIndex(residueIndex - 2, 'trace'))
-            this.pos(this.p2, this.getAtomIndex(residueIndex - 1, 'trace'))
+            this.pos(this.p0, this.getAtomIndex(residueIndex - 3 as ResidueIndex, 'trace'))
+            this.pos(this.p1, this.getAtomIndex(residueIndex - 2 as ResidueIndex, 'trace'))
+            this.pos(this.p2, this.getAtomIndex(residueIndex - 1 as ResidueIndex, 'trace'))
             this.pos(this.p3, this.getAtomIndex(residueIndex, 'trace'))
-            this.pos(this.p4, this.getAtomIndex(residueIndex + 1, 'trace'))
-            this.pos(this.p5, this.getAtomIndex(residueIndex + 2, 'trace'))
-            this.pos(this.p6, this.getAtomIndex(residueIndex + 3, 'trace'))
+            this.pos(this.p4, this.getAtomIndex(residueIndex + 1 as ResidueIndex, 'trace'))
+            this.pos(this.p5, this.getAtomIndex(residueIndex + 2 as ResidueIndex, 'trace'))
+            this.pos(this.p6, this.getAtomIndex(residueIndex + 3 as ResidueIndex, 'trace'))
 
-            // this.pos(this.v01, this.getAtomIndex(residueIndex - 2, 'direction'))
-            this.pos(this.v12, this.getAtomIndex(residueIndex - 1, 'direction'))
+            // this.pos(this.v01, this.getAtomIndex(residueIndex - 2 as ResidueIndex, 'direction'))
+            this.pos(this.v12, this.getAtomIndex(residueIndex - 1 as ResidueIndex, 'direction'))
             this.pos(this.v23, this.getAtomIndex(residueIndex, 'direction'))
-            // this.pos(this.v34, this.getAtomIndex(residueIndex + 1, 'direction'))
+            // this.pos(this.v34, this.getAtomIndex(residueIndex + 1 as ResidueIndex, 'direction'))
 
             this.value.secStrucType = this.unit.model.properties.secondaryStructure.type[residueIndex]
 
-            this.setControlPoint(value.t0, this.p0, this.p1, this.p2, residueIndex - 2)
-            this.setControlPoint(value.t1, this.p1, this.p2, this.p3, residueIndex - 1)
+            this.setControlPoint(value.t0, this.p0, this.p1, this.p2, residueIndex - 2 as ResidueIndex)
+            this.setControlPoint(value.t1, this.p1, this.p2, this.p3, residueIndex - 1 as ResidueIndex)
             this.setControlPoint(value.t2, this.p2, this.p3, this.p4, residueIndex)
-            this.setControlPoint(value.t3, this.p3, this.p4, this.p5, residueIndex + 1)
-            this.setControlPoint(value.t4, this.p4, this.p5, this.p6, residueIndex + 2)
+            this.setControlPoint(value.t3, this.p3, this.p4, this.p5, residueIndex + 1 as ResidueIndex)
+            this.setControlPoint(value.t4, this.p4, this.p5, this.p6, residueIndex + 2 as ResidueIndex)
 
-            // this.setDirectionVector(value.d12, this.v01, this.v12, this.v23, residueIndex)
-            // this.setDirectionVector(value.d23, this.v12, this.v23, this.v34, residueIndex + 1)
             Vec3.copy(value.d12, this.v12)
             Vec3.copy(value.d23, this.v23)
 

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

@@ -91,17 +91,15 @@ export function getAtomicHierarchyAndConformation(format: mmCIF_Format, atom_sit
         };
     }
 
+    const conformation = getConformation(atom_site)
+
     const hierarchySegments: AtomicSegments = {
         residueAtomSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, Interval.ofBounds(0, atom_site._rowCount)),
         chainAtomSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, Interval.ofBounds(0, atom_site._rowCount)),
     }
 
     const hierarchyKeys = getAtomicKeys(hierarchyData, entities, hierarchySegments);
-    const hierarchyRanges = getAtomicRanges(hierarchyData, hierarchySegments, formatData.chemicalComponentMap);
+    const hierarchyRanges = getAtomicRanges(hierarchyData, hierarchySegments, conformation, formatData.chemicalComponentMap);
     const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments, ...hierarchyRanges };
-    return {
-        sameAsPrevious: false,
-        hierarchy,
-        conformation: getConformation(atom_site)
-    };
+    return { sameAsPrevious: false, hierarchy, conformation };
 }

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

@@ -83,6 +83,7 @@ export interface AtomicKeys {
 export interface AtomicRanges {
     polymerRanges: SortedRanges<ElementIndex>
     gapRanges: SortedRanges<ElementIndex>
+    cyclicPolymerMap: Map<ResidueIndex, ResidueIndex>
 }
 
 type _Hierarchy = AtomicData & AtomicSegments & AtomicKeys & AtomicRanges

+ 49 - 6
src/mol-model/structure/model/properties/utils/atomic-ranges.ts

@@ -8,15 +8,51 @@ import { AtomicSegments } from '../atomic';
 import { AtomicData, AtomicRanges } from '../atomic/hierarchy';
 import { Segmentation, Interval } from 'mol-data/int';
 import SortedRanges from 'mol-data/int/sorted-ranges';
-import { ChemicalComponent } from '../chemical-component';
+import { ChemicalComponentMap } from '../chemical-component';
 import { MoleculeType, isPolymer } from '../../types';
-import { ElementIndex } from '../../indexing';
+import { ElementIndex, ResidueIndex } from '../../indexing';
+import { getAtomIdForAtomRole } from '../../../util';
+import { AtomicConformation } from '../atomic/conformation';
+import { Vec3 } from 'mol-math/linear-algebra';
 
 // TODO add gaps at the ends of the chains by comparing to the polymer sequence data
 
-export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, chemicalComponentMap: Map<string, ChemicalComponent>): AtomicRanges {
+function getMoleculeType(compId: string, chemicalComponentMap: ChemicalComponentMap) {
+    const cc = chemicalComponentMap.get(compId)
+    return cc ? cc.moleculeType : MoleculeType.unknown
+}
+
+function getElementIndexForAtomId(rI: ResidueIndex, atomId: string, data: AtomicData, segments: AtomicSegments, ): ElementIndex {
+    const { offsets } = segments.residueAtomSegments
+    const { label_atom_id } = data.atoms
+    for (let j = offsets[rI], _j = offsets[rI + 1]; j < _j; j++) {
+        if (label_atom_id.value(j) === atomId) return j as ElementIndex
+    }
+    return offsets[rI] as ElementIndex
+}
+
+function areBackboneConnected(riStart: ResidueIndex, riEnd: ResidueIndex, data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, chemicalComponentMap: ChemicalComponentMap) {
+    const { label_comp_id } = data.residues
+    const mtStart = getMoleculeType(label_comp_id.value(riStart), chemicalComponentMap)
+    const mtEnd = getMoleculeType(label_comp_id.value(riEnd), chemicalComponentMap)
+    if (!isPolymer(mtStart) || !isPolymer(mtEnd)) return false
+
+    const startId = getAtomIdForAtomRole(mtStart, 'backboneStart')
+    const endId = getAtomIdForAtomRole(mtEnd, 'backboneEnd')
+
+    const eiStart = getElementIndexForAtomId(riStart, startId, data, segments)
+    const eiEnd = getElementIndexForAtomId(riEnd, endId, data, segments)
+
+    const { x, y, z } = conformation
+    const pStart = Vec3.create(x[eiStart], y[eiStart], z[eiStart])
+    const pEnd = Vec3.create(x[eiEnd], y[eiEnd], z[eiEnd])
+    return Vec3.distance(pStart, pEnd) < 2
+}
+
+export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, chemicalComponentMap: ChemicalComponentMap): AtomicRanges {
     const polymerRanges: number[] = []
     const gapRanges: number[] = []
+    const cyclicPolymerMap = new Map<ResidueIndex, ResidueIndex>()
     const chainIt = Segmentation.transientSegments(segments.chainAtomSegments, Interval.ofBounds(0, data.atoms._rowCount))
     const residueIt = Segmentation.transientSegments(segments.residueAtomSegments, Interval.ofBounds(0, data.atoms._rowCount))
     const { label_seq_id, label_comp_id } = data.residues
@@ -34,11 +70,17 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, chem
         prevEnd = -1
         startIndex = -1
 
+        const riStart = segments.residueAtomSegments.index[chainSegment.start]
+        const riEnd = segments.residueAtomSegments.index[chainSegment.end - 1]
+        if (areBackboneConnected(riStart, riEnd, data, segments, conformation, chemicalComponentMap)) {
+            cyclicPolymerMap.set(riStart, riEnd)
+            cyclicPolymerMap.set(riEnd, riStart)
+        }
+
         while (residueIt.hasNext) {
             const residueSegment = residueIt.move();
             const residueIndex = residueSegment.index
-            const cc = chemicalComponentMap.get(label_comp_id.value(residueIndex))
-            const moleculeType = cc ? cc.moleculeType : MoleculeType.unknown
+            const moleculeType = getMoleculeType(label_comp_id.value(residueIndex), chemicalComponentMap)
             const seqId = label_seq_id.value(residueIndex)
             if (isPolymer(moleculeType)) {
                 if (startIndex !== -1) {
@@ -67,6 +109,7 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, chem
 
     return {
         polymerRanges: SortedRanges.ofSortedRanges(polymerRanges as ElementIndex[]),
-        gapRanges: SortedRanges.ofSortedRanges(gapRanges as ElementIndex[])
+        gapRanges: SortedRanges.ofSortedRanges(gapRanges as ElementIndex[]),
+        cyclicPolymerMap
     }
 }

+ 39 - 0
src/mol-model/structure/model/types.ts

@@ -54,6 +54,45 @@ export const enum MoleculeType {
     saccharide
 }
 
+const AtomRole = {
+    trace: '', direction: '', backboneStart: '', backboneEnd: ''
+}
+export type AtomRole = keyof typeof AtomRole
+
+export const MoleculeTypeAtomRoleId: { [k: number]: { [k in AtomRole]: string } } = {
+    [MoleculeType.protein]: {
+        trace: 'CA', // TODO 'BB'
+        direction: 'O', // TODO 'OC1', 'O1', 'OX1', 'OXT'
+        backboneStart: 'N',
+        backboneEnd: 'C'
+    },
+    [MoleculeType.RNA]: {
+        trace: 'C4\'', // TODO 'C4*'
+        direction: 'C3\'', // 'C3*'
+        backboneStart: 'P',
+        backboneEnd: 'O3\'' // TODO 'O3*'
+    },
+    [MoleculeType.DNA]: {
+        trace: 'C3\'', // TODO 'C3*'
+        direction: 'C1\'', // TODO 'C1*'
+        backboneStart: 'P',
+        backboneEnd: 'O3\'' // TODO 'O3*'
+    }
+}
+
+export const ProteinBackboneAtoms = [
+    'CA', 'C', 'N', 'O',
+    'O1', 'O2', 'OC1', 'OC2', 'OX1', 'OXT',
+    'H', 'H1', 'H2', 'H3', 'HA', 'HN',
+    'BB'
+]
+
+export const NucleicBackboneAtoms = [
+    'P', 'OP1', 'OP2',
+    'O2\'', 'O3\'', 'O4\'', 'O5\'', 'C1\'', 'C2\'', 'C3\'', 'C4\'', 'C5\'',
+    'O2*', 'O3*', 'O4*', 'O5*', 'C1*', 'C2*', 'C3*', 'C4*', 'C5*'
+]
+
 /** Chemical component types as defined in the mmCIF CCD */
 export enum ComponentType {
     // protein

+ 15 - 1
src/mol-model/structure/util.ts

@@ -5,7 +5,7 @@
  */
 
 import { Model, ResidueIndex, ElementIndex } from './model';
-import { MoleculeType } from './model/types';
+import { MoleculeType, AtomRole, MoleculeTypeAtomRoleId } from './model/types';
 
 export function getMoleculeType(model: Model, rI: ResidueIndex) {
     const compId = model.atomicHierarchy.residues.label_comp_id.value(rI)
@@ -14,6 +14,15 @@ export function getMoleculeType(model: Model, rI: ResidueIndex) {
     return cc ? cc.moleculeType : MoleculeType.unknown
 }
 
+export function getAtomIdForAtomRole(moleculeType: MoleculeType, atomRole: AtomRole) {
+    const m = MoleculeTypeAtomRoleId[moleculeType]
+    if (m !== undefined) {
+        const a = m[atomRole]
+        if (a !== undefined) return a
+    }
+    return ''
+}
+
 export function getElementIndexForAtomId(model: Model, rI: ResidueIndex, atomId: string): ElementIndex {
     const { offsets } = model.atomicHierarchy.residueAtomSegments
     const { label_atom_id } = model.atomicHierarchy.atoms
@@ -23,6 +32,11 @@ export function getElementIndexForAtomId(model: Model, rI: ResidueIndex, atomId:
     return offsets[rI] as ElementIndex
 }
 
+export function getElementIndexForAtomRole(model: Model, rI: ResidueIndex, atomRole: AtomRole) {
+    const atomId = getAtomIdForAtomRole(getMoleculeType(model, rI), atomRole)
+    return getElementIndexForAtomId(model, rI, atomId)
+}
+
 export function residueLabel(model: Model, rI: number) {
     const { residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy
     const { label_comp_id, label_seq_id } = residues

+ 2 - 2
src/mol-view/stage.ts

@@ -83,7 +83,7 @@ export class Stage {
         // this.loadPdbid('2np2') // dna
         // this.loadPdbid('1d66') // dna
         // this.loadPdbid('9dna') // A form dna
-        this.loadPdbid('1bna') // B form dna
+        // this.loadPdbid('1bna') // B form dna
         // this.loadPdbid('199d') // C form dna
         // this.loadPdbid('4lb6') // Z form dna
         // this.loadPdbid('1egk') // 4-way dna-rna junction
@@ -91,7 +91,7 @@ export class Stage {
         // this.loadPdbid('1xv6') // rna, modified nucleotides
         // this.loadPdbid('3bbm') // rna with linker
         // this.loadPdbid('1gfl') // GFP, flourophore has carbonyl oxygen removed
-        // this.loadPdbid('1sfi') // contains cyclic peptid
+        this.loadPdbid('1sfi') // contains cyclic peptid
         // this.loadPdbid('3sn6') // discontinuous chains
         // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`)
         // this.loadMmcifUrl(`../../examples/1cbs_updated.cif`)