molstar
Version:
A comprehensive macromolecular library.
306 lines (301 loc) • 12.5 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.loadIHMRestraints = loadIHMRestraints;
const behavior_1 = require("../../extensions/mvs/behavior");
const load_1 = require("../../extensions/mvs/load");
const mvs_builder_1 = require("../../extensions/mvs/tree/mvs/mvs-builder");
const parser_1 = require("../../mol-io/reader/cif/text/parser");
const linear_algebra_1 = require("../../mol-math/linear-algebra");
const mmcif_1 = require("../../mol-model-formats/structure/mmcif");
const coarse_1 = require("../../mol-model/structure/model/properties/coarse");
const mol_plugin_ui_1 = require("../../mol-plugin-ui");
const react18_1 = require("../../mol-plugin-ui/react18");
const spec_1 = require("../../mol-plugin-ui/spec");
const config_1 = require("../../mol-plugin/config");
const spec_2 = require("../../mol-plugin/spec");
const mol_task_1 = require("../../mol-task");
const data_source_1 = require("../../mol-util/data-source");
const download_1 = require("../../mol-util/download");
require("./index.html");
require("../../mol-plugin-ui/skin/light.scss");
async function createViewer(root) {
const spec = (0, spec_1.DefaultPluginUISpec)();
const plugin = await (0, mol_plugin_ui_1.createPluginUI)({
target: root,
render: react18_1.renderReact18,
spec: {
...spec,
layout: {
initial: {
isExpanded: true,
showControls: false
}
},
components: {
remoteState: 'none',
},
behaviors: [
...spec.behaviors,
spec_2.PluginSpec.Behavior(behavior_1.MolViewSpec)
],
config: [
[config_1.PluginConfig.Viewport.ShowAnimation, false],
[config_1.PluginConfig.Viewport.ShowTrajectoryControls, false],
]
}
});
return plugin;
}
function getCoarseElementPosition(e, model, position) {
if (!e.kind)
linear_algebra_1.Vec3.set(position, 0, 0, 0);
const { x, y, z } = model.coarseConformation[e.kind];
const idx = e.index;
linear_algebra_1.Vec3.set(position, x[idx], y[idx], z[idx]);
}
const _elementRef = (0, coarse_1.CoarseElementReference)();
function resolvePosition(model, key, position) {
if (model.coarseHierarchy.index.findElement(key, _elementRef)) {
getCoarseElementPosition(_elementRef, model, position);
return true;
}
const rI = model.atomicHierarchy.index.findResidueLabel(key);
if (rI < 0)
return false;
const atomStart = model.atomicHierarchy.residueAtomSegments.offsets[rI];
const atomEnd = model.atomicHierarchy.residueAtomSegments.offsets[rI + 1];
const atomId = model.atomicHierarchy.atoms.label_atom_id;
let aI = atomStart;
// Find CA otherwise use the first atom.
// Possible future improvement: use the atom closest to the center of mass of the residue.
for (; aI < atomEnd; aI++) {
if (atomId.value(aI) === 'CA')
break;
}
if (aI === atomEnd)
aI = atomStart;
const { x, y, z } = model.atomicConformation;
linear_algebra_1.Vec3.set(position, x[aI], y[aI], z[aI]);
return true;
}
const HarmonicRestraintTolerance = 0.1;
async function parseInfo(plugin, url) {
var _a;
const data = await plugin.runTask((0, data_source_1.ajaxGet)(url));
const parsed = await plugin.runTask((0, parser_1.parseCifText)(data));
if (parsed.isError) {
console.error(parsed);
return { entity_labels: [], model_restraints: [] };
}
const trajectory = await plugin.runTask((0, mmcif_1.trajectoryFromMmCIF)(parsed.result.blocks[0], parsed.result));
const dataBlocks = parsed.result.blocks;
const ihm_cross_link_restraint = dataBlocks[0].categories['ihm_cross_link_restraint'];
const entity_id_1 = ihm_cross_link_restraint.getField('entity_id_1');
const asym_id_1 = ihm_cross_link_restraint.getField('asym_id_1');
const seq_id_1 = ihm_cross_link_restraint.getField('seq_id_1');
const comp_id_1 = ihm_cross_link_restraint.getField('comp_id_1');
const entity_id_2 = ihm_cross_link_restraint.getField('entity_id_2');
const asym_id_2 = ihm_cross_link_restraint.getField('asym_id_2');
const seq_id_2 = ihm_cross_link_restraint.getField('seq_id_2');
const comp_id_2 = ihm_cross_link_restraint.getField('comp_id_2');
const restraint_type = ihm_cross_link_restraint.getField('restraint_type');
const threshold = ihm_cross_link_restraint.getField('distance_threshold');
const e1key = (0, coarse_1.CoarseElementKey)();
const e2key = (0, coarse_1.CoarseElementKey)();
const a = linear_algebra_1.Vec3.zero();
const b = linear_algebra_1.Vec3.zero();
const entity_labels = [];
const entity = dataBlocks[0].categories['entity'];
const entity_id = entity.getField('id');
const pdbx_description = entity.getField('pdbx_description');
for (let i = 0; i < entity.rowCount; i++) {
entity_labels.push([entity_id === null || entity_id === void 0 ? void 0 : entity_id.str(i), pdbx_description === null || pdbx_description === void 0 ? void 0 : pdbx_description.str(i)]);
}
const model_restraints = [];
for (let modelIndex = 0; modelIndex < trajectory.frameCount; modelIndex++) {
const _model = trajectory.getFrameAtIndex(modelIndex);
const model = mol_task_1.Task.is(_model) ? await plugin.runTask(_model) : _model;
const restraints = [];
model_restraints.push(restraints);
for (let i = 0; i < ihm_cross_link_restraint.rowCount; i++) {
e1key.label_entity_id = entity_id_1.str(i);
e1key.label_asym_id = asym_id_1.str(i);
e1key.label_seq_id = seq_id_1.int(i);
e2key.label_entity_id = entity_id_2.str(i);
e2key.label_asym_id = asym_id_2.str(i);
e2key.label_seq_id = seq_id_2.int(i);
if (!resolvePosition(model, e1key, a) || !resolvePosition(model, e2key, b)) {
continue;
}
const restraintType = (_a = restraint_type.str(i)) === null || _a === void 0 ? void 0 : _a.toLowerCase();
const thresholdValue = threshold.float(i);
const distance = linear_algebra_1.Vec3.distance(a, b);
let satisfied = true;
if (restraintType === 'harmonic') {
const thresholdValue = threshold.float(i);
satisfied = distance >= (1 - HarmonicRestraintTolerance) * thresholdValue && distance <= (1 + HarmonicRestraintTolerance) * thresholdValue;
}
else if (restraintType === 'upper bound') {
satisfied = distance <= thresholdValue;
}
else if (restraintType === 'lower bound') {
satisfied = distance >= thresholdValue;
}
restraints.push({
e1: { ...e1key, label_comp_id: comp_id_1.str(i) },
e2: { ...e2key, label_comp_id: comp_id_2.str(i) },
a: linear_algebra_1.Vec3.clone(a),
b: linear_algebra_1.Vec3.clone(b),
restraintType,
threshold: thresholdValue,
satisfied,
distance,
});
}
}
return { entity_labels, model_restraints };
}
function baseStructure(url, modelIndex, info, options) {
const builder = (0, mvs_builder_1.createMVSBuilder)();
const structure = builder
.download({ url })
.parse({ format: 'mmcif' })
.modelStructure({ model_index: modelIndex });
structure
.component({ selector: 'coarse' })
.representation({ type: 'spacefill' })
.color({ custom: { molstar_use_default_coloring: true } })
.opacity({ opacity: 0.51 });
structure
.component({ selector: 'polymer' })
.representation({ type: 'cartoon' })
.color({ custom: { molstar_use_default_coloring: true } })
.opacity({ opacity: 0.51 });
if (!(options === null || options === void 0 ? void 0 : options.noEntityLabels)) {
const primitives = structure.primitives();
for (const [label_entity_id, text] of info.entity_labels) {
if (!text)
continue;
primitives
.label({ position: { label_entity_id }, text, label_size: 16, label_color: '#cccccc' });
}
}
return [builder, structure];
}
function drawConstraints([, structure], restraints, options) {
var _a, _b;
const primitives = structure.primitives();
for (const r of restraints) {
if (!options.filter(r))
continue;
const radius = (_b = (_a = options.radius) === null || _a === void 0 ? void 0 : _a.call(options, r)) !== null && _b !== void 0 ? _b : 1;
primitives.tube({
start: r.a,
end: r.b,
color: options.color(r) || 'white',
tooltip: options.tooltip(r),
radius: radius,
dash_length: radius,
});
}
}
function restraintTooltip(r) {
return `
- Element 1: ${r.e1.label_entity_id} ${r.e1.label_asym_id} ${r.e1.label_seq_id} ${r.e1.label_comp_id}
- Element 2: ${r.e2.label_entity_id} ${r.e2.label_asym_id} ${r.e2.label_seq_id} ${r.e2.label_comp_id}
- Distance: ${r.distance.toFixed(2)} Å
- Threshold: ${r.threshold.toFixed(2)} Å
- Constraint: ${r.restraintType}
- Satisfied: ${r.satisfied ? 'Yes' : 'No'}
`;
}
async function loadIHMRestraints(root, url) {
url !== null && url !== void 0 ? url : (url = 'https://pdb-ihm.org/cif/8zz1.cif');
const plugin = await createViewer(root);
const info = await parseInfo(plugin, url);
const modelIndex = 0;
const restraints = info.model_restraints[modelIndex];
const nVialoted = restraints.filter(r => !r.satisfied).length;
const nSatisfied = restraints.length - nVialoted;
const snapshots = [];
let mvs = baseStructure(url, modelIndex, info);
drawConstraints(mvs, restraints, {
filter: r => true,
color: r => r.e1.label_entity_id === r.e2.label_entity_id && r.e1.label_asym_id === r.e2.label_asym_id ? 'yellow' : 'blue',
radius: r => 1,
tooltip: restraintTooltip,
});
snapshots.push(mvs[0].getSnapshot({
title: 'All Restraints',
linger_duration_ms: 5000,
description: `
### All Restraints
- Yellow: Intra-chain restraints
- Blue: Inter-chain restraints
`,
}));
mvs = baseStructure(url, modelIndex, info);
drawConstraints(mvs, restraints, {
filter: r => true,
color: r => r.satisfied ? 'green' : 'red',
radius: r => 1,
tooltip: restraintTooltip,
});
snapshots.push(mvs[0].getSnapshot({
title: 'Restraint Validation',
linger_duration_ms: 5000,
description: `
### Restraint Validation
- Red: ${nVialoted} Violated restraints
- Green: ${nSatisfied} Satisfied restraints
`,
}));
mvs = baseStructure(url, modelIndex, info);
drawConstraints(mvs, restraints, {
filter: r => !r.satisfied,
color: r => r.satisfied ? 'green' : 'red',
radius: r => 1,
tooltip: restraintTooltip,
});
snapshots.push(mvs[0].getSnapshot({
title: 'Violated Restraints',
linger_duration_ms: 5000,
description: `
### Violated Restraints
${nVialoted} restraints are violated.
`,
}));
mvs = baseStructure(url, modelIndex, info);
drawConstraints(mvs, restraints, {
filter: r => r.satisfied,
color: r => r.satisfied ? 'green' : 'red',
radius: r => 1,
tooltip: restraintTooltip,
});
snapshots.push(mvs[0].getSnapshot({
title: 'Satisfied Restraints',
linger_duration_ms: 5000,
description: `
### Satisfied Restraints
${nSatisfied} restraints are satisfied.
`,
}));
const data = {
kind: 'multiple',
snapshots,
metadata: {
title: 'I/HM Restraints',
version: '1.0',
timestamp: new Date().toISOString(),
}
};
await (0, load_1.loadMVS)(plugin, data, { sanityChecks: true, keepCamera: true });
return data;
}
window.loadIHMRestraints = loadIHMRestraints;
window.molStarDownload = download_1.download;