molstar
Version:
A comprehensive macromolecular library.
204 lines (203 loc) • 8.34 kB
JavaScript
"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 };
}