diff --git a/src/mol-model-formats/structure/mmcif/atomic.ts b/src/mol-model-formats/structure/mmcif/atomic.ts index 422d45776d1fdd1f8b89378a0f7f4e902c8de3f5..b43be7fe712175330a35626d2840383eda44e77b 100644 --- a/src/mol-model-formats/structure/mmcif/atomic.ts +++ b/src/mol-model-formats/structure/mmcif/atomic.ts @@ -101,7 +101,7 @@ export function getAtomicHierarchyAndConformation(format: mmCIF_Format, atom_sit const index = getAtomicIndex(hierarchyData, entities, hierarchySegments); const derived = getAtomicDerivedData(hierarchyData, index, formatData.chemicalComponentMap); - const hierarchyRanges = getAtomicRanges(hierarchyData, hierarchySegments, conformation, derived.residue.moleculeType); + const hierarchyRanges = getAtomicRanges(hierarchyData, hierarchySegments, conformation, index, derived.residue.moleculeType); const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, ...hierarchyRanges, index, derived }; return { sameAsPrevious: false, hierarchy, conformation }; } \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts index 02bde17f01248af5c255b69f53de721f609d3a3d..33c5993245ff39f69f73a5baf4a1e8dbb3a08c45 100644 --- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts +++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts @@ -145,7 +145,7 @@ export interface AtomicIndex { /** * Index of the 1st occurence of this residue. * auth_seq_id is used because label_seq_id is undefined for "ligands" in mmCIF. - * @param pdbx_PDB_ins_code Empty string for undefined + * @param key.pdbx_PDB_ins_code Empty string for undefined * @returns index or -1 if not present. */ findResidue(key: AtomicIndex.ResidueKey): ResidueIndex, @@ -153,7 +153,7 @@ export interface AtomicIndex { /** * Index of the 1st occurence of this residue. - * @param pdbx_PDB_ins_code Empty string for undefined + * @param key.pdbx_PDB_ins_code Empty string for undefined * @returns index or -1 if not present. */ findResidueAuth(key: AtomicIndex.ResidueAuthKey): ResidueIndex, @@ -161,7 +161,7 @@ export interface AtomicIndex { /** * Find the residue index where the spefied residue should be inserted to maintain the ordering (entity_id, asym_id, seq_id, ins_code). * Useful for determining ranges for sequence-level annotations. - * @param pdbx_PDB_ins_code Empty string for undefined + * @param key.pdbx_PDB_ins_code Use empty string for undefined */ findResidueInsertion(key: AtomicIndex.ResidueLabelKey): ResidueIndex, @@ -181,11 +181,16 @@ export interface AtomicIndex { /** * Find element index of an atom on a given residue. - * @param key * @returns index or -1 if the atom is not present. */ findAtomOnResidue(residueIndex: ResidueIndex, label_atom_id: string, label_alt_id?: string): ElementIndex + /** + * Find element index of any given atom on a given residue. + * @returns first found index or -1 if none of the given atoms are present. + */ + findAtomsOnResidue(residueIndex: ResidueIndex, label_atom_ids: Set<string>): ElementIndex + // TODO: add indices that support comp_id? } diff --git a/src/mol-model/structure/model/properties/utils/atomic-derived.ts b/src/mol-model/structure/model/properties/utils/atomic-derived.ts index 47daf9dffaf6bdbc92539ad263f72de68f77fb2c..80c1d10e59e9bbf13e9cfac3ecb3854dcda7cf6a 100644 --- a/src/mol-model/structure/model/properties/utils/atomic-derived.ts +++ b/src/mol-model/structure/model/properties/utils/atomic-derived.ts @@ -37,10 +37,15 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi moleculeType[i] = molType const traceAtomId = getAtomIdForAtomRole(molType, 'trace') - traceElementIndex[i] = index.findAtomOnResidue(i as ResidueIndex, traceAtomId) + let traceIndex = index.findAtomsOnResidue(i as ResidueIndex, traceAtomId) + if (traceIndex === -1) { + const coarseAtomId = getAtomIdForAtomRole(molType, 'coarseBackbone') + traceIndex = index.findAtomsOnResidue(i as ResidueIndex, coarseAtomId) + } + traceElementIndex[i] = traceIndex const directionAtomId = getAtomIdForAtomRole(molType, 'direction') - directionElementIndex[i] = index.findAtomOnResidue(i as ResidueIndex, directionAtomId) + directionElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionAtomId) } return { diff --git a/src/mol-model/structure/model/properties/utils/atomic-index.ts b/src/mol-model/structure/model/properties/utils/atomic-index.ts index 9460ed19474e0daf1308069ab345a115bd4042de..17bc9dc6426fd67b758304a87b7930bee0a0dd4f 100644 --- a/src/mol-model/structure/model/properties/utils/atomic-index.ts +++ b/src/mol-model/structure/model/properties/utils/atomic-index.ts @@ -158,6 +158,10 @@ class Index implements AtomicIndex { return findAtomByNameAndAltLoc(this.residueOffsets[rI], this.residueOffsets[rI + 1], this.map.label_atom_id, this.map.label_alt_id, label_atom_id, label_alt_id); } + findAtomsOnResidue(rI: ResidueIndex, label_atom_ids: Set<string>) { + return findAtomByNames(this.residueOffsets[rI], this.residueOffsets[rI + 1], this.map.label_atom_id, label_atom_ids) + } + constructor(private map: Mapping) { this.entityIndex = map.entities.getEntityIndex; this.residueOffsets = this.map.segments.residueAtomSegments.offsets; @@ -171,6 +175,13 @@ function findAtomByName(start: ElementIndex, end: ElementIndex, data: Column<str return -1 as ElementIndex; } +function findAtomByNames(start: ElementIndex, end: ElementIndex, data: Column<string>, atomNames: Set<string>): ElementIndex { + for (let i = start; i < end; i++) { + if (atomNames.has(data.value(i))) return i; + } + return -1 as ElementIndex; +} + function findAtomByNameAndAltLoc(start: ElementIndex, end: ElementIndex, nameData: Column<string>, altLocData: Column<string>, atomName: string, altLoc: string): ElementIndex { for (let i = start; i < end; i++) { diff --git a/src/mol-model/structure/model/properties/utils/atomic-ranges.ts b/src/mol-model/structure/model/properties/utils/atomic-ranges.ts index ed6816364115f26aa64dbf6a981bf35b3a8c751d..86ddca7d17eed50bf2166f989a6cd4e6c681a429 100644 --- a/src/mol-model/structure/model/properties/utils/atomic-ranges.ts +++ b/src/mol-model/structure/model/properties/utils/atomic-ranges.ts @@ -5,7 +5,7 @@ */ import { AtomicSegments } from '../atomic'; -import { AtomicData, AtomicRanges } from '../atomic/hierarchy'; +import { AtomicData, AtomicRanges, AtomicIndex } from '../atomic/hierarchy'; import { Segmentation, Interval } from 'mol-data/int'; import SortedRanges from 'mol-data/int/sorted-ranges'; import { MoleculeType, isPolymer } from '../../types'; @@ -16,33 +16,26 @@ import { Vec3 } from 'mol-math/linear-algebra'; // TODO add gaps at the ends of the chains by comparing to the polymer sequence data -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 - } - return offsets[rI] -} - -function areBackboneConnected(riStart: ResidueIndex, riEnd: ResidueIndex, data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, moleculeType: ArrayLike<MoleculeType>) { +function areBackboneConnected(riStart: ResidueIndex, riEnd: ResidueIndex, data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, index: AtomicIndex, moleculeType: ArrayLike<MoleculeType>) { const mtStart = moleculeType[riStart] const mtEnd = moleculeType[riEnd] if (!isPolymer(mtStart) || !isPolymer(mtEnd)) return false - const startId = getAtomIdForAtomRole(mtStart, 'backboneStart') - const endId = getAtomIdForAtomRole(mtEnd, 'backboneEnd') + let eiStart = index.findAtomsOnResidue(riStart, getAtomIdForAtomRole(mtStart, 'backboneStart')) + let eiEnd = index.findAtomsOnResidue(riEnd, getAtomIdForAtomRole(mtEnd, 'backboneEnd')) - const eiStart = getElementIndexForAtomId(riStart, startId, data, segments) - const eiEnd = getElementIndexForAtomId(riEnd, endId, data, segments) + if (eiStart === -1 || eiEnd === -1) { + eiStart = index.findAtomsOnResidue(riStart, getAtomIdForAtomRole(mtStart, 'coarseBackbone')) + eiEnd = index.findAtomsOnResidue(riEnd, getAtomIdForAtomRole(mtEnd, 'coarseBackbone')) + } 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) < 10 + return Vec3.distance(pStart, pEnd) < 10 // TODO better distance check, take into account if protein/nucleic and if coarse } -export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, moleculeType: ArrayLike<MoleculeType>): AtomicRanges { +export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, index: AtomicIndex, moleculeType: ArrayLike<MoleculeType>): AtomicRanges { const polymerRanges: number[] = [] const gapRanges: number[] = [] const cyclicPolymerMap = new Map<ResidueIndex, ResidueIndex>() @@ -65,7 +58,7 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conf const riStart = segments.residueAtomSegments.index[chainSegment.start] const riEnd = segments.residueAtomSegments.index[chainSegment.end - 1] - if (areBackboneConnected(riStart, riEnd, data, segments, conformation, moleculeType)) { + if (areBackboneConnected(riStart, riEnd, data, segments, conformation, index, moleculeType)) { cyclicPolymerMap.set(riStart, riEnd) cyclicPolymerMap.set(riEnd, riStart) } @@ -85,7 +78,7 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conf } else { const riStart = segments.residueAtomSegments.index[residueSegment.start] const riEnd = segments.residueAtomSegments.index[prevEnd - 1] - if (!areBackboneConnected(riStart, riEnd, data, segments, conformation, moleculeType)) { + if (!areBackboneConnected(riStart, riEnd, data, segments, conformation, index, moleculeType)) { polymerRanges.push(startIndex, prevEnd - 1) startIndex = residueSegment.start } diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts index a550f8f32516575bf3c926ed5a2728e759829e8c..2031a00c06ce8c830154c9224ea42aa362593f79 100644 --- a/src/mol-model/structure/model/types.ts +++ b/src/mol-model/structure/model/types.ts @@ -59,32 +59,36 @@ export const enum MoleculeType { saccharide } -export type AtomRole = 'trace' | 'direction' | 'backboneStart' | 'backboneEnd' +export type AtomRole = 'trace' | 'direction' | 'backboneStart' | 'backboneEnd' | 'coarseBackbone' -export const MoleculeTypeAtomRoleId: { [k: number]: { [k in AtomRole]: string } } = { +export const MoleculeTypeAtomRoleId: { [k: number]: { [k in AtomRole]: Set<string> } } = { [MoleculeType.protein]: { - trace: 'CA', // TODO 'BB' - direction: 'O', // TODO 'OC1', 'O1', 'OX1', 'OXT' - backboneStart: 'N', - backboneEnd: 'C' + trace: new Set(['CA']), + direction: new Set(['O', 'OC1', 'O1', 'OX1', 'OXT']), + backboneStart: new Set(['N']), + backboneEnd: new Set(['C']), + coarseBackbone: new Set(['CA', 'BB']) }, [MoleculeType.RNA]: { - trace: 'C4\'', // TODO 'C4*' - direction: 'C3\'', // 'C3*' - backboneStart: 'P', - backboneEnd: 'O3\'' // TODO 'O3*' + trace: new Set(['C4\'', 'C4*']), + direction: new Set(['C3\'', 'C3*']), + backboneStart: new Set(['P']), + backboneEnd: new Set(['O3\'', 'O3*']), + coarseBackbone: new Set(['P']) }, [MoleculeType.DNA]: { - trace: 'C3\'', // TODO 'C3*' - direction: 'C1\'', // TODO 'C1*' - backboneStart: 'P', - backboneEnd: 'O3\'' // TODO 'O3*' + trace: new Set(['C3\'', 'C3*']), + direction: new Set(['C1\'', 'C1*']), + backboneStart: new Set(['P']), + backboneEnd: new Set(['O3\'', 'O3*']), + coarseBackbone: new Set(['P']) }, [MoleculeType.PNA]: { - trace: 'N4\'', // TODO 'N4*' - direction: 'C7\'', // TODO 'C7*' - backboneStart: 'N1\'', // TODO 'N1*' - backboneEnd: 'C1\'' // TODO 'C1*' + trace: new Set(['N4\'', 'N4*']), + direction: new Set(['C7\'', 'C7*']), + backboneStart: new Set(['N1\'', 'N1*']), + backboneEnd: new Set(['C1\'', 'C1*']), + coarseBackbone: new Set(['P']) } } diff --git a/src/mol-model/structure/util.ts b/src/mol-model/structure/util.ts index 3f65f5f16755cb2ce569e0940f73e07d07dc4c7a..87e34b6e024c77874205055fa2b9448981dce467 100644 --- a/src/mol-model/structure/util.ts +++ b/src/mol-model/structure/util.ts @@ -35,13 +35,14 @@ export function getAtomicMoleculeType(model: Model, rI: ResidueIndex): MoleculeT return model.atomicHierarchy.derived.residue.moleculeType[rI] } +const EmptyAtomIds = new Set<string>() export function getAtomIdForAtomRole(moleculeType: MoleculeType, atomRole: AtomRole) { const m = MoleculeTypeAtomRoleId[moleculeType] if (m !== undefined) { const a = m[atomRole] if (a !== undefined) return a } - return '' + return EmptyAtomIds } export function residueLabel(model: Model, rI: number) { diff --git a/src/mol-repr/structure/visual/polymer-trace-mesh.ts b/src/mol-repr/structure/visual/polymer-trace-mesh.ts index 1da10764176ca24e053608db0e38aa325d3be4db..11ef2c8a6428d177d247ee2053fcf2e0ef7f750d 100644 --- a/src/mol-repr/structure/visual/polymer-trace-mesh.ts +++ b/src/mol-repr/structure/visual/polymer-trace-mesh.ts @@ -72,11 +72,11 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc addSheet(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, w1, h1, arrowHeight, v.secStrucFirst, v.secStrucLast) } else { let h0: number, h1: number, h2: number - if (isHelix) { + if (isHelix && !v.isCoarseBackbone) { h0 = w0 * aspectRatio h1 = w1 * aspectRatio h2 = w2 * aspectRatio - } else if (isNucleicType) { + } else if (isNucleicType && !v.isCoarseBackbone) { h0 = w0 * aspectRatio; [w0, h0] = [h0, w0] h1 = w1 * aspectRatio; diff --git a/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts b/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts index 5332a697035c9b1f42f7ed175fb709af5280caa0..9099462cd9e394ea32546b6a35386930c056198c 100644 --- a/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts +++ b/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts @@ -35,6 +35,7 @@ interface PolymerTraceElement { secStrucFirst: boolean, secStrucLast: boolean secStrucType: SecondaryStructureType moleculeType: MoleculeType + isCoarseBackbone: boolean p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, p4: Vec3 d12: Vec3, d23: Vec3 @@ -51,6 +52,7 @@ function createPolymerTraceElement (unit: Unit): PolymerTraceElement { secStrucFirst: false, secStrucLast: false, secStrucType: SecStrucTypeNA, moleculeType: MoleculeType.unknown, + isCoarseBackbone: false, p0: Vec3.zero(), p1: Vec3.zero(), p2: Vec3.zero(), p3: Vec3.zero(), p4: Vec3.zero(), d12: Vec3.create(1, 0, 0), d23: Vec3.create(1, 0, 0), } @@ -199,6 +201,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement> value.centerNext.element = this.traceElementIndex[residueIndexNext1] this.pos(this.p6, this.traceElementIndex[residueIndexNext3]) this.pos(this.v23, this.directionElementIndex[residueIndex]) + value.isCoarseBackbone = this.directionElementIndex[residueIndex] === -1 this.setControlPoint(value.p0, this.p0, this.p1, this.p2, residueIndexPrev2) this.setControlPoint(value.p1, this.p1, this.p2, this.p3, residueIndexPrev1)