molstar
Version:
A comprehensive macromolecular library.
440 lines (439 loc) • 24.4 kB
JavaScript
"use strict";
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
* @author Aliaksei Chareshneu <chareshneu.tech@gmail.com>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BuiltinLoadingExtensions = exports.MolstarLoadingContext = void 0;
exports.loadMVS = loadMVS;
const snapshots_1 = require("../../mol-plugin-state/manager/snapshots.js");
const objects_1 = require("../../mol-plugin-state/objects.js");
const data_1 = require("../../mol-plugin-state/transforms/data.js");
const model_1 = require("../../mol-plugin-state/transforms/model.js");
const representation_1 = require("../../mol-plugin-state/transforms/representation.js");
const volume_1 = require("../../mol-plugin-state/transforms/volume.js");
const commands_1 = require("../../mol-plugin/commands.js");
const mol_state_1 = require("../../mol-state/index.js");
const mol_task_1 = require("../../mol-task/index.js");
const behavior_1 = require("./behavior.js");
const camera_1 = require("./camera.js");
const annotation_prop_1 = require("./components/annotation-prop.js");
const annotation_structure_component_1 = require("./components/annotation-structure-component.js");
const annotation_tooltips_prop_1 = require("./components/annotation-tooltips-prop.js");
const representation_2 = require("./components/custom-label/representation.js");
const custom_tooltips_prop_1 = require("./components/custom-tooltips-prop.js");
const is_mvs_model_prop_1 = require("./components/is-mvs-model-prop.js");
const primitives_1 = require("./components/primitives.js");
const trajectory_1 = require("./components/trajectory.js");
const animation_1 = require("./helpers/animation.js");
const is_hidden_custom_state_1 = require("./load-extensions/is-hidden-custom-state.js");
const non_covalent_interactions_1 = require("./load-extensions/non-covalent-interactions.js");
const load_generic_1 = require("./load-generic.js");
const load_helpers_1 = require("./load-helpers.js");
const mvs_data_1 = require("./mvs-data.js");
const animation_tree_1 = require("./tree/animation/animation-tree.js");
const tree_validation_1 = require("./tree/generic/tree-validation.js");
const conversion_1 = require("./tree/molstar/conversion.js");
const molstar_tree_1 = require("./tree/molstar/molstar-tree.js");
const mvs_tree_1 = require("./tree/mvs/mvs-tree.js");
function loadMVS(plugin, data, options = {}) {
const task = mol_task_1.Task.create('Load MVS', ctx => _loadMVS(ctx, plugin, data, options));
return plugin.runTask(task);
}
/** Load a MolViewSpec (MVS) state(s) into the Mol* plugin as plugin state snapshots. */
async function _loadMVS(ctx, plugin, data, options = {}) {
plugin.errorContext.clear('mvs');
try {
const mvsExtensionLoaded = plugin.state.hasBehavior(behavior_1.MolViewSpec);
if (!mvsExtensionLoaded)
throw new Error('MolViewSpec extension is not loaded.');
// Stop any currently running audio
plugin.managers.markdownExtensions.audio.dispose();
// Reset canvas props to default so that modifyCanvasProps works as expected
(0, camera_1.resetCanvasProps)(plugin);
// console.log(`MVS tree:\n${MVSData.toPrettyString(data)}`)
const multiData = data.kind === 'multiple' ? data : mvs_data_1.MVSData.stateToStates(data);
const entries = [];
for (let i = 0; i < multiData.snapshots.length; i++) {
const snapshot = multiData.snapshots[i];
const previousSnapshot = i > 0 ? multiData.snapshots[i - 1] : multiData.snapshots[multiData.snapshots.length - 1];
(0, tree_validation_1.validateTree)(mvs_tree_1.MVSTreeSchema, snapshot.root, 'MVS', plugin);
if (snapshot.animation) {
(0, tree_validation_1.validateTree)(animation_tree_1.MVSAnimationSchema, snapshot.animation, 'Animation', plugin);
}
if (options.sanityChecks)
(0, conversion_1.mvsSanityCheck)(snapshot.root);
const molstarTree = (0, conversion_1.convertMvsToMolstar)(snapshot.root, options.sourceUrl);
(0, tree_validation_1.validateTree)(molstar_tree_1.MolstarTreeSchema, molstarTree, 'Converted Molstar', plugin);
const entry = molstarTreeToEntry(plugin, molstarTree, snapshot.animation, { ...snapshot.metadata, previousTransitionDurationMs: previousSnapshot.metadata.transition_duration_ms }, options);
await assignStateTransition(ctx, plugin, entry, snapshot, options, i, multiData.snapshots.length);
entries.push(entry);
if (ctx.shouldUpdate) {
await ctx.update({ message: 'Loading MVS...', current: i, max: multiData.snapshots.length });
}
}
if (!options.appendSnapshots) {
plugin.managers.snapshot.clear();
}
for (const entry of entries) {
plugin.managers.snapshot.add(entry);
}
if (entries.length > 0) {
await commands_1.PluginCommands.State.Snapshots.Apply(plugin, { id: entries[0].snapshot.id });
}
}
catch (err) {
plugin.log.error(`${err}`);
throw err;
}
finally {
if (!options.doNotReportErrors) {
for (const error of plugin.errorContext.get('mvs')) {
plugin.log.warn(error);
commands_1.PluginCommands.Toast.Show(plugin, {
title: 'Error',
message: error,
timeoutMs: 10000
});
}
}
plugin.errorContext.clear('mvs');
}
}
async function assignStateTransition(ctx, plugin, parentEntry, parent, options, snapshotIndex, snapshotCount) {
var _a, _b, _c, _d;
const transitions = await (0, animation_1.generateStateTransition)(ctx, parent, snapshotIndex, snapshotCount);
if (!(transitions === null || transitions === void 0 ? void 0 : transitions.frames.length))
return;
const animation = {
autoplay: !!((_a = transitions.tree.params) === null || _a === void 0 ? void 0 : _a.autoplay),
loop: !!((_b = transitions.tree.params) === null || _b === void 0 ? void 0 : _b.loop),
frames: [],
};
for (let i = 0; i < transitions.frames.length; i++) {
const frame = transitions.frames[i];
const molstarTree = (0, conversion_1.convertMvsToMolstar)(frame[0], options.sourceUrl);
const entry = molstarTreeToEntry(plugin, molstarTree, parent.animation, { ...parent.metadata, previousTransitionDurationMs: transitions.frametimeMs }, options);
mol_state_1.StateTree.reuseTransformParams(entry.snapshot.data.tree, parentEntry.snapshot.data.tree);
animation.frames.push({
durationInMs: frame[1],
data: entry.snapshot.data,
camera: ((_c = transitions.tree.params) === null || _c === void 0 ? void 0 : _c.include_camera) ? entry.snapshot.camera : undefined,
canvas3d: ((_d = transitions.tree.params) === null || _d === void 0 ? void 0 : _d.include_canvas) ? entry.snapshot.canvas3d : undefined,
});
if (ctx.shouldUpdate) {
await ctx.update({ message: `Loading animation for snapshot ${snapshotIndex + 1}/${snapshotCount}...`, current: i + 1, max: transitions.frames.length });
}
}
parentEntry.snapshot.transition = animation;
}
function molstarTreeToEntry(plugin, tree, animation, metadata, options) {
var _a, _b, _c, _d;
const context = exports.MolstarLoadingContext.create();
const snapshot = (0, load_generic_1.loadTreeVirtual)(plugin, tree, MolstarLoadingActions, context, { replaceExisting: true, extensions: (_a = options === null || options === void 0 ? void 0 : options.extensions) !== null && _a !== void 0 ? _a : exports.BuiltinLoadingExtensions });
snapshot.canvas3d = {
props: plugin.canvas3d ? (0, camera_1.modifyCanvasProps)(plugin.canvas3d.props, context.canvas, animation) : undefined,
};
if (!(options === null || options === void 0 ? void 0 : options.keepCamera)) {
snapshot.camera = (0, camera_1.createPluginStateSnapshotCamera)(plugin, context, metadata);
}
snapshot.durationInMs = metadata.linger_duration_ms + ((_b = metadata.previousTransitionDurationMs) !== null && _b !== void 0 ? _b : 0);
if ((_c = tree.custom) === null || _c === void 0 ? void 0 : _c.molstar_on_load_markdown_commands) {
snapshot.onLoadMarkdownCommands = tree.custom.molstar_on_load_markdown_commands;
}
const entryParams = {
key: metadata.key,
name: metadata.title,
description: metadata.description,
descriptionFormat: (_d = metadata.description_format) !== null && _d !== void 0 ? _d : 'markdown',
};
const entry = snapshots_1.PluginStateSnapshotManager.Entry(snapshot, entryParams);
return entry;
}
exports.MolstarLoadingContext = {
create() {
return {
annotationMap: new Map(),
camera: { focuses: [] },
};
},
};
/** Loading actions for loading a `MolstarTree`, per node kind. */
const MolstarLoadingActions = {
root(updateParent, node, context) {
context.nearestReprMap = (0, load_helpers_1.makeNearestReprMap)(node);
return updateParent;
},
download(updateParent, node) {
return load_generic_1.UpdateTarget.apply(updateParent, data_1.Download, {
url: node.params.url,
isBinary: node.params.is_binary,
});
},
parse(updateParent, node) {
const format = node.params.format;
switch (format) {
case 'cif':
return load_generic_1.UpdateTarget.apply(updateParent, data_1.ParseCif, {});
case 'pdb':
case 'pdbqt':
case 'gro':
case 'xyz':
case 'mol':
case 'sdf':
case 'mol2':
case 'xtc':
case 'lammpstrj':
case 'dcd':
case 'nctraj':
case 'trr':
return updateParent;
case 'psf':
return load_generic_1.UpdateTarget.apply(updateParent, data_1.ParsePsf, {});
case 'prmtop':
return load_generic_1.UpdateTarget.apply(updateParent, data_1.ParsePrmtop, {});
case 'top':
return load_generic_1.UpdateTarget.apply(updateParent, data_1.ParseTop, {});
case 'map':
return load_generic_1.UpdateTarget.apply(updateParent, data_1.ParseCcp4, {});
case 'dx':
case 'dxbin':
return load_generic_1.UpdateTarget.apply(updateParent, data_1.ParseDx, {});
default:
console.error(`Unknown format in "parse" node: "${format}"`);
return undefined;
}
},
coordinates(updateParent, node) {
const format = node.params.format;
switch (format) {
case 'nctraj':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.CoordinatesFromNctraj);
case 'dcd':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.CoordinatesFromDcd);
case 'trr':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.CoordinatesFromTrr);
case 'xtc':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.CoordinatesFromXtc);
case 'lammpstrj':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.CoordinatesFromLammpstraj);
default:
console.error(`Unknown format in "coordinates" node: "${format}"`);
return undefined;
}
},
trajectory(updateParent, node) {
var _a, _b;
const format = node.params.format;
switch (format) {
case 'cif':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.TrajectoryFromMmCif, {
blockHeader: (_a = node.params.block_header) !== null && _a !== void 0 ? _a : '', // Must set to '' because just undefined would get overwritten by createDefaults
blockIndex: (_b = node.params.block_index) !== null && _b !== void 0 ? _b : undefined,
});
case 'pdb':
case 'pdbqt':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.TrajectoryFromPDB, { isPdbqt: format === 'pdbqt' });
case 'gro':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.TrajectoryFromGRO);
case 'xyz':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.TrajectoryFromXYZ);
case 'mol':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.TrajectoryFromMOL);
case 'sdf':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.TrajectoryFromSDF);
case 'mol2':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.TrajectoryFromMOL2);
case 'lammpstrj':
return load_generic_1.UpdateTarget.apply(updateParent, model_1.TrajectoryFromLammpsTrajData);
default:
console.error(`Unknown format in "trajectory" node: "${format}"`);
return undefined;
}
},
trajectory_with_coordinates(updateParent, node) {
const result = load_generic_1.UpdateTarget.apply(updateParent, trajectory_1.MVSTrajectoryWithCoordinates, {
coordinatesRef: node.params.coordinates_ref,
});
return load_generic_1.UpdateTarget.setMvsDependencies(result, [node.params.coordinates_ref]);
},
topology_with_coordinates(updateParent, node) {
let parsed;
const format = node.params.format;
switch (format) {
case 'psf':
parsed = load_generic_1.UpdateTarget.apply(updateParent, model_1.TopologyFromPsf, {});
break;
case 'prmtop':
parsed = load_generic_1.UpdateTarget.apply(updateParent, model_1.TopologyFromPrmtop, {});
break;
case 'top':
parsed = load_generic_1.UpdateTarget.apply(updateParent, model_1.TopologyFromTop, {});
break;
default:
console.error(`Unknown format in "topology_with_coordinates" node: "${format}"`);
return undefined;
}
const result = load_generic_1.UpdateTarget.apply(parsed, trajectory_1.MVSTrajectoryWithCoordinates, {
coordinatesRef: node.params.coordinates_ref,
});
return load_generic_1.UpdateTarget.setMvsDependencies(result, [node.params.coordinates_ref]);
},
model(updateParent, node, context) {
const annotations = (0, load_helpers_1.collectAnnotationReferences)(node, context);
const model = load_generic_1.UpdateTarget.apply(updateParent, model_1.ModelFromTrajectory, {
modelIndex: node.params.model_index,
});
load_generic_1.UpdateTarget.apply(model, model_1.CustomModelProperties, {
properties: {
[is_mvs_model_prop_1.IsMVSModelProvider.descriptor.name]: { isMvs: true },
[annotation_prop_1.MVSAnnotationsProvider.descriptor.name]: { annotations },
},
autoAttach: [
is_mvs_model_prop_1.IsMVSModelProvider.descriptor.name,
annotation_prop_1.MVSAnnotationsProvider.descriptor.name,
],
});
return model;
},
structure(updateParent, node, context) {
var _a;
const props = (0, load_helpers_1.structureProps)(node);
const struct = load_generic_1.UpdateTarget.apply(updateParent, model_1.StructureFromModel, props);
const transformed = (0, load_helpers_1.transformAndInstantiateStructure)(struct, node);
const annotationTooltips = (0, load_helpers_1.collectAnnotationTooltips)(node, context);
const inlineTooltips = (0, load_helpers_1.collectInlineTooltips)(node, context);
load_generic_1.UpdateTarget.apply(struct, model_1.CustomStructureProperties, {
properties: {
[annotation_tooltips_prop_1.MVSAnnotationTooltipsProvider.descriptor.name]: { tooltips: annotationTooltips },
[custom_tooltips_prop_1.CustomTooltipsProvider.descriptor.name]: { tooltips: inlineTooltips },
},
autoAttach: [
annotation_tooltips_prop_1.MVSAnnotationTooltipsProvider.descriptor.name,
custom_tooltips_prop_1.CustomTooltipsProvider.descriptor.name,
],
}); // CustomStructureProperties must be applied even when `annotationTooltips` and `inlineTooltips` are empty, otherwise tooltips would persists across MVS snapshots
const inlineLabels = (0, load_helpers_1.collectInlineLabels)(node, context);
if (inlineLabels.length > 0) {
const nearestReprNode = (_a = context.nearestReprMap) === null || _a === void 0 ? void 0 : _a.get(node);
load_generic_1.UpdateTarget.apply(struct, representation_1.StructureRepresentation3D, {
type: {
name: representation_2.CustomLabelRepresentationProvider.name,
params: { items: inlineLabels },
},
colorTheme: (0, load_helpers_1.colorThemeForNode)(nearestReprNode, context),
});
}
return transformed;
},
tooltip: undefined, // No action needed, already loaded in `structure`
tooltip_from_uri: undefined, // No action needed, already loaded in `structure`
tooltip_from_source: undefined, // No action needed, already loaded in `structure`
component(updateParent, node) {
if ((0, load_helpers_1.isPhantomComponent)(node)) {
return updateParent;
}
const selector = node.params.selector;
return (0, load_helpers_1.transformAndInstantiateStructure)(load_generic_1.UpdateTarget.apply(updateParent, model_1.StructureComponent, {
type: (0, load_helpers_1.componentPropsFromSelector)(selector),
label: (0, load_helpers_1.prettyNameFromSelector)(selector),
nullIfEmpty: false,
}), node);
},
component_from_uri(updateParent, node, context) {
if ((0, load_helpers_1.isPhantomComponent)(node))
return undefined;
const props = (0, load_helpers_1.componentFromXProps)(node, context);
return (0, load_helpers_1.transformAndInstantiateStructure)(load_generic_1.UpdateTarget.apply(updateParent, annotation_structure_component_1.MVSAnnotationStructureComponent, props), node);
},
component_from_source(updateParent, node, context) {
if ((0, load_helpers_1.isPhantomComponent)(node))
return undefined;
const props = (0, load_helpers_1.componentFromXProps)(node, context);
return (0, load_helpers_1.transformAndInstantiateStructure)(load_generic_1.UpdateTarget.apply(updateParent, annotation_structure_component_1.MVSAnnotationStructureComponent, props), node);
},
representation(updateParent, node, context) {
return load_generic_1.UpdateTarget.apply(updateParent, representation_1.StructureRepresentation3D, {
...(0, load_helpers_1.representationProps)(node),
colorTheme: (0, load_helpers_1.colorThemeForNode)(node, context),
});
},
volume(updateParent, node) {
var _a, _b, _c;
let volume;
if ((_a = updateParent.transformer) === null || _a === void 0 ? void 0 : _a.definition.to.includes(objects_1.PluginStateObject.Format.Ccp4)) {
volume = load_generic_1.UpdateTarget.apply(updateParent, volume_1.VolumeFromCcp4, {});
}
else if ((_b = updateParent.transformer) === null || _b === void 0 ? void 0 : _b.definition.to.includes(objects_1.PluginStateObject.Format.Dx)) {
volume = load_generic_1.UpdateTarget.apply(updateParent, volume_1.VolumeFromDx, {});
}
else if ((_c = updateParent.transformer) === null || _c === void 0 ? void 0 : _c.definition.to.includes(objects_1.PluginStateObject.Format.Cif)) {
volume = load_generic_1.UpdateTarget.apply(updateParent, volume_1.VolumeFromDensityServerCif, { blockHeader: node.params.channel_id || undefined });
}
else {
console.error(`Unsupported volume format`);
return undefined;
}
return (0, load_helpers_1.transformAndInstantiateVolume)(volume, node);
},
volume_representation(updateParent, node, context) {
return load_generic_1.UpdateTarget.apply(updateParent, representation_1.VolumeRepresentation3D, {
...(0, load_helpers_1.volumeRepresentationProps)(node),
colorTheme: (0, load_helpers_1.volumeColorThemeForNode)(node, context),
});
},
color: undefined, // No action needed, already loaded in `representation`
color_from_uri: undefined, // No action needed, already loaded in `representation`
color_from_source: undefined, // No action needed, already loaded in `representation`
label: undefined, // No action needed, already loaded in `structure`
label_from_uri(updateParent, node, context) {
const props = (0, load_helpers_1.labelFromXProps)(node, context);
return load_generic_1.UpdateTarget.apply(updateParent, representation_1.StructureRepresentation3D, props);
},
label_from_source(updateParent, node, context) {
const props = (0, load_helpers_1.labelFromXProps)(node, context);
return load_generic_1.UpdateTarget.apply(updateParent, representation_1.StructureRepresentation3D, props);
},
focus(updateParent, node, context) {
context.camera.focuses.push({ target: updateParent.selector, params: node.params });
return updateParent;
},
camera(updateParent, node, context) {
context.camera.cameraParams = node.params;
return updateParent;
},
canvas(updateParent, node, context) {
context.canvas = node;
return updateParent;
},
primitives(updateParent, tree, context) {
const refs = (0, primitives_1.getPrimitiveStructureRefs)(tree);
const clip = (0, load_helpers_1.clippingForNode)(tree);
const data = load_generic_1.UpdateTarget.apply(updateParent, primitives_1.MVSInlinePrimitiveData, { node: tree });
load_generic_1.UpdateTarget.setMvsDependencies(data, refs); // MVSInlinePrimitiveData must depend on `refs` because it caches positions
return applyPrimitiveVisuals(data, refs, clip);
},
primitives_from_uri(updateParent, tree, context) {
const refs = new Set(tree.params.references);
const clip = (0, load_helpers_1.clippingForNode)(tree);
const data = load_generic_1.UpdateTarget.apply(updateParent, primitives_1.MVSDownloadPrimitiveData, { uri: tree.params.uri, format: tree.params.format });
load_generic_1.UpdateTarget.setMvsDependencies(data, refs); // MVSInlinePrimitiveData must depend on `refs` because it caches positions
return applyPrimitiveVisuals(data, refs, clip);
},
};
function applyPrimitiveVisuals(data, refs, clip) {
const mesh = load_generic_1.UpdateTarget.setMvsDependencies(load_generic_1.UpdateTarget.apply(data, primitives_1.MVSBuildPrimitiveShape, { kind: 'mesh', clip }, { state: { isGhost: true } }), refs);
load_generic_1.UpdateTarget.apply(mesh, primitives_1.MVSShapeRepresentation3D);
const labels = load_generic_1.UpdateTarget.setMvsDependencies(load_generic_1.UpdateTarget.apply(data, primitives_1.MVSBuildPrimitiveShape, { kind: 'labels', clip }, { state: { isGhost: true } }), refs);
load_generic_1.UpdateTarget.apply(labels, primitives_1.MVSShapeRepresentation3D);
const lines = load_generic_1.UpdateTarget.setMvsDependencies(load_generic_1.UpdateTarget.apply(data, primitives_1.MVSBuildPrimitiveShape, { kind: 'lines', clip }, { state: { isGhost: true } }), refs);
load_generic_1.UpdateTarget.apply(lines, primitives_1.MVSShapeRepresentation3D);
return data;
}
exports.BuiltinLoadingExtensions = [
non_covalent_interactions_1.NonCovalentInteractionsExtension,
is_hidden_custom_state_1.IsHiddenCustomStateExtension,
];