UNPKG

molstar

Version:

A comprehensive macromolecular library.

204 lines (203 loc) 8.34 kB
"use strict"; /** * Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.GeometryEdits = exports.TopologyEdits = void 0; const linear_algebra_1 = require("../../mol-math/linear-algebra"); const atomic_1 = require("../../mol-model/structure/model/properties/atomic"); const types_1 = require("../../mol-model/structure/model/types"); const r_groups_1 = require("./r-groups"); exports.TopologyEdits = { setElement: async (graph, atomIds, type_symbol) => { for (const id of atomIds) { graph.modifyAtom(id, { type_symbol }); } }, addElement: async (graph, parentId, type_symbol) => { var _a; const p = graph.getAtom(parentId); if (!p) return; const c = graph.getAtomCoords(p); const dir = approximateAddAtomDirection(graph, p); const r = 2 / 5 * ((0, atomic_1.VdwRadius)((0, types_1.ElementSymbol)((_a = p.row.type_symbol) !== null && _a !== void 0 ? _a : 'C')) + (0, atomic_1.VdwRadius)((0, types_1.ElementSymbol)(type_symbol))); const newAtom = graph.addAtom({ ...p.row, // NOTE: this is not correct for editing protein atoms // as they should have atom names from CCD, or at least the should be // unique. This should be fine for small ligand editing. auth_atom_id: type_symbol, label_atom_id: type_symbol, type_symbol, Cartn_x: c[0] + dir[0] * r, Cartn_y: c[1] + dir[1] * r, Cartn_z: c[2] + dir[2] * r }); graph.addOrUpdateBond(p, newAtom, { value_order: 'sing', type_id: 'covale' }); return newAtom; }, removeAtoms: async (graph, atomIds) => { for (const id of atomIds) { graph.removeAtom(id); } }, removeBonds: async (graph, atomIds) => { for (let i = 0; i < atomIds.length; ++i) { for (let j = i + 1; j < atomIds.length; ++j) { graph.removeBond(atomIds[i], atomIds[j]); } } }, updateBonds: async (graph, atomIds, props) => { // TODO: iterate on the all-pairs behavior // e.g. only add bonds if there is no path connecting them, // or by a distance threshold, ... for (let i = 0; i < atomIds.length; ++i) { for (let j = i + 1; j < atomIds.length; ++j) { graph.addOrUpdateBond(atomIds[i], atomIds[j], props); } } }, attachRgroup: async (graph, atomId, name) => { await (0, r_groups_1.attachRGroup)(graph, name, atomId); } }; exports.GeometryEdits = { twist: (graph, atomIds) => { if (atomIds.length !== 2) { throw new Error('Twist requires exactly two atoms.'); } const { left, right } = splitGraph(graph, atomIds[0], atomIds[1]); const active = left.length <= right.length ? left : right; const a = left.length <= right.length ? atomIds[0] : atomIds[1]; const b = left.length <= right.length ? atomIds[1] : atomIds[0]; const pivot = graph.getAtomCoords(a); const axis = linear_algebra_1.Vec3.sub((0, linear_algebra_1.Vec3)(), pivot, graph.getAtomCoords(b)); linear_algebra_1.Vec3.normalize(axis, axis); const basePositions = active.map(a => graph.getAtomCoords(a)); const xform = (0, linear_algebra_1.Quat)(); const p = (0, linear_algebra_1.Vec3)(); return (angle) => { linear_algebra_1.Quat.setAxisAngle(xform, axis, angle); for (let i = 0; i < active.length; ++i) { linear_algebra_1.Vec3.copy(p, basePositions[i]); linear_algebra_1.Vec3.sub(p, p, pivot); linear_algebra_1.Vec3.transformQuat(p, p, xform); linear_algebra_1.Vec3.add(p, p, pivot); graph.modifyAtom(active[i], { Cartn_x: p[0], Cartn_y: p[1], Cartn_z: p[2] }); } return graph; }; }, stretch: (graph, atomIds) => { if (atomIds.length !== 2) { throw new Error('Stretch requires exactly two atoms.'); } const { left, right } = splitGraph(graph, atomIds[0], atomIds[1]); const a = graph.getAtomCoords(atomIds[0]); const b = graph.getAtomCoords(atomIds[1]); const center = linear_algebra_1.Vec3.add((0, linear_algebra_1.Vec3)(), b, a); linear_algebra_1.Vec3.scale(center, center, 0.5); const baseDelta = linear_algebra_1.Vec3.sub((0, linear_algebra_1.Vec3)(), a, center); const baseLeft = left.map(a => graph.getAtomCoords(a)); const baseRight = right.map(a => graph.getAtomCoords(a)); const p = (0, linear_algebra_1.Vec3)(); const delta = (0, linear_algebra_1.Vec3)(); return (factor) => { linear_algebra_1.Vec3.scale(delta, baseDelta, factor); for (let i = 0; i < left.length; ++i) { linear_algebra_1.Vec3.copy(p, baseLeft[i]); linear_algebra_1.Vec3.add(p, p, delta); graph.modifyAtom(left[i], { Cartn_x: p[0], Cartn_y: p[1], Cartn_z: p[2] }); } for (let i = 0; i < right.length; ++i) { linear_algebra_1.Vec3.copy(p, baseRight[i]); linear_algebra_1.Vec3.sub(p, p, delta); graph.modifyAtom(right[i], { Cartn_x: p[0], Cartn_y: p[1], Cartn_z: p[2] }); } return graph; }; }, }; function approximateAddAtomDirection(graph, parent) { let deltas = []; const bonds = graph.bondByKey.get(parent.key); if (!(bonds === null || bonds === void 0 ? void 0 : bonds.length)) return linear_algebra_1.Vec3.create(1, 0, 0); const c = graph.getAtomCoords(parent); for (const b of bonds) { const delta = linear_algebra_1.Vec3.sub((0, linear_algebra_1.Vec3)(), graph.getAtomCoords(b.atom_2), c); deltas.push(delta); } if (deltas.length === 1) { const ret = linear_algebra_1.Vec3.negate((0, linear_algebra_1.Vec3)(), deltas[0]); linear_algebra_1.Vec3.normalize(ret, ret); return ret; } if (deltas.length === 2) { const ret = linear_algebra_1.Vec3.add((0, linear_algebra_1.Vec3)(), deltas[0], deltas[1]); linear_algebra_1.Vec3.normalize(ret, ret); linear_algebra_1.Vec3.negate(ret, ret); return ret; } // Take the first three deltas and cross-product them deltas = deltas.slice(0, 3); const crossProducts = []; for (let i = 0; i < deltas.length; ++i) { for (let j = i + 1; j < deltas.length; ++j) { const cross = linear_algebra_1.Vec3.cross((0, linear_algebra_1.Vec3)(), deltas[i], deltas[j]); linear_algebra_1.Vec3.normalize(cross, cross); crossProducts.push(cross); } } for (let i = 1; i < crossProducts.length; ++i) { linear_algebra_1.Vec3.matchDirection(crossProducts[i], crossProducts[i], crossProducts[0]); } const avg = linear_algebra_1.Vec3.create(0, 0, 0); for (const cp of crossProducts) { linear_algebra_1.Vec3.add(avg, avg, cp); } linear_algebra_1.Vec3.normalize(avg, avg); return avg; } function getAtomDepths(graph, atomId) { return graph.traverse(atomId, 'bfs', new Map(), (a, depths, pred) => { depths.set(a.key, pred ? depths.get(pred.atom_1.key) + 1 : 0); }); } function splitGraph(graph, leftId, rightId) { const xs = getAtomDepths(graph, leftId); const ys = getAtomDepths(graph, rightId); const l = []; const r = []; for (const a of graph.atoms) { if (xs.has(a.key) && ys.has(a.key)) { if (xs.get(a.key) < ys.get(a.key)) l.push(a); else r.push(a); } else if (xs.has(a.key)) { l.push(a); } else if (ys.has(a.key)) { r.push(a); } } return { left: l, right: r }; }