UNPKG

molstar

Version:

A comprehensive macromolecular library.

306 lines (301 loc) 12.5 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.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;