Compare commits

..

3 Commits

Author SHA1 Message Date
dsehnal
1bd162b977 tweaks 2025-01-04 11:07:55 +01:00
dsehnal
c7fb71738e header 2025-01-04 11:03:01 +01:00
dsehnal
9413481253 Fix plugin interactions when CSS scale is applied 2025-01-04 11:02:37 +01:00
159 changed files with 22673 additions and 28275 deletions

View File

@@ -5,75 +5,18 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v4.12.0] - 2025-02-28
- Fix PDBj structure data URL
- Improve logic when to cull in renderer
- Add `atom.ihm.has-seq-id` and `atom.ihm.overlaps-seq-id-range` symbol to the query language
- MolViewSpec extension:
- Add box, arrow, ellipse, ellipsoid, angle primitives
- Add basic support for volumetric data (map, Volume Server)
- Add support for `molstar_color_theme_name` custom extension
- Better IH/M support:
- Support `coarse` components
- Support `spacefill` representation
- Support `carbohydrate` representation
- Support for `custom.molstar_use_default_coloring` property on Color node.
- Use `atom.ihm.has-seq-id` and `atom.ihm.overlaps-seq-id-range` for matching `label_seq_id` locations to support querying coarse elements.
- Add ihm-restraints example
- Add `mvs-kinase-story` example
- Remove static uses of `ColorTheme` and `SizeTheme` fields. Should resolvent "undefined" errors in certain builds
- Add `transform` property to clip objects
- Add support for trimming `image` geometry to a box
- Improve/fix iso-level support of `slice` representation
- Add support for rotating `slice` representation around an axis
- Add default color support for palette based themes
- Add `plane` structure representation
- Can be colored with any structure theme
- Can be colored with the `external-volume` theme
- Can show atoms as a cutout
- Supports principal axes and bounding box as a reference frame
- Add `Camera` section to "Screenshot / State" controls
- Add `CoarseIndex` for fast lookup of coarse elements
## [v4.11.0] - 2025-01-26
- Fix for tubular helices issue (Fixes #1422)
- Volume UI improvements
- Render all volume entries instead of selecting them one-by-one
- Toggle visibility of all volumes
- More accessible iso value control
- Render all volume entries instead of selecting them one-by-one
- Toggle visibility of all volumes
- More accessible iso value control
- Support wheel event on sliders
- MolViewSpec extension:
- Add validation for discriminated union params
- Primitives: remove triangle_colors, line_colors, have implicit grouping instead; rename many parameters
- UI configuration options
- Support removal of independent selection controls in the viewport
- Support custom selection controls
- Support for custom granularity dropdown options
- Support for custom Sequence Viewer mode options
- Add validation for discriminated union params
- Primitives: remove triangle_colors, line_colors, have implicit grouping instead; rename many parameters
- Add `external-structure` theme that colors any geometry by structure properties
- Support float and half-float data type for direct-volume rendering and GPU isosurface extraction
- Minor documentation updates
- Add support for position-location to `volume-value` color theme
- Add support for color themes to `slice` representation
- Improve/fix palette support in volume color themes
- Fix `Plane3D.projectPoint`
- Fix marking related `image` rendering issues
- Handle pixels without a group
- Take fog into account
- MolViewSpec extension: Initial support for customizable representation parameters
- Quick Styles section reorganized
- UI color improvements (scrollbar contrast, toggle button hover color)
- Add `overrideWater` param for entity-id color theme
- Renames PDB-Dev to PDB-IHM and adjusts data source
- Fix vertex based themes for spheres shader
- Add volume dot representation
- Add volume-value size theme
- Sequence panel: Mark focused loci (bold+underline)
- Change modifier key behavior in Normal Mode (default = select only, Ctrl/Cmd = add to selection, Shift = extend last selected range)
- Handle Firefox's limit on vertex ids per draw (#1116)
- Fix behavior of `Vec3.makeRotation(out, a, b)` when `a ≈ -b`
- Fix plugin mouse interactions when CSS `scale` transform is applied
## [v4.10.0] - 2024-12-15
@@ -141,7 +84,7 @@ Note that since we don't clearly distinguish between a public and private interf
- Fix `findPredecessorIndex` bug when repeating values
- MolViewSpec: Support for transparency and custom properties
- MolViewSpec: MVP Support for geometrical primitives (mesh, lines, line, label, distance measurement)
- Mesoscale Explorer: Add support for 4-character PDB IDs (e.g., 8ZZC) in PDB-IHM/PDB-Dev loader
- Mesoscale Explorer: Add support for 4-character PDB IDs (e.g., 8ZZC) in PDB-Dev loader
- Fix Sequence View in Safari 18
- Improve performance of `IndexPairBonds` assignment when operator keys are available
- ModelArchive QualityAssessment extension:
@@ -194,7 +137,7 @@ Note that since we don't clearly distinguish between a public and private interf
- Improve entity-id coloring for structures with multiple models from the same source (#1221)
- Wrap screenshot & image generation in a `Task`
- AlphaFold DB: Add BinaryCIF support when fetching data
- PDB-IHM/PDB-Dev: Add support for 4-character PDB IDs (e.g., 8ZZC)
- PDB-Dev: Add support for 4-character PDB IDs (e.g., 8ZZC)
- Fix polymer-gap visual coloring with cartoon theme
- Add formal-charge color theme (#328)
- Add more coloring options to cartoon theme

44409
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "4.12.1",
"version": "4.10.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -114,8 +114,7 @@
"Eric E <etongfu@outlook.com>",
"Xavier Martinez <xavier.martinez.xm@gmail.com>",
"Alex Chan <smalldirkalex@gmail.com>",
"Simeon Borko <simeon.borko@gmail.com>",
"Ventura Rivera <venturaxrivera@gmail.com>"
"Simeon Borko <simeon.borko@gmail.com>"
],
"license": "MIT",
"devDependencies": {
@@ -123,19 +122,19 @@
"@types/gl": "^6.0.5",
"@types/jest": "^29.5.14",
"@types/pngjs": "^6.0.5",
"@types/react": "^18.3.18",
"@types/react": "^18.3.16",
"@types/react-dom": "^18.3.5",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"benchmark": "^2.1.4",
"concurrently": "^9.1.2",
"concurrently": "^9.1.0",
"cpx2": "^8.0.0",
"crypto-browserify": "^3.12.1",
"css-loader": "^7.1.2",
"eslint": "^8.57.1",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^11.3.0",
"fs-extra": "^11.2.0",
"http-server": "^14.1.1",
"jest": "^29.7.0",
"jpeg-js": "^0.4.4",
@@ -144,22 +143,22 @@
"raw-loader": "^4.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sass": "^1.83.4",
"sass": "^1.83.0",
"sass-loader": "^16.0.4",
"simple-git": "^3.27.0",
"stream-browserify": "^3.0.0",
"style-loader": "^4.0.0",
"ts-jest": "^29.2.5",
"typescript": "^5.7.3",
"typescript": "^5.7.2",
"webpack": "^5.97.1",
"webpack-cli": "^6.0.1"
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@types/argparse": "^2.0.17",
"@types/benchmark": "^2.1.5",
"@types/compression": "1.7.5",
"@types/express": "^5.0.0",
"@types/node": "^18.19.74",
"@types/node": "^18.19.68",
"@types/node-fetch": "^2.6.12",
"@types/swagger-ui-dist": "3.30.5",
"argparse": "^2.0.1",
@@ -171,11 +170,11 @@
"immutable": "^5.0.3",
"io-ts": "^2.2.22",
"node-fetch": "^2.7.0",
"react-markdown": "^9.0.3",
"react-markdown": "^9.0.1",
"rxjs": "^7.8.1",
"swagger-ui-dist": "^5.18.2",
"tslib": "^2.8.1",
"util.promisify": "^1.1.3",
"util.promisify": "^1.1.2",
"xhr2": "^0.2.1"
},
"peerDependencies": {

View File

@@ -25,7 +25,7 @@ import { MesoFocusLoci } from './behavior/camera';
import { GraphicsMode, MesoscaleState } from './data/state';
import { MesoSelectLoci } from './behavior/select';
import { Transparency } from '../../mol-gl/webgl/render-item';
import { LoadModel, loadExampleEntry, loadPdb, loadPdbIhm, loadUrl, openState } from './ui/states';
import { LoadModel, loadExampleEntry, loadPdb, loadPdbDev, loadUrl, openState } from './ui/states';
import { Asset } from '../../mol-util/assets';
import { AnimateCameraSpin } from '../../mol-plugin-state/animation/built-in/camera-spin';
import { AnimateCameraRock } from '../../mol-plugin-state/animation/built-in/camera-rock';
@@ -120,15 +120,8 @@ export class MesoscaleExplorer {
await loadPdb(this.plugin, id);
}
/**
* @deprecated Scheduled for removal in v5. Use {@link loadPdbIhm | loadPdbIhm(id: string)} instead.
*/
async loadPdbDev(id: string) {
await this.loadPdbIhm(id);
}
async loadPdbIhm(id: string) {
await loadPdbIhm(this.plugin, id);
await loadPdbDev(this.plugin, id);
}
static async create(elementOrId: string | HTMLElement, options: Partial<MesoscaleExplorerOptions> = {}) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -12,7 +12,7 @@ import { Color } from '../../../mol-util/color';
import { Spheres } from '../../../mol-geo/geometry/spheres/spheres';
import { Clip } from '../../../mol-util/clip';
import { escapeRegExp, stringToWords } from '../../../mol-util/string';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { ParamMapping } from '../../../mol-util/param-mapping';
import { EntityNode } from '../ui/entities';
import { DistinctColorsProps, distinctColors } from '../../../mol-util/color/distinct';
@@ -211,8 +211,7 @@ export function getClipObjects(values: SimpleClipProps, boundingSphere: Sphere3D
invert: values.invert,
position,
scale,
rotation: values.rotation,
transform: Mat4.identity(),
rotation: values.rotation
}];
}

View File

@@ -93,15 +93,9 @@
return;
}
var pdbihm = getParam('pdbihm', '[^&]+').trim();
if (pdbihm) {
me.loadPdbIhm(pdbihm);
return;
}
// support for deprecated pdb-dev param
var pdbdev = getParam('pdbdev', '[^&]+').trim();
if (pdbdev) {
me.loadPdbIhm(pdbdev);
me.loadPdbDev(pdbdev);
return;
}
window.addEventListener('unload', () => {

View File

@@ -211,15 +211,15 @@ export async function loadPdb(ctx: PluginContext, id: string) {
await createHierarchy(ctx, data.ref);
}
export async function loadPdbIhm(ctx: PluginContext, id: string) {
export async function loadPdbDev(ctx: PluginContext, id: string) {
await reset(ctx);
let url: string;
// 4 character PDB id, TODO: support extended PDB ID
if (id.match(/^[1-9][A-Z0-9]{3}$/i) !== null) {
url = `https://pdb-ihm.org/bcif/${id.toLowerCase()}.bcif`;
url = `https://pdb-dev.wwpdb.org/bcif/${id.toLowerCase()}.bcif`;
} else {
const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
url = `https://pdb-ihm.org/bcif/${nId.toUpperCase()}.bcif`;
url = `https://pdb-dev.wwpdb.org/bcif/${nId.toUpperCase()}.bcif`;
}
const data = await ctx.builders.data.download({ url, isBinary: true });
await createHierarchy(ctx, data.ref);
@@ -231,7 +231,7 @@ export const LoadDatabase = StateAction.build({
display: { name: 'Database', description: 'Load from Database' },
params: (a, ctx: PluginContext) => {
return {
source: PD.Select('pdb', PD.objectToOptions({ pdb: 'PDB', pdbIhm: 'PDB-IHM' })),
source: PD.Select('pdb', PD.objectToOptions({ pdb: 'PDB', pdbDev: 'PDB-Dev' })),
entry: PD.Text(''),
};
},
@@ -239,8 +239,8 @@ export const LoadDatabase = StateAction.build({
})(({ params }, ctx: PluginContext) => Task.create('Loading from database...', async taskCtx => {
if (params.source === 'pdb') {
await loadPdb(ctx, params.entry);
} else if (params.source === 'pdbIhm') {
await loadPdbIhm(ctx, params.entry);
} else if (params.source === 'pdbDev') {
await loadPdbDev(ctx, params.entry);
}
}));
@@ -426,7 +426,7 @@ export class ExplorerInfo extends PluginUIComponent<{}, { isDisabled: boolean, s
driver.setSteps([
// Left panel
{ element: '#explorerinfo', popover: { title: 'Explorer Header Info', description: 'This section displays the explorer header with version information, documentation access, and tour navigation. Use the right and left arrow keys to navigate the tour.', side: 'left', align: 'start' } },
{ element: '#database', popover: { title: 'Import from PDB', description: 'Load structures directly from PDB and PDB-IHM databases.', side: 'bottom', align: 'start' } },
{ element: '#database', popover: { title: 'Import from PDB', description: 'Load structures directly from PDB and PDB-DEV databases.', side: 'bottom', align: 'start' } },
{ element: '#loader', popover: { title: 'Import from File', description: 'Load local files (.molx, .molj, .zip, .cif, .bcif) using this option.', side: 'bottom', align: 'start' } },
{ element: '#example', popover: { title: 'Example Models and Tours', description: 'Select from a range of example models and tours provided.', side: 'left', align: 'start' } },
{ element: '#session', popover: { title: 'Session Management', description: 'Download the current session in .molx format.', side: 'top', align: 'start' } },

View File

@@ -288,21 +288,14 @@ export class Viewer {
}));
}
/**
* @deprecated Scheduled for removal in v5. Use {@link loadPdbIhm | loadPdbIhm(pdbIhm: string)} instead.
*/
loadPdbDev(pdbDev: string) {
return this.loadPdbIhm(pdbDev);
}
loadPdbIhm(pdbIhm: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'pdb-ihm' as const,
name: 'pdb-dev' as const,
params: {
provider: {
id: pdbIhm,
id: pdbDev,
encoding: 'bcif',
},
options: params.source.params.options,

View File

@@ -111,11 +111,8 @@
var pdb = getParam('pdb', '[^&]+').trim();
if (pdb) viewer.loadPdb(pdb);
var pdbIhm = getParam('pdb-ihm', '[^&]+').trim();
if (pdbIhm) viewer.loadPdbIhm(pdbIhm);
// support for deprecated pdb-dev param
var pdbDev = getParam('pdb-dev', '[^&]+').trim();
if (pdbDev) viewer.loadPdbIhm(pdbDev);
if (pdbDev) viewer.loadPdbDev(pdbDev);
var emdb = getParam('emdb', '[^&]+').trim();
if (emdb) viewer.loadEmdb(emdb);

View File

@@ -1,7 +1,6 @@
import { isPositionLocation } from '../../mol-geo/util/location-iterator';
import { Vec3 } from '../../mol-math/linear-algebra';
import { ColorTheme } from '../../mol-theme/color';
import { ColorThemeCategory } from '../../mol-theme/color/categories';
import { ThemeDataContext } from '../../mol-theme/theme';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
@@ -44,7 +43,7 @@ export function CustomColorTheme(
export const CustomColorThemeProvider: ColorTheme.Provider<{}, 'basic-wrapper-custom-color-theme'> = {
name: 'basic-wrapper-custom-color-theme',
label: 'Custom Color Theme',
category: ColorThemeCategory.Misc,
category: ColorTheme.Category.Misc,
factory: CustomColorTheme,
getParams: () => ({}),
defaultValues: { },

View File

@@ -1,31 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* IHM Restraints Example</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}
#viewer {
position: absolute;
left: 20px;
top: 20px;
width: 640px;
height: 480px;
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
<script type="text/javascript" src="./index.js"></script>
</head>
<body>
<div id="viewer"></div>
<script>
loadIHMRestraints(document.getElementById('viewer'));
</script>
</body>
</html>

View File

@@ -1,341 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { MolViewSpec } from '../../extensions/mvs/behavior';
import { loadMVS } from '../../extensions/mvs/load';
import { MVSData_States, Snapshot } from '../../extensions/mvs/mvs-data';
import { createMVSBuilder } from '../../extensions/mvs/tree/mvs/mvs-builder';
import { parseCifText } from '../../mol-io/reader/cif/text/parser';
import { Vec3 } from '../../mol-math/linear-algebra';
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
import { Model } from '../../mol-model/structure';
import { CoarseElementKey, CoarseElementReference } from '../../mol-model/structure/model/properties/coarse';
import { createPluginUI } from '../../mol-plugin-ui';
import { renderReact18 } from '../../mol-plugin-ui/react18';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginContext } from '../../mol-plugin/context';
import { PluginSpec } from '../../mol-plugin/spec';
import { Task } from '../../mol-task';
import { ajaxGet } from '../../mol-util/data-source';
import './index.html';
require('../../mol-plugin-ui/skin/light.scss');
async function createViewer(root: HTMLElement) {
const spec = DefaultPluginUISpec();
const plugin = await createPluginUI({
target: root,
render: renderReact18,
spec: {
...spec,
layout: {
initial: {
isExpanded: true,
showControls: false
}
},
components: {
remoteState: 'none',
},
behaviors: [
...spec.behaviors,
PluginSpec.Behavior(MolViewSpec)
],
config: [
[PluginConfig.Viewport.ShowAnimation, false],
[PluginConfig.Viewport.ShowTrajectoryControls, false],
]
}
});
return plugin;
}
interface IHMRestraintInfo {
e1: CoarseElementKey & { label_comp_id: string },
e2: CoarseElementKey & { label_comp_id: string },
a: Vec3,
b: Vec3,
restraintType: 'harmonic' | 'upper bound' | 'lower bound',
threshold: number,
satisfied: boolean,
distance: number,
}
function getCoarseElementPosition(e: CoarseElementReference, model: Model, position: Vec3) {
if (!e.kind) Vec3.set(position, 0, 0, 0);
const { x, y, z } = model.coarseConformation[e.kind!];
const idx = e.index;
Vec3.set(position, x[idx], y[idx], z[idx]);
}
const _elementRef = CoarseElementReference();
function resolvePosition(model: Model, key: CoarseElementKey, position: Vec3) {
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;
Vec3.set(position, x[aI], y[aI], z[aI]);
return true;
}
const HarmonicRestraintTolerance = 0.1;
async function parseRestraints(plugin: PluginContext, url: string) {
const data = await plugin.runTask(ajaxGet(url)) as string;
const parsed = await plugin.runTask(parseCifText(data));
if (parsed.isError) {
console.error(parsed);
return [];
}
const trajectory = await plugin.runTask(trajectoryFromMmCIF(parsed.result.blocks[0], parsed.result));
const dataBlocks = parsed.result.blocks;
const cat = dataBlocks[0].categories['ihm_cross_link_restraint'];
const entity_id_1 = cat.getField('entity_id_1')!;
const asym_id_1 = cat.getField('asym_id_1')!;
const seq_id_1 = cat.getField('seq_id_1')!;
const comp_id_1 = cat.getField('comp_id_1')!;
const entity_id_2 = cat.getField('entity_id_2')!;
const asym_id_2 = cat.getField('asym_id_2')!;
const seq_id_2 = cat.getField('seq_id_2')!;
const comp_id_2 = cat.getField('comp_id_2')!;
const restraint_type = cat.getField('restraint_type')!;
const threshold = cat.getField('distance_threshold')!;
const e1key = CoarseElementKey();
const e2key = CoarseElementKey();
const a = Vec3.zero();
const b = Vec3.zero();
const modelRestraints: IHMRestraintInfo[][] = [];
for (let modelIndex = 0; modelIndex < trajectory.frameCount; modelIndex++) {
const _model = trajectory.getFrameAtIndex(modelIndex);
const model = Task.is(_model) ? await plugin.runTask(_model) : _model;
const restraints: IHMRestraintInfo[] = [];
modelRestraints.push(restraints);
for (let i = 0; i < cat.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: 'harmonic' | 'upper bound' | 'lower bound' = restraint_type.str(i)?.toLowerCase() as any;
const thresholdValue = threshold.float(i);
const distance = 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: Vec3.clone(a),
b: Vec3.clone(b),
restraintType,
threshold: thresholdValue,
satisfied,
distance,
});
}
}
return modelRestraints;
}
function baseStructure(url: string, modelIndex: number) {
const builder = 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 });
return [builder, structure] as const;
}
function drawConstraints([, structure]: ReturnType<typeof baseStructure>, restraints: IHMRestraintInfo[], options: {
filter: (r: IHMRestraintInfo) => boolean,
color: (r: IHMRestraintInfo) => any,
radius?: (r: IHMRestraintInfo) => number,
tooltip: (r: IHMRestraintInfo) => string | undefined,
}) {
const primitives = structure.primitives();
for (const r of restraints) {
if (!options.filter(r)) continue;
const radius = options.radius?.(r) ?? 1;
primitives.tube({
start: r.a as any,
end: r.b as any,
color: options.color(r) || 'white',
tooltip: options.tooltip(r),
radius: radius,
dash_length: radius,
});
}
}
function restraintTooltip(r: IHMRestraintInfo) {
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'}
`;
}
export async function loadIHMRestraints(root: HTMLElement, url?: string) {
url ??= 'https://pdb-ihm.org/cif/8zz1.cif';
const plugin = await createViewer(root);
const modelRestraints = await parseRestraints(plugin, url);
const modelIndex = 0;
const restraints = modelRestraints[modelIndex];
const nVialoted = restraints.filter(r => !r.satisfied).length;
const nSatisfied = restraints.length - nVialoted;
const snapshots: Snapshot[] = [];
let mvs = baseStructure(url, modelIndex);
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);
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);
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);
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: `
### Violated Restraints
${nSatisfied} restraints are violated.
`,
}));
const data: MVSData_States = {
kind: 'multiple',
snapshots,
metadata: {
title: 'I/HM Restraints',
version: '1.0',
timestamp: new Date().toISOString(),
}
};
await loadMVS(plugin, data, { sanityChecks: true, replaceExisting: true, keepSnapshotCamera: true });
}
(window as any).loadIHMRestraints = loadIHMRestraints;

View File

@@ -1,37 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { BehaviorSubject } from 'rxjs';
import { MVSData } from '../../extensions/mvs/mvs-data';
import type { MolComponentViewerModel } from './elements/viewer';
export type MolComponentCommand =
| { kind: 'load-mvs', format?: 'mvsj' | 'mvsx', url?: string, data?: MVSData }
export class MolComponentContext {
commands = new BehaviorSubject<MolComponentCommand | undefined>(undefined);
behavior = {
viewers: new BehaviorSubject<{ name?: string, model: MolComponentViewerModel }[]>([]),
};
dispatch(command: MolComponentCommand) {
this.commands.next(command);
}
constructor(public name?: string) {
}
}
export function getMolComponentContext(options?: { name?: string, container?: object }) {
const container: any = options?.container ?? window;
container.componentContexts ??= {};
const name = options?.name ?? '<default>';
if (!container.componentContexts[name]) {
container.componentContexts[name] = new MolComponentContext(options?.name);
}
return container.componentContexts[name];
}

View File

@@ -1,118 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { BehaviorSubject, distinctUntilChanged, map } from 'rxjs';
import { PluginComponent } from '../../../mol-plugin-state/component';
import { getMolComponentContext, MolComponentContext } from '../context';
import { MolComponentViewerModel } from './viewer';
import Markdown from 'react-markdown';
import { useBehavior } from '../../../mol-plugin-ui/hooks/use-behavior';
import { createRoot } from 'react-dom/client';
import { PluginStateSnapshotManager } from '../../../mol-plugin-state/manager/snapshots';
import { MarkdownAnchor } from '../../../mol-plugin-ui/controls';
import { PluginReactContext } from '../../../mol-plugin-ui/base';
export class MolComponentSnapshotMarkdownModel extends PluginComponent {
readonly context: MolComponentContext;
root: HTMLElement | undefined = undefined;
state = new BehaviorSubject<{
entry?: PluginStateSnapshotManager.Entry,
index?: number,
all: PluginStateSnapshotManager.Entry[],
}>({ all: [] });
get viewer() {
return this.context.behavior.viewers.value?.find(v => this.options?.viewerName === v.name);
}
sync() {
const mng = this.viewer?.model.plugin?.managers.snapshot;
this.state.next({
entry: mng?.current,
index: mng?.current ? mng?.getIndex(mng.current) : undefined,
all: mng?.state.entries.toArray() ?? [],
});
}
async mount(root: HTMLElement) {
this.root = root;
createRoot(root).render(<MolComponentSnapshotMarkdownUI model={this} />);
let currentViewer: MolComponentViewerModel | undefined = undefined;
let sub: { unsubscribe: () => void } | undefined = undefined;
this.subscribe(this.context.behavior.viewers.pipe(
map(xs => xs.find(v => this.options?.viewerName === v.name)),
distinctUntilChanged((a, b) => a?.model === b?.model)
), viewer => {
if (currentViewer !== viewer) {
currentViewer = viewer?.model;
sub?.unsubscribe();
}
if (!viewer) return;
sub = this.subscribe(viewer.model.plugin?.managers.snapshot.events.changed, () => {
this.sync();
});
});
this.sync();
}
constructor(private options?: { context?: { name?: string, container?: object }, viewerName?: string }) {
super();
this.context = getMolComponentContext(options?.context);
}
}
export function MolComponentSnapshotMarkdownUI({ model }: { model: MolComponentSnapshotMarkdownModel }) {
const state = useBehavior(model.state);
if (state.all.length === 0) {
return <div>
<i>No snapshot loaded</i>
</div>;
}
return <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<div>
<button onClick={() => model.viewer?.model.plugin?.managers.snapshot.applyNext(-1)} style={{ marginRight: 8 }}>Prev</button>
<button onClick={() => model.viewer?.model.plugin?.managers.snapshot.applyNext(1)} style={{ marginRight: 8 }}>Next</button>
{typeof state.index === 'number' ? state.index + 1 : '-'}/{state.all.length}
</div>
<div style={{ flexGrow: 1, overflow: 'hidden', overflowY: 'auto', position: 'relative' }}>
<div style={{ position: 'absolute', inset: 0 }}>
<PluginReactContext.Provider value={model.viewer?.model.plugin as any}>
<Markdown skipHtml components={{ a: MarkdownAnchor }}>{state.entry?.description ?? 'Description not available'}</Markdown>
</PluginReactContext.Provider>
</div>
</div>
</div>;
}
export class MolComponentSnapshotMarkdownViewer extends HTMLElement {
private model: MolComponentSnapshotMarkdownModel | undefined = undefined;
async connectedCallback() {
this.model = new MolComponentSnapshotMarkdownModel({
context: { name: this.getAttribute('context-name') ?? undefined },
viewerName: this.getAttribute('viewer-name') ?? undefined,
});
await this.model.mount(this);
}
disconnectedCallback() {
this.model?.dispose();
this.model = undefined;
}
constructor() {
super();
}
}
window.customElements.define('mc-snapshot-markdown', MolComponentSnapshotMarkdownViewer);

View File

@@ -1,113 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { MolViewSpec } from '../../../extensions/mvs/behavior';
import { loadMVS } from '../../../extensions/mvs/load';
import { MVSData } from '../../../extensions/mvs/mvs-data';
import { PluginComponent } from '../../../mol-plugin-state/component';
import { createPluginUI } from '../../../mol-plugin-ui';
import { renderReact18 } from '../../../mol-plugin-ui/react18';
import { DefaultPluginUISpec } from '../../../mol-plugin-ui/spec';
import { PluginConfig } from '../../../mol-plugin/config';
import { PluginContext } from '../../../mol-plugin/context';
import { PluginSpec } from '../../../mol-plugin/spec';
import { getMolComponentContext, MolComponentContext } from '../context';
export class MolComponentViewerModel extends PluginComponent {
readonly context: MolComponentContext;
plugin?: PluginContext = undefined;
async mount(root: HTMLElement) {
const spec = DefaultPluginUISpec();
this.plugin = await createPluginUI({
target: root,
render: renderReact18,
spec: {
...spec,
layout: {
initial: {
isExpanded: false,
showControls: false,
controlsDisplay: 'landscape',
},
},
components: {
remoteState: 'none',
viewport: {
snapshotDescription: EmptyDescription,
}
},
behaviors: [
...spec.behaviors,
PluginSpec.Behavior(MolViewSpec)
],
config: [
[PluginConfig.Viewport.ShowAnimation, false],
]
}
});
this.subscribe(this.context.commands, async (cmd) => {
if (!cmd) return;
if (cmd.kind === 'load-mvs') {
if (cmd.url) {
const data = await this.plugin!.runTask(this.plugin!.fetch({ url: cmd.url, type: 'string' }));
const mvsData = MVSData.fromMVSJ(data);
await loadMVS(this.plugin!, mvsData, { sanityChecks: true, sourceUrl: cmd.url, replaceExisting: true });
} else if (cmd.data) {
await loadMVS(this.plugin!, cmd.data, { sanityChecks: true, replaceExisting: true });
}
}
});
const viewers = this.context.behavior.viewers.value;
const next = [...viewers, { name: this.options?.name, model: this }];
this.context.behavior.viewers.next(next);
}
constructor(private options?: { context?: { name?: string, container?: object }, name?: string }) {
super();
this.context = getMolComponentContext(options?.context);
const viewers = this.context.behavior.viewers.value;
const index = viewers.findIndex(v => v.name === options?.name);
if (index >= 0) {
const next = [...viewers];
next[index].model.dispose();
next.splice(index, 0);
this.context.behavior.viewers.next(next);
}
}
}
function EmptyDescription() {
return <></>;
}
export class MolComponentViewer extends HTMLElement {
private model: MolComponentViewerModel | undefined = undefined;
async connectedCallback() {
this.model = new MolComponentViewerModel({
name: this.getAttribute('name') ?? undefined,
context: { name: this.getAttribute('context-name') ?? undefined },
});
await this.model.mount(this);
}
disconnectedCallback() {
this.model?.dispose();
this.model = undefined;
}
constructor() {
super();
}
}
window.customElements.define('mc-viewer', MolComponentViewer);

View File

@@ -1,59 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>The Kinase Story</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#viewer {
position: absolute;
left: 5px;
top: 5px;
right: 34%;
bottom: 5px;
}
#snapshot {
position: absolute;
left: 66%;
top: 5px;
right: 5px;
bottom: 5px;
padding: 16px;
border: 1px solid #ccc;
border-left: none;
background: #F6F5F3;
z-index: -1;
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
<script type="text/javascript" src="./index.js"></script>
</head>
<body>
<div id="viewer">
<mc-viewer name="v1" />
</div>
<div id="snapshot" class="markdown-explanation ">
<mc-snapshot-markdown viewer-name="v1" />
</div>
<script>
setTimeout(() => {
const story = window.buildStory();
window.mc.getContext().dispatch({
kind: 'load-mvs',
data: story,
});
}, 0);
</script>
</body>
</html>

View File

@@ -1,22 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { getMolComponentContext } from './context';
import './index.html';
import './elements/snapshot-markdown';
import './elements/viewer';
import { buildStory } from './kinase-story';
require('../../mol-plugin-ui/skin/light.scss');
require('./styles.scss');
export class MolComponents {
getContext(name?: string) {
return getMolComponentContext({ name });
}
}
(window as any).mc = new MolComponents();
(window as any).buildStory = buildStory;

View File

@@ -1,731 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { decodeColor } from '../../extensions/mvs/helpers/utils';
import { MVSData_States } from '../../extensions/mvs/mvs-data';
import { createMVSBuilder, Structure as MVSStructure, Representation, Root } from '../../extensions/mvs/tree/mvs/mvs-builder';
import { MVSNodeParams } from '../../extensions/mvs/tree/mvs/mvs-tree';
import { ColorT, ComponentExpressionT, isPrimitiveComponentExpressions, PrimitivePositionT } from '../../extensions/mvs/tree/mvs/param-types';
import { Mat3, Mat4, Vec3 } from '../../mol-math/linear-algebra';
const Domains = {
ChainA: { auth_asym_id: 'A' },
SH2: { auth_asym_id: 'A', beg_auth_seq_id: 146, end_auth_seq_id: 247 },
SH3: { auth_asym_id: 'A', beg_auth_seq_id: 83, end_auth_seq_id: 145 },
P_loop: { auth_asym_id: 'A', beg_auth_seq_id: 246, end_auth_seq_id: 255 },
Activation_loop: { auth_asym_id: 'A', beg_auth_seq_id: 384, end_auth_seq_id: 402 },
};
const DomainColors = {
SH2: '#8ED1A4' as ColorT,
SH2_BCR: '#D03B4B' as ColorT,
SH3: '#64B9AA' as ColorT,
P_loop: 'pink' as ColorT,
Activation_loop: 'red' as ColorT,
DFG_motif: 'orange' as ColorT,
};
const Colors = {
'1opl': '#4577B2' as ColorT,
'2gqg': '#BC536D' as ColorT,
'1iep': '#B9E3A0' as ColorT,
'3ik3': '#F3774B' as ColorT,
'3oxz': '#7D7EA5' as ColorT,
'active-site': '#F3794C' as ColorT,
'binding-site': '#FEEB9F' as ColorT,
};
// Obtained using https://www.rcsb.org/alignment
const Superpositions = {
'1opl': [-0.6321036327, 0.3450463255, 0.6938213248, 0, -0.6288677634, -0.7515716885, -0.1991615756, 0, 0.4527364948, -0.5622126202, 0.6920597055, 0, 36.3924122492, 118.2516908402, -26.4992054179, 1] as unknown as Mat4,
'3ik3': [-0.7767826245, -0.6295936551, 0.0148520572, 0, 0.6059737752, -0.7408035481, 0.2898376906, 0, -0.1714775143, 0.2341408391, 0.9569605684, 0, 21.0648276775, 53.0266628762, -0.3385906075, 1] as unknown as Mat4,
'2gqg': [0.0648740828, -0.7163272638, 0.6947421137, 0, 0.0160329972, -0.6953706204, -0.7184724374, 0, 0.9977646498, 0.0577490387, -0.0336266582, 0, -31.0690973964, 146.0940883054, 39.7107422531, 1] as unknown as Mat4,
'3oxz': [0.7989033646, 0.5984398921, -0.0601922711, 0, -0.1303123126, 0.269921501, 0.9540236289, 0, 0.5871729857, -0.754328893, 0.2936252816, 0, -8.0697093741, 58.1709160658, 19.0363028443, 1] as unknown as Mat4,
};
const Steps = [
{
header: 'A Kinase Out of Control',
key: 'intro',
description: `
### The Structural Story of BCR-ABL: A Kinase Out of Control
BCR-ABL is a classic case of how structural biology can drive drug discovery. This story will help you understand:
- How the [ABL kinase is normally regulated](#regulated-kinase).
- How a small genetic fusion creates a [rogue kinase](#rogue-kinase).
- How ATP binding fuels [uncontrolled cancer growth](#unstoppable-signaling).
- How [Imatinib revolutionized treatment](#imatinib) by locking the kinase in an inactive state.
- How a [single mutation (T315I) enabled resistance](#mutation) and brought new challenges.
- How [Ponatinib](#ponatinib) and future inhibitors are being designed to keep up in this ongoing battle.
`,
state: (): Root => {
const builder = createMVSBuilder();
const _1opl = structure(builder, '1opl');
const [_1opl_poly,] = polymer(_1opl, { color: Colors['1opl'] });
_1opl_poly.label({ text: 'ABL Kinase' });
ligand(_1opl, {
selector: { label_asym_id: 'C' },
uniform_color: Colors['1opl'],
});
ligand(_1opl, {
selector: { label_asym_id: 'D' },
surface: true,
carbon_color: Colors['1opl'],
});
return builder;
},
camera: {
position: [103.72, 69.35, 20.52],
target: [0.36, 55.32, 21.8],
up: [-0.01, 0.01, -1],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'The ABL Kinase: A Well-Regulated Enzyme',
key: 'regulated-kinase',
description: `
### The ABL Kinase: A Well-Regulated Enzyme
Normally, the ABL kinase ([PDB ID 1OPL](https://www.ebi.ac.uk/pdbe/entry/pdb/1opl/index)) is a well-regulated enzyme, kept in check by its SH3 and SH2 domains which fold back onto the kinase domain like a safety lock.
`,
state: () => {
const builder = createMVSBuilder();
const _1opl = structure(builder, '1opl');
const [_1opl_poly, _1opl_poly_repr] = polymer(_1opl, { color: Colors['1opl'] });
ligand(_1opl, {
selector: { label_asym_id: 'C' },
uniform_color: Colors['1opl'],
});
ligand(_1opl, {
selector: { label_asym_id: 'D' },
surface: true,
carbon_color: Colors['1opl'],
});
domains(_1opl, _1opl_poly_repr, [
[Domains.SH2, DomainColors.SH2, 'SH2'],
[Domains.SH3, DomainColors.SH3, 'SH3'],
], { label_size: 9 });
return builder;
},
camera: {
position: [-18.33, -30.35, 48.2],
target: [-10.37, 49.7, 12.68],
up: [-0.27, -0.37, -0.89],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'The Birth of a Rogue Kinase',
key: 'rogue-kinase',
transition_duration_ms: 750,
description: `
### The Birth of a Rogue Kinase
But in BCR-ABL, this safety mechanism is gone. A reciprocal translocation between chromosomes 9 and 22 creates the Philadelphia chromosome (Ph),
fusing the ABL1 gene from chromosome 9 with the BCR gene on chromosome 22. This fusion produces the chimeric protein, BCR-ABL, which lacks the
regulation of the wildtype protein. Read more about this [here](https://www.cancer.gov/publications/dictionaries/cancer-terms/def/philadelphia-chromosome)
and [the history of its discovery](https://pmc.ncbi.nlm.nih.gov/articles/PMC1934591/).
Comparing the normal protein to the kinase domain alone ([PDB ID 2GQG](https://www.ebi.ac.uk/pdbe/entry/pdb/2gqg/index), in light red), you can
see how the SH3 and SH2 domains (teal in normal ABL, red in BCR-ABL, with SH3 domain being unresolved in the crystal structure) are no longer positioned to restrain the kinase.
With this lock removed, BCR-ABL is stuck in an active conformation, like an accelerator pedal jammed to the floor. Without
its normal regulation, BCR-ABL will keep signaling, unchecked causing unregulated cell growth and cancer — [chronic myeloid leukemia (CML)](https://en.wikipedia.org/wiki/Chronic_myelogenous_leukemia).
`,
state: () => {
const builder = createMVSBuilder();
const _1opl = structure(builder, '1opl');
const [_1opl_poly, _1opl_poly_repr] = polymer(_1opl, { color: Colors['1opl'] });
ligand(_1opl, {
selector: { label_asym_id: 'C' },
uniform_color: Colors['1opl'],
});
ligand(_1opl, {
selector: { label_asym_id: 'D' },
surface: true,
carbon_color: Colors['1opl'],
});
domains(_1opl, _1opl_poly_repr, [
[Domains.SH2, DomainColors.SH2, 'SH2'],
[Domains.SH3, DomainColors.SH3, 'SH3'],
], { label_size: 9 });
const _2gqg = structure(builder, '2gqg');
const [, _2gqg_poly_repr] = polymer(_2gqg, { color: '#BF99A1' });
domains(_2gqg, _2gqg_poly_repr, [
[Domains.SH2, DomainColors['SH2_BCR'], 'SH2 (BCR)'],
], { label_size: 6 });
return builder;
},
camera: {
position: [30.7, -18.5, 13.47],
target: [3.99, 47.45, 0.08],
up: [-0.22, -0.28, -0.94],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'ATP Binding and Unstoppable Signaling [1/2]',
key: 'unstoppable-signaling',
description: `
### ATP Binding and Unstoppable Signaling
To function, every kinase needs [ATP](https://en.wikipedia.org/wiki/Kinase), and BCR-ABL is no exception.
Here, you can see non-hydrolysable ATP analogue [(AMP-PNP)](https://www.ebi.ac.uk/pdbe-srv/pdbechem/chemicalCompound/show/ANP)
nestled in the active site. Look closely at the active site residues—Lys271, Glu286, and Asp381 (in orange).
They form a crucial network that stabilizes the AMP-PNP (and also the ATP) and in the ATP bound kinase catalyzes phosphorylation,
allowing BCR-ABL to continuously activate downstream signaling pathways.
`,
state: () => {
const builder = createMVSBuilder();
const _2gqg = structure(builder, '2gqg');
const [, _2gqg_poly_repr] = polymer(_2gqg, { color: Colors['2gqg'] });
ligand(_2gqg, {
selector: { label_asym_id: 'C' },
surface: true,
label: 'AMP-PNP ATP',
label_size: 2,
label_color: Colors['2gqg'],
});
domains(_2gqg, _2gqg_poly_repr, [
[Domains.SH2, DomainColors['SH2_BCR'], 'SH2'],
[Domains.P_loop, DomainColors['P_loop'], 'P Loop'],
[Domains.Activation_loop, DomainColors['Activation_loop'], 'Activation Loop (active)', { label_size: 3 }],
], { label_size: 3 });
drawInteractions(_2gqg, [
['H-bond', { auth_asym_id: 'A', auth_seq_id: 318, auth_atom_id: 'O' }, { label_asym_id: 'C', label_atom_id: 'N' }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 318, auth_atom_id: 'N' }, { label_asym_id: 'C', label_atom_id: 'N1' }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 315, auth_atom_id: 'OG1' }, { label_asym_id: 'C', label_atom_id: 'N2' }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 620, auth_atom_id: 'O' }, { label_asym_id: 'C', label_atom_id: 'N3' }],
]);
bindingSite(_2gqg, [
[{ auth_asym_id: 'A', auth_seq_id: 271 }, 'Lys271'],
[{ auth_asym_id: 'A', auth_seq_id: 286 }, 'Glu286'],
[{ auth_asym_id: 'A', auth_seq_id: 381 }, 'Asp381'],
], { color: Colors['active-site'] });
return builder;
},
camera: {
position: [38.76, 81.69, 5.8],
target: [13.01, 60.13, 11.63],
up: [0.19, -0.46, -0.87],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'ATP Binding and Unstoppable Signaling [2/2]',
description: `
### ATP Binding and Unstoppable Signaling
Note the location of the activation loop (in red) which sits in its active conformation.
In a normal kinase, ATP binding is a carefully controlled step. But in BCR-ABL, there's no regulation. ATP binds, reactions happen, and
the leukemia-driving signals keep firing.
`,
state: (): Root => {
return Steps.find((s: any) => s.key === 'unstoppable-signaling')?.state()!;
},
camera: {
position: [98.66, 82.23, 14.15],
target: [12.31, 54.23, 18.79],
up: [0.06, -0.35, -0.93],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'Imatinib: The Drug That Changed Everything [1/2]',
key: 'imatinib',
description: `
### Imatinib: The Drug That Changed Everything
For years, chronic myeloid leukemia (CML) was a death sentence. Then came Imatinib (Gleevec), a molecule designed to fit into the ATP-binding pocket
and lock BCR-ABL in an inactive conformation. The Imatinib-bound structure ([PDB ID 1IEP](https://www.ebi.ac.uk/pdbe/entry/pdb/1iep/index))
shows the difference: the kinase is frozen. The drug forms a key hydrogen bond with Thr315, known as the gatekeeper residue; as well as Met318, Asp381, Glu286, Ile360 and His361.
`,
state: () => {
const builder = createMVSBuilder();
const _1iep = structure(builder, '1iep');
const [, _1iep_poly_repr] = polymer(_1iep, { color: Colors['1iep'] });
ligand(_1iep, {
selector: { label_asym_id: 'G' },
surface: true,
label: 'Imatinib',
label_size: 2,
label_color: Colors['1iep'],
});
drawInteractions(_1iep, [
['H-bond', { auth_asym_id: 'A', auth_seq_id: 286, auth_atom_id: 'OE2' }, { label_asym_id: 'G', label_atom_id: 'N21' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 315, auth_atom_id: 'OG1' }, { label_asym_id: 'G', label_atom_id: 'N13' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 318, auth_atom_id: 'N' }, { label_asym_id: 'G', label_atom_id: 'N3' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 318, auth_atom_id: 'O' }, { label_asym_id: 'G', label_atom_id: 'N3' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 360, auth_atom_id: 'O' }, { label_asym_id: 'G', label_atom_id: 'N51' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 361, auth_atom_id: 'O' }, { label_asym_id: 'G', label_atom_id: 'N51' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 381, auth_atom_id: 'N' }, { label_asym_id: 'G', label_atom_id: 'O29' }, { skipResidue: true }],
]);
ligand(_1iep, {
selector: { auth_asym_id: 'A', auth_seq_id: 315 },
label: 'Thr315',
label_size: 2,
carbon_color: 'red',
label_color: 'red',
});
bindingSite(_1iep, [
[{ auth_asym_id: 'A', auth_seq_id: 271 }, 'Lys271'],
[{ auth_asym_id: 'A', auth_seq_id: 286 }, 'Glu286'],
[{ auth_asym_id: 'A', auth_seq_id: 381 }, 'Asp381'],
], { color: Colors['active-site'] });
bindingSite(_1iep, [
[{ auth_asym_id: 'A', auth_seq_id: 318 }, 'Met318'],
[{ auth_asym_id: 'A', auth_seq_id: 360 }, 'Ile360'],
[{ auth_asym_id: 'A', auth_seq_id: 361 }, 'His361'],
], { color: Colors['binding-site'] });
return builder;
},
camera: {
position: [40.32, 68.65, 13.5],
target: [16, 53.82, 14.88],
up: [0.26, -0.5, -0.83],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'Imatinib: The Drug That Changed Everything [2/2]',
description: `
### Imatinib: The Drug That Changed Everything
Notice how the P-loop, which normally cradles ATP, has shifted into a closed conformation and the activation loop is also flipped into its closed
conformation. Imatinib doesn't just block ATP—it forces the kinase into a state where it can't function at all. The change is decisive: BCR-ABL is silenced.
`,
state: () => {
const builder = createMVSBuilder();
const _1iep = structure(builder, '1iep');
const [, _1iep_poly_repr] = polymer(_1iep, { color: Colors['1iep'] });
ligand(_1iep, {
selector: { label_asym_id: 'G' },
surface: true,
label: 'Imatinib',
label_size: 2,
label_color: Colors['1iep'],
});
domains(_1iep, _1iep_poly_repr, [
[Domains.P_loop, DomainColors['P_loop'], 'P Loop'],
[Domains.Activation_loop, DomainColors['Activation_loop'], 'Activation Loop (inactive)', { label_size: 3 }],
], { label_size: 3 });
drawInteractions(_1iep, [
['H-bond', { auth_asym_id: 'A', auth_seq_id: 286, auth_atom_id: 'OE2' }, { label_asym_id: 'G', label_atom_id: 'N21' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 315, auth_atom_id: 'OG1' }, { label_asym_id: 'G', label_atom_id: 'N13' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 318, auth_atom_id: 'N' }, { label_asym_id: 'G', label_atom_id: 'N3' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 318, auth_atom_id: 'O' }, { label_asym_id: 'G', label_atom_id: 'N3' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 360, auth_atom_id: 'O' }, { label_asym_id: 'G', label_atom_id: 'N51' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 361, auth_atom_id: 'O' }, { label_asym_id: 'G', label_atom_id: 'N51' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 381, auth_atom_id: 'N' }, { label_asym_id: 'G', label_atom_id: 'O29' }, { skipResidue: true }],
]);
ligand(_1iep, {
selector: { auth_asym_id: 'A', auth_seq_id: 315 },
label: 'Thr315',
label_size: 2,
carbon_color: 'red',
label_color: 'red',
});
bindingSite(_1iep, [
[{ auth_asym_id: 'A', auth_seq_id: 271 }, 'Lys271'],
[{ auth_asym_id: 'A', auth_seq_id: 286 }, 'Glu286'],
[{ auth_asym_id: 'A', auth_seq_id: 381 }, 'Asp381'],
], { color: Colors['active-site'] });
bindingSite(_1iep, [
[{ auth_asym_id: 'A', auth_seq_id: 318 }, 'Met318'],
[{ auth_asym_id: 'A', auth_seq_id: 360 }, 'Ile360'],
[{ auth_asym_id: 'A', auth_seq_id: 361 }, 'His361'],
], { color: Colors['binding-site'] });
return builder;
},
camera: {
position: [91.47, 73.63, 20.78],
target: [12.53, 54.2, 19.09],
up: [0.04, -0.07, -1],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'Resistance Strikes: The T315I Mutation [1/2]',
key: 'mutation',
description: `
### Resistance Strikes: The T315I Mutation
For a while, it seemed like leukemia had been beaten. But then, in some patients, the cancer returned. The culprit?
What was once a threonine (Thr) is now an isoleucine (Ile), a single mutation [T315I](https://doi.org/10.1016/j.ccr.2011.03.003), shown on [PDB ID 3IK3](https://www.ebi.ac.uk/pdbe/entry/pdb/3ik3/index) in orange.
Forming a hydrogen bond with Imatinib, Thr315 was a crucial contact point. With bulkier and non-polar isoleucine in its place, the contact is lost and the drug won't bind.
`,
state: () => {
const builder = createMVSBuilder();
const _1iep = structure(builder, '1iep');
const [, _1iep_poly_repr] = polymer(_1iep, { color: Colors['1iep'] });
ligand(_1iep, {
selector: { label_asym_id: 'G' },
surface: true,
label: 'Imatinib',
label_size: 2,
label_color: Colors['1iep'],
});
ligand(_1iep, {
selector: { auth_asym_id: 'A', auth_seq_id: 315 },
carbon_color: Colors['1iep'],
opacity: 0.51,
});
const _3ik3 = structure(builder, '3ik3');
const [, _3ik3_poly_repr] = polymer(_3ik3, { color: Colors['3ik3'] });
ligand(_3ik3, {
selector: { auth_asym_id: 'A', auth_seq_id: 315 },
label: 'T315I',
label_size: 2,
carbon_color: 'red',
label_color: 'red',
});
return builder;
},
camera: {
position: [13.69, 72.8, 4.44],
target: [13.02, 54.12, 9.71],
up: [0.39, -0.26, -0.88],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'Resistance Strikes: The T315I Mutation [2/2]',
description: `
### Resistance Strikes: The T315I Mutation
This mutation prevents Imatinib binding but still allows ANP-PNP ATP to nestle into the active site.
The result? Resistance. BCR-ABL is active again, and the leukemia returns, this time untouchable by Imatinib.
`,
state: () => {
const builder = createMVSBuilder();
const _2gqg = structure(builder, '2gqg');
const [, _2gqg_poly_repr] = polymer(_2gqg, { color: Colors['2gqg'] });
ligand(_2gqg, {
selector: { label_asym_id: 'C' },
surface: true,
label: 'ANP-PNP ATP',
label_size: 2,
label_color: Colors['2gqg'],
});
drawInteractions(_2gqg, [
['H-bond', { auth_asym_id: 'A', auth_seq_id: 318, auth_atom_id: 'O' }, { label_asym_id: 'C', label_atom_id: 'N' }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 318, auth_atom_id: 'N' }, { label_asym_id: 'C', label_atom_id: 'N1' }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 620, auth_atom_id: 'O' }, { label_asym_id: 'C', label_atom_id: 'N3' }],
]);
bindingSite(_2gqg, [
[{ auth_asym_id: 'A', auth_seq_id: 271 }, 'Lys271'],
[{ auth_asym_id: 'A', auth_seq_id: 286 }, 'Glu286'],
[{ auth_asym_id: 'A', auth_seq_id: 381 }, 'Asp381'],
], { color: Colors['active-site'] });
const _3ik3 = structure(builder, '3ik3');
const [, _3ik3_poly_repr] = polymer(_3ik3, { color: Colors['3ik3'] });
ligand(_3ik3, {
selector: { auth_asym_id: 'A', auth_seq_id: 315 },
label: 'T315I',
label_size: 2,
carbon_color: 'red',
label_color: 'red',
});
return builder;
},
camera: {
position: [19.42, 97.24, -0.29],
target: [13.02, 54.12, 9.71],
up: [0.37, -0.26, -0.89],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'Fighting Back: Ponatinib and the Future of Kinase Inhibitors',
key: 'ponatinib',
description: `
### Fighting Back: Ponatinib and the Future of Kinase Inhibitors
The battle didn't end there. Scientists knew they needed a new inhibitor—one that could work even against T315I. Enter Ponatinib (shown in [PDB ID 3OXZ](https://www.ebi.ac.uk/pdbe/entry/pdb/3oxz/index)), a next-generation
drug designed to bypass this resistance. Viewing the Ponatinib-bound structure, you'll see how it differs from Imatinib. Instead of being blocked by T315I,
Ponatinib has a flexible triple-bond linker, allowing it to slip into the binding site without clashing with the mutation.
Look closely at the interactions—Ponatinib forms new hydrophobic contacts that compensate for the loss of the Thr315 interaction. This structure tells a story of rational drug design: scientists
used everything they learned about BCR-ABL's structure to engineer a molecule that could fit where others failed.
But the story isn't over. New mutations continue to arise, and leukemia is still finding ways to outmaneuver our drugs. The future may lie in allosteric
inhibitors that bind outside the ATP pocket, or even in protein degradation strategies that eliminate BCR-ABL entirely. Whatever the next breakthrough is,
it will start here—with a deep understanding of structure and function, and the power of visualization to reveal the molecular battles happening
inside every cancer cell.
`,
state: () => {
const builder = createMVSBuilder();
const _3oxz = structure(builder, '3oxz');
const [, _3oxz_poly_repr] = polymer(_3oxz, { color: Colors['3oxz'] });
ligand(_3oxz, {
selector: { label_asym_id: 'B' },
surface: true,
label: 'Ponatinib',
label_size: 2,
label_color: Colors['3oxz'],
});
ligand(_3oxz, {
selector: { auth_asym_id: 'A', auth_seq_id: 315 },
label: 'T315I',
label_size: 2,
carbon_color: 'red',
label_color: 'red',
});
drawInteractions(_3oxz, [
['H-bond', { auth_asym_id: 'A', auth_seq_id: 360, auth_atom_id: 'O' }, { label_asym_id: 'B', label_atom_id: 'N4' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 361, auth_atom_id: 'O' }, { label_asym_id: 'B', label_atom_id: 'N4' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 286, auth_atom_id: 'OE2' }, { label_asym_id: 'B', label_atom_id: 'N2' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 381, auth_atom_id: 'N' }, { label_asym_id: 'B', label_atom_id: 'O1' }, { skipResidue: true }],
['H-bond', { auth_asym_id: 'A', auth_seq_id: 318, auth_atom_id: 'N' }, { label_asym_id: 'B', label_atom_id: 'N1' }, { skipResidue: true }],
]);
bindingSite(_3oxz, [
[{ auth_asym_id: 'A', auth_seq_id: 286 }, 'Glu286'],
[{ auth_asym_id: 'A', auth_seq_id: 318 }, 'Met318'],
[{ auth_asym_id: 'A', auth_seq_id: 360 }, 'Ile360'],
[{ auth_asym_id: 'A', auth_seq_id: 361 }, 'His361'],
[{ auth_asym_id: 'A', auth_seq_id: 381 }, 'Asp381'],
], { color: Colors['active-site'] });
return builder;
},
camera: {
position: [61.15, 66.58, 19.72],
target: [9.61, 50.49, 14.08],
up: [0.15, -0.15, -0.98],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'The End',
key: 'end',
description: `
### The End
That's all folks! We hope you enjoyed this interactive journey through the structural biology of BCR-ABL.
The next time you look at a macromolecular structure, remember: each atom tells a story, and each discovery shapes the future of medicine.
Read more [here](https://pmc.ncbi.nlm.nih.gov/articles/PMC3513788/).
`,
state: (): Root => {
return Steps[0].state();
},
camera: {
position: [103.72, 69.35, 20.52],
target: [0.36, 55.32, 21.8],
up: [-0.01, 0.01, -1],
} satisfies MVSNodeParams<'camera'>,
}
];
type Interaction = [label: string, polymer: PrimitivePositionT, ligand: PrimitivePositionT, options?: { skipResidue?: boolean }]
function drawInteractions(structure: MVSStructure, interactions: Interaction[]) {
const primitives = structure.primitives();
const interactingResidues: ComponentExpressionT[] = [];
const addedResidues = new Set<string>();
for (const [tooltip, a, b, options] of interactions) {
primitives.tube({ start: a, end: b, color: '#4289B5', tooltip, radius: 0.1, dash_length: 0.1 });
if (options?.skipResidue) continue;
const expressions = isPrimitiveComponentExpressions(a) ? a.expressions! : [a as ComponentExpressionT];
for (const _e of expressions) {
const e = { ..._e };
delete e.auth_atom_id;
delete e.label_atom_id;
const key = JSON.stringify(e);
if (addedResidues.has(key)) continue;
interactingResidues.push(e);
addedResidues.add(key);
}
}
structure
.component({ selector: interactingResidues })
.representation({ type: 'ball_and_stick' })
.color({
custom: {
molstar_color_theme_name: 'element-symbol',
molstar_color_theme_params: { carbonColor: { name: 'element-symbol', params: {} } },
}
});
}
function transform(structure: MVSStructure, id: keyof typeof Superpositions) {
const rotation = Mat3.fromMat4(Mat3.zero(), Superpositions[id]);
const translation = Mat4.getTranslation(Vec3.zero(), Superpositions[id]) as any;
return structure.transform({ rotation, translation });
}
function structure(builder: Root, id: string): MVSStructure {
let ret = builder
.download({ url: pdbUrl(id) })
.parse({ format: 'bcif' })
.modelStructure();
if (id in Superpositions) {
ret = transform(ret, id as any);
}
return ret;
}
function domains(structure: MVSStructure, reprensentation: Representation, domains: [selector: ComponentExpressionT, color: ColorT, label?: string, options?: { label_size?: number }][], options?: { label_size?: number }) {
const hasLabels = domains.some(d => !!d[2]);
const primitives = hasLabels ? structure.primitives() : undefined;
for (const [selector, color, label, opts] of domains) {
reprensentation.color({ selector, color });
if (label) primitives!.label({ position: selector, text: label, label_color: color, label_size: opts?.label_size ?? options?.label_size ?? 1.5 });
}
}
function polymer(structure: MVSStructure, options: { color: ColorT }) {
const component = structure.component({ selector: { label_asym_id: 'A' } });
const reprensentation = component.representation({ type: 'cartoon' });
reprensentation.color({ color: options.color });
return [component, reprensentation] as const;
}
function ligand(structure: MVSStructure, options: {
selector: ComponentExpressionT | ComponentExpressionT[],
label?: string,
surface?: boolean,
carbon_color?: ColorT,
uniform_color?: ColorT,
label_color?: ColorT,
label_size?: number,
opacity?: number,
}) {
const comp = structure.component({ selector: options.selector });
const coloring = options.uniform_color
? { color: options.uniform_color }
: {
custom: {
molstar_color_theme_name: 'element-symbol',
molstar_color_theme_params: { carbonColor: options?.carbon_color ? { name: 'uniform', params: { value: decodeColor(options?.carbon_color) } } : { name: 'element-symbol', params: { } } }
}
};
if (options.surface) comp.representation({ type: 'surface' }).color(coloring).opacity({ opacity: 0.33 });
const repr = comp.representation({ type: 'ball_and_stick' }).color(coloring);
if (options.opacity) repr.opacity({ opacity: options.opacity });
const label_color: ColorT = options?.label_color ?? options.uniform_color ?? options.carbon_color ?? '#5B53A4';
if (options.label) {
structure.primitives().label({
position: Array.isArray(options.selector) ? { expressions: options.selector } : options.selector,
text: options.label,
label_color,
label_size: options?.label_size ?? 1.5
});
}
return comp;
}
function bindingSite(structure: MVSStructure, residues: [selector: ComponentExpressionT, label: string][], options: {
color?: ColorT,
label_size?: number,
}) {
const color: ColorT = options.color ?? '#5B53A4';
const coloring = {
custom: {
molstar_color_theme_name: 'element-symbol',
molstar_color_theme_params: { carbonColor: { name: 'uniform', params: { value: decodeColor(color) } } }
}
};
structure.component({ selector: residues.map(r => r[0]) }).representation({ type: 'ball_and_stick' }).color(coloring);
const primitives = structure.primitives();
for (const [selector, label] of residues) {
primitives.label({
position: selector,
text: label,
label_color: color,
label_size: options?.label_size ?? 1.5
});
}
}
function pdbUrl(id: string) {
return `https://www.ebi.ac.uk/pdbe/entry-files/download/${id.toLowerCase()}.bcif`;
}
export function buildStory(): MVSData_States {
const snapshots = Steps.map((s, i) => {
const builder = s.state();
if (s.camera) builder.camera(s.camera);
const description = i > 0 ? `${s.description}\n\n[Go to start](#intro)` : s.description;
return builder.getSnapshot({
title: s.header,
key: s.key,
description,
description_format: 'markdown',
linger_duration_ms: 5000,
transition_duration_ms: s.transition_duration_ms ?? 1500,
});
});
return {
kind: 'multiple',
snapshots,
metadata: {
title: 'The Structural Story of BCR-ABL: A Kinase Out of Control',
version: '1.0',
timestamp: new Date().toISOString(),
}
};
}

View File

@@ -1,50 +0,0 @@
# MolViewSpec Kinase Story Example
This example illustrates:
- Using MolViewSpec to tell a story
- A proof of concept for separating Mol* into a ready-to-use web component library.
### Usage
- Clone Mol* GitHub repo and build it.
```bash
git clone https://github.com/molstar/molstar.git
cd molstar
npm install
npm build
```
- Get `molstar.css` and `index.js` from `build/examples/mvs-kinase-story` and include these to your HTML page
```html
<link rel="stylesheet" type="text/css" href="molstar.css" />
<script type="text/javascript" src="index.js"></script>
```
- Plate the components in your page wrapper in `<div>` elements to set up positioning:
```html
<div class="viewer">
<mc-viewer name="v1" />
</div>
<div class="snapshot">
<mc-snapshot-markdown viewer-name="v1" />
</div>
```
- Load MolViewSpec state:
```html
<script>
window.mc.getContext().dispatch({
kind: 'load-mvs',
format: 'mvsj',
url: 'https://path/to/file.mvsj',
// or provide data direcly
// data: mvsJSON
});
</script>
```
See [index.html](./index.html) for a full example.

View File

@@ -1,164 +0,0 @@
.markdown-explanation {
// Adapted from skeleton.css, The MIT License (MIT), Copyright (c) 2011-2014 Dave Gamache
line-height: 1.4;
font-weight: 400;
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #222;
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 0;
margin-bottom: 2rem;
font-weight: 300;
}
h1 {
font-size: 3.4rem;
line-height: 1.2;
letter-spacing: -.1rem;
}
h2 {
font-size: 3.0rem;
line-height: 1.25;
letter-spacing: -.1rem;
}
h3 {
font-size: 2.4rem;
line-height: 1.3;
letter-spacing: -.1rem;
}
h4 {
font-size: 2.1rem;
line-height: 1.35;
letter-spacing: -.08rem;
}
h5 {
font-size: 1.8rem;
line-height: 1.5;
letter-spacing: -.05rem;
}
h6 {
font-size: 1.5rem;
line-height: 1.6;
letter-spacing: 0;
}
button {
display: inline-block;
height: 38px;
padding: 0 30px;
color: #555;
text-align: center;
font-size: 11px;
font-weight: 600;
line-height: 38px;
letter-spacing: .1rem;
text-transform: uppercase;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border-radius: 4px;
border: 1px solid #bbb;
cursor: pointer;
box-sizing: border-box;
}
ul {
list-style: circle inside;
}
ol {
list-style: decimal inside;
}
ol,
ul {
padding-left: 0;
margin-top: 0;
}
ul ul,
ul ol,
ol ol,
ol ul {
margin: 1.5rem 0 1.5rem 3rem;
font-size: 90%;
}
li {
margin-bottom: 0.2rem;
}
code {
padding: .2rem .5rem;
margin: 0 .2rem;
font-size: 90%;
white-space: nowrap;
background: #F1F1F1;
border: 1px solid #E1E1E1;
border-radius: 4px;
}
pre>code {
display: block;
padding: 1rem 1.5rem;
white-space: pre;
}
th,
td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #E1E1E1;
}
th:first-child,
td:first-child {
padding-left: 0;
}
th:last-child,
td:last-child {
padding-right: 0;
}
button,
.button {
margin-bottom: 1rem;
}
input,
textarea,
select,
fieldset {
margin-bottom: 1.5rem;
}
pre,
blockquote,
dl,
figure,
table,
p,
ul,
ol,
form {
margin-bottom: 2rem;
}
hr {
margin-top: 2rem;
margin-bottom: 2rem;
border-width: 0;
border-top: 1px solid #E1E1E1;
}
}

View File

@@ -14,7 +14,6 @@ import { Location } from '../../mol-model/location';
import { ScaleLegend, TableLegend } from '../../mol-util/legend';
import { getPalette, getPaletteParams } from '../../mol-util/color/palette';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
import { ColorThemeCategory } from '../../mol-theme/color/categories';
const DefaultColor = Color(0xCCCCCC);
@@ -103,7 +102,7 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<AssemblySymmetryClusterColorThemeParams, AssemblySymmetryData.Tag.Cluster> = {
name: AssemblySymmetryData.Tag.Cluster,
label: 'Assembly Symmetry Cluster',
category: ColorThemeCategory.Symmetry,
category: ColorTheme.Category.Symmetry,
factory: AssemblySymmetryClusterColorTheme,
getParams: getAssemblySymmetryClusterColorThemeParams,
defaultValues: PD.getDefaultValues(AssemblySymmetryClusterColorThemeParams),

View File

@@ -18,7 +18,6 @@ import { getColorMapParams } from '../../../mol-util/color/params';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { TableLegend } from '../../../mol-util/legend';
import { ObjectKeys } from '../../../mol-util/type-helpers';
import { ColorThemeCategory } from '../../../mol-theme/color/categories';
const Description = 'Assigns colors to confal pyramids';
@@ -63,7 +62,7 @@ export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values
export const ConfalPyramidsColorThemeProvider: ColorTheme.Provider<ConfalPyramidsColorThemeParams, 'confal-pyramids'> = {
name: 'confal-pyramids',
label: 'Confal Pyramids',
category: ColorThemeCategory.Residue,
category: ColorTheme.Category.Residue,
factory: ConfalPyramidsColorTheme,
getParams: getConfalPyramidsColorThemeParams,
defaultValues: PD.getDefaultValues(ConfalPyramidsColorThemeParams),

View File

@@ -18,7 +18,6 @@ import { getColorMapParams } from '../../../mol-util/color/params';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { TableLegend } from '../../../mol-util/legend';
import { ObjectKeys } from '../../../mol-util/type-helpers';
import { ColorThemeCategory } from '../../../mol-theme/color/categories';
const Description = 'Assigns colors to NtC Tube segments';
@@ -88,7 +87,7 @@ export function NtCTubeColorTheme(ctx: ThemeDataContext, props: PD.Values<NtCTub
export const NtCTubeColorThemeProvider: ColorTheme.Provider<NtCTubeColorThemeParams, 'ntc-tube'> = {
name: 'ntc-tube',
label: 'NtC Tube',
category: ColorThemeCategory.Residue,
category: ColorTheme.Category.Residue,
factory: NtCTubeColorTheme,
getParams: getNtCTubeColorThemeParams,
defaultValues: PD.getDefaultValues(NtCTubeColorThemeParams),

View File

@@ -15,7 +15,6 @@ import { Color } from '../../../../mol-util/color';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { TableLegend } from '../../../../mol-util/legend';
import { ColorThemeCategory } from '../../../../mol-theme/color/categories';
const DefaultColor = Color(0xaaaaaa);
const ConfidenceColors = {
@@ -88,7 +87,7 @@ export function PLDDTConfidenceColorTheme(ctx: ThemeDataContext, props: PD.Value
export const PLDDTConfidenceColorThemeProvider: ColorTheme.Provider<PLDDTConfidenceColorThemeParams, 'plddt-confidence'> = {
name: 'plddt-confidence',
label: 'pLDDT Confidence',
category: ColorThemeCategory.Validation,
category: ColorTheme.Category.Validation,
factory: PLDDTConfidenceColorTheme,
getParams: getPLDDTConfidenceColorThemeParams,
defaultValues: PD.getDefaultValues(getPLDDTConfidenceColorThemeParams({})),

View File

@@ -12,7 +12,6 @@ import { ThemeDataContext } from '../../../../mol-theme/theme';
import { Color, ColorScale } from '../../../../mol-util/color';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ColorThemeCategory } from '../../../../mol-theme/color/categories';
const DefaultColor = Color(0xaaaaaa);
@@ -72,7 +71,7 @@ export function QmeanScoreColorTheme(ctx: ThemeDataContext, props: PD.Values<Qme
export const QmeanScoreColorThemeProvider: ColorTheme.Provider<QmeanScoreColorThemeParams, 'qmean-score'> = {
name: 'qmean-score',
label: 'QMEAN Score',
category: ColorThemeCategory.Validation,
category: ColorTheme.Category.Validation,
factory: QmeanScoreColorTheme,
getParams: getQmeanScoreColorThemeParams,
defaultValues: PD.getDefaultValues(getQmeanScoreColorThemeParams({})),

View File

@@ -9,7 +9,7 @@ import { MVSData } from '../mvs-data';
describe('MVSData', () => {
it.skip('MVSData functions work', async () => {
it('MVSData functions work', async () => {
const data = fs.readFileSync('examples/mvs/1cbs.mvsj', { encoding: 'utf8' });
const mvsData = MVSData.fromMVSJ(data);
expect(mvsData).toBeTruthy();
@@ -26,7 +26,7 @@ describe('MVSData', () => {
expect(prettyString.length).toBeGreaterThan(0);
});
it.skip('MVSData builder works', async () => {
it('MVSData builder works', async () => {
const builder = MVSData.createBuilder();
expect(builder).toBeTruthy();

View File

@@ -6,8 +6,8 @@
import { Location } from '../../../mol-model/location';
import { Bond, StructureElement } from '../../../mol-model/structure';
import type { ColorTheme, LocationColor } from '../../../mol-theme/color';
import type { ThemeDataContext } from '../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ThemeDataContext } from '../../../mol-theme/theme';
import { ColorNames } from '../../../mol-util/color/names';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { decodeColor } from '../helpers/utils';
@@ -73,7 +73,7 @@ export function MVSAnnotationColorTheme(ctx: ThemeDataContext, props: MVSAnnotat
export const MVSAnnotationColorThemeProvider: ColorTheme.Provider<MVSAnnotationColorThemeParams, 'mvs-annotation'> = {
name: 'mvs-annotation',
label: 'MVS Annotation',
category: 'Miscellaneous', // ColorTheme.Category.Misc can cause webpack build error due to import ordering
category: ColorTheme.Category.Misc,
factory: MVSAnnotationColorTheme,
getParams: ctx => MVSAnnotationColorThemeParams,
defaultValues: PD.getDefaultValues(MVSAnnotationColorThemeParams),

View File

@@ -7,7 +7,6 @@
import { Location } from '../../../mol-model/location';
import { Bond, Structure, StructureElement } from '../../../mol-model/structure';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ColorThemeCategory } from '../../../mol-theme/color/categories';
import { ThemeDataContext } from '../../../mol-theme/theme';
import { Color } from '../../../mol-util/color';
import { ColorNames } from '../../../mol-util/color/names';
@@ -140,7 +139,7 @@ export function makeMultilayerColorThemeProvider(colorThemeRegistry: ColorTheme.
return {
name: MultilayerColorThemeName,
label: 'MVS Multi-layer',
category: ColorThemeCategory.Misc,
category: ColorTheme.Category.Misc,
factory: (ctx, props) => makeMultilayerColorTheme(ctx, props, colorThemeRegistry),
getParams: (ctx: ThemeDataContext) => makeMultilayerColorThemeParams(colorThemeRegistry, ctx),
defaultValues: DefaultMultilayerColorThemeProps,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Adam Midlik <midlik@gmail.com>
@@ -8,17 +8,12 @@
import { Lines } from '../../../mol-geo/geometry/lines/lines';
import { LinesBuilder } from '../../../mol-geo/geometry/lines/lines-builder';
import { addFixedCountDashedCylinder, addSimpleCylinder, BasicCylinderProps } from '../../../mol-geo/geometry/mesh/builder/cylinder';
import { addEllipsoid } from '../../../mol-geo/geometry/mesh/builder/ellipsoid';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
import { Text } from '../../../mol-geo/geometry/text/text';
import { TextBuilder } from '../../../mol-geo/geometry/text/text-builder';
import { Box, BoxCage } from '../../../mol-geo/primitive/box';
import { Circle } from '../../../mol-geo/primitive/circle';
import { Primitive } from '../../../mol-geo/primitive/primitive';
import { Box3D, Sphere3D } from '../../../mol-math/geometry';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { radToDeg } from '../../../mol-math/misc';
import { Shape } from '../../../mol-model/shape';
import { Structure, StructureElement, StructureSelection } from '../../../mol-model/structure';
import { StructureQueryHelper } from '../../../mol-plugin-state/helpers/structure-query';
@@ -283,15 +278,6 @@ const Builders: Record<PrimitiveParams['kind'], PrimitiveBuilder> = {
},
resolveRefs: resolveLineRefs,
},
arrow: {
builders: {
mesh: addArrowMesh,
},
resolveRefs: (params: PrimitiveParams<'arrow'>, refs: Set<string>) => {
addRef(params.start, refs);
if (params.end) addRef(params.end, refs);
},
},
label: {
builders: {
label: addPrimitiveLabel,
@@ -305,45 +291,6 @@ const Builders: Record<PrimitiveParams['kind'], PrimitiveBuilder> = {
},
resolveRefs: resolveLineRefs,
},
angle_measurement: {
builders: {
mesh: addAngleMesh,
label: addAngleLabel,
},
resolveRefs: (params: PrimitiveParams<'angle_measurement'>, refs: Set<string>) => {
addRef(params.a, refs);
addRef(params.b, refs);
addRef(params.c, refs);
},
},
ellipse: {
builders: {
mesh: addEllipseMesh,
},
resolveRefs: (params: PrimitiveParams<'ellipse'>, refs: Set<string>) => {
addRef(params.center, refs);
if (params.major_axis_endpoint) addRef(params.major_axis_endpoint, refs);
if (params.minor_axis_endpoint) addRef(params.minor_axis_endpoint, refs);
},
},
ellipsoid: {
builders: {
mesh: addEllipsoidMesh,
},
resolveRefs: (params: PrimitiveParams<'ellipsoid'>, refs: Set<string>) => {
addRef(params.center, refs);
if (params.major_axis_endpoint) addRef(params.major_axis_endpoint, refs);
if (params.minor_axis_endpoint) addRef(params.minor_axis_endpoint, refs);
},
},
box: {
builders: {
mesh: addBoxMesh,
},
resolveRefs: (params: PrimitiveParams<'box'>, refs: Set<string>) => {
addRef(params.center, refs);
},
}
};
@@ -655,86 +602,6 @@ function addTubeMesh(context: PrimitiveBuilderContext, { groups, mesh }: MeshBui
}
}
const ArrowState = {
start: Vec3.zero(),
end: Vec3.zero(),
dir: Vec3.zero(),
startCap: Vec3.zero(),
endCap: Vec3.zero(),
};
function addArrowMesh(context: PrimitiveBuilderContext, { groups, mesh }: MeshBuilderState, node: MVSNode<'primitive'>, params: PrimitiveParams<'arrow'>) {
resolveBasePosition(context, params.start, ArrowState.start);
if (params.end) {
resolveBasePosition(context, params.end, ArrowState.end);
}
if (params.direction) {
Vec3.add(ArrowState.end, ArrowState.start, params.direction as any as Vec3);
}
Vec3.sub(ArrowState.dir, ArrowState.end, ArrowState.start);
Vec3.normalize(ArrowState.dir, ArrowState.dir);
if (params.length) {
Vec3.scaleAndAdd(ArrowState.end, ArrowState.start, ArrowState.dir, params.length);
}
const length = Vec3.distance(ArrowState.start, ArrowState.end);
if (length < 1e-3) return;
const tubeRadius = params.tube_radius;
const tubeProps: BasicCylinderProps = {
radiusBottom: tubeRadius,
radiusTop: tubeRadius,
topCap: !params.show_end_cap,
bottomCap: !params.show_start_cap,
};
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.color);
groups.updateTooltip(mesh.currentGroup, params.tooltip);
const startRadius = params.start_cap_radius ?? tubeRadius;
if (params.show_start_cap) {
Vec3.scaleAndAdd(ArrowState.startCap, ArrowState.start, ArrowState.dir, startRadius);
addSimpleCylinder(mesh, ArrowState.startCap, ArrowState.start, {
radiusBottom: startRadius,
radiusTop: 0,
topCap: false,
bottomCap: true,
radialSegments: 12,
});
} else {
Vec3.copy(ArrowState.startCap, ArrowState.start);
}
const endRadius = params.end_cap_radius ?? tubeRadius;
if (params.show_end_cap) {
Vec3.scaleAndAdd(ArrowState.endCap, ArrowState.end, ArrowState.dir, -endRadius);
addSimpleCylinder(mesh, ArrowState.endCap, ArrowState.end, {
radiusBottom: endRadius,
radiusTop: 0,
topCap: false,
bottomCap: true,
radialSegments: 12,
});
} else {
Vec3.copy(ArrowState.endCap, ArrowState.end);
}
if (params.show_tube) {
if (params.tube_dash_length) {
const dist = Vec3.distance(ArrowState.startCap, ArrowState.endCap);
const count = Math.ceil(dist / (2 * params.tube_dash_length));
addFixedCountDashedCylinder(mesh, ArrowState.startCap, ArrowState.endCap, 1.0, count, true, tubeProps);
} else {
addSimpleCylinder(mesh, ArrowState.startCap, ArrowState.endCap, tubeProps);
}
}
}
function getDistanceLabel(context: PrimitiveBuilderContext, params: PrimitiveParams<'distance_measurement'>) {
resolveBasePosition(context, params.start, lStart);
resolveBasePosition(context, params.end, lEnd);
@@ -779,332 +646,17 @@ function addDistanceLabel(context: PrimitiveBuilderContext, state: LabelBuilderS
labels.add(label, labelPos[0], labelPos[1], labelPos[2], 1.05 * (params.radius), 1, group);
}
const AngleState = {
a: Vec3(),
b: Vec3(),
c: Vec3(),
ba: Vec3(),
bc: Vec3(),
labelPos: Vec3(),
radius: 0,
};
function syncAngleState(context: PrimitiveBuilderContext, params: PrimitiveParams<'angle_measurement'>) {
resolveBasePosition(context, params.a, AngleState.a);
resolveBasePosition(context, params.b, AngleState.b);
resolveBasePosition(context, params.c, AngleState.c);
Vec3.sub(AngleState.ba, AngleState.a, AngleState.b);
Vec3.sub(AngleState.bc, AngleState.c, AngleState.b);
const value = radToDeg(Vec3.angle(AngleState.ba, AngleState.bc));
const angle = `${round(value, 2)}\u00B0`;
const label = typeof params.label_template === 'string' ? params.label_template.replace('{{angle}}', angle) : angle;
if (typeof params.section_radius === 'number') {
AngleState.radius = params.section_radius;
} else {
AngleState.radius = Math.min(Vec3.magnitude(AngleState.ba), Vec3.magnitude(AngleState.bc));
if (typeof params.section_radius_scale === 'number') {
AngleState.radius *= params.section_radius_scale;
}
}
return label;
}
function addAngleMesh(context: PrimitiveBuilderContext, state: MeshBuilderState, node: MVSNode<'primitive'>, params: PrimitiveParams<'angle_measurement'>) {
const label = syncAngleState(context, params);
const { groups, mesh } = state;
if (params.show_vector) {
const radius = 0.01;
const cylinderProps: BasicCylinderProps = {
radiusBottom: radius,
radiusTop: radius,
topCap: true,
bottomCap: true,
};
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.vector_color);
groups.updateTooltip(mesh.currentGroup, label);
let count = Math.ceil(Vec3.magnitude(AngleState.ba) / (2 * radius));
addFixedCountDashedCylinder(mesh, AngleState.a, AngleState.b, 1.0, count, true, cylinderProps);
count = Math.ceil(Vec3.magnitude(AngleState.bc) / (2 * radius));
addFixedCountDashedCylinder(mesh, AngleState.b, AngleState.c, 1.0, count, true, cylinderProps);
}
if (params.show_section) {
const angle = Vec3.angle(AngleState.ba, AngleState.bc);
Vec3.normalize(AngleState.ba, AngleState.ba);
Vec3.normalize(AngleState.bc, AngleState.bc);
Vec3.scale(AngleState.ba, AngleState.ba, AngleState.radius);
Vec3.scale(AngleState.bc, AngleState.bc, AngleState.radius);
addEllipseMesh(context, state, node, {
kind: 'ellipse',
as_circle: true,
center: AngleState.b as any,
major_axis_endpoint: null,
major_axis: AngleState.ba as any,
minor_axis_endpoint: null,
minor_axis: AngleState.bc as any,
radius_major: AngleState.radius,
radius_minor: AngleState.radius,
theta_start: 0,
theta_end: angle,
color: params.section_color,
tooltip: label,
});
}
}
function addAngleLabel(context: PrimitiveBuilderContext, state: LabelBuilderState, node: MVSNode<'primitive'>, params: PrimitiveParams<'angle_measurement'>) {
const { labels, groups } = state;
const label = syncAngleState(context, params);
Vec3.normalize(AngleState.ba, AngleState.ba);
Vec3.normalize(AngleState.bc, AngleState.bc);
Vec3.scale(AngleState.ba, AngleState.ba, AngleState.radius);
Vec3.scale(AngleState.bc, AngleState.bc, AngleState.radius);
let size: number | undefined;
if (typeof params.label_size === 'number') {
size = params.label_size;
} else {
size = Math.max(AngleState.radius * (params.label_auto_size_scale), params.label_auto_size_min);
}
Vec3.add(AngleState.labelPos, AngleState.ba, AngleState.bc);
Vec3.normalize(AngleState.labelPos, AngleState.labelPos);
Vec3.scale(AngleState.labelPos, AngleState.labelPos, AngleState.radius);
Vec3.add(AngleState.labelPos, AngleState.labelPos, AngleState.b);
const group = groups.allocateSingle(node);
groups.updateColor(group, params.label_color);
groups.updateSize(group, size);
labels.add(label, AngleState.labelPos[0], AngleState.labelPos[1], AngleState.labelPos[2], 1, 1, group);
}
function resolveLabelRefs(params: PrimitiveParams<'label'>, refs: Set<string>) {
addRef(params.position, refs);
}
const PrimitiveLabelState = {
position: Vec3.zero(),
sphere: Sphere3D.zero(),
};
function addPrimitiveLabel(context: PrimitiveBuilderContext, state: LabelBuilderState, node: MVSNode<'primitive'>, params: PrimitiveParams<'label'>) {
const { labels, groups } = state;
resolvePosition(context, params.position, PrimitiveLabelState.position, PrimitiveLabelState.sphere, undefined);
resolveBasePosition(context, params.position, labelPos);
const group = groups.allocateSingle(node);
groups.updateColor(group, params.label_color);
groups.updateSize(group, params.label_size);
const offset = PrimitiveLabelState.sphere.radius + params.label_offset;
labels.add(params.text, PrimitiveLabelState.position[0], PrimitiveLabelState.position[1], PrimitiveLabelState.position[2], offset, 1, group);
}
const circleCache = new Map<string, Primitive>();
function getCircle(options: { thetaStart?: number, thetaEnd?: number }) {
const key = JSON.stringify(options);
if (circleCache.has(key)) return circleCache.get(key)!;
const thetaLength = (options.thetaEnd ?? 2 * Math.PI) - (options.thetaStart ?? 0);
if (Math.abs(thetaLength) < 1e-3) return null;
const circle = Circle({
radius: 1,
thetaStart: options.thetaStart ?? 0,
thetaLength,
segments: Math.ceil(2 * Math.PI / thetaLength * 64),
});
circleCache.set(key, circle);
return circle;
}
const EllipseState = {
centerPos: Vec3.zero(),
majorPos: Vec3.zero(),
minorPos: Vec3.zero(),
majorAxis: Vec3.zero(),
minorAxis: Vec3.zero(),
scale: Vec3.zero(),
normal: Vec3.zero(),
scaleXform: Mat4.identity(),
rotationXform: Mat4.identity(),
translationXform: Mat4.identity(),
xform: Mat4.identity(),
};
function addEllipseMesh(context: PrimitiveBuilderContext, state: MeshBuilderState, node: MVSNode<'primitive'>, params: PrimitiveParams<'ellipse'>) {
// Unit circle in the XZ plane (Y up)
// X = minor axis, Y = normal, Z = major axis
const circle = getCircle({ thetaStart: params.theta_start, thetaEnd: params.theta_end });
if (!circle) return;
resolvePosition(context, params.center, EllipseState.centerPos, undefined, undefined);
if (params.major_axis_endpoint) {
resolvePosition(context, params.major_axis_endpoint, EllipseState.majorPos, undefined, undefined);
Vec3.sub(EllipseState.majorAxis, EllipseState.majorPos, EllipseState.centerPos);
} else {
Vec3.copy(EllipseState.majorAxis, params.major_axis as any as Vec3);
}
if (params.minor_axis_endpoint) {
resolvePosition(context, params.minor_axis_endpoint, EllipseState.minorPos, undefined, undefined);
Vec3.sub(EllipseState.minorAxis, EllipseState.minorPos, EllipseState.centerPos);
} else {
Vec3.copy(EllipseState.minorAxis, params.minor_axis as any as Vec3);
}
const { mesh, groups } = state;
// Translation
Mat4.fromTranslation(EllipseState.translationXform, EllipseState.centerPos);
// Scale
if (params.as_circle) {
const r = params.radius_major ?? Vec3.magnitude(EllipseState.majorAxis);
Vec3.set(EllipseState.scale, r, 1, r);
} else {
const major = params.radius_major ?? Vec3.magnitude(EllipseState.majorAxis);
const minor = params.radius_minor ?? Vec3.magnitude(EllipseState.minorAxis);
Vec3.set(EllipseState.scale, minor, 1, major);
}
Mat4.fromScaling(EllipseState.scaleXform, EllipseState.scale);
// Rotation
Vec3.normalize(EllipseState.minorAxis, EllipseState.minorAxis);
Vec3.normalize(EllipseState.majorAxis, EllipseState.majorAxis);
Vec3.cross(EllipseState.normal, EllipseState.majorAxis, EllipseState.minorAxis);
Mat4.targetTo(EllipseState.rotationXform, Vec3.origin, EllipseState.majorAxis, EllipseState.normal);
Mat4.mul(EllipseState.rotationXform, EllipseState.rotationXform, Mat4.rotY180);
// Final xform
Mat4.mul3(EllipseState.xform, EllipseState.translationXform, EllipseState.rotationXform, EllipseState.scaleXform);
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.color);
groups.updateTooltip(mesh.currentGroup, params.tooltip);
MeshBuilder.addPrimitive(mesh, EllipseState.xform, circle);
MeshBuilder.addPrimitiveFlipped(mesh, EllipseState.xform, circle);
}
const EllipsoidState = {
centerPos: Vec3.zero(),
majorPos: Vec3.zero(),
minorPos: Vec3.zero(),
majorAxis: Vec3.zero(),
minorAxis: Vec3.zero(),
sphere: Sphere3D.zero(),
radius: Vec3.zero(),
extent: Vec3.zero(),
up: Vec3.zero(),
};
function addEllipsoidMesh(context: PrimitiveBuilderContext, state: MeshBuilderState, node: MVSNode<'primitive'>, params: PrimitiveParams<'ellipsoid'>) {
resolvePosition(context, params.center, EllipsoidState.centerPos, EllipsoidState.sphere, undefined);
if (params.major_axis_endpoint) {
resolvePosition(context, params.major_axis_endpoint, EllipsoidState.majorPos, undefined, undefined);
Vec3.sub(EllipsoidState.majorAxis, EllipsoidState.majorPos, EllipsoidState.centerPos);
} else if (params.major_axis) {
Vec3.copy(EllipsoidState.majorAxis, params.major_axis as any as Vec3);
} else {
Vec3.copy(EllipsoidState.majorAxis, Vec3.unitX);
}
if (params.minor_axis_endpoint) {
resolvePosition(context, params.minor_axis_endpoint, EllipsoidState.minorPos, undefined, undefined);
Vec3.sub(EllipsoidState.minorAxis, EllipsoidState.minorPos, EllipsoidState.centerPos);
} else if (params.minor_axis) {
Vec3.copy(EllipsoidState.minorAxis, params.minor_axis as any as Vec3);
} else {
Vec3.copy(EllipsoidState.minorAxis, Vec3.unitY);
}
if (typeof params.radius === 'number') {
Vec3.set(EllipsoidState.radius, params.radius, params.radius, params.radius);
} else if (params.radius) {
Vec3.copy(EllipsoidState.radius, params.radius as any as Vec3);
} else {
const r = EllipsoidState.sphere.radius;
Vec3.set(EllipsoidState.radius, r, r, r);
}
if (typeof params.radius_extent === 'number') {
Vec3.set(EllipsoidState.extent, params.radius_extent, params.radius_extent, params.radius_extent);
} else if (params.radius_extent) {
Vec3.copy(EllipsoidState.extent, params.radius_extent as any as Vec3);
} else {
Vec3.set(EllipsoidState.extent, 0, 0, 0);
}
Vec3.add(EllipsoidState.radius, EllipsoidState.radius, EllipsoidState.extent);
const { mesh, groups } = state;
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.color);
groups.updateTooltip(mesh.currentGroup, params.tooltip);
Vec3.normalize(EllipsoidState.majorAxis, EllipsoidState.majorAxis);
Vec3.normalize(EllipsoidState.minorAxis, EllipsoidState.minorAxis);
Vec3.cross(EllipsoidState.up, EllipsoidState.majorAxis, EllipsoidState.minorAxis);
addEllipsoid(mesh, EllipsoidState.centerPos, EllipsoidState.up, EllipsoidState.minorAxis, EllipsoidState.radius, 3);
}
const BoxState = {
center: Vec3.zero(),
boundary: Box3D.zero(),
size: Vec3.zero(),
cage: BoxCage(),
translationXform: Mat4.identity(),
scaleXform: Mat4.identity(),
xform: Mat4.identity(),
};
function addBoxMesh(context: PrimitiveBuilderContext, state: MeshBuilderState, node: MVSNode<'primitive'>, params: PrimitiveParams<'box'>) {
if (!params.show_edges && !params.show_faces) return;
resolvePosition(context, params.center, BoxState.center, undefined, BoxState.boundary);
if (params.extent) {
Box3D.expand(BoxState.boundary, BoxState.boundary, params.extent as unknown as Vec3);
}
if (Box3D.volume(BoxState.boundary) < 1e-3) return;
const { mesh, groups } = state;
Mat4.fromScaling(BoxState.scaleXform, Box3D.size(BoxState.size, BoxState.boundary));
Mat4.fromTranslation(BoxState.translationXform, BoxState.center);
Mat4.mul(BoxState.xform, BoxState.translationXform, BoxState.scaleXform);
if (params.show_faces) {
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.face_color);
groups.updateTooltip(mesh.currentGroup, params.tooltip);
MeshBuilder.addPrimitive(mesh, BoxState.xform, Box());
}
if (params.show_edges) {
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.edge_color);
groups.updateTooltip(mesh.currentGroup, params.tooltip);
MeshBuilder.addCage(mesh, BoxState.xform, BoxCage(), params.edge_radius, 2, 8);
}
labels.add(params.text, labelPos[0], labelPos[1], labelPos[2], params.label_offset, 1, group);
}

View File

@@ -1,8 +1,7 @@
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Column, Table } from '../../../mol-data/db';

View File

@@ -1,8 +1,7 @@
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Column } from '../../../mol-data/db';
@@ -263,7 +262,7 @@ function matchesRange<T>(requiredMin: T | undefined | null, requiredMax: T | und
export function rowToExpression(row: MVSAnnotationRow): Expression {
const { and } = MS.core.logic;
const { eq, gre: gte, lte } = MS.core.rel;
const { macromolecular, ihm } = MS.struct.atomProperty;
const { macromolecular } = MS.struct.atomProperty;
const propTests: Partial<Record<string, Expression>> = {};
if (isDefined(row.label_entity_id)) {
@@ -281,16 +280,11 @@ export function rowToExpression(row: MVSAnnotationRow): Expression {
}
const residueTests: Expression[] = [];
if (isDefined(row.label_seq_id)) {
residueTests.push(ihm.hasSeqId({ 0: row.label_seq_id }));
}
if (isDefined(row.label_seq_id)) residueTests.push(eq([macromolecular.label_seq_id(), row.label_seq_id]));
if (isDefined(row.auth_seq_id)) residueTests.push(eq([macromolecular.auth_seq_id(), row.auth_seq_id]));
if (isDefined(row.pdbx_PDB_ins_code)) residueTests.push(eq([macromolecular.pdbx_PDB_ins_code(), row.pdbx_PDB_ins_code]));
if (isDefined(row.beg_label_seq_id) || isDefined(row.end_label_seq_id)) {
residueTests.push(ihm.overlapsSeqIdRange({ beg: row.beg_label_seq_id, end: row.end_label_seq_id }));
}
if (isDefined(row.beg_label_seq_id)) residueTests.push(gte([macromolecular.label_seq_id(), row.beg_label_seq_id]));
if (isDefined(row.end_label_seq_id)) residueTests.push(lte([macromolecular.label_seq_id(), row.end_label_seq_id]));
if (isDefined(row.beg_auth_seq_id)) residueTests.push(gte([macromolecular.auth_seq_id(), row.beg_auth_seq_id]));
if (isDefined(row.end_auth_seq_id)) residueTests.push(lte([macromolecular.auth_seq_id(), row.end_auth_seq_id]));
if (residueTests.length === 1) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -125,9 +125,6 @@ export interface UpdateTarget {
readonly selector: StateObjectSelector,
readonly targetManager: TargetManager,
readonly mvsDependencyRefs: Set<string>,
readonly transformer?: StateTransformer,
readonly transformParams?: any,
}
export const UpdateTarget = {
/** Create a new update, with `selector` pointing to the root. */
@@ -145,7 +142,7 @@ export const UpdateTarget = {
}
const ref = target.targetManager.getChildRef(target.selector, refSuffix);
const msResult = target.update.to(target.selector).apply(transformer, params, { ...options, ref }).selector;
const result: UpdateTarget = { ...target, selector: msResult, mvsDependencyRefs: new Set(), transformer, transformParams: params };
const result: UpdateTarget = { ...target, selector: msResult, mvsDependencyRefs: new Set() };
target.targetManager.allTargets.push(result);
return result;
},

View File

@@ -1,15 +1,13 @@
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Mat3, Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { Volume } from '../../mol-model/volume';
import { StructureComponentParams } from '../../mol-plugin-state/helpers/structure-component';
import { StructureFromModel, TransformStructureConformation } from '../../mol-plugin-state/transforms/model';
import { StructureRepresentation3D, VolumeRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { StructureRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { StateTransformer } from '../../mol-state';
import { arrayDistinct } from '../../mol-util/array';
import { canonicalJsonString } from '../../mol-util/json';
@@ -29,7 +27,6 @@ import { MolstarLoadingContext } from './load';
import { Subtree, getChildren } from './tree/generic/tree-schema';
import { dfs, formatObject } from './tree/generic/tree-utils';
import { MolstarKind, MolstarNode, MolstarNodeParams, MolstarSubtree, MolstarTree } from './tree/molstar/molstar-tree';
import { DefaultColor } from './tree/mvs/mvs-tree';
export const AnnotationFromUriKinds = new Set(['color_from_uri', 'component_from_uri', 'label_from_uri', 'tooltip_from_uri'] satisfies MolstarKind[]);
@@ -38,6 +35,10 @@ export type AnnotationFromUriKind = ElementOfSet<typeof AnnotationFromUriKinds>
export const AnnotationFromSourceKinds = new Set(['color_from_source', 'component_from_source', 'label_from_source', 'tooltip_from_source'] satisfies MolstarKind[]);
export type AnnotationFromSourceKind = ElementOfSet<typeof AnnotationFromSourceKinds>
/** Color to be used e.g. for representations without 'color' node */
export const DefaultColor = 'white';
/** Return a 4x4 matrix representing a rotation followed by a translation */
export function transformFromRotationTranslation(rotation: number[] | null | undefined, translation: number[] | null | undefined): Mat4 {
if (rotation && rotation.length !== 9) throw new Error(`'rotation' param for 'transform' node must be array of 9 elements, found ${rotation}`);
@@ -284,30 +285,19 @@ export function componentFromXProps(node: MolstarNode<'component_from_uri' | 'co
/** Create props for `StructureRepresentation3D` transformer from a representation node. */
export function representationProps(node: MolstarSubtree<'representation'>): Partial<StateTransformer.Params<StructureRepresentation3D>> {
const alpha = alphaForNode(node);
const params = node.params;
switch (params.type) {
switch (node.params.type) {
case 'cartoon':
return {
type: { name: 'cartoon', params: { alpha, tubularHelices: params.tubular_helices } },
sizeTheme: { name: 'uniform', params: { value: params.size_factor } },
type: { name: 'cartoon', params: { alpha } },
};
case 'ball_and_stick':
return {
type: { name: 'ball-and-stick', params: { sizeFactor: (params.size_factor ?? 1) * 0.5, sizeAspectRatio: 0.5, alpha, ignoreHydrogens: params.ignore_hydrogens } },
};
case 'spacefill':
return {
type: { name: 'spacefill', params: { alpha, ignoreHydrogens: params.ignore_hydrogens } },
sizeTheme: { name: 'physical', params: { scale: params.size_factor } },
};
case 'carbohydrate':
return {
type: { name: 'carbohydrate', params: { alpha, sizeFactor: params.size_factor ?? 1 } },
type: { name: 'ball-and-stick', params: { sizeFactor: 0.5, sizeAspectRatio: 0.5, alpha } },
};
case 'surface':
return {
type: { name: 'molecular-surface', params: { alpha, ignoreHydrogens: params.ignore_hydrogens } },
sizeTheme: { name: 'physical', params: { scale: params.size_factor } },
type: { name: 'molecular-surface', params: { alpha } },
sizeTheme: { name: 'physical', params: { scale: 1 } },
};
default:
throw new Error('NotImplementedError');
@@ -315,7 +305,7 @@ export function representationProps(node: MolstarSubtree<'representation'>): Par
}
/** Create value for `type.params.alpha` prop for `StructureRepresentation3D` transformer from a representation node based on 'opacity' nodes in its subtree. */
export function alphaForNode(node: MolstarSubtree<'representation' | 'volume_representation'>): number {
export function alphaForNode(node: MolstarSubtree<'representation'>): number {
const children = getChildren(node).filter(c => c.kind === 'opacity');
if (children.length > 0) {
return children[children.length - 1].params.opacity;
@@ -323,14 +313,8 @@ export function alphaForNode(node: MolstarSubtree<'representation' | 'volume_rep
return 1;
}
}
function hasMolStarUseDefaultColoring(node: MolstarNode): boolean {
if (!node.custom) return false;
return 'molstar_use_default_coloring' in node.custom || 'molstar_color_theme_name' in node.custom;
}
/** Create value for `colorTheme` prop for `StructureRepresentation3D` transformer from a representation node based on color* nodes in its subtree. */
export function colorThemeForNode(node: MolstarSubtree<'color' | 'color_from_uri' | 'color_from_source' | 'representation'> | undefined, context: MolstarLoadingContext): StateTransformer.Params<StructureRepresentation3D>['colorTheme'] | undefined {
export function colorThemeForNode(node: MolstarSubtree<'color' | 'color_from_uri' | 'color_from_source' | 'representation'> | undefined, context: MolstarLoadingContext): StateTransformer.Params<StructureRepresentation3D>['colorTheme'] {
if (node?.kind === 'representation') {
const children = getChildren(node).filter(c => c.kind === 'color' || c.kind === 'color_from_uri' || c.kind === 'color_from_source') as MolstarNode<'color' | 'color_from_uri' | 'color_from_source'>[];
if (children.length === 0) {
@@ -338,23 +322,12 @@ export function colorThemeForNode(node: MolstarSubtree<'color' | 'color_from_uri
name: 'uniform',
params: { value: decodeColor(DefaultColor) },
};
} else if (children.length === 1 && hasMolStarUseDefaultColoring(children[0])) {
if (children[0].custom?.molstar_use_default_coloring) return undefined;
const custom = children[0].custom;
return {
name: custom?.molstar_color_theme_name ?? undefined,
params: custom?.molstar_color_theme_params ?? {},
};
} else if (children.length === 1 && appliesColorToWholeRepr(children[0])) {
return colorThemeForNode(children[0], context);
} else {
const layers: MultilayerColorThemeProps['layers'] = children.map(
c => {
const theme = colorThemeForNode(c, context);
if (!theme) return undefined;
return { theme, selection: componentPropsFromSelector(c.kind === 'color' ? c.params.selector : undefined) };
}
).filter(t => !!t);
c => ({ theme: colorThemeForNode(c, context), selection: componentPropsFromSelector(c.kind === 'color' ? c.params.selector : undefined) })
);
return {
name: MultilayerColorThemeName,
params: { layers },
@@ -416,36 +389,3 @@ export function makeNearestReprMap(root: MolstarTree) {
});
return map;
}
/** Create props for `VolumeRepresentation3D` transformer from a representation node. */
export function volumeRepresentationProps(node: MolstarSubtree<'volume_representation'>): Partial<StateTransformer.Params<VolumeRepresentation3D>> {
const alpha = alphaForNode(node);
const params = node.params;
switch (params.type) {
case 'isosurface':
const isoValue = typeof params.absolute_isovalue === 'number' ? Volume.IsoValue.absolute(params.absolute_isovalue) : Volume.IsoValue.relative(params.relative_isovalue ?? 0);
const visuals: ('wireframe' | 'solid')[] = [];
if (params.show_wireframe) visuals.push('wireframe');
if (params.show_faces) visuals.push('solid');
return {
type: { name: 'isosurface', params: { alpha, isoValue, visuals } },
};
default:
throw new Error('NotImplementedError');
}
}
/** Create value for `colorTheme` prop for `StructureRepresentation3D` transformer from a representation node based on color* nodes in its subtree. */
export function volumeColorThemeForNode(node: MolstarSubtree<'volume_representation'> | undefined, context: MolstarLoadingContext): StateTransformer.Params<VolumeRepresentation3D>['colorTheme'] | undefined {
if (node?.kind !== 'volume_representation') return undefined;
const children = getChildren(node).filter(c => c.kind === 'color') as MolstarNode<'color'>[];
if (children.length === 0) {
return {
name: 'uniform',
params: { value: decodeColor(DefaultColor) },
};
} if (children.length === 1) {
return colorThemeForNode(children[0], context);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -7,11 +7,9 @@
*/
import { PluginStateSnapshotManager } from '../../mol-plugin-state/manager/snapshots';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { Download, ParseCcp4, ParseCif } from '../../mol-plugin-state/transforms/data';
import { Download, ParseCif } from '../../mol-plugin-state/transforms/data';
import { CustomModelProperties, CustomStructureProperties, ModelFromTrajectory, StructureComponent, StructureFromModel, TrajectoryFromMmCif, TrajectoryFromPDB, TransformStructureConformation } from '../../mol-plugin-state/transforms/model';
import { ShapeRepresentation3D, StructureRepresentation3D, VolumeRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { VolumeFromCcp4, VolumeFromDensityServerCif } from '../../mol-plugin-state/transforms/volume';
import { ShapeRepresentation3D, StructureRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import { StateObjectSelector } from '../../mol-state';
@@ -26,7 +24,7 @@ import { IsMVSModelProps, IsMVSModelProvider } from './components/is-mvs-model-p
import { getPrimitiveStructureRefs, MVSBuildPrimitiveShape, MVSDownloadPrimitiveData, MVSInlinePrimitiveData } from './components/primitives';
import { NonCovalentInteractionsExtension } from './load-extensions/non-covalent-interactions';
import { LoadingActions, LoadingExtension, loadTree, loadTreeVirtual, UpdateTarget } from './load-generic';
import { AnnotationFromSourceKind, AnnotationFromUriKind, collectAnnotationReferences, collectAnnotationTooltips, collectInlineLabels, collectInlineTooltips, colorThemeForNode, componentFromXProps, componentPropsFromSelector, isPhantomComponent, labelFromXProps, makeNearestReprMap, prettyNameFromSelector, representationProps, structureProps, transformProps, volumeColorThemeForNode, volumeRepresentationProps } from './load-helpers';
import { AnnotationFromSourceKind, AnnotationFromUriKind, collectAnnotationReferences, collectAnnotationTooltips, collectInlineLabels, collectInlineTooltips, colorThemeForNode, componentFromXProps, componentPropsFromSelector, isPhantomComponent, labelFromXProps, makeNearestReprMap, prettyNameFromSelector, representationProps, structureProps, transformProps } from './load-helpers';
import { MVSData, SnapshotMetadata } from './mvs-data';
import { validateTree } from './tree/generic/tree-schema';
import { convertMvsToMolstar, mvsSanityCheck } from './tree/molstar/conversion';
@@ -34,24 +32,13 @@ import { MolstarNode, MolstarNodeParams, MolstarSubtree, MolstarTree, MolstarTre
import { MVSTreeSchema } from './tree/mvs/mvs-tree';
export interface MVSLoadOptions {
replaceExisting?: boolean,
keepCamera?: boolean,
keepSnapshotCamera?: boolean,
extensions?: MolstarLoadingExtension<any>[],
sanityChecks?: boolean,
sourceUrl?: string,
doNotReportErrors?: boolean
}
/** Load a MolViewSpec (MVS) tree into the Mol* plugin.
* If `options.replaceExisting`, remove all objects in the current Mol* state; otherwise add to the current state.
* If `options.keepCamera`, ignore any camera positioning from the MVS state and keep the current camera position instead.
* If `options.keepSnapshotCamera`, ignore any camera positioning when generating snapshots.
* If `options.sanityChecks`, run some sanity checks and print potential issues to the console.
* If `options.extensions` is provided, apply specified set of MVS-loading extensions (not a part of standard MVS specification); default: apply all builtin extensions; use `extensions: []` to avoid applying builtin extensions.
* `options.sourceUrl` serves as the base for resolving relative URLs/URIs and may itself be relative to the window URL. */
export async function loadMVS(plugin: PluginContext, data: MVSData, options: MVSLoadOptions = {}) {
export async function loadMVS(plugin: PluginContext, data: MVSData, options: { replaceExisting?: boolean, keepCamera?: boolean, extensions?: MolstarLoadingExtension<any>[], sanityChecks?: boolean, sourceUrl?: string, doNotReportErrors?: boolean } = {}) {
plugin.errorContext.clear('mvs');
try {
const mvsExtensionLoaded = plugin.state.hasBehavior(MolViewSpec);
@@ -126,15 +113,13 @@ async function loadMolstarTree(plugin: PluginContext, tree: MolstarTree, options
}
}
function molstarTreeToEntry(plugin: PluginContext, tree: MolstarTree, metadata: SnapshotMetadata & { previousTransitionDurationMs?: number }, options?: { replaceExisting?: boolean, keepCamera?: boolean, keepSnapshotCamera?: boolean }) {
function molstarTreeToEntry(plugin: PluginContext, tree: MolstarTree, metadata: SnapshotMetadata & { previousTransitionDurationMs?: number }, options?: { replaceExisting?: boolean, keepCamera?: boolean }) {
const context = MolstarLoadingContext.create();
const snapshot = loadTreeVirtual(plugin, tree, MolstarLoadingActions, context, options);
snapshot.canvas3d = {
props: plugin.canvas3d ? modifyCanvasProps(plugin.canvas3d.props, context.canvas) : undefined,
};
if (!options?.keepSnapshotCamera) {
snapshot.camera = createPluginStateSnapshotCamera(plugin, context, metadata);
}
snapshot.camera = createPluginStateSnapshotCamera(plugin, context, metadata);
snapshot.durationInMs = metadata.linger_duration_ms + (metadata.previousTransitionDurationMs ?? 0);
const entryParams: PluginStateSnapshotManager.EntryParams = {
@@ -187,8 +172,6 @@ const MolstarLoadingActions: LoadingActions<MolstarTree, MolstarLoadingContext>
return UpdateTarget.apply(updateParent, ParseCif, {});
} else if (format === 'pdb') {
return updateParent;
} else if (format === 'map') {
return UpdateTarget.apply(updateParent, ParseCcp4, {});
} else {
console.error(`Unknown format in "parse" node: "${format}"`);
return undefined;
@@ -289,22 +272,6 @@ const MolstarLoadingActions: LoadingActions<MolstarTree, MolstarLoadingContext>
colorTheme: colorThemeForNode(node, context),
});
},
volume(updateParent: UpdateTarget, node: MolstarNode<'volume'>): UpdateTarget | undefined {
if (updateParent.transformer?.definition.to.includes(PluginStateObject.Format.Ccp4)) {
return UpdateTarget.apply(updateParent, VolumeFromCcp4, { });
} else if (updateParent.transformer?.definition.to.includes(PluginStateObject.Format.Cif)) {
return UpdateTarget.apply(updateParent, VolumeFromDensityServerCif, { blockHeader: node.params.channel_id || undefined });
} else {
console.error(`Unsupported volume format`);
return undefined;
}
},
volume_representation(updateParent: UpdateTarget, node: MolstarNode<'volume_representation'>, context: MolstarLoadingContext): UpdateTarget {
return UpdateTarget.apply(updateParent, VolumeRepresentation3D, {
...volumeRepresentationProps(node),
colorTheme: 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`

View File

@@ -1,8 +1,7 @@
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { omitObjectKeys, pickObjectKeys } from '../../../../mol-util/object';
@@ -18,7 +17,6 @@ export const ParseFormatMvsToMolstar = {
mmcif: { format: 'cif', is_binary: false },
bcif: { format: 'cif', is_binary: true },
pdb: { format: 'pdb', is_binary: false },
map: { format: 'map', is_binary: true },
} satisfies { [p in ParseFormatT]: { format: MolstarParseFormatT, is_binary: boolean } };
@@ -73,7 +71,6 @@ const StructureFormatExtensions: Record<ParseFormatT, (FileExtension | '*')[]> =
mmcif: ['.cif', '.mmif'],
bcif: ['.bcif'],
pdb: ['.pdb', '.ent'],
map: ['.map', '.ccp4', '.mrc', '.mrcs'],
};
/** Run some sanity check on a MVSTree. Return a list of potential problems (`undefined` if there are none) */

View File

@@ -1,8 +1,7 @@
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { deepClone, pickObjectKeys } from '../../../../mol-util/object';
@@ -152,10 +151,6 @@ export class Parse extends _Base<'parse'> {
ref: params.ref,
}));
}
/** Add a 'volume' node representing raw volume data */
volume(params: MVSNodeParams<'volume'> & CustomAndRef = {}): any {
return this.addChild('volume', params);
}
}
@@ -253,37 +248,10 @@ export class Representation extends _Base<'representation'> {
}
/** MVS builder pointing to a 'component' or 'component_from_uri' or 'component_from_source' node */
export class Volume extends _Base<'volume'> implements FocusMixin {
/** Add a 'representation' node and return builder pointing to it. 'representation' node instructs to create a visual representation of a component. */
representation(params: Partial<MVSNodeParams<'volume_representation'>> & CustomAndRef = {}): VolumeRepresentation {
const fullParams: MVSNodeParams<'volume_representation'> = { ...params, type: params.type ?? 'isosurface' };
return new VolumeRepresentation(this._root, this.addChild('volume_representation', fullParams));
}
focus = bindMethod(this, FocusMixinImpl, 'focus');
}
/** MVS builder pointing to a 'volume_representation' node */
export class VolumeRepresentation extends _Base<'volume_representation'> implements FocusMixin {
/** Add a 'color' node and return builder pointing back to the representation node. 'color' node instructs to apply color to a visual representation. */
color(params: MVSNodeParams<'color'> & CustomAndRef): VolumeRepresentation {
this.addChild('color', params);
return this;
}
/** Add an 'opacity' node and return builder pointing back to the representation node. 'opacity' node instructs to customize opacity/transparency of a visual representation. */
opacity(params: MVSNodeParams<'opacity'> & CustomAndRef): VolumeRepresentation {
this.addChild('opacity', params);
return this;
}
focus = bindMethod(this, FocusMixinImpl, 'focus');
}
type MVSPrimitiveSubparams<TKind extends MVSNodeParams<'primitive'>['kind']> = Omit<Extract<MVSNodeParams<'primitive'>, { kind: TKind }>, 'kind'>;
/** MVS builder pointing to a 'primitives' node */
export class Primitives extends _Base<'primitives'> implements FocusMixin {
class Primitives extends _Base<'primitives'> implements FocusMixin {
/** Construct custom meshes/shapes in a low-level fashion by providing vertices and indices. */
mesh(params: MVSPrimitiveSubparams<'mesh'> & CustomAndRef): Primitives {
this.addChild('primitive', { kind: 'mesh', ...params });
@@ -299,11 +267,6 @@ export class Primitives extends _Base<'primitives'> implements FocusMixin {
this.addChild('primitive', { kind: 'tube', ...params });
return this;
}
/** Defines an arrow. */
arrow(params: MVSPrimitiveSubparams<'arrow'> & CustomAndRef): Primitives {
this.addChild('primitive', { kind: 'arrow', ...params });
return this;
}
/** Defines a tube, connecting a start and an end point, with label containing distance between start and end. */
distance(params: MVSPrimitiveSubparams<'distance_measurement'> & CustomAndRef): Primitives {
this.addChild('primitive', { kind: 'distance_measurement', ...params });
@@ -314,21 +277,6 @@ export class Primitives extends _Base<'primitives'> implements FocusMixin {
this.addChild('primitive', { kind: 'label', ...params });
return this;
}
/** Defines an ellipse. */
ellipse(params: MVSPrimitiveSubparams<'ellipse'> & CustomAndRef): Primitives {
this.addChild('primitive', { kind: 'ellipse', ...params });
return this;
}
/** Defines an ellipsoid */
ellipsoid(params: MVSPrimitiveSubparams<'ellipsoid'> & CustomAndRef): Primitives {
this.addChild('primitive', { kind: 'ellipsoid', ...params });
return this;
}
/** Defines a box. */
box(params: MVSPrimitiveSubparams<'box'> & CustomAndRef): Primitives {
this.addChild('primitive', { kind: 'box', ...params });
return this;
}
focus = bindMethod(this, FocusMixinImpl, 'focus');
}

View File

@@ -5,9 +5,9 @@
* @author Adam Midlik <midlik@gmail.com>
*/
import { bool, float, int, mapping, nullable, OptionalField, RequiredField, str, union } from '../generic/field-schema';
import { bool, float, int, mapping, nullable, OptionalField, RequiredField, str } from '../generic/field-schema';
import { SimpleParamsSchema, UnionParamsSchema } from '../generic/params-schema';
import { ColorT, FloatList, IntList, PrimitivePositionT, Vector3 } from './param-types';
import { ColorT, FloatList, IntList, PrimitivePositionT } from './param-types';
const _TubeBase = {
@@ -75,39 +75,6 @@ const TubeParams = {
tooltip: OptionalField(nullable(str), null, 'Tooltip to show when hovering over the tube. If not specified, uses the parent primitives group `tooltip`.'),
};
const ArrowParams = {
/** Start point of the tube. */
start: RequiredField(PrimitivePositionT, 'Start point of the arrow.'),
/** End point of the tube. */
end: OptionalField(nullable(PrimitivePositionT), null, 'End point of the arrow.'),
/** If specified, the endpoint is computed as start + direction. */
direction: OptionalField(nullable(Vector3), null, 'If specified, the endpoint is computed as start + direction.'),
/** Length of the arrow. If unset, the distance between start and end is used. */
length: OptionalField(nullable(float), null, 'Length of the arrow. If unset, the distance between start and end is used.'),
/** Draw a cap at the start of the arrow. */
show_start_cap: OptionalField(bool, false, 'Draw a cap at the start of the arrow.'),
/** Length of the start cap. */
start_cap_length: OptionalField(float, 0.1, 'Length of the start cap.'),
/** Radius of the start cap. */
start_cap_radius: OptionalField(float, 0.1, 'Radius of the start cap.'),
/** Draw an arrow at the end of the arrow. */
show_end_cap: OptionalField(bool, false, 'Draw a cap at the end of the arrow.'),
/** Height of the arrow at the end. */
end_cap_length: OptionalField(float, 0.1, 'Length of the end cap.'),
/** Radius of the arrow at the end. */
end_cap_radius: OptionalField(float, 0.1, 'Radius of the end cap.'),
/** Draw a tube connecting the start and end points. */
show_tube: OptionalField(bool, true, 'Draw a tube connecting the start and end points.'),
/** Tube radius (in Angstroms). */
tube_radius: OptionalField(float, 0.05, 'Tube radius (in Angstroms).'),
/** Length of each dash and gap between dashes. If not specified (null), draw full line. */
tube_dash_length: OptionalField(nullable(float), null, 'Length of each dash and gap between dashes. If not specified (null), draw full line.'),
/** Color of the tube. If not specified, uses the parent primitives group `color`. */
color: OptionalField(nullable(ColorT), null, 'Color of the tube. If not specified, uses the parent primitives group `color`.'),
/** Tooltip to show when hovering over the tube. If not specified, uses the parent primitives group `tooltip`. */
tooltip: OptionalField(nullable(str), null, 'Tooltip to show when hovering over the arrow. If not specified, uses the parent primitives group `tooltip`.'),
};
const DistanceMeasurementParams = {
..._TubeBase,
/** Template used to construct the label. Use {{distance}} as placeholder for the distance. */
@@ -122,37 +89,6 @@ const DistanceMeasurementParams = {
label_color: OptionalField(nullable(ColorT), null, 'Color of the label. If not specified, uses the parent primitives group `label_color`.'),
};
const AngleMeasurementParams = {
/** Point A. */
a: RequiredField(PrimitivePositionT, 'Point A.'),
/** Point B. */
b: RequiredField(PrimitivePositionT, 'Point B.'),
/** Point C. */
c: RequiredField(PrimitivePositionT, 'Point C.'),
/** Template used to construct the label. Use {{angle}} as placeholder for the angle in radians. */
label_template: OptionalField(str, '{{angle}}', 'Template used to construct the label. Use {{angle}} as placeholder for the angle in radians.'),
/** Size of the label (text height in Angstroms). If not specified, size will be relative to the distance (see label_auto_size_scale, label_auto_size_min). */
label_size: OptionalField(nullable(float), null, 'Size of the label (text height in Angstroms). If not specified, size will be relative to the distance (see label_auto_size_scale, label_auto_size_min).'),
/** Scaling factor for relative size. */
label_auto_size_scale: OptionalField(float, 0.33, 'Scaling factor for relative size.'),
/** Minimum size for relative size. */
label_auto_size_min: OptionalField(float, 0, 'Minimum size for relative size.'),
/** Color of the label. If not specified, uses the parent primitives group `label_color`. */
label_color: OptionalField(nullable(ColorT), null, 'Color of the label. If not specified, uses the parent primitives group `label_color`.'),
/** Draw vectors between (a, b) and (b, c). */
show_vector: OptionalField(bool, true, 'Draw vectors between (a, b) and (b, c).'),
/** Color of the vectors. */
vector_color: OptionalField(nullable(ColorT), null, 'Color of the vectors.'),
/** Draw a filled circle section representing the angle. */
show_section: OptionalField(bool, true, 'Draw a filled circle section representing the angle.'),
/** Color of the angle section. If not specified, the primitives group color is used. */
section_color: OptionalField(nullable(ColorT), null, 'Color of the angle section. If not specified, the primitives group color is used.'),
/** Radius of the angle section. In angstroms. */
section_radius: OptionalField(nullable(float), null, 'Radius of the angle section. In angstroms.'),
/** Factor to scale the radius of the angle section. Ignored if section_radius is set. */
section_radius_scale: OptionalField(float, 0.33, 'Factor to scale the radius of the angle section. Ignored if section_radius is set.'),
};
const PrimitiveLabelParams = {
/** Position of this label. */
position: RequiredField(PrimitivePositionT, 'Position of this label.'),
@@ -166,73 +102,6 @@ const PrimitiveLabelParams = {
label_offset: OptionalField(float, 0, 'Camera-facing offset to prevent overlap with geometry.'),
};
const EllipseParams = {
/** Color of the primitive. If not specified, uses the parent primitives group `color`. */
color: OptionalField(nullable(ColorT), null, 'Color of the ellipse. If not specified, uses the parent primitives group `color`.'),
/** If true, ignores radius_minor/magnitude of the minor axis */
as_circle: OptionalField(bool, false, 'If true, ignores radius_minor/magnitude of the minor axis.'),
/** ellipse center. */
center: RequiredField(PrimitivePositionT, 'The center of the ellipse.'),
/** Major axis of this ellipse. */
major_axis: OptionalField(nullable(Vector3), null, 'Major axis of this ellipse.'),
/** Minor axis of this ellipse. */
minor_axis: OptionalField(nullable(Vector3), null, 'Minor axis of this ellipse.'),
/** Major axis endpoint. If specified, overrides major axis to be major_axis_endpoint - center. */
major_axis_endpoint: OptionalField(nullable(PrimitivePositionT), null, 'Major axis endpoint. If specified, overrides major axis to be major_axis_endpoint - center.'),
/** Minor axis endpoint. If specified, overrides minor axis to be minor_axis_endpoint - center. */
minor_axis_endpoint: OptionalField(nullable(PrimitivePositionT), null, 'Minor axis endpoint. If specified, overrides minor axis to be minor_axis_endpoint - center.'),
/** Radius of the major axis. If unset, the length of the major axis is used. */
radius_major: OptionalField(nullable(float), null, 'Radius of the major axis. If unset, the length of the major axis is used.'),
/** Radius of the minor axis. If unset, the length of the minor axis is used. */
radius_minor: OptionalField(nullable(float), null, 'Radius of the minor axis. If unset, the length of the minor axis is used.'),
/** Start of the arc. In radians */
theta_start: OptionalField(float, 0, 'Start of the arc. In radians'),
/** End of the arc. In radians */
theta_end: OptionalField(float, 2 * Math.PI, 'End of the arc. In radians'),
/** Tooltip to show when hovering over the tube. If not specified, uses the parent primitives group `tooltip`. */
tooltip: OptionalField(nullable(str), null, 'Tooltip to show when hovering over the tube. If not specified, uses the parent primitives group `tooltip`.'),
};
const EllipsoidParams = {
/** Color of the primitive. If not specified, uses the parent primitives group `color`. */
color: OptionalField(nullable(ColorT), null, 'Color of the ellipsoid. If not specified, uses the parent primitives group `color`.'),
/** Ellipsoid center. */
center: RequiredField(PrimitivePositionT, 'The center of the ellipsoid.'),
/** Major axis of this ellipsoid. */
major_axis: OptionalField(nullable(Vector3), null, 'Major axis of this ellipsoid.'),
/** Minor axis of this ellipsoid. */
minor_axis: OptionalField(nullable(Vector3), null, 'Minor axis of this ellipsoid.'),
/** Major axis endpoint. If specified, overrides major axis to be major_axis_endpoint - center. */
major_axis_endpoint: OptionalField(nullable(PrimitivePositionT), null, 'Major axis endpoint. If specified, overrides major axis to be major_axis_endpoint - center.'),
/** Minor axis endpoint. If specified, overrides minor axis to be minor_axis_endpoint - center. */
minor_axis_endpoint: OptionalField(nullable(PrimitivePositionT), null, 'Minor axis endpoint. If specified, overrides minor axis to be minor_axis_endpoint - center.'),
/** Radii of the ellipsoid along each axis. */
radius: OptionalField(nullable(union([Vector3, float])), null, 'Radii of the ellipsoid along each axis.'),
/** Added to the radii of the ellipsoid along each axis. */
radius_extent: OptionalField(nullable(union([Vector3, float])), null, 'Added to the radii of the ellipsoid along each axis.'),
/** Tooltip to show when hovering over the tube. If not specified, uses the parent primitives group `tooltip`. */
tooltip: OptionalField(nullable(str), null, 'Tooltip to show when hovering over the tube. If not specified, uses the parent primitives group `tooltip`.'),
};
const BoxParams = {
/** The center of the box. */
center: RequiredField(PrimitivePositionT, 'The center of the box.'),
/** The width, the height, and the depth of the box. Added to the bounding box determined by the center. */
extent: OptionalField(nullable(Vector3), null, 'The width, the height, and the depth of the box. Added to the bounding box determined by the center.'),
/** Determine whether to render the faces of the box. */
show_faces: OptionalField(bool, true, 'Determine whether to render the faces of the box.'),
/** Color of the box faces. */
face_color: OptionalField(nullable(ColorT), null, 'Color of the box faces.'),
/** Determine whether to render the edges of the box. */
show_edges: OptionalField(bool, false, 'Determine whether to render the edges of the box.'),
/** Radius of the box edges. In angstroms. */
edge_radius: OptionalField(float, 0.1, 'Radius of the box edges. In angstroms.'),
/** Color of the box edges. */
edge_color: OptionalField(nullable(ColorT), null, 'Color of the edges.'),
/** Tooltip to show when hovering over the tube. If not specified, uses the parent primitives group `tooltip`. */
tooltip: OptionalField(nullable(str), null, 'Tooltip to show when hovering over the tube. If not specified, uses the parent primitives group `tooltip`.'),
};
export const MVSPrimitiveParams = UnionParamsSchema(
'kind',
'Kind of geometrical primitive',
@@ -240,12 +109,7 @@ export const MVSPrimitiveParams = UnionParamsSchema(
'mesh': SimpleParamsSchema(MeshParams),
'lines': SimpleParamsSchema(LinesParams),
'tube': SimpleParamsSchema(TubeParams),
'arrow': SimpleParamsSchema(ArrowParams),
'distance_measurement': SimpleParamsSchema(DistanceMeasurementParams),
'angle_measurement': SimpleParamsSchema(AngleMeasurementParams),
'label': SimpleParamsSchema(PrimitiveLabelParams),
'ellipse': SimpleParamsSchema(EllipseParams),
'ellipsoid': SimpleParamsSchema(EllipsoidParams),
'box': SimpleParamsSchema(BoxParams),
},
);

View File

@@ -1,72 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { bool, float, nullable, OptionalField } from '../generic/field-schema';
import { SimpleParamsSchema, UnionParamsSchema } from '../generic/params-schema';
const Cartoon = {
/** Scales the corresponding visuals */
size_factor: OptionalField(float, 1, 'Scales the corresponding visuals.'),
/** Simplify corkscrew helices to tubes. */
tubular_helices: OptionalField(bool, false, 'Simplify corkscrew helices to tubes.'),
};
const BallAndStick = {
/** Scales the corresponding visuals */
size_factor: OptionalField(float, 1, 'Scales the corresponding visuals.'),
/** Controls whether hydrogen atoms are drawn. */
ignore_hydrogens: OptionalField(bool, false, 'Controls whether hydrogen atoms are drawn.'),
};
const Spacefill = {
/** Scales the corresponding visuals */
size_factor: OptionalField(float, 1, 'Scales the corresponding visuals.'),
/** Controls whether hydrogen atoms are drawn. */
ignore_hydrogens: OptionalField(bool, false, 'Controls whether hydrogen atoms are drawn.'),
};
const Carbohydrate = {
/** Scales the corresponding visuals */
size_factor: OptionalField(float, 1, 'Scales the corresponding visuals.'),
};
const Surface = {
/** Scales the corresponding visuals */
size_factor: OptionalField(float, 1, 'Scales the corresponding visuals.'),
/** Controls whether hydrogen atoms are drawn. */
ignore_hydrogens: OptionalField(bool, false, 'Controls whether hydrogen atoms are drawn.'),
};
export const MVSRepresentationParams = UnionParamsSchema(
'type',
'Representation type',
{
cartoon: SimpleParamsSchema(Cartoon),
ball_and_stick: SimpleParamsSchema(BallAndStick),
spacefill: SimpleParamsSchema(Spacefill),
carbohydrate: SimpleParamsSchema(Carbohydrate),
surface: SimpleParamsSchema(Surface),
},
);
const VolumeIsoSurface = {
/** Relative isovalue. */
relative_isovalue: OptionalField(nullable(float), null, 'Relative isovalue.'),
/** Absolute isovalue. Overrides `relative_isovalue`. */
absolute_isovalue: OptionalField(nullable(float), null, 'Absolute isovalue. Overrides `relative_isovalue`.'),
/** Show mesh wireframe. Defaults to false. */
show_wireframe: OptionalField(bool, false, 'Show mesh wireframe. Defaults to false.'),
/** Show mesh faces. Defaults to true. */
show_faces: OptionalField(bool, true, 'Show mesh faces. Defaults to true.'),
};
export const MVSVolumeRepresentationParams = UnionParamsSchema(
'type',
'Representation type',
{
'isosurface': SimpleParamsSchema(VolumeIsoSurface),
},
);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -8,9 +8,8 @@
import { float, int, list, literal, nullable, OptionalField, RequiredField, str, tuple, union } from '../generic/field-schema';
import { SimpleParamsSchema } from '../generic/params-schema';
import { NodeFor, ParamsOfKind, SubtreeOfKind, TreeFor, TreeSchema, TreeSchemaWithAllRequired } from '../generic/tree-schema';
import { MVSRepresentationParams, MVSVolumeRepresentationParams } from './mvs-tree-representations';
import { MVSPrimitiveParams } from './mvs-tree-primitives';
import { ColorT, ComponentExpressionT, ComponentSelectorT, Matrix, ParseFormatT, SchemaFormatT, SchemaT, StrList, StructureTypeT, Vector3 } from './param-types';
import { ColorT, ComponentExpressionT, ComponentSelectorT, Matrix, ParseFormatT, RepresentationTypeT, SchemaFormatT, SchemaT, StrList, StructureTypeT, Vector3 } from './param-types';
const _DataFromUriParams = {
@@ -43,9 +42,6 @@ const _DataFromSourceParams = {
field_name: RequiredField(str, 'Name of the column in CIF or field name (key) in JSON that contains the dependent variable (color/label/tooltip/component_id...).'),
};
/** Color to be used e.g. for representations without 'color' node */
export const DefaultColor = 'white';
/** Schema for `MVSTree` (MolViewSpec tree) */
export const MVSTreeSchema = TreeSchema({
rootKind: 'root',
@@ -146,29 +142,18 @@ export const MVSTreeSchema = TreeSchema({
representation: {
description: 'This node instructs to create a visual representation of a component.',
parent: ['component', 'component_from_uri', 'component_from_source'],
params: MVSRepresentationParams,
},
/** This node instructs to create a volume from a parsed data resource. "Volume" refers to an internal representation of volumetric data without any visual representation. */
volume: {
description: 'This node instructs to create a volume from a parsed data resource. "Volume" refers to an internal representation of volumetric data without any visual representation.',
parent: ['parse'],
params: SimpleParamsSchema({
channel_id: OptionalField(nullable(str), null, 'Channel identifier (only applies when the input data contain multiple channels).'),
/** Method of visual representation of the component. */
type: RequiredField(RepresentationTypeT, 'Method of visual representation of the component.'),
}),
},
/** This node instructs to create a visual representation of a volume. */
volume_representation: {
description: 'This node instructs to create a visual representation of a volume.',
parent: ['volume'],
params: MVSVolumeRepresentationParams,
},
/** This node instructs to apply color to a visual representation. */
color: {
description: 'This node instructs to apply color to a visual representation.',
parent: ['representation', 'volume_representation'],
parent: ['representation'],
params: SimpleParamsSchema({
/** Color to apply to the representation. Can be either an X11 color name (e.g. `"red"`) or a hexadecimal code (e.g. `"#FF0011"`). */
color: OptionalField(ColorT, DefaultColor, 'Color to apply to the representation. Can be either an X11 color name (e.g. `"red"`) or a hexadecimal code (e.g. `"#FF0011"`).'),
color: RequiredField(ColorT, 'Color to apply to the representation. Can be either an X11 color name (e.g. `"red"`) or a hexadecimal code (e.g. `"#FF0011"`).'),
/** Defines to what part of the representation this color should be applied. */
selector: OptionalField(union([ComponentSelectorT, ComponentExpressionT, list(ComponentExpressionT)]), 'all', 'Defines to what part of the representation this color should be applied.'),
}),
@@ -196,7 +181,7 @@ export const MVSTreeSchema = TreeSchema({
/** This node instructs to apply opacity/transparency to a visual representation. */
opacity: {
description: 'This node instructs to apply opacity/transparency to a visual representation.',
parent: ['representation', 'volume_representation'],
parent: ['representation'],
params: SimpleParamsSchema({
/** Opacity of a representation. 0.0: fully transparent, 1.0: fully opaque. */
opacity: RequiredField(float, 'Opacity of a representation. 0.0: fully transparent, 1.0: fully opaque.'),
@@ -263,7 +248,7 @@ export const MVSTreeSchema = TreeSchema({
/** This node instructs to set the camera focus to a component (zoom in). */
focus: {
description: 'This node instructs to set the camera focus to a component (zoom in).',
parent: ['root', 'component', 'component_from_uri', 'component_from_source', 'primitives', 'primitives_from_uri', 'volume', 'volume_representation'],
parent: ['root', 'component', 'component_from_uri', 'component_from_source', 'primitives', 'primitives_from_uri'],
params: SimpleParamsSchema({
/** Vector describing the direction of the view (camera position -> focused target). */
direction: OptionalField(Vector3, [0, 0, -1], 'Vector describing the direction of the view (camera position -> focused target).'),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -12,18 +12,18 @@ import { ColorNames } from '../../../../mol-util/color/names';
/** `format` parameter values for `parse` node in MVS tree */
export const ParseFormatT = literal('mmcif', 'bcif', 'pdb', 'map');
export const ParseFormatT = literal('mmcif', 'bcif', 'pdb');
export type ParseFormatT = ValueFor<typeof ParseFormatT>
/** `format` parameter values for `parse` node in Molstar tree */
export const MolstarParseFormatT = literal('cif', 'pdb', 'map');
export const MolstarParseFormatT = literal('cif', 'pdb');
export type MolstarParseFormatT = ValueFor<typeof MolstarParseFormatT>
/** `kind` parameter values for `structure` node in MVS tree */
export const StructureTypeT = literal('model', 'assembly', 'symmetry', 'symmetry_mates');
/** `selector` parameter values for `component` node in MVS tree */
export const ComponentSelectorT = literal('all', 'polymer', 'protein', 'nucleic', 'branched', 'ligand', 'ion', 'water', 'coarse');
export const ComponentSelectorT = literal('all', 'polymer', 'protein', 'nucleic', 'branched', 'ligand', 'ion', 'water');
/** `selector` parameter values for `component` node in MVS tree */
export const ComponentExpressionT = iots.partial({
@@ -45,6 +45,9 @@ export const ComponentExpressionT = iots.partial({
});
export type ComponentExpressionT = ValueFor<typeof ComponentExpressionT>
/** `type` parameter values for `representation` node in MVS tree */
export const RepresentationTypeT = literal('ball_and_stick', 'cartoon', 'surface');
/** `schema` parameter values for `*_from_uri` and `*_from_source` nodes in MVS tree */
export const SchemaT = literal('whole_structure', 'entity', 'chain', 'auth_chain', 'residue', 'auth_residue', 'residue_range', 'auth_residue_range', 'atom', 'auth_atom', 'all_atomic');
@@ -61,7 +64,7 @@ export const Matrix = list(float);
/** Primitives-related types */
export const PrimitiveComponentExpressionT = iots.partial({ structure_ref: str, expression_schema: SchemaT, expressions: list(ComponentExpressionT) });
export type PrimitiveComponentExpressionT = ValueFor<typeof PrimitiveComponentExpressionT>
export const PrimitivePositionT = iots.union([Vector3, ComponentExpressionT, PrimitiveComponentExpressionT]);
export const PrimitivePositionT = iots.union([Vector3, ComponentExpressionT, list(PrimitiveComponentExpressionT)]);
export type PrimitivePositionT = ValueFor<typeof PrimitivePositionT>
export const FloatList = list(float);
@@ -90,7 +93,6 @@ export const ColorNamesT = literal(...Object.keys(ColorNames) as (keyof ColorNam
/** `color` parameter values for `color` node in MVS tree */
export const ColorT = union([ColorNameT, HexColorT]);
export type ColorT = ValueFor<typeof ColorT>
/** Type helpers */
export function isVector3(x: any): x is Vector3 {

View File

@@ -13,7 +13,6 @@ import { Color } from '../../../mol-util/color';
import { TableLegend } from '../../../mol-util/legend';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { ColorThemeCategory } from '../../../mol-theme/color/categories';
const ValidationColors = [
Color.fromRgb(170, 170, 170), // not applicable
@@ -91,7 +90,7 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params, 'pdbe-structure-quality-report'> = {
name: 'pdbe-structure-quality-report',
label: 'Structure Quality Report',
category: ColorThemeCategory.Validation,
category: ColorTheme.Category.Validation,
factory: StructureQualityReportColorTheme,
getParams: ctx => {
const issueTypes = StructureQualityReport.getIssueTypes(ctx.structure);

View File

@@ -12,7 +12,6 @@ import { StructureElement, Model, ElementIndex, Bond } from '../../../../mol-mod
import { Location } from '../../../../mol-model/location';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ValidationReportProvider, ValidationReport } from '../prop';
import { ColorThemeCategory } from '../../../../mol-theme/color/categories';
const DefaultColor = Color(0xCCCCCC);
@@ -71,7 +70,7 @@ export function DensityFitColorTheme(ctx: ThemeDataContext, props: {}): ColorThe
export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationReport.Tag.DensityFit> = {
name: ValidationReport.Tag.DensityFit,
label: 'Density Fit',
category: ColorThemeCategory.Validation,
category: ColorTheme.Category.Validation,
factory: DensityFitColorTheme,
getParams: () => ({}),
defaultValues: PD.getDefaultValues({}),

View File

@@ -15,7 +15,6 @@ import { ValidationReportProvider, ValidationReport } from '../prop';
import { TableLegend } from '../../../../mol-util/legend';
import { PolymerType } from '../../../../mol-model/structure/model/types';
import { SetUtils } from '../../../../mol-util/set';
import { ColorThemeCategory } from '../../../../mol-theme/color/categories';
const DefaultColor = Color(0x909090);
@@ -109,7 +108,7 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Value
export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQualityColorThemeParams, ValidationReport.Tag.GeometryQuality> = {
name: ValidationReport.Tag.GeometryQuality,
label: 'Geometry Quality',
category: ColorThemeCategory.Validation,
category: ColorTheme.Category.Validation,
factory: GeometryQualityColorTheme,
getParams: getGeometricQualityColorThemeParams,
defaultValues: PD.getDefaultValues(getGeometricQualityColorThemeParams({})),

View File

@@ -12,7 +12,6 @@ import { StructureElement, Model, ElementIndex, Bond } from '../../../../mol-mod
import { Location } from '../../../../mol-model/location';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ValidationReportProvider, ValidationReport } from '../prop';
import { ColorThemeCategory } from '../../../../mol-theme/color/categories';
const DefaultColor = Color(0xCCCCCC);
@@ -62,7 +61,7 @@ export function RandomCoilIndexColorTheme(ctx: ThemeDataContext, props: {}): Col
export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}, ValidationReport.Tag.RandomCoilIndex> = {
name: ValidationReport.Tag.RandomCoilIndex,
label: 'Random Coil Index',
category: ColorThemeCategory.Validation,
category: ColorTheme.Category.Validation,
factory: RandomCoilIndexColorTheme,
getParams: () => ({}),
defaultValues: PD.getDefaultValues({}),

View File

@@ -6,7 +6,6 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Location } from '../../../mol-model/location';
import { SbNcbrPartialChargesPropertyProvider } from './property';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { ColorThemeCategory } from '../../../mol-theme/color/categories';
const Colors = {
Bond: Color(0xffffff),
@@ -134,7 +133,7 @@ PartialChargesThemeParams,
> = {
label: 'SB NCBR Partial Charges',
name: 'sb-ncbr-partial-charges',
category: ColorThemeCategory.Atom,
category: ColorTheme.Category.Atom,
factory: PartialChargesColorTheme,
getParams: getPartialChargesThemeParams,
defaultValues: PD.getDefaultValues(PartialChargesThemeParams),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -392,6 +392,8 @@ namespace Canvas3D {
let y = 0;
let width = 128;
let height = 128;
let canvasScaleRatioX = 1;
let canvasScaleRatioY = 1;
let forceNextRender = false;
let currentTime = 0;
@@ -645,7 +647,7 @@ namespace Canvas3D {
function identify(x: number, y: number): PickData | undefined {
const cam = p.camera.stereo.name === 'on' ? stereoCamera : camera;
return webgl.isContextLost ? undefined : pickHelper.identify(x, y, cam);
return webgl.isContextLost ? undefined : pickHelper.identify(x / canvasScaleRatioX, y / canvasScaleRatioY, cam);
}
function commit(isSynchronous: boolean = false) {
@@ -1158,6 +1160,12 @@ namespace Canvas3D {
function updateViewport() {
const oldX = x, oldY = y, oldWidth = width, oldHeight = height;
const canvasRect = canvas?.getBoundingClientRect();
canvasScaleRatioX = (canvasRect?.width ?? gl.drawingBufferWidth) / gl.drawingBufferWidth;
if (!canvasScaleRatioX) canvasScaleRatioX = 1;
canvasScaleRatioY = (canvasRect?.height ?? gl.drawingBufferHeight) / gl.drawingBufferHeight;
if (!canvasScaleRatioY) canvasScaleRatioY = 1;
if (p.viewport.name === 'canvas') {
x = 0;
y = 0;
@@ -1184,7 +1192,7 @@ namespace Canvas3D {
pickHelper.setViewport(x, y, width, height);
renderer.setViewport(x, y, width, height);
Viewport.set(camera.viewport, x, y, width, height);
Viewport.set(controls.viewport, x, y, width, height);
Viewport.set(controls.viewport, x, y, width * canvasScaleRatioX, height * canvasScaleRatioY);
hiZ.setViewport(x, y, width, height);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -130,7 +130,6 @@ export namespace BaseGeometry {
uClipObjectPosition: ValueCell.create(clip.objects.position),
uClipObjectRotation: ValueCell.create(clip.objects.rotation),
uClipObjectScale: ValueCell.create(clip.objects.scale),
uClipObjectTransform: ValueCell.create(clip.objects.transform),
instanceGranularity: ValueCell.create(props.instanceGranularity),
uLod: ValueCell.create(Vec4.create(props.lod[0], props.lod[1], props.lod[2], 0)),
@@ -153,7 +152,6 @@ export namespace BaseGeometry {
ValueCell.update(values.uClipObjectPosition, clip.objects.position);
ValueCell.update(values.uClipObjectRotation, clip.objects.rotation);
ValueCell.update(values.uClipObjectScale, clip.objects.scale);
ValueCell.update(values.uClipObjectTransform, clip.objects.transform);
ValueCell.updateIfChanged(values.instanceGranularity, props.instanceGranularity);
ValueCell.update(values.uLod, Vec4.set(values.uLod.ref.value, props.lod[0], props.lod[1], props.lod[2], 0));

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -24,8 +24,6 @@ export type ColorData = {
uColor: ValueCell<Vec3>,
tColor: ValueCell<TextureImage<Uint8Array>>,
tColorGrid: ValueCell<Texture>,
uPaletteDomain: ValueCell<Vec2>,
uPaletteDefault: ValueCell<Vec3>,
tPalette: ValueCell<TextureImage<Uint8Array>>,
uColorTexDim: ValueCell<Vec2>,
uColorGridDim: ValueCell<Vec3>,
@@ -38,9 +36,6 @@ export function createColors(locationIt: LocationIterator, positionIt: LocationI
const data = _createColors(locationIt, positionIt, colorTheme, colorData);
if (colorTheme.palette) {
ValueCell.updateIfChanged(data.dUsePalette, true);
const [min, max] = colorTheme.palette.domain || [0, 1];
ValueCell.update(data.uPaletteDomain, Vec2.set(data.uPaletteDomain.ref.value, min, max));
ValueCell.update(data.uPaletteDefault, Color.toVec3Normalized(data.uPaletteDefault.ref.value, colorTheme.palette.defaultColor ?? Color(0xCCCCCC)));
updatePaletteTexture(colorTheme.palette, data.tPalette);
} else {
ValueCell.updateIfChanged(data.dUsePalette, false);
@@ -108,8 +103,6 @@ export function createValueColor(value: Color, colorData?: ColorData): ColorData
uColor: ValueCell.create(Color.toVec3Normalized(Vec3(), value)),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tColorGrid: ValueCell.create(createNullTexture()),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
uPaletteDefault: ValueCell.create(Vec3()),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
@@ -138,8 +131,6 @@ export function createTextureColor(colors: TextureImage<Uint8Array>, type: Color
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create(colors),
tColorGrid: ValueCell.create(createNullTexture()),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
uPaletteDefault: ValueCell.create(Vec3()),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(colors.width, colors.height)),
uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
@@ -242,8 +233,6 @@ export function createGridColor(grid: ColorVolume, type: ColorType, colorData?:
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tColorGrid: ValueCell.create(colors),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
uPaletteDefault: ValueCell.create(Vec3()),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(width, height)),
uColorGridDim: ValueCell.create(Vec3.clone(dimension)),
@@ -266,8 +255,6 @@ function createDirectColor(colorData?: ColorData): ColorData {
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tColorGrid: ValueCell.create(createNullTexture()),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
uPaletteDefault: ValueCell.create(Vec3()),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -214,7 +214,7 @@ export namespace DirectVolume {
const { bboxSize, bboxMin, bboxMax, gridDimension, transform: gridTransform } = directVolume;
const { instanceCount, groupCount } = locationIt;
const positionIt = createPositionIterator(directVolume, transform);
const positionIt = Utils.createPositionIterator(directVolume, transform);
const color = createColors(locationIt, positionIt, theme.color);
const marker = props.instanceGranularity

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -23,7 +23,6 @@ import { RenderObjectValues } from '../../mol-gl/render-object';
import { TextureMesh } from './texture-mesh/texture-mesh';
import { Image } from './image/image';
import { Cylinders } from './cylinders/cylinders';
import { arrayMaxPackedIntToRGB } from '../../mol-util/number-packing';
export type GeometryKind = 'mesh' | 'points' | 'spheres' | 'cylinders' | 'text' | 'lines' | 'direct-volume' | 'image' | 'texture-mesh'
@@ -106,7 +105,7 @@ export namespace Geometry {
case 'direct-volume':
return 1;
case 'image':
return arrayMaxPackedIntToRGB(geometry.groupTexture.ref.value.array, 4) + 1;
return arrayMax(geometry.groupTexture.ref.value.array) + 1;
case 'texture-mesh':
return geometry.groupCount;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -9,7 +9,7 @@ import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { RenderableState } from '../../../mol-gl/renderable';
import { calculateTransformBoundingSphere, createTextureImage, TextureImage } from '../../../mol-gl/renderable/util';
import { Sphere3D } from '../../../mol-math/geometry';
import { Vec2, Vec4, Vec3, Quat, Mat4 } from '../../../mol-math/linear-algebra';
import { Vec2, Vec4, Vec3 } from '../../../mol-math/linear-algebra';
import { Theme } from '../../../mol-theme/theme';
import { ValueCell } from '../../../mol-util';
import { Color } from '../../../mol-util/color';
@@ -59,37 +59,16 @@ interface Image {
readonly imageTextureDim: ValueCell<Vec2>,
readonly cornerBuffer: ValueCell<Float32Array>,
readonly groupTexture: ValueCell<TextureImage<Uint8Array>>,
readonly valueTexture: ValueCell<TextureImage<Float32Array>>,
readonly trimType: ValueCell<number>,
readonly trimCenter: ValueCell<Vec3>,
readonly trimRotation: ValueCell<Quat>,
readonly trimScale: ValueCell<Vec3>,
readonly trimTransform: ValueCell<Mat4>,
readonly isoLevel: ValueCell<number>,
/** Bounding sphere of the image */
boundingSphere: Sphere3D
}
namespace Image {
export type Trim = {
type: 0 | 1 | 2 | 3 | 4 | 5,
center: Vec3,
rotation: Quat,
scale: Vec3,
transform: Mat4,
}
export function createEmptyTrim(): Trim {
return { type: 0, center: Vec3(), rotation: Quat(), scale: Vec3(), transform: Mat4() };
}
export function create(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, valueTexture: TextureImage<Float32Array>, trim: Trim, isoLevel: number, image?: Image): Image {
export function create(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, image?: Image): Image {
return image ?
update(imageTexture, corners, groupTexture, valueTexture, trim, isoLevel, image) :
fromData(imageTexture, corners, groupTexture, valueTexture, trim, isoLevel);
update(imageTexture, corners, groupTexture, image) :
fromData(imageTexture, corners, groupTexture);
}
function hashCode(image: Image) {
@@ -98,7 +77,7 @@ namespace Image {
]);
}
function fromData(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, valueTexture: TextureImage<Float32Array>, trim: Trim, isoLevel: number): Image {
function fromData(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>): Image {
const boundingSphere = Sphere3D();
let currentHash = -1;
@@ -111,13 +90,6 @@ namespace Image {
imageTextureDim: ValueCell.create(Vec2.create(width, height)),
cornerBuffer: ValueCell.create(corners),
groupTexture: ValueCell.create(groupTexture),
valueTexture: ValueCell.create(valueTexture),
trimType: ValueCell.create(trim.type),
trimCenter: ValueCell.create(trim.center),
trimRotation: ValueCell.create(trim.rotation),
trimScale: ValueCell.create(trim.scale),
trimTransform: ValueCell.create(trim.transform),
isoLevel: ValueCell.create(isoLevel),
get boundingSphere() {
const newHash = hashCode(image);
if (newHash !== currentHash) {
@@ -131,7 +103,7 @@ namespace Image {
return image;
}
function update(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, valueTexture: TextureImage<Float32Array>, trim: Trim, isoLevel: number, image: Image): Image {
function update(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, image: Image): Image {
const width = imageTexture.width;
const height = imageTexture.height;
@@ -139,15 +111,6 @@ namespace Image {
ValueCell.update(image.imageTextureDim, Vec2.set(image.imageTextureDim.ref.value, width, height));
ValueCell.update(image.cornerBuffer, corners);
ValueCell.update(image.groupTexture, groupTexture);
ValueCell.update(image.valueTexture, valueTexture);
ValueCell.updateIfChanged(image.trimType, trim.type);
ValueCell.update(image.trimCenter, Vec3.copy(image.trimCenter.ref.value, trim.center));
ValueCell.update(image.trimRotation, Quat.copy(image.trimRotation.ref.value, trim.rotation));
ValueCell.update(image.trimScale, Vec3.copy(image.trimScale.ref.value, trim.scale));
ValueCell.update(image.trimTransform, Mat4.copy(image.trimTransform.ref.value, trim.transform));
ValueCell.updateIfChanged(image.isoLevel, isoLevel);
return image;
}
@@ -155,9 +118,7 @@ namespace Image {
const imageTexture = createTextureImage(0, 4, Uint8Array);
const corners = image ? image.cornerBuffer.ref.value : new Float32Array(8 * 3);
const groupTexture = createTextureImage(0, 4, Uint8Array);
const valueTexture = createTextureImage(0, 1, Float32Array);
const trim = createEmptyTrim();
return create(imageTexture, corners, groupTexture, valueTexture, trim, -1, image);
return create(imageTexture, corners, groupTexture, image);
}
export const Params = {
@@ -175,16 +136,12 @@ namespace Image {
updateBoundingSphere,
createRenderableState,
updateRenderableState,
createPositionIterator
createPositionIterator: () => LocationIterator(1, 1, 1, () => NullLocation)
};
function createPositionIterator(_image: Image, _transform: TransformData): LocationIterator {
return LocationIterator(1, 1, 1, () => NullLocation);
}
function createValues(image: Image, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): ImageValues {
const { instanceCount, groupCount } = locationIt;
const positionIt = createPositionIterator(image, transform);
const positionIt = Utils.createPositionIterator(image, transform);
const color = createColors(locationIt, positionIt, theme.color);
const marker = props.instanceGranularity
@@ -229,15 +186,6 @@ namespace Image {
uImageTexDim: image.imageTextureDim,
tImageTex: image.imageTexture,
tGroupTex: image.groupTexture,
tValueTex: image.valueTexture,
uTrimType: image.trimType,
uTrimCenter: image.trimCenter,
uTrimRotation: image.trimRotation,
uTrimScale: image.trimScale,
uTrimTransform: image.trimTransform,
uIsoLevel: image.isoLevel,
};
}

View File

@@ -1,25 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3, Mat4 } from '../../../../mol-math/linear-algebra';
import { SegmentedPlane } from '../../../primitive/plane';
import { MeshBuilder } from '../mesh-builder';
const tmpPlaneMat = Mat4.identity();
const tmpVec = Vec3();
function setPlaneMat(m: Mat4, center: Vec3, dirMajor: Vec3, dirMinor: Vec3, scale: Vec3) {
Vec3.add(tmpVec, center, dirMajor);
Mat4.targetTo(m, center, tmpVec, dirMinor);
Mat4.setTranslation(m, center);
Mat4.mul(m, m, Mat4.rotY90);
return Mat4.scale(m, m, scale);
}
export function addPlane(state: MeshBuilder.State, center: Vec3, dirMajor: Vec3, dirMinor: Vec3, scale: Vec3, widthSegments: number, heightSegments: number) {
const plane = SegmentedPlane(widthSegments, heightSegments);
MeshBuilder.addPrimitive(state, setPlaneMat(tmpPlaneMat, center, dirMajor, dirMinor, scale), plane);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -37,53 +37,4 @@ export function Plane(): Primitive {
export function PlaneCage(): Cage {
return planeCage;
}
//
export function SegmentedPlane(widthSegments: number, heightSegments: number): Primitive {
const widthSegments1 = widthSegments + 1;
const heightSegments1 = heightSegments + 1;
const segmentWidth = 1 / widthSegments;
const segmentHeight = 1 / heightSegments;
const vertices = new Float32Array(widthSegments1 * heightSegments1 * 3);
const normals = new Float32Array(widthSegments1 * heightSegments1 * 3);
const indices = new Uint32Array(widthSegments * heightSegments * 6);
let i = 0;
for (let iy = 0; iy < heightSegments1; ++iy) {
const y = iy * segmentHeight - 0.5;
for (let ix = 0; ix < widthSegments1; ++ix) {
const x = ix * segmentWidth - 0.5;
vertices[i] = x;
vertices[i + 1] = -y;
vertices[i + 2] = 0;
normals[i] = 0;
normals[i + 1] = 0;
normals[i + 2] = 1;
i += 3;
}
}
let j = 0;
for (let iy = 0; iy < heightSegments; ++iy) {
for (let ix = 0; ix < widthSegments; ++ix) {
const a = ix + widthSegments1 * iy;
const b = ix + widthSegments1 * (iy + 1);
const c = (ix + 1) + widthSegments1 * (iy + 1);
const d = (ix + 1) + widthSegments1 * iy;
indices[j] = a;
indices[j + 1] = b;
indices[j + 2] = d;
indices[j + 3] = b;
indices[j + 4] = c;
indices[j + 5] = d;
j += 6;
}
}
return { vertices, normals, indices };
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -23,15 +23,6 @@ export const ImageSchema = {
uImageTexDim: UniformSpec('v2'),
tImageTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'),
tGroupTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'),
tValueTex: TextureSpec('image-float32', 'alpha', 'float', 'linear'),
uTrimType: UniformSpec('i'),
uTrimCenter: UniformSpec('v3'),
uTrimRotation: UniformSpec('q'),
uTrimScale: UniformSpec('v3'),
uTrimTransform: UniformSpec('m4'),
uIsoLevel: UniformSpec('f'),
dInterpolation: DefineSpec('string', InterpolationTypeNames),
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -203,8 +203,6 @@ export const ColorSchema = {
uColorTexDim: UniformSpec('v2'),
uColorGridDim: UniformSpec('v3'),
uColorGridTransform: UniformSpec('v4'),
uPaletteDomain: UniformSpec('v2'),
uPaletteDefault: UniformSpec('v3'),
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tPalette: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tColorGrid: TextureSpec('texture', 'rgb', 'ubyte', 'linear'),
@@ -325,7 +323,6 @@ export const BaseSchema = {
uClipObjectPosition: UniformSpec('v3[]', 'material'),
uClipObjectRotation: UniformSpec('v4[]', 'material'),
uClipObjectScale: UniformSpec('v3[]', 'material'),
uClipObjectTransform: UniformSpec('m4[]', 'material'),
aInstance: AttributeSpec('float32', 1, 1),
/**

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -270,6 +270,7 @@ namespace Renderer {
return;
}
// TODO: check what happens if sphere surrounds frustum fully
if (!Frustum3D.intersectsSphere3D(frustum, r.values.boundingSphere.ref.value)) {
return;
}
@@ -282,13 +283,8 @@ namespace Renderer {
if (d - radius > maxDistance) return;
}
if (isOccluded !== null && isOccluded(r.values.boundingSphere.ref.value)) {
return;
}
const hasInstanceGrid = r.values.instanceGrid.ref.value.cellSize > 0;
const hasMultipleInstances = r.values.uInstanceCount.ref.value > 1;
if (hasInstanceGrid && (hasMultipleInstances || r.values.lodLevels)) {
const hasInstanceGrid = r.values.instanceGrid.ref.value.cellSize > 1;
if (hasInstanceGrid || (hasInstanceGrid && r.values.lodLevels)) {
r.cull(cameraPlane, frustum, isOccluded, ctx.stats);
} else {
r.uncull();

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -252,8 +252,7 @@ namespace Scene {
const xray = (p.values.dXrayShaded?.ref.value === 'on' || p.values.dXrayShaded?.ref.value === 'inverted') ? 0.5 : 1;
const fuzzy = p.values.dPointStyle?.ref.value === 'fuzzy' ? 0.5 : 1;
const text = p.values.dGeometryType.ref.value === 'text' ? 0.5 : 1;
const image = p.values.dGeometryType.ref.value === 'image' ? 0.5 : 1;
opacityAverage += (1 - p.values.transparencyAverage.ref.value) * alpha * xray * fuzzy * text * image;
opacityAverage += (1 - p.values.transparencyAverage.ref.value) * alpha * xray * fuzzy * text;
count += 1;
}
return count > 0 ? opacityAverage / count : 0;
@@ -273,8 +272,7 @@ namespace Scene {
if (p.values.dXrayShaded?.ref.value === 'on' ||
p.values.dXrayShaded?.ref.value === 'inverted' ||
p.values.dPointStyle?.ref.value === 'fuzzy' ||
p.values.dGeometryType.ref.value === 'text' ||
p.values.dGeometryType.ref.value === 'image'
p.values.dGeometryType.ref.value === 'text'
) transparenyValues.push(0.5);
if (p.values.transparencyMin.ref.value > 0) transparenyValues.push(p.values.transparencyMin.ref.value);
transparencyMin = Math.min(transparencyMin, ...transparenyValues);

View File

@@ -29,9 +29,9 @@ export const assign_color_varying = `
vColor.rgb = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
#endif
#elif defined(dColorType_vertex)
vColor.rgb = readFromTexture(tColor, vertexId, uColorTexDim).rgb;
vColor.rgb = readFromTexture(tColor, VertexID, uColorTexDim).rgb;
#elif defined(dColorType_vertexInstance)
vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + vertexId, uColorTexDim).rgb;
vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
#elif defined(dColorType_volume)
vec3 cgridPos = (uColorGridTransform.w * (position - uColorGridTransform.xyz)) / uColorGridDim;
vColor.rgb = texture3dFrom2dLinear(tColorGrid, cgridPos, uColorGridDim, uColorTexDim).rgb;
@@ -41,7 +41,7 @@ export const assign_color_varying = `
#endif
#ifdef dUsePalette
vPaletteV = ((vColor.r * 256.0 * 256.0 * 255.0 + vColor.g * 256.0 * 255.0 + vColor.b * 255.0) - 1.0) / PALETTE_SCALE;
vPaletteV = ((vColor.r * 256.0 * 256.0 * 255.0 + vColor.g * 256.0 * 255.0 + vColor.b * 255.0) - 1.0) / 16777215.0;
#endif
#ifdef dOverpaint
@@ -50,7 +50,7 @@ export const assign_color_varying = `
#elif defined(dOverpaintType_groupInstance)
vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
#elif defined(dOverpaintType_vertexInstance)
vOverpaint = readFromTexture(tOverpaint, int(aInstance) * uVertexCount + vertexId, uOverpaintTexDim);
vOverpaint = readFromTexture(tOverpaint, int(aInstance) * uVertexCount + VertexID, uOverpaintTexDim);
#elif defined(dOverpaintType_volumeInstance)
vec3 ogridPos = (uOverpaintGridTransform.w * (vModelPosition - uOverpaintGridTransform.xyz)) / uOverpaintGridDim;
vOverpaint = texture3dFrom2dLinear(tOverpaintGrid, ogridPos, uOverpaintGridDim, uOverpaintTexDim);
@@ -71,7 +71,7 @@ export const assign_color_varying = `
#elif defined(dEmissiveType_groupInstance)
vEmissive = readFromTexture(tEmissive, aInstance * float(uGroupCount) + group, uEmissiveTexDim).a;
#elif defined(dEmissiveType_vertexInstance)
vEmissive = readFromTexture(tEmissive, int(aInstance) * uVertexCount + vertexId, uEmissiveTexDim).a;
vEmissive = readFromTexture(tEmissive, int(aInstance) * uVertexCount + VertexID, uEmissiveTexDim).a;
#elif defined(dEmissiveType_volumeInstance)
vec3 egridPos = (uEmissiveGridTransform.w * (vModelPosition - uEmissiveGridTransform.xyz)) / uEmissiveGridDim;
vEmissive = texture3dFrom2dLinear(tEmissiveGrid, egridPos, uEmissiveGridDim, uEmissiveTexDim).a;
@@ -85,7 +85,7 @@ export const assign_color_varying = `
#elif defined(dSubstanceType_groupInstance)
vSubstance = readFromTexture(tSubstance, aInstance * float(uGroupCount) + group, uSubstanceTexDim);
#elif defined(dSubstanceType_vertexInstance)
vSubstance = readFromTexture(tSubstance, int(aInstance) * uVertexCount + vertexId, uSubstanceTexDim);
vSubstance = readFromTexture(tSubstance, int(aInstance) * uVertexCount + VertexID, uSubstanceTexDim);
#elif defined(dSubstanceType_volumeInstance)
vec3 sgridPos = (uSubstanceGridTransform.w * (vModelPosition - uSubstanceGridTransform.xyz)) / uSubstanceGridDim;
vSubstance = texture3dFrom2dLinear(tSubstanceGrid, sgridPos, uSubstanceGridDim, uSubstanceTexDim);
@@ -102,7 +102,7 @@ export const assign_color_varying = `
#elif defined(dEmissiveType_groupInstance)
vEmissive = readFromTexture(tEmissive, aInstance * float(uGroupCount) + group, uEmissiveTexDim).a;
#elif defined(dEmissiveType_vertexInstance)
vEmissive = readFromTexture(tEmissive, int(aInstance) * uVertexCount + vertexId, uEmissiveTexDim).a;
vEmissive = readFromTexture(tEmissive, int(aInstance) * uVertexCount + VertexID, uEmissiveTexDim).a;
#elif defined(dEmissiveType_volumeInstance)
vec3 egridPos = (uEmissiveGridTransform.w * (vModelPosition - uEmissiveGridTransform.xyz)) / uEmissiveGridDim;
vEmissive = texture3dFrom2dLinear(tEmissiveGrid, egridPos, uEmissiveGridDim, uEmissiveTexDim).a;
@@ -131,7 +131,7 @@ export const assign_color_varying = `
#elif defined(dTransparencyType_groupInstance)
vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
#elif defined(dTransparencyType_vertexInstance)
vTransparency = readFromTexture(tTransparency, int(aInstance) * uVertexCount + vertexId, uTransparencyTexDim).a;
vTransparency = readFromTexture(tTransparency, int(aInstance) * uVertexCount + VertexID, uTransparencyTexDim).a;
#elif defined(dTransparencyType_volumeInstance)
vec3 tgridPos = (uTransparencyGridTransform.w * (vModelPosition - uTransparencyGridTransform.xyz)) / uTransparencyGridDim;
vTransparency = texture3dFrom2dLinear(tTransparencyGrid, tgridPos, uTransparencyGridDim, uTransparencyTexDim).a;

View File

@@ -1,6 +1,6 @@
export const assign_group = `
#ifdef dGeometryType_textureMesh
float group = unpackRGBToInt(readFromTexture(tGroup, vertexId, uGeoTexDim).rgb);
float group = unpackRGBToInt(readFromTexture(tGroup, VertexID, uGeoTexDim).rgb);
#else
float group = aGroup;
#endif

View File

@@ -2,7 +2,7 @@ export const assign_position = `
mat4 model = uModel * aTransform;
mat4 modelView = uView * model;
#ifdef dGeometryType_textureMesh
vec3 position = readFromTexture(tPosition, vertexId, uGeoTexDim).xyz;
vec3 position = readFromTexture(tPosition, VertexID, uGeoTexDim).xyz;
#else
vec3 position = aPosition;
#endif

View File

@@ -1,9 +1,8 @@
export const clip_instance = `
#if defined(dClipVariant_instance) && dClipObjectCount != 0
vec3 mCenter = (uModel * aTransform * vec4(uInvariantBoundingSphere.xyz, 1.0)).xyz;
if (clipTest(mCenter)) {
vec4 mCenter = uModel * aTransform * vec4(uInvariantBoundingSphere.xyz, 1.0);
if (clipTest(vec4(mCenter.xyz, uInvariantBoundingSphere.w)))
// move out of [ -w, +w ] to 'discard' in vert shader
gl_Position.z = 2.0 * gl_Position.w;
}
#endif
`;

View File

@@ -1,6 +1,6 @@
export const clip_pixel = `
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
if (clipTest(vModelPosition))
if (clipTest(vec4(vModelPosition, 0.0)))
discard;
#endif
`;

View File

@@ -1,110 +1,112 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Ludovic Autin <autin@scripps.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
export const common_clip = `
vec3 quaternionTransform(const in vec4 q, const in vec3 v) {
vec3 t = 2.0 * cross(q.xyz, v);
return v + q.w * t + cross(q.xyz, t);
}
vec4 computePlane(const in vec3 normal, const in vec3 inPoint) {
return vec4(normalize(normal), -dot(normal, inPoint));
}
float planeSD(const in vec4 plane, const in vec3 center) {
return -dot(plane.xyz, center - plane.xyz * -plane.w);
}
float sphereSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
return (
length(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position) / size) - 1.0
) * min(min(size.x, size.y), size.z);
}
float cubeSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
vec3 d = abs(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position)) - size;
return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
}
float cylinderSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position);
vec2 d = abs(vec2(length(t.xz), t.y)) - size.xy;
return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}
float infiniteConeSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position);
float q = length(t.xy);
return dot(size.xy, vec2(q, t.z));
}
float getSignedDistance(const in vec3 center, const in int type, const in vec3 position, const in vec4 rotation, const in vec3 scale, const in mat4 transform) {
vec3 c = (transform * vec4(center, 1.0)).xyz;
if (type == 1) {
vec3 normal = quaternionTransform(rotation, vec3(0.0, 1.0, 0.0));
vec4 plane = computePlane(normal, position);
return planeSD(plane, c);
} else if (type == 2) {
return sphereSD(position, rotation, scale * 0.5, c);
} else if (type == 3) {
return cubeSD(position, rotation, scale * 0.5, c);
} else if (type == 4) {
return cylinderSD(position, rotation, scale * 0.5, c);
} else if (type == 5) {
return infiniteConeSD(position, rotation, scale * 0.5, c);
} else {
return 0.1;
#if dClipObjectCount != 0
vec3 quaternionTransform(const in vec4 q, const in vec3 v) {
vec3 t = 2.0 * cross(q.xyz, v);
return v + q.w * t + cross(q.xyz, t);
}
}
#if __VERSION__ == 100
// 8-bit
int bitwiseAnd(in int a, in int b) {
int d = 128;
int result = 0;
for (int i = 0; i < 8; ++i) {
if (d <= 0) break;
if (a >= d && b >= d) result += d;
if (a >= d) a -= d;
if (b >= d) b -= d;
d /= 2;
vec4 computePlane(const in vec3 normal, const in vec3 inPoint) {
return vec4(normalize(normal), -dot(normal, inPoint));
}
float planeSD(const in vec4 plane, const in vec3 center) {
return -dot(plane.xyz, center - plane.xyz * -plane.w);
}
float sphereSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
return (
length(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position) / size) - 1.0
) * min(min(size.x, size.y), size.z);
}
float cubeSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
vec3 d = abs(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position)) - size;
return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
}
float cylinderSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position);
vec2 d = abs(vec2(length(t.xz), t.y)) - size.xy;
return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}
float infiniteConeSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position);
float q = length(t.xy);
return dot(size.xy, vec2(q, t.z));
}
float getSignedDistance(const in vec3 center, const in int type, const in vec3 position, const in vec4 rotation, const in vec3 scale) {
if (type == 1) {
vec3 normal = quaternionTransform(rotation, vec3(0.0, 1.0, 0.0));
vec4 plane = computePlane(normal, position);
return planeSD(plane, center);
} else if (type == 2) {
return sphereSD(position, rotation, scale * 0.5, center);
} else if (type == 3) {
return cubeSD(position, rotation, scale * 0.5, center);
} else if (type == 4) {
return cylinderSD(position, rotation, scale * 0.5, center);
} else if (type == 5) {
return infiniteConeSD(position, rotation, scale * 0.5, center);
} else {
return 0.1;
}
return result;
}
bool hasBit(const in int mask, const in int bit) {
return bitwiseAnd(mask, bit) == 0;
}
#else
bool hasBit(const in int mask, const in int bit) {
return (mask & bit) == 0;
}
#endif
#if __VERSION__ == 100
// 8-bit
int bitwiseAnd(in int a, in int b) {
int d = 128;
int result = 0;
for (int i = 0; i < 8; ++i) {
if (d <= 0) break;
if (a >= d && b >= d) result += d;
if (a >= d) a -= d;
if (b >= d) b -= d;
d /= 2;
}
return result;
}
bool clipTest(const in vec3 center) {
// flag is a bit-flag for clip-objects to ignore (note, object ids start at 1 not 0)
#if defined(dClipping)
int flag = int(floor(vClipping * 255.0 + 0.5));
bool hasBit(const in int mask, const in int bit) {
return bitwiseAnd(mask, bit) == 0;
}
#else
int flag = 0;
bool hasBit(const in int mask, const in int bit) {
return (mask & bit) == 0;
}
#endif
#pragma unroll_loop_start
for (int i = 0; i < dClipObjectCount; ++i) {
if (flag == 0 || hasBit(flag, UNROLLED_LOOP_INDEX + 1)) {
bool test = getSignedDistance(center, uClipObjectType[i], uClipObjectPosition[i], uClipObjectRotation[i], uClipObjectScale[i], uClipObjectTransform[i]) <= 0.0;
if ((!uClipObjectInvert[i] && test) || (uClipObjectInvert[i] && !test)) {
return true;
bool clipTest(const in vec4 sphere) {
// flag is a bit-flag for clip-objects to ignore (note, object ids start at 1 not 0)
#if defined(dClipping)
int flag = int(floor(vClipping * 255.0 + 0.5));
#else
int flag = 0;
#endif
#pragma unroll_loop_start
for (int i = 0; i < dClipObjectCount; ++i) {
if (flag == 0 || hasBit(flag, UNROLLED_LOOP_INDEX + 1)) {
// TODO take sphere radius into account?
bool test = getSignedDistance(sphere.xyz, uClipObjectType[i], uClipObjectPosition[i], uClipObjectRotation[i], uClipObjectScale[i]) <= 0.0;
if ((!uClipObjectInvert[i] && test) || (uClipObjectInvert[i] && !test)) {
return true;
}
}
}
#pragma unroll_loop_end
return false;
}
#pragma unroll_loop_end
return false;
}
#endif
`;

View File

@@ -15,7 +15,6 @@ uniform vec4 uLod;
uniform vec3 uClipObjectPosition[dClipObjectCount];
uniform vec4 uClipObjectRotation[dClipObjectCount];
uniform vec3 uClipObjectScale[dClipObjectCount];
uniform mat4 uClipObjectTransform[dClipObjectCount];
#if defined(dClipping)
#if __VERSION__ == 100 || defined(dClippingType_instance) || !defined(dVaryingGroup)

View File

@@ -19,7 +19,6 @@ uniform int uPickType;
uniform vec3 uClipObjectPosition[dClipObjectCount];
uniform vec4 uClipObjectRotation[dClipObjectCount];
uniform vec3 uClipObjectScale[dClipObjectCount];
uniform mat4 uClipObjectTransform[dClipObjectCount];
#if defined(dClipping)
uniform vec2 uClippingTexDim;

View File

@@ -42,8 +42,6 @@ export const common = `
#define TWO_PI 6.2831853
#define HALF_PI 1.570796325
#define PALETTE_SCALE 16777214.0 // (1 << 24) - 2
#define saturate(a) clamp(a, 0.0, 1.0)
#if __VERSION__ == 100

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -84,7 +84,7 @@ bool CylinderImpostor(
viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
fragmentDepth = calcDepth(viewPosition);
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
if (clipTest(modelPosition)) {
if (clipTest(vec4(modelPosition, 0.0))) {
objectClipped = true;
fragmentDepth = -1.0;
#ifdef dSolidInterior
@@ -108,7 +108,7 @@ bool CylinderImpostor(
viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
fragmentDepth = calcDepth(viewPosition);
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
if (clipTest(modelPosition)) {
if (clipTest(vec4(modelPosition, 0.0))) {
objectClipped = true;
fragmentDepth = -1.0;
#ifdef dSolidInterior
@@ -138,7 +138,7 @@ bool CylinderImpostor(
viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
fragmentDepth = calcDepth(viewPosition);
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
if (clipTest(modelPosition)) {
if (clipTest(vec4(modelPosition, 0.0))) {
objectClipped = true;
fragmentDepth = -1.0;
#ifdef dSolidInterior

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -38,8 +38,6 @@ uniform float uIsOrtho;
uniform vec3 uCameraDir;
void main() {
int vertexId = VertexID;
#include assign_group
#include assign_color_varying
#include assign_marker_varying

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
@@ -18,7 +18,6 @@ precision highp int;
uniform vec3 uClipObjectPosition[dClipObjectCount];
uniform vec4 uClipObjectRotation[dClipObjectCount];
uniform vec3 uClipObjectScale[dClipObjectCount];
uniform mat4 uClipObjectTransform[dClipObjectCount];
#endif
#include common_clip
@@ -123,7 +122,6 @@ uniform mat4 uCartnToUnit;
#endif
#ifdef dUsePalette
uniform vec2 uPaletteDomain;
uniform sampler2D tPalette;
#endif
@@ -242,7 +240,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
if (clipTest(modelPosition)) {
if (clipTest(vec4(vModelPosition, 0.0))) {
prevValue = value;
pos += step;
continue;
@@ -273,8 +271,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
#endif
#if defined(dColorType_direct) && defined(dUsePalette)
float paletteValue = (value - uPaletteDomain[0]) / (uPaletteDomain[1] - uPaletteDomain[0]);
material.rgb = texture2D(tPalette, vec2(clamp(paletteValue, 0.0, 1.0), 0.0)).rgb;
material.rgb = texture2D(tPalette, vec2(value, 0.0)).rgb;
#elif defined(dColorType_uniform)
material.rgb = uColor;
#elif defined(dColorType_instance)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -12,46 +12,12 @@ precision highp int;
#include common_frag_params
#include common_clip
uniform float uEmissive;
// Density value to estimate object thickness
uniform float uDensity;
#if defined(dRenderVariant_color) || defined(dRenderVariant_tracing)
#ifdef dOverpaint
#if defined(dOverpaintType_instance) || defined(dOverpaintType_groupInstance)
varying vec4 vOverpaint;
uniform vec2 uOverpaintTexDim;
uniform sampler2D tOverpaint;
#endif
uniform float uOverpaintStrength;
#endif
#endif
#if defined(dRenderVariant_color) || defined(dRenderVariant_tracing) || defined(dRenderVariant_emissive)
#ifdef dEmissive
#if defined(dEmissiveType_instance) || defined(dEmissiveType_groupInstance)
varying float vEmissive;
uniform vec2 uEmissiveTexDim;
uniform sampler2D tEmissive;
#endif
uniform float uEmissiveStrength;
#endif
#endif
#ifdef dTransparency
#if defined(dTransparencyType_instance) || defined(dTransparencyType_groupInstance)
varying float vTransparency;
uniform vec2 uTransparencyTexDim;
uniform sampler2D tTransparency;
#endif
uniform float uTransparencyStrength;
#endif
uniform vec2 uImageTexDim;
uniform sampler2D tImageTex;
uniform sampler2D tGroupTex;
uniform sampler2D tValueTex;
uniform vec2 uMarkerTexDim;
uniform sampler2D tMarker;
@@ -59,19 +25,6 @@ uniform sampler2D tMarker;
varying vec2 vUv;
varying float vInstance;
#ifdef dUsePalette
uniform sampler2D tPalette;
uniform vec3 uPaletteDefault;
#endif
uniform int uTrimType;
uniform vec3 uTrimCenter;
uniform vec4 uTrimRotation;
uniform vec3 uTrimScale;
uniform mat4 uTrimTransform;
uniform float uIsoLevel;
#if defined(dInterpolation_catmulrom) || defined(dInterpolation_mitchell) || defined(dInterpolation_bspline)
#define dInterpolation_cubic
#endif
@@ -138,102 +91,36 @@ uniform float uIsoLevel;
#endif
void main() {
if (uTrimType != 0 && getSignedDistance(vModelPosition, uTrimType, uTrimCenter, uTrimRotation, uTrimScale, uTrimTransform) > 0.0) discard;
#include fade_lod
#include clip_pixel
#if defined(dInterpolation_cubic)
#ifdef dUsePalette
vec4 material = texture2D(tImageTex, vUv);
if (material.rgb != vec3(1.0)) {
material = biCubic(tImageTex, vUv);
}
#else
vec4 material = biCubic(tImageTex, vUv);
#endif
vec4 imageData = biCubic(tImageTex, vUv);
#else
vec4 material = texture2D(tImageTex, vUv);
vec4 imageData = texture2D(tImageTex, vUv);
#endif
imageData.a = clamp(imageData.a, 0.0, 1.0);
if (imageData.a > 0.9) imageData.a = 1.0;
if (uIsoLevel >= 0.0) {
if (texture2D(tValueTex, vUv).r < uIsoLevel) discard;
material.a = uAlpha;
} else {
if (material.a == 0.0) discard;
material.a *= uAlpha;
}
imageData.a *= uAlpha;
if (imageData.a < 0.05)
discard;
float fragmentDepth = gl_FragCoord.z;
vec3 packedGroup = texture2D(tGroupTex, vUv).rgb;
float group = packedGroup == vec3(0.0) ? -1.0 : unpackRGBToInt(packedGroup);
// apply per-group transparency
#if defined(dTransparency) && (defined(dRenderVariant_pick) || defined(dRenderVariant_color) || defined(dRenderVariant_emissive) || defined(dRenderVariant_tracing))
float transparency = 0.0;
#if defined(dTransparencyType_instance)
transparency = readFromTexture(tTransparency, vInstance, uTransparencyTexDim).a;
#elif defined(dTransparencyType_groupInstance)
transparency = readFromTexture(tTransparency, vInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
#endif
transparency *= uTransparencyStrength;
float ta = 1.0 - transparency;
if (transparency < 0.09) ta = 1.0; // hard cutoff looks better
#if defined(dRenderVariant_pick)
if (ta * uAlpha < uPickingAlphaThreshold)
discard; // ignore so the element below can be picked
#elif defined(dRenderVariant_emissive)
if (ta < 1.0)
discard; // emissive not supported with transparency
#elif defined(dRenderVariant_color) || defined(dRenderVariant_tracing)
material.a *= ta;
#endif
#endif
if ((uRenderMask == MaskOpaque && material.a < 1.0) ||
(uRenderMask == MaskTransparent && material.a == 1.0)
if ((uRenderMask == MaskOpaque && imageData.a < 1.0) ||
(uRenderMask == MaskTransparent && imageData.a == 1.0)
) {
discard;
}
#if defined(dNeedsMarker)
float marker = uMarker;
if (group == -1.0) {
marker = 0.0;
} else if (uMarker == -1.0) {
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}
#endif
#if defined(dRenderVariant_color) || defined(dRenderVariant_tracing) || defined(dRenderVariant_emissive)
float emissive = uEmissive;
if (group == -1.0) {
emissive = 0.0;
} else {
#ifdef dEmissive
#if defined(dEmissiveType_instance)
emissive += readFromTexture(tEmissive, vInstance, uEmissiveTexDim).a * uEmissiveStrength;
#elif defined(dEmissiveType_groupInstance)
emissive += readFromTexture(tEmissive, vInstance * float(uGroupCount) + group, uEmissiveTexDim).a * uEmissiveStrength;
#endif
#endif
}
#endif
#if defined(dRenderVariant_pick)
if (group == -1.0) discard;
#include check_picking_alpha
if (imageData.a < 0.3)
discard;
#ifdef requiredDrawBuffers
gl_FragColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
gl_FragData[1] = vec4(packIntToRGB(vInstance), 1.0);
gl_FragData[2] = vec4(packIntToRGB(group), 1.0);
gl_FragData[2] = vec4(texture2D(tGroupTex, vUv).rgb, 1.0);
gl_FragData[3] = packDepthToRGBA(fragmentDepth);
#else
gl_FragColor = vColor;
@@ -242,61 +129,50 @@ void main() {
} else if (uPickType == 2) {
gl_FragColor = vec4(packIntToRGB(vInstance), 1.0);
} else {
gl_FragColor = vec4(packIntToRGB(group), 1.0);
gl_FragColor = vec4(texture2D(tGroupTex, vUv).rgb, 1.0);
}
#endif
#elif defined(dRenderVariant_depth)
if (imageData.a < 0.05)
discard;
if (uRenderMask == MaskOpaque) {
gl_FragColor = packDepthToRGBA(fragmentDepth);
} else if (uRenderMask == MaskTransparent) {
gl_FragColor = packDepthWithAlphaToRGBA(fragmentDepth, material.a);
gl_FragColor = packDepthWithAlphaToRGBA(fragmentDepth, imageData.a);
}
#elif defined(dRenderVariant_marking)
float marker = uMarker;
if (uMarker == -1.0) {
float group = unpackRGBToInt(texture2D(tGroupTex, vUv).rgb);
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}
if (uMarkingType == 1) {
if (marker > 0.0)
if (marker > 0.0 || imageData.a < 0.05)
discard;
gl_FragColor = packDepthToRGBA(fragmentDepth);
} else {
if (marker == 0.0)
if (marker == 0.0 || imageData.a < 0.05)
discard;
float depthTest = 1.0;
if (uMarkingDepthTest) {
depthTest = (fragmentDepth >= getDepthPacked(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0;
}
bool isHighlight = intMod(marker, 2.0) > 0.1;
float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
if (fogFactor == 1.0)
discard;
gl_FragColor = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0 - fogFactor);
gl_FragColor = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0);
}
#elif defined(dRenderVariant_emissive)
gl_FragColor = vec4(emissive);
gl_FragColor = vec4(0.0);
#elif defined(dRenderVariant_color) || defined(dRenderVariant_tracing)
#ifdef dUsePalette
if (material.rgb == vec3(1.0)) {
material.rgb = uPaletteDefault;
} else {
float v = ((material.r * 256.0 * 256.0 * 255.0 + material.g * 256.0 * 255.0 + material.b * 255.0) - 1.0) / PALETTE_SCALE;
material.rgb = texture2D(tPalette, vec2(v, 0.0)).rgb;
}
#endif
gl_FragColor = imageData;
// mix material with overpaint
#if defined(dOverpaint)
vec4 overpaint = vec4(0.0);
if (group != -1.0) {
#if defined(dOverpaintType_instance)
overpaint = readFromTexture(tOverpaint, vInstance, uOverpaintTexDim);
#elif defined(dOverpaintType_groupInstance)
overpaint = readFromTexture(tOverpaint, vInstance * float(uGroupCount) + group, uOverpaintTexDim);
#endif
overpaint *= uOverpaintStrength;
}
material.rgb = mix(material.rgb, overpaint.rgb, overpaint.a);
#endif
float marker = uMarker;
if (uMarker == -1.0) {
float group = unpackRGBToInt(texture2D(tGroupTex, vUv).rgb);
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}
gl_FragColor = material;
#include apply_marker_color
#if defined(dRenderVariant_color)
@@ -304,8 +180,8 @@ void main() {
#include wboit_write
#include dpoit_write
#elif defined(dRenderVariant_tracing)
gl_FragData[1] = vec4(normalize(vViewPosition), emissive);
gl_FragData[2] = vec4(material.rgb, uDensity);
gl_FragData[1] = vec4(normalize(vViewPosition), 0.0);
gl_FragData[2] = vec4(imageData.rgb, uDensity);
#endif
#endif
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -19,8 +19,6 @@ varying vec2 vUv;
varying float vInstance;
void main() {
int vertexId = VertexID;
#include assign_position
vUv = aUv;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
@@ -40,7 +40,6 @@ void trimSegment(const in vec4 start, inout vec4 end) {
void main(){
float aspect = uViewport.z / uViewport.w;
int vertexId = VertexID;
#include assign_group
#include assign_color_varying

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -32,8 +32,6 @@ attribute float aInstance;
varying vec3 vNormal;
void main(){
int vertexId = VertexID;
#include assign_group
#include assign_marker_varying
#include assign_clipping_varying
@@ -42,7 +40,7 @@ void main(){
#include clip_instance
#ifdef dGeometryType_textureMesh
vec3 normal = readFromTexture(tNormal, vertexId, uGeoTexDim).xyz;
vec3 normal = readFromTexture(tNormal, VertexID, uGeoTexDim).xyz;
#else
vec3 normal = aNormal;
#endif

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -24,8 +24,6 @@ attribute float aInstance;
attribute float aGroup;
void main(){
int vertexId = VertexID;
#include assign_group
#include assign_color_varying
#include assign_marker_varying

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -52,7 +52,7 @@ bool SphereImpostor(out vec3 modelPos, out vec3 cameraPos, out vec3 cameraNormal
bool objectClipped = false;
#if !defined(dClipPrimitive) && defined(dClipVariant_pixel) && dClipObjectCount != 0
if (clipTest(modelPos)) {
if (clipTest(vec4(modelPos, 0.0))) {
objectClipped = true;
fragmentDepth = -1.0;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -70,9 +70,7 @@ void main(void){
mapping = vec2(1.0, -1.0);
}
int vertexId = VertexID / 6;
vec4 positionGroup = readFromTexture(tPositionGroup, vertexId, uTexDim);
vec4 positionGroup = readFromTexture(tPositionGroup, VertexID / 6, uTexDim);
vec3 position = positionGroup.rgb;
float group = positionGroup.a;
@@ -130,7 +128,7 @@ void main(void){
}
#if defined(dClipPrimitive) && !defined(dClipVariant_instance) && dClipObjectCount != 0
if (clipTest(vModelPosition)) {
if (clipTest(vec4(vModelPosition.xyz, 0.0))) {
// move out of [ -w, +w ] to 'discard' in vert shader
gl_Position.z = 2.0 * gl_Position.w;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -38,8 +38,6 @@ varying vec2 vTexCoord;
#include matrix_scale
void main(void){
int vertexId = VertexID;
#include assign_group
#include assign_color_varying
#include assign_marker_varying

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -21,10 +21,6 @@ import { fillSerial } from '../../mol-util/array';
import { deepClone } from '../../mol-util/object';
import { cloneUniformValues, UniformsList } from './uniform';
// Handle Firefox's preference [webgl.max-vert-ids-per-draw] which defaults to 30_000_000
// since FF119, see https://bugzilla.mozilla.org/show_bug.cgi?id=1849433
const MaxDrawCount = 30_000_000;
const getNextRenderItemId = idFactory();
export type DrawMode = 'points' | 'lines' | 'line-strip' | 'line-loop' | 'triangles' | 'triangle-strip' | 'triangle-fan'
@@ -315,16 +311,10 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
}
}
} else {
let offset = 0;
while (true) {
const count = Math.min(drawCount - offset, MaxDrawCount);
if (elementsBuffer) {
instancedArrays.drawElementsInstanced(glDrawMode, count, elementsBuffer._dataType, offset * elementsBuffer._bpe, instanceCount);
} else {
instancedArrays.drawArraysInstanced(glDrawMode, offset, count, instanceCount);
}
offset += count;
if (offset >= drawCount) break;
if (elementsBuffer) {
instancedArrays.drawElementsInstanced(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount);
} else {
instancedArrays.drawArraysInstanced(glDrawMode, 0, drawCount, instanceCount);
}
if (isTimingMode) {
stats.calls.drawInstanced += 1;

View File

@@ -1,10 +1,10 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat3, Mat4, Quat, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { Mat3, Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { ValueCell } from '../../mol-util';
import { GLRenderingContext } from './compat';
import { RenderableSchema } from '../../mol-gl/renderable/schema';
@@ -18,7 +18,6 @@ export type UniformKindValue = {
'v2': Vec2; 'v2[]': number[]
'v3': Vec3; 'v3[]': number[]
'v4': Vec4; 'v4[]': number[]
'q': Quat; 'q[]': number[]
'iv2': Vec2; 'iv2[]': number[]
'iv3': Vec3; 'iv3[]': number[]
'iv4': Vec4; 'iv4[]': number[]
@@ -40,7 +39,6 @@ export function getUniformType(gl: GLRenderingContext, kind: UniformKind) {
case 'v2': case 'v2[]': return gl.FLOAT_VEC2;
case 'v3': case 'v3[]': return gl.FLOAT_VEC3;
case 'v4': case 'v4[]': return gl.FLOAT_VEC4;
case 'q': case 'q[]': return gl.FLOAT_VEC4;
case 'iv2': case 'iv2[]': return gl.INT_VEC2;
case 'iv3': case 'iv3[]': return gl.INT_VEC3;
case 'iv4': case 'iv4[]': return gl.INT_VEC4;
@@ -79,7 +77,6 @@ function getUniformSetter(kind: UniformKind): UniformSetter {
case 'v2': case 'v2[]': return uniform2fv;
case 'v3': case 'v3[]': return uniform3fv;
case 'v4': case 'v4[]': return uniform4fv;
case 'q': case 'q[]': return uniform4fv;
case 'iv2': case 'iv2[]': return uniform2iv;
case 'iv3': case 'iv3[]': return uniform3iv;
case 'iv4': case 'iv4[]': return uniform4iv;
@@ -110,7 +107,6 @@ export function getUniformGlslType(kind: UniformKind): string {
case 'v2': return 'vec2';
case 'v3': return 'vec3';
case 'v4': return 'vec4';
case 'q': return 'vec4';
case 'm3': return 'mat3';
case 'm4': return 'mat4';
}

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.401, IHM 1.27, MA 1.4.7.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.399, IHM 1.27, MA 1.4.6.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.401, IHM 1.27, MA 1.4.7.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.399, IHM 1.27, MA 1.4.6.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.401, IHM 1.27, MA 1.4.7.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.399, IHM 1.27, MA 1.4.6.
*
* @author molstar/ciftools package
*/
@@ -705,7 +705,7 @@ export const mmCIF_Schema = {
/**
* An abbreviation that identifies the database.
*/
database_id: Aliased<'alphafolddb' | 'cas' | 'csd' | 'emdb' | 'icsd' | 'modelarchive' | 'mdf' | 'modbase' | 'ndb' | 'nbs' | 'pdb' | 'pdb-dev' | 'pdf' | 'rcsb' | 'swiss-model_repository' | 'ebi' | 'pdbe' | 'bmrb' | 'wwpdb' | 'pdb_acc'>(lstr),
database_id: Aliased<'alphafolddb' | 'cas' | 'csd' | 'emdb' | 'icsd' | 'modelarchive' | 'mdf' | 'modbase' | 'ndb' | 'nbs' | 'pdb' | 'pdf' | 'rcsb' | 'swiss-model_repository' | 'ebi' | 'pdbe' | 'bmrb' | 'wwpdb' | 'pdb_acc'>(lstr),
/**
* The code assigned by the database identified in
* _database_2.database_id.
@@ -884,7 +884,7 @@ export const mmCIF_Schema = {
* (DT) for Thymidine-5'-monophosphate
* (MSE) for Selenomethionine
* (SEP) for Phosphoserine
* (TPO) for Phosphothreonine
* (PTO) for Phosphothreonine
* (PTR) for Phosphotyrosine
* (PCA) for Pyroglutamic acid
* (UNK) for Unknown amino acid
@@ -2098,7 +2098,7 @@ export const mmCIF_Schema = {
/**
* The name of the database containing the related entry.
*/
db_name: Aliased<'BIOISIS' | 'BMCD' | 'BMRB' | 'EMDB' | 'NDB' | 'PDB' | 'PDB-Dev' | 'SASBDB' | 'TargetDB' | 'TargetTrack'>(str),
db_name: str,
/**
* A description of the related entry.
*/
@@ -4939,19 +4939,25 @@ export const mmCIF_Schema = {
*/
ma_model_list: {
/**
* A unique identifier for the structural model being deposited.
* A unique identifier for the model / model group combination.
*/
ordinal_id: int,
/**
* A unique identifier for the structural model being deposited.
* This data item was practically a duplicate of _ma_model_list.ordinal_id
* and has been deprecated with dictionary version 1.4.7.
*/
model_id: int,
/**
* An identifier to group structural models into collections or sets.
* This data item has been deprecated with dictionary version 1.4.7.
* See ma_model_group category.
* A cluster of models and its representative can either be grouped together
* or can be separate groups in the ma_model_list table. The choice between
* the two options should be decided based on how the modeling was carried out
* and how the representative was chosen. If the representative is a member of
* the ensemble (i.e., best scoring model), then it is recommended that the
* representative and the ensemble belong to the same model group. If the
* representative is calculated from the ensemble (i.e., centroid), then it is
* recommended that the representative be separated into a different group.
* If the models do not need to be grouped into collections, then the
* _ma_model_list.model_group_id is the same as _ma_model_list.model_id.
*/
model_group_id: int,
/**
@@ -4960,8 +4966,6 @@ export const mmCIF_Schema = {
model_name: str,
/**
* A decsriptive name for the model group.
* This data item has been deprecated with dictionary version 1.4.7.
* See ma_model_group category.
*/
model_group_name: str,
/**
@@ -5127,11 +5131,11 @@ export const mmCIF_Schema = {
/**
* The type of QA metric.
*/
type: Aliased<'zscore' | 'energy' | 'distance' | 'normalized score' | 'pLDDT' | 'pLDDT in [0,1]' | 'pLDDT all-atom' | 'pLDDT all-atom in [0,1]' | 'pLDDT to polymer' | 'PAE' | 'pTM' | 'ipTM' | 'contact probability' | 'boolean' | 'other'>(str),
type: Aliased<'zscore' | 'energy' | 'distance' | 'normalized score' | 'pLDDT' | 'pLDDT in [0,1]' | 'pLDDT all-atom' | 'pLDDT all-atom in [0,1]' | 'PAE' | 'pTM' | 'ipTM' | 'contact probability' | 'other'>(str),
/**
* The mode of calculation of the QA metric.
*/
mode: Aliased<'local' | 'global' | 'local-pairwise' | 'per-feature' | 'per-feature-pair'>(str),
mode: Aliased<'local' | 'global' | 'local-pairwise'>(str),
/**
* Identifier to the set of software used to calculate the QA metric.
* This data item is a pointer to the _ma_software_group.group_id in the
@@ -5150,7 +5154,7 @@ export const mmCIF_Schema = {
ordinal_id: int,
/**
* The identifier for the structural model, for which global QA metric is provided.
* This data item is a pointer to _ma_model_list.ordinal_id
* This data item is a pointer to _ma_model_list.model_id
* in the MA_MODEL_LIST category.
*/
model_id: int,
@@ -5168,10 +5172,6 @@ export const mmCIF_Schema = {
/**
* Data items in the MA_QA_METRIC_LOCAL category captures the
* details of the local QA metrics, calculated at the residue-level.
* Data in this category can be extracted into a separate file which
* is linked to the main file using the categories
* ma_associated_archive_file_details or ma_entry_associated_files
* with file_content set to "QA metrics".
*/
ma_qa_metric_local: {
/**
@@ -5180,7 +5180,7 @@ export const mmCIF_Schema = {
ordinal_id: int,
/**
* The identifier for the structural model, for which local QA metric is provided.
* This data item is a pointer to _ma_model_list.ordinal_id
* This data item is a pointer to _ma_model_list.model_id
* in the MA_MODEL_LIST category.
*/
model_id: int,
@@ -5219,15 +5219,6 @@ export const mmCIF_Schema = {
/**
* Data items in the MA_QA_METRIC_LOCAL_PAIRWISE category captures the
* details of the local QA metrics, calculated at the pairwise residue level.
* In cases where the metric is symmetric, it is enough to store just one value per pair.
* For asymmetric metrics, the order of residues is expected to be meaningful
* (e.g. PAE where PAE_ij is defined by aligning residue i (label_*_1) and measuring
* the error on residue j (label_*_2)).
* In all cases, it is perfectly valid to only provide values for a subset of residue pairs.
* Data in this category is expected to be very large and can be extracted into a
* separate file which is linked to the main file using the categories
* ma_associated_archive_file_details or ma_entry_associated_files with file_content
* set to "QA metrics".
*/
ma_qa_metric_local_pairwise: {
/**
@@ -5236,7 +5227,7 @@ export const mmCIF_Schema = {
ordinal_id: int,
/**
* The identifier for the structural model, for which local QA metric is provided.
* This data item is a pointer to _ma_model_list.ordinal_id
* This data item is a pointer to _ma_model_list.model_id
* in the MA_MODEL_LIST category.
*/
model_id: int,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -129,12 +129,6 @@ namespace Box3D {
return out;
}
export function expandUniformly(out: Box3D, box: Box3D, delta: number): Box3D {
Vec3.subScalar(out.min, box.min, delta);
Vec3.addScalar(out.max, box.max, delta);
return out;
}
export function scale(out: Box3D, box: Box3D, scale: number) {
Vec3.scale(out.min, box.min, scale);
Vec3.scale(out.max, box.max, scale);
@@ -224,18 +218,6 @@ namespace Box3D {
Vec3.scale(out, dir, tmin);
return Vec3.set(out, out[0] + x, out[1] + y, out[2] + z);
}
export function center(out: Vec3, box: Box3D): Vec3 {
return Vec3.center(out, box.max, box.min);
}
export function exactEquals(a: Box3D, b: Box3D) {
return Vec3.exactEquals(a.min, b.min) && Vec3.exactEquals(a.max, b.max);
}
export function equals(a: Box3D, b: Box3D) {
return Vec3.equals(a.min, b.min) && Vec3.equals(a.max, b.max);
}
}
export { Box3D };

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
@@ -86,7 +86,7 @@ namespace Plane3D {
}
export function projectPoint(out: Vec3, plane: Plane3D, point: Vec3) {
return Vec3.scaleAndAdd(out, point, plane.normal, -distanceToPoint(plane, point));
return Vec3.scaleAndAdd(out, out, plane.normal, -distanceToPoint(plane, point));
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -27,7 +27,6 @@ import { Vec3 } from './vec3';
import { EPSILON } from './common';
import { assertUnreachable, NumberArray } from '../../../mol-util/type-helpers';
import { Euler } from './euler';
import { Mat4 } from './mat4';
interface Quat extends Array<number> { [d: number]: number, '@type': 'quat', length: 4 }
interface ReadonlyQuat extends Array<number> { readonly [d: number]: number, '@type': 'quat', length: 4 }
@@ -283,12 +282,6 @@ namespace Quat {
return out;
}
const m3tmp = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] as unknown as Mat3;
export function fromMat4(out: Quat, m: Mat4) {
Mat3.fromMat4(m3tmp, m);
return fromMat3(out, m3tmp);
}
export function fromEuler(out: Quat, euler: Euler, order: Euler.Order) {
const [x, y, z] = euler;
@@ -374,12 +367,6 @@ namespace Quat {
return out;
}
const m4tmp = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] as unknown as Mat4;
export function fromBasis(out: Quat, x: Vec3, y: Vec3, z: Vec3) {
Mat4.fromBasis(m4tmp, x, y, z);
return fromMat4(out, m4tmp);
}
export function clone(a: Quat) {
const out = zero();
out[0] = a[0];

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -584,8 +584,8 @@ namespace Vec3 {
const by = angle(a, b);
if (Math.abs(by) < 0.0001) return Mat4.setIdentity(mat);
if (Math.abs(by - Math.PI) < EPSILON) {
// choose arbitrary orthogonal axis
return Mat4.fromRotation(mat, Math.PI, Math.abs(a[0]) < 0.9 ? Vec3.unitX : Vec3.unitZ);
// here, axis can be [0,0,0] but the rotation is a simple flip
return Mat4.fromScaling(mat, negUnit);
}
const axis = cross(rotTemp, a, b);
return Mat4.fromRotation(mat, by, axis);
@@ -645,11 +645,6 @@ namespace Vec3 {
return normalize(out, cross(out, triangleNormalTmpAB, triangleNormalTmpAC));
}
const centerTmpV = zero();
export function center(out: Vec3, a: Vec3, b: Vec3): Vec3 {
return Vec3.scaleAndAdd(out, a, Vec3.sub(centerTmpV, b, a), 0.5);
}
export function toString(a: Vec3, precision?: number) {
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)}]`;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -16,7 +16,6 @@ import { ElementIndex, ChainIndex } from '../../../mol-model/structure/model/ind
import { getCoarseRanges } from '../../../mol-model/structure/model/properties/utils/coarse-ranges';
import { IhmSphereObjSite, IhmGaussianObjSite, AtomSite, BasicSchema } from './schema';
import { Model } from '../../../mol-model/structure';
import { getCoarseIndex } from '../../../mol-model/structure/model/properties/utils/coarse-index';
export interface CoarseData {
model_id: number,
@@ -51,7 +50,6 @@ export function getCoarse(data: CoarseData, chemicalComponentMap: Model['propert
isDefined: true,
spheres: { ...sphereData, ...sphereKeys, ...sphereRanges },
gaussians: { ...gaussianData, ...gaussianKeys, ...gaussianRanges },
index: getCoarseIndex({ spheres: sphereData, gaussians: gaussianData })
},
conformation: {
id: UUID.create22(),

View File

@@ -15,7 +15,6 @@ import { AccessibleSurfaceArea } from '../accessible-surface-area/shrake-rupley'
import { CustomProperty } from '../../common/custom-property';
import { Location } from '../../../mol-model/location';
import { hash2 } from '../../../mol-data/util';
import { ColorThemeCategory } from '../../../mol-theme/color/categories';
const DefaultColor = Color(0xFAFAFA);
const Description = 'Assigns a color based on the relative accessible surface area of a residue.';
@@ -77,7 +76,7 @@ export function AccessibleSurfaceAreaColorTheme(ctx: ThemeDataContext, props: PD
export const AccessibleSurfaceAreaColorThemeProvider: ColorTheme.Provider<AccessibleSurfaceAreaColorThemeParams, 'accessible-surface-area'> = {
name: 'accessible-surface-area',
label: 'Accessible Surface Area',
category: ColorThemeCategory.Residue,
category: ColorTheme.Category.Residue,
factory: AccessibleSurfaceAreaColorTheme,
getParams: getAccessibleSurfaceAreaColorThemeParams,
defaultValues: PD.getDefaultValues(AccessibleSurfaceAreaColorThemeParams),

View File

@@ -15,7 +15,6 @@ import { TableLegend } from '../../../mol-util/legend';
import { Interactions } from '../interactions/interactions';
import { CustomProperty } from '../../common/custom-property';
import { hash2 } from '../../../mol-data/util';
import { ColorThemeCategory } from '../../../mol-theme/color/categories';
const DefaultColor = Color(0xCCCCCC);
const Description = 'Assigns colors according the interaction type of a link.';
@@ -111,7 +110,7 @@ export function InteractionTypeColorTheme(ctx: ThemeDataContext, props: PD.Value
export const InteractionTypeColorThemeProvider: ColorTheme.Provider<InteractionTypeColorThemeParams, 'interaction-type'> = {
name: 'interaction-type',
label: 'Interaction Type',
category: ColorThemeCategory.Misc,
category: ColorTheme.Category.Misc,
factory: InteractionTypeColorTheme,
getParams: getInteractionTypeColorThemeParams,
defaultValues: PD.getDefaultValues(InteractionTypeColorThemeParams),

View File

@@ -11,7 +11,6 @@ import { ThemeDataContext } from '../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { CustomProperty } from '../../common/custom-property';
import { CrossLinkRestraintProvider, CrossLinkRestraint } from './property';
import { ColorThemeCategory } from '../../../mol-theme/color/categories';
const DefaultColor = Color(0xCCCCCC);
const Description = 'Colors cross-links by the deviation of the observed distance versus the modeled distance (e.g. modeled / `ihm_cross_link_restraint.distance_threshold`).';
@@ -64,7 +63,7 @@ export function CrossLinkColorTheme(ctx: ThemeDataContext, props: PD.Values<Cros
export const CrossLinkColorThemeProvider: ColorTheme.Provider<CrossLinkColorThemeParams, 'cross-link'> = {
name: 'cross-link',
label: 'Cross Link',
category: ColorThemeCategory.Misc,
category: ColorTheme.Category.Misc,
factory: CrossLinkColorTheme,
getParams: getCrossLinkColorThemeParams,
defaultValues: PD.getDefaultValues(CrossLinkColorThemeParams),

View File

@@ -7,7 +7,6 @@
import { Location } from '../../../mol-model/location';
import { Bond, StructureElement, Unit } from '../../../mol-model/structure';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ColorThemeCategory } from '../../../mol-theme/color/categories';
import { ThemeDataContext } from '../../../mol-theme/theme';
import { Color } from '../../../mol-util/color';
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
@@ -83,7 +82,7 @@ export function SIFTSMappingColorTheme(ctx: ThemeDataContext, props: PD.Values<S
export const SIFTSMappingColorThemeProvider: ColorTheme.Provider<SIFTSMappingColorThemeParams, 'sifts-mapping'> = {
name: 'sifts-mapping',
label: 'SIFTS Mapping',
category: ColorThemeCategory.Residue,
category: ColorTheme.Category.Residue,
factory: SIFTSMappingColorTheme,
getParams: getSIFTSMappingColorThemeParams,
defaultValues: PD.getDefaultValues(SIFTSMappingColorThemeParams),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -31,17 +31,4 @@ export function isDataLocation(x: any): x is DataLocation {
return !!x && x.kind === 'data-location';
}
/**
* A direct Location.
*
* For it, the location is implicitly clear from context and is not explicitly given.
* This is used for themes with direct-volume rendering where the location is the volume
* grid cell itself and coloring is applied in a shader on the GPU.
*/
export const DirectLocation = { kind: 'direct-location' as const };
export type DirectLocation = typeof DirectLocation
export function isDirectLocation(x: any): x is DirectLocation {
return !!x && x.kind === 'direct-location';
}
export type Location = StructureElement.Location | Bond.Location | ShapeGroup.Location | PositionLocation | DataLocation | NullLocation | DirectLocation | Volume.Cell.Location | Volume.Segment.Location
export type Location = StructureElement.Location | Bond.Location | ShapeGroup.Location | PositionLocation | DataLocation | NullLocation | Volume.Segment.Location

View File

@@ -9,7 +9,6 @@ import { Column } from '../../../../../mol-data/db';
import { Segmentation } from '../../../../../mol-data/int';
import { ElementIndex, ChainIndex, EntityIndex } from '../../indexing';
import { SortedRanges } from '../../../../../mol-data/int/sorted-ranges';
import { EmptyCoarseIndex } from '../utils/coarse-index';
export interface CoarsedElementKeys {
/** Assign a key to each element */
@@ -61,8 +60,7 @@ export type CoarseElements = CoarsedElementKeys & CoarseElementData & CoarseRang
export interface CoarseHierarchy {
isDefined: boolean,
spheres: CoarseElements,
gaussians: CoarseElements,
index: CoarseIndex
gaussians: CoarseElements
}
const EmptyCoarseElements: CoarseElements = {
@@ -83,39 +81,10 @@ const EmptyCoarseElements: CoarseElements = {
gapRanges: SortedRanges.ofSortedRanges([]),
};
export interface CoarseIndex {
/**
* Find element index of a sphere
* @param key
* @returns index or -1 if the atom is not present.
*/
findSphereElement(key: CoarseElementKey): ElementIndex
/**
* Find element index of a gaussian
* @param key
* @returns index or -1 if the atom is not present.
*/
findGaussianElement(key: CoarseElementKey): ElementIndex
/**
* Finds coarse element and assigns a reference to it.
* @param key
*/
findElement(key: CoarseElementKey, out: CoarseElementReference): boolean
}
export interface CoarseElementReference { kind?: 'spheres' | 'gaussians', index: ElementIndex }
export function CoarseElementReference(): CoarseElementReference { return { kind: undefined, index: -1 as ElementIndex }; }
export interface CoarseElementKey { label_entity_id: string, label_asym_id: string, label_seq_id: number }
export function CoarseElementKey(): CoarseElementKey { return { label_entity_id: '', label_asym_id: '', label_seq_id: -1 }; }
export namespace CoarseHierarchy {
export const Empty: CoarseHierarchy = {
isDefined: false,
spheres: EmptyCoarseElements,
gaussians: EmptyCoarseElements,
index: EmptyCoarseIndex,
gaussians: EmptyCoarseElements
};
}

View File

@@ -1,102 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { ElementIndex } from '../../indexing';
import { CoarseElementData, CoarseElementReference, CoarseIndex, CoarseElementKey } from '../coarse';
export function getCoarseIndex(data: { spheres: CoarseElementData, gaussians: CoarseElementData }): CoarseIndex {
return new Index(data);
}
class EmptyIndex implements CoarseIndex {
findElement(key: CoarseElementKey, out: CoarseElementReference): boolean {
out.kind = undefined;
out.index = -1 as ElementIndex;
return false;
}
findSphereElement(key: CoarseElementKey): ElementIndex {
return -1 as ElementIndex;
}
findGaussianElement(key: CoarseElementKey): ElementIndex {
return -1 as ElementIndex;
}
}
export const EmptyCoarseIndex: CoarseIndex = new EmptyIndex();
class Index implements CoarseIndex {
private _sphereMapping: CoarseElementMapping | undefined = void 0;
private _gaussianMapping: CoarseElementMapping | undefined = void 0;
get sphereMapping() {
if (!this._sphereMapping) this._sphereMapping = buildMapping(this.data.spheres);
return this._sphereMapping;
}
get gaussianMapping() {
if (!this._gaussianMapping) this._gaussianMapping = buildMapping(this.data.gaussians);
return this._gaussianMapping;
}
findSphereElement(key: CoarseElementKey): ElementIndex {
const mapping = this.sphereMapping;
let xs: any = mapping[key.label_entity_id];
if (!xs) return -1 as ElementIndex;
xs = xs[key.label_asym_id];
if (!xs) return -1 as ElementIndex;
return xs[key.label_seq_id] ?? -1;
}
findGaussianElement(key: CoarseElementKey): ElementIndex {
const mapping = this.gaussianMapping;
let xs: any = mapping[key.label_entity_id];
if (!xs) return -1 as ElementIndex;
xs = xs[key.label_asym_id];
if (!xs) return -1 as ElementIndex;
return xs[key.label_seq_id] ?? -1;
}
findElement(key: CoarseElementKey, out: CoarseElementReference): boolean {
const sphere = this.findSphereElement(key);
if (sphere >= 0) {
out.kind = 'spheres';
out.index = sphere;
return true;
}
const gaussian = this.findGaussianElement(key);
if (gaussian >= 0) {
out.kind = 'gaussians';
out.index = gaussian;
return true;
}
return false;
}
constructor(private data: { spheres: CoarseElementData, gaussians: CoarseElementData }) {
}
}
type CoarseElementMapping = { [entityId: string]: { [chainId: string]: { [seqId: number]: ElementIndex } } };
function buildMapping({ count, entity_id, asym_id, seq_id_begin, seq_id_end }: CoarseElementData): CoarseElementMapping {
const ret: CoarseElementMapping = {};
for (let i = 0; i < count; i++) {
const entityId = entity_id.value(i);
const asymId = asym_id.value(i);
if (!ret[entityId]) ret[entityId] = {};
if (!ret[entityId][asymId]) ret[entityId][asymId] = {};
const elements = ret[entityId][asymId];
const seqIdBegin = seq_id_begin.value(i);
const seqIdEnd = seq_id_end.value(i);
for (let seqId = seqIdBegin; seqId <= seqIdEnd; seqId++) {
elements[seqId] = i as ElementIndex;
}
}
return ret;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -112,9 +112,9 @@ const residue = {
const chain = {
key: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.chainIndex[l.element]),
label_asym_id: p(l => !Unit.isAtomic(l.unit) ? l.unit.coarseElements.asym_id.value(l.element) : l.unit.model.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
label_asym_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
auth_asym_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
label_entity_id: p(l => !Unit.isAtomic(l.unit) ? l.unit.coarseElements.entity_id.value(l.element) : l.unit.model.atomicHierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
label_entity_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
};
const coarse = {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -8,7 +8,6 @@
import { SpacegroupCell, Box3D, Sphere3D } from '../../mol-math/geometry';
import { Tensor, Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { Histogram, calculateHistogram } from '../../mol-math/histogram';
import { lerp } from '../../mol-math/interpolate';
/** The basic unit cell that contains the grid data. */
interface Grid {
@@ -77,59 +76,6 @@ namespace Grid {
}
return histograms[binCount];
}
export function makeGetTrilinearlyInterpolated(grid: Grid, transform: 'none' | 'relative') {
const cartnToGrid = Grid.getGridToCartesianTransform(grid);
Mat4.invert(cartnToGrid, cartnToGrid);
const gridCoords = Vec3();
const { stats } = grid;
const { dimensions, get } = grid.cells.space;
const data = grid.cells.data;
const [mi, mj, mk] = dimensions;
return function getTrilinearlyInterpolated(position: Vec3): number {
Vec3.copy(gridCoords, position);
Vec3.transformMat4(gridCoords, gridCoords, cartnToGrid);
const i = Math.trunc(gridCoords[0]);
const j = Math.trunc(gridCoords[1]);
const k = Math.trunc(gridCoords[2]);
if (i < 0 || i >= mi || j < 0 || j >= mj || k < 0 || k >= mk) {
return Number.NaN;
}
const u = gridCoords[0] - i;
const v = gridCoords[1] - j;
const w = gridCoords[2] - k;
// Tri-linear interpolation for the value
const ii = Math.min(i + 1, mi - 1);
const jj = Math.min(j + 1, mj - 1);
const kk = Math.min(k + 1, mk - 1);
let a = get(data, i, j, k);
let b = get(data, ii, j, k);
let c = get(data, i, jj, k);
let d = get(data, ii, jj, k);
const x = lerp(lerp(a, b, u), lerp(c, d, u), v);
a = get(data, i, j, kk);
b = get(data, ii, j, kk);
c = get(data, i, jj, kk);
d = get(data, ii, jj, kk);
const y = lerp(lerp(a, b, u), lerp(c, d, u), v);
const value = lerp(x, y, w);
if (transform === 'relative') {
return (value - stats.mean) / stats.sigma;
} else {
return value;
}
};
}
}
export { Grid };

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -220,18 +220,6 @@ export namespace Volume {
export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && OrderedSet.areEqual(a.indices, b.indices); }
export function isLociEmpty(loci: Loci) { return OrderedSet.size(loci.indices) === 0; }
export interface Location {
readonly kind: 'cell-location',
volume: Volume
cell: CellIndex
}
export function Location(volume?: Volume, cell?: CellIndex): Location {
return { kind: 'cell-location', volume: volume as any, cell: cell as any };
}
export function isLocation(x: any): x is Location {
return !!x && x.kind === 'cell-location';
}
const boundaryHelper = new BoundaryHelper('98');
const tmpBoundaryPos = Vec3();
export function getBoundingSphere(volume: Volume, indices: OrderedSet<CellIndex>, boundingSphere?: Sphere3D) {

View File

@@ -63,13 +63,13 @@ const DownloadStructure = StateAction.build({
}, { pivot: 'id' }),
options
}, { isFlat: true, label: 'PDB' }),
'pdb-ihm': PD.Group({
'pdb-dev': PD.Group({
provider: PD.Group({
id: PD.Text('8zzc', { label: 'PDB-IHM Id(s)', description: 'One or more comma/space separated ids.' }),
id: PD.Text('PDBDEV_00000001', { label: 'PDB-Dev Id(s)', description: 'One or more comma/space separated ids.' }),
encoding: PD.Select('bcif', PD.arrayToOptions(['cif', 'bcif'] as const)),
}, { pivot: 'id' }),
options
}, { isFlat: true, label: 'PDB-IHM' }),
}, { isFlat: true, label: 'PDB-Dev' }),
'swissmodel': PD.Group({
id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma/space separated ACs.' }),
options
@@ -124,22 +124,22 @@ const DownloadStructure = StateAction.build({
);
asTrajectory = !!src.params.options.asTrajectory;
break;
case 'pdb-ihm':
case 'pdb-dev':
const map = (id: string) => id.startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
downloadParams = await getDownloadParams(src.params.provider.id,
id => {
// 4 character PDB id, TODO: support extended PDB ID
if (id.match(/^[1-9][A-Z0-9]{3}$/i) !== null) {
return src.params.provider.encoding === 'bcif'
? `https://pdb-ihm.org/bcif/${id.toLowerCase()}.bcif`
: `https://pdb-ihm.org/cif/${id.toLowerCase()}.cif`;
? `https://pdb-dev.wwpdb.org/bcif/${id.toLowerCase()}.bcif`
: `https://pdb-dev.wwpdb.org/cif/${id.toLowerCase()}.cif`;
}
const nId = map(id.toUpperCase());
return src.params.provider.encoding === 'bcif'
? `https://pdb-ihm.org/bcif/${nId}.bcif`
: `https://pdb-ihm.org/cif/${nId}.cif`;
? `https://pdb-dev.wwpdb.org/bcif/${nId}.bcif`
: `https://pdb-dev.wwpdb.org/cif/${nId}.cif`;
},
id => { const nId = id.toUpperCase(); return nId.match(/^[1-9][A-Z0-9]{3}$/) ? `PDB-IHM: ${nId}` : map(nId); },
id => { const nId = id.toUpperCase(); return nId.match(/^[1-9][A-Z0-9]{3}$/) ? `PDB-Dev: ${nId}` : map(nId); },
src.params.provider.encoding === 'bcif'
);
asTrajectory = !!src.params.options.asTrajectory;
@@ -235,7 +235,7 @@ async function getPdbeDownloadParams(src: ReturnType<DownloadStructure['createDe
async function getPdbjDownloadParams(src: ReturnType<DownloadStructure['createDefaultParams']>['source']) {
if (src.name !== 'pdb' || src.params.provider.server.name !== 'pdbj') throw new Error('expected pdbj');
return getDownloadParams(src.params.provider.id, id => `https://data.pdbjlc1.pdbj.org/pub/pdb/data/structures/divided/mmCIF/${id.toLowerCase().substring(1, 3)}/${id.toLowerCase()}.cif`, id => `PDBj: ${id} (cif)`, false);
return getDownloadParams(src.params.provider.id, id => `https://data.pdbjbk1.pdbj.org/pub/pdb/data/structures/divided/mmCIF/${id.toLowerCase().substring(1, 3)}/${id.toLowerCase()}.cif`, id => `PDBj: ${id} (cif)`, false);
}
async function getRcsbDownloadParams(src: ReturnType<DownloadStructure['createDefaultParams']>['source']) {

View File

@@ -1,9 +1,8 @@
/**
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Adam Midlik <midlik@gmail.com>
*/
import { PresetProvider } from '../preset-provider';
@@ -122,7 +121,7 @@ const auto = StructureRepresentationPresetProvider({
params: () => CommonParams,
apply(ref, params, plugin) {
const structure = StateObjectRef.resolveAndCheck(plugin.state.data, ref)?.obj?.data;
if (!structure) return {};
if (!structure) return { };
const thresholds = plugin.config.get(PluginConfig.Structure.SizeThresholds) || Structure.DefaultSizeThresholds;
const size = Structure.getSize(structure, thresholds);
@@ -152,7 +151,7 @@ const empty = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-empty',
display: { name: 'Empty', description: 'Removes all existing representations.' },
async apply(ref, params, plugin) {
return {};
return { };
}
});
@@ -397,9 +396,12 @@ const illustrative = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-illustrative',
display: {
name: 'Illustrative', group: 'Miscellaneous',
description: 'Show everything in spacefill representation with illustrative colors and ignore light.'
description: '...'
},
params: () => CommonParams,
params: () => ({
...CommonParams,
showCarbohydrateSymbol: PD.Boolean(false)
}),
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
@@ -414,47 +416,7 @@ const illustrative = StructureRepresentationPresetProvider({
const { update, builder, typeParams, color } = reprBuilder(plugin, params, structure);
const representations = {
all: builder.buildRepresentation(update, components.all, {
type: 'spacefill',
typeParams: { ...typeParams, ignoreLight: true },
color: 'illustrative',
colorParams: { style: { name: 'entity-id', params: { overrideWater: true } } },
}, { tag: 'all' }),
};
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params);
return { components, representations };
}
});
const molecularSurface = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-molecular-surface',
display: {
name: 'Molecular Surface', group: 'Miscellaneous',
description: 'Show everything in molecular surface representation with illustrative colors.'
},
params: () => CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const components = {
all: await presetStaticComponent(plugin, structureCell, 'all'),
branched: undefined
};
const structure = structureCell.obj!.data;
const { update, builder, typeParams, color } = reprBuilder(plugin, params, structure);
const representations = {
all: builder.buildRepresentation(update, components.all, {
type: 'molecular-surface',
typeParams,
color: 'entity-id',
colorParams: { overrideWater: true },
}, { tag: 'all' }),
all: builder.buildRepresentation(update, components.all, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'illustrative' }, { tag: 'all' }),
};
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params);
@@ -512,7 +474,6 @@ export const PresetStructureRepresentations = {
'protein-and-nucleic': proteinAndNucleic,
'coarse-surface': coarseSurface,
illustrative,
'molecular-surface': molecularSurface,
'auto-lod': autoLod,
};
export type PresetStructureRepresentations = typeof PresetStructureRepresentations;

Some files were not shown because too many files have changed in this diff Show More