UNPKG

molstar

Version:

A comprehensive macromolecular library.

231 lines (230 loc) 12.9 kB
"use strict"; /** * Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Adam Midlik <midlik@gmail.com> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.wwPDBStructConnExtensionFunctions = void 0; const mmcif_1 = require("../../../mol-model-formats/structure/mmcif"); const objects_1 = require("../../../mol-plugin-state/objects"); const model_1 = require("../../../mol-plugin-state/transforms/model"); const representation_1 = require("../../../mol-plugin-state/transforms/representation"); const state_1 = require("../../../mol-plugin/behavior/static/state"); const builder_1 = require("../../../mol-script/language/builder"); const names_1 = require("../../../mol-util/color/names"); /** Amount by which to expand the camera radius when zooming to atoms involved in struct_conn (angstroms) */ const EXTRA_RADIUS = 4; /** Tags for state tree nodes managed by this extension */ const TAGS = { RESIDUE_SEL: 'structconn-focus-residue-sel', ATOM_SEL: 'structconn-focus-atom-sel', RESIDUE_REPR: 'structconn-focus-residue-repr', RESIDUE_NCI_REPR: 'structconn-focus-residue-nci-repr', ATOM_REPR: 'structconn-focus-atom-repr', }; /** Parameters for 3D representation of atoms involved in struct_conn (pink bubbles) */ const ATOMS_VISUAL_PARAMS = { type: { name: 'ball-and-stick', params: { sizeFactor: 0.25, sizeAspectRatio: 0.73, adjustCylinderLength: true, xrayShaded: true, aromaticBonds: false, multipleBonds: 'off', dashCount: 1, dashCap: false } }, colorTheme: { name: 'uniform', params: { value: names_1.ColorNames.magenta } }, sizeTheme: { name: 'physical', params: {} }, }; /** Parameters for 3D representation of residues involved in struct_conn (normal ball-and-stick) */ const RESIDUES_VISUAL_PARAMS = { type: { name: 'ball-and-stick', params: { sizeFactor: 0.16 } }, colorTheme: { name: 'element-symbol', params: {} }, sizeTheme: { name: 'physical', params: {} }, }; /** All public functions provided by the StructConn extension */ exports.wwPDBStructConnExtensionFunctions = { /** Return an object with all struct_conn records for a loaded structure. * Applies to the first structure belonging to `entry` (e.g. '1tqn'), * or to the first loaded structure overall if `entry` is `undefined`. */ getStructConns(plugin, entry) { var _a; const structNode = selectStructureNode(plugin, entry); const structure = (_a = structNode === null || structNode === void 0 ? void 0 : structNode.obj) === null || _a === void 0 ? void 0 : _a.data; if (structure) return extractStructConns(structure.model); else return {}; }, /** Create visuals for residues and atoms involved in a struct_conn with ID `structConnId` * and zoom on them. If `keepExisting` is false (default), remove any such visuals created by previous calls to this function. * Also hide all carbohydrate SNFG visuals within the structure (as they would occlude our residues of interest). * Return a promise that resolves to the number of involved atoms which were successfully selected (2, 1, or 0). */ async inspectStructConn(plugin, entry, structConnId, keepExisting = false) { var _a, _b; var _c; const structNode = selectStructureNode(plugin, entry); const structure = (_a = structNode === null || structNode === void 0 ? void 0 : structNode.obj) === null || _a === void 0 ? void 0 : _a.data; if (!structure) { console.error('Structure not found'); return 0; } const conns = (_b = (_c = structure.model._staticPropertyData)['wwpdb-struct-conn-extension-data']) !== null && _b !== void 0 ? _b : (_c['wwpdb-struct-conn-extension-data'] = extractStructConns(structure.model)); const conn = conns[structConnId]; if (!conn) { console.error(`The structure does not contain struct_conn "${structConnId}"`); return 0; } if (!keepExisting) { await removeAllStructConnInspections(plugin, structNode); } const nSelectedAtoms = await addStructConnInspection(plugin, structNode, conn); hideSnfgNodes(plugin, structNode); return nSelectedAtoms; }, /** Remove anything created by `inspectStructConn` within the structure and * make visible any carbohydrate SNFG visuals that have been hidden by `inspectStructConn`. */ async clearStructConnInspections(plugin, entry) { const structNode = selectStructureNode(plugin, entry); if (!structNode) return; await removeAllStructConnInspections(plugin, structNode); unhideSnfgNodes(plugin, structNode); }, }; /** Return the first structure node belonging to `entry` (e.g. '1tqn'), * or to the first loaded structure node overall if `entry` is `undefined`. * Includes only "root" structures, not structure components. */ function selectStructureNode(plugin, entry) { const structNodes = plugin.state.data .selectQ(q => q.rootsOfType(objects_1.PluginStateObject.Molecule.Structure)); if (entry) { const result = structNodes.find(node => node.obj && node.obj.data.model.entry.toLowerCase() === entry.toLowerCase()); if (!result) { console.warn(`Structure with entry ID "${entry}" was not found. Available structures: ${structNodes.map(node => { var _a; return (_a = node.obj) === null || _a === void 0 ? void 0 : _a.data.model.entry; })}`); } return result; } else { if (structNodes.length > 1) { console.warn(`Structure entry ID was not specified, but there is more than one loaded structure (${structNodes.map(node => { var _a; return (_a = node.obj) === null || _a === void 0 ? void 0 : _a.data.model.entry; })}). Taking the first structure.`); } if (structNodes.length === 0) { console.warn(`There are no loaded structures.`); } return structNodes[0]; } } /** Return an object with all struct_conn records read from mmCIF. * Return {} if the model comes from another format than mmCIF. */ function extractStructConns(model) { if (!mmcif_1.MmcifFormat.is(model.sourceData)) { console.error('Cannot get struct_conn because source data are not mmCIF.'); return {}; } const mmcifData = model.sourceData.data; const { id, ptnr1_label_asym_id: asym1, ptnr1_label_seq_id: seq1, ptnr1_auth_seq_id: authSeq1, pdbx_ptnr1_PDB_ins_code: authInsCode1, ptnr1_label_comp_id: comp1, ptnr1_label_atom_id: atom1, pdbx_ptnr1_label_alt_id: alt1, ptnr2_label_asym_id: asym2, ptnr2_label_seq_id: seq2, ptnr2_auth_seq_id: authSeq2, pdbx_ptnr2_PDB_ins_code: authInsCode2, ptnr2_label_comp_id: comp2, ptnr2_label_atom_id: atom2, pdbx_ptnr2_label_alt_id: alt2, pdbx_dist_value: distance } = mmcifData.db.struct_conn; const n = id.rowCount; const result = {}; for (let i = 0; i < n; i++) { const conn = { id: id.value(i), distance: distance.value(i), partner1: { asymId: asym1.value(i), seqId: seq1.valueKind(i) === 0 /* Column.ValueKinds.Present */ ? seq1.value(i) : undefined, authSeqId: authSeq1.valueKind(i) === 0 /* Column.ValueKinds.Present */ ? authSeq1.value(i) : undefined, insCode: authInsCode1.value(i), compId: comp1.value(i), atomId: atom1.value(i), altId: alt1.value(i), }, partner2: { asymId: asym2.value(i), seqId: seq2.valueKind(i) === 0 /* Column.ValueKinds.Present */ ? seq2.value(i) : undefined, authSeqId: authSeq2.valueKind(i) === 0 /* Column.ValueKinds.Present */ ? authSeq2.value(i) : undefined, insCode: authInsCode2.value(i), compId: comp2.value(i), atomId: atom2.value(i), altId: alt2.value(i), }, }; result[conn.id] = conn; } return result; } /** Return MolScript expression for atoms or residues involved in a struct_conn */ function structConnExpression(conn, by) { const { core, struct } = builder_1.MolScriptBuilder; const partnerExpressions = []; for (const partner of [conn.partner1, conn.partner2]) { const propTests = { 'chain-test': core.rel.eq([struct.atomProperty.macromolecular.label_asym_id(), partner.asymId]), 'group-by': struct.atomProperty.core.operatorName(), }; if (partner.seqId !== undefined) { propTests['residue-test'] = core.rel.eq([struct.atomProperty.macromolecular.label_seq_id(), partner.seqId]); } else if (partner.authSeqId !== undefined) { // for the case of water and carbohydrates (see 5elb, covale3 vs covale5) propTests['residue-test'] = core.logic.and([ core.rel.eq([struct.atomProperty.macromolecular.auth_seq_id(), partner.authSeqId]), core.rel.eq([struct.atomProperty.macromolecular.pdbx_PDB_ins_code(), partner.insCode]), ]); } if (by === 'residues' && partner.altId !== '') { propTests['atom-test'] = core.rel.eq([struct.atomProperty.macromolecular.label_alt_id(), partner.altId]); } if (by === 'atoms') { propTests['atom-test'] = core.logic.and([ core.rel.eq([struct.atomProperty.macromolecular.label_atom_id(), partner.atomId]), core.rel.eq([struct.atomProperty.macromolecular.label_alt_id(), partner.altId]), ]); } partnerExpressions.push(struct.filter.first([struct.generator.atomGroups(propTests)])); } return struct.combinator.merge(partnerExpressions.map(e => struct.modifier.union([e]))); } /** Create visuals for residues and atoms involved in a struct_conn and zoom on them. * Return a promise that resolves to the number of involved atoms which were successfully selected (2, 1, or 0). */ async function addStructConnInspection(plugin, structNode, conn) { var _a, _b, _c, _d; const expressionByResidues = structConnExpression(conn, 'residues'); const expressionByAtoms = structConnExpression(conn, 'atoms'); const update = plugin.build(); update.to(structNode).apply(model_1.StructureComponent, { label: `${conn.id} (residues)`, type: { name: 'expression', params: expressionByResidues } }, { tags: [TAGS.RESIDUE_SEL] }).apply(representation_1.StructureRepresentation3D, RESIDUES_VISUAL_PARAMS, { tags: [TAGS.RESIDUE_REPR] }); const atomsSelection = update.to(structNode).apply(model_1.StructureComponent, { label: `${conn.id} (atoms)`, type: { name: 'expression', params: expressionByAtoms } }, { tags: [TAGS.ATOM_SEL] }); const atomsVisual = update.to(atomsSelection.ref).apply(representation_1.StructureRepresentation3D, ATOMS_VISUAL_PARAMS, { tags: [TAGS.ATOM_REPR] }); await update.commit(); plugin.managers.camera.focusRenderObjects((_a = atomsVisual.selector.data) === null || _a === void 0 ? void 0 : _a.repr.renderObjects, { extraRadius: EXTRA_RADIUS }); const nSelectedAtoms = (_d = (_c = (_b = atomsSelection.selector.obj) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.elementCount) !== null && _d !== void 0 ? _d : 0; return nSelectedAtoms; } /** Remove anything created by `addStructConnInspection` */ async function removeAllStructConnInspections(plugin, structNode) { const selNodes = [ ...plugin.state.data.selectQ(q => q.byRef(structNode.transform.ref).subtree().withTag(TAGS.RESIDUE_SEL)), ...plugin.state.data.selectQ(q => q.byRef(structNode.transform.ref).subtree().withTag(TAGS.ATOM_SEL)), ]; const update = plugin.build(); for (const node of selNodes) { update.delete(node); } await update.commit(); } /** Hide all carbohydrate SNFG visuals */ function hideSnfgNodes(plugin, structNode) { const snfgNodes = plugin.state.data.selectQ(q => q.byRef(structNode.transform.ref).subtree().withTag('branched-snfg-3d')); for (const node of snfgNodes) { (0, state_1.setSubtreeVisibility)(plugin.state.data, node.transform.ref, true); // true means hidden } } /** Make visible all carbohydrate SNFG visuals that have been hidden by `hideSnfgNodes` */ function unhideSnfgNodes(plugin, structNode) { const snfgNodes = plugin.state.data.selectQ(q => q.byRef(structNode.transform.ref).subtree().withTag('branched-snfg-3d')); for (const node of snfgNodes) { try { (0, state_1.setSubtreeVisibility)(plugin.state.data, node.transform.ref, false); // false means visible } catch (_a) { // this is OK, the node has been removed } } }