diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index e944d9c8d93060cb76f250996a056c293040050d..9dc2185554d02c35b7130e06baf8640f62f908b3 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model/structure/model/formats/mmcif.ts @@ -24,6 +24,7 @@ import { getSequence } from './mmcif/sequence'; import { sortAtomSite } from './mmcif/sort'; import mmCIF_Format = Format.mmCIF +import { StructConn } from './mmcif/bonds/struct_conn'; type AtomSite = mmCIF_Database['atom_site'] @@ -166,6 +167,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) { diff --git a/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts b/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts index 04d74a0a7910dc3643e785f912e1f2eba400f829..b89b473df9e6dfccb1d0732099290c60a1c4382d 100644 --- a/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts +++ b/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; 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 index d0c0e5f07e0ab70bc4db546a0dcc68395a06b5d4..f2ea4703a18f1ef90982750a70c37a1036e963ab 100644 --- a/src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts +++ b/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); diff --git a/src/mol-model/structure/model/formats/mmcif/util.ts b/src/mol-model/structure/model/formats/mmcif/util.ts index 2bccf1a97fb9209d8250ebc4b726447bb29214d3..672acf2efc9049c90a361e289088ce1240100c8b 100644 --- a/src/mol-model/structure/model/formats/mmcif/util.ts +++ b/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; diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts index 513f36110ba1dee18369d89d4a045d90a0144ae6..f03c8521349e6fa316d2b97c870ffad4b7425920 100644 --- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts +++ b/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> // TODO: include entity segments? } diff --git a/src/mol-model/structure/structure/unit/links/inter-compute.ts b/src/mol-model/structure/structure/unit/links/inter-compute.ts index 40eb99a7a87d94f7da5d007def10c286b4db1590..bc1dd6becd6a954511d6b10948acaea5df43d999 100644 --- a/src/mol-model/structure/structure/unit/links/inter-compute.ts +++ b/src/mol-model/structure/structure/unit/links/inter-compute.ts @@ -45,7 +45,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; for (let _aI = 0; _aI < atomCount; _aI++) { const aI = atomsA[_aI];