mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 14:04:36 +08:00
Compare commits
76 Commits
support-sc
...
v4.12.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e195b048a1 | ||
|
|
ae5bb81b27 | ||
|
|
8c04c57bc5 | ||
|
|
ec46a444f1 | ||
|
|
559e0326b6 | ||
|
|
82b93bc2a8 | ||
|
|
62f940bc48 | ||
|
|
4e0be8e7b4 | ||
|
|
128502edf0 | ||
|
|
aad4d4a86c | ||
|
|
9bc7e27243 | ||
|
|
a5111356c1 | ||
|
|
9b11f7ffde | ||
|
|
93ce6d2807 | ||
|
|
5c9d5d3a3d | ||
|
|
f40307db39 | ||
|
|
4e6000fa6c | ||
|
|
26e5817bf2 | ||
|
|
8469be80d0 | ||
|
|
029edc95c8 | ||
|
|
dd9aaf055f | ||
|
|
fcfb2d940c | ||
|
|
f5b5109d0f | ||
|
|
ca99c800f1 | ||
|
|
dbd5570370 | ||
|
|
3f805c7a82 | ||
|
|
12c71dc5ba | ||
|
|
2fe3a926aa | ||
|
|
60c2096575 | ||
|
|
f4e9df5e4d | ||
|
|
c304b82772 | ||
|
|
9edd171350 | ||
|
|
f7d1bd7c04 | ||
|
|
7422c255ab | ||
|
|
5497215784 | ||
|
|
577bf1c77c | ||
|
|
c9bddccaf7 | ||
|
|
ac292f9267 | ||
|
|
f0b8d75b10 | ||
|
|
0dacbcb3bc | ||
|
|
0789241ea3 | ||
|
|
ddb0799dc4 | ||
|
|
cbfa341fa3 | ||
|
|
1c19bd90df | ||
|
|
300e5c8985 | ||
|
|
0861a78db6 | ||
|
|
a8e403ad85 | ||
|
|
4e350496b2 | ||
|
|
dd21ddcc80 | ||
|
|
a88121f779 | ||
|
|
1ab91d1979 | ||
|
|
267788388d | ||
|
|
43c0333be3 | ||
|
|
3b90a269b0 | ||
|
|
4aa5e1d7fc | ||
|
|
679db48938 | ||
|
|
2f96b42df7 | ||
|
|
dd6f3bd76e | ||
|
|
f1b7e478c7 | ||
|
|
416442aa27 | ||
|
|
a5f65b6e6f | ||
|
|
938ac0cc8f | ||
|
|
7dacf60478 | ||
|
|
9cdb8a3a92 | ||
|
|
242982e661 | ||
|
|
6da20a6989 | ||
|
|
f27b651230 | ||
|
|
7c818c0cc9 | ||
|
|
e7d7ba26b0 | ||
|
|
7e64121059 | ||
|
|
894bba1d3a | ||
|
|
d9db775fe8 | ||
|
|
a7fbc7b4c4 | ||
|
|
c0596298d6 | ||
|
|
8f32dde599 | ||
|
|
4d8f00900d |
73
CHANGELOG.md
73
CHANGELOG.md
@@ -5,18 +5,75 @@ 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
|
||||
- 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 `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
|
||||
- Fix plugin mouse interactions when CSS `scale` transform is applied
|
||||
- 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`
|
||||
|
||||
## [v4.10.0] - 2024-12-15
|
||||
|
||||
@@ -84,7 +141,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-Dev loader
|
||||
- Mesoscale Explorer: Add support for 4-character PDB IDs (e.g., 8ZZC) in PDB-IHM/PDB-Dev loader
|
||||
- Fix Sequence View in Safari 18
|
||||
- Improve performance of `IndexPairBonds` assignment when operator keys are available
|
||||
- ModelArchive QualityAssessment extension:
|
||||
@@ -137,7 +194,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-Dev: Add support for 4-character PDB IDs (e.g., 8ZZC)
|
||||
- PDB-IHM/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
|
||||
|
||||
2340
package-lock.json
generated
2340
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "4.10.0",
|
||||
"version": "4.12.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -114,7 +114,8 @@
|
||||
"Eric E <etongfu@outlook.com>",
|
||||
"Xavier Martinez <xavier.martinez.xm@gmail.com>",
|
||||
"Alex Chan <smalldirkalex@gmail.com>",
|
||||
"Simeon Borko <simeon.borko@gmail.com>"
|
||||
"Simeon Borko <simeon.borko@gmail.com>",
|
||||
"Ventura Rivera <venturaxrivera@gmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
@@ -122,19 +123,19 @@
|
||||
"@types/gl": "^6.0.5",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"@types/react": "^18.3.16",
|
||||
"@types/react": "^18.3.18",
|
||||
"@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.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"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.2.0",
|
||||
"fs-extra": "^11.3.0",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
@@ -143,22 +144,22 @@
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"sass": "^1.83.0",
|
||||
"sass": "^1.83.4",
|
||||
"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.2",
|
||||
"typescript": "^5.7.3",
|
||||
"webpack": "^5.97.1",
|
||||
"webpack-cli": "^5.1.4"
|
||||
"webpack-cli": "^6.0.1"
|
||||
},
|
||||
"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.68",
|
||||
"@types/node": "^18.19.74",
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"@types/swagger-ui-dist": "3.30.5",
|
||||
"argparse": "^2.0.1",
|
||||
@@ -170,11 +171,11 @@
|
||||
"immutable": "^5.0.3",
|
||||
"io-ts": "^2.2.22",
|
||||
"node-fetch": "^2.7.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-markdown": "^9.0.3",
|
||||
"rxjs": "^7.8.1",
|
||||
"swagger-ui-dist": "^5.18.2",
|
||||
"tslib": "^2.8.1",
|
||||
"util.promisify": "^1.1.2",
|
||||
"util.promisify": "^1.1.3",
|
||||
"xhr2": "^0.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -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, loadPdbDev, loadUrl, openState } from './ui/states';
|
||||
import { LoadModel, loadExampleEntry, loadPdb, loadPdbIhm, 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,8 +120,15 @@ 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 loadPdbDev(this.plugin, id);
|
||||
await this.loadPdbIhm(id);
|
||||
}
|
||||
|
||||
async loadPdbIhm(id: string) {
|
||||
await loadPdbIhm(this.plugin, id);
|
||||
}
|
||||
|
||||
static async create(elementOrId: string | HTMLElement, options: Partial<MesoscaleExplorerOptions> = {}) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 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 { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { Mat4, 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,7 +211,8 @@ export function getClipObjects(values: SimpleClipProps, boundingSphere: Sphere3D
|
||||
invert: values.invert,
|
||||
position,
|
||||
scale,
|
||||
rotation: values.rotation
|
||||
rotation: values.rotation,
|
||||
transform: Mat4.identity(),
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -93,9 +93,15 @@
|
||||
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.loadPdbDev(pdbdev);
|
||||
me.loadPdbIhm(pdbdev);
|
||||
return;
|
||||
}
|
||||
window.addEventListener('unload', () => {
|
||||
|
||||
@@ -211,15 +211,15 @@ export async function loadPdb(ctx: PluginContext, id: string) {
|
||||
await createHierarchy(ctx, data.ref);
|
||||
}
|
||||
|
||||
export async function loadPdbDev(ctx: PluginContext, id: string) {
|
||||
export async function loadPdbIhm(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-dev.wwpdb.org/bcif/${id.toLowerCase()}.bcif`;
|
||||
url = `https://pdb-ihm.org/bcif/${id.toLowerCase()}.bcif`;
|
||||
} else {
|
||||
const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
|
||||
url = `https://pdb-dev.wwpdb.org/bcif/${nId.toUpperCase()}.bcif`;
|
||||
url = `https://pdb-ihm.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', pdbDev: 'PDB-Dev' })),
|
||||
source: PD.Select('pdb', PD.objectToOptions({ pdb: 'PDB', pdbIhm: 'PDB-IHM' })),
|
||||
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 === 'pdbDev') {
|
||||
await loadPdbDev(ctx, params.entry);
|
||||
} else if (params.source === 'pdbIhm') {
|
||||
await loadPdbIhm(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-DEV databases.', side: 'bottom', align: 'start' } },
|
||||
{ element: '#database', popover: { title: 'Import from PDB', description: 'Load structures directly from PDB and PDB-IHM 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' } },
|
||||
|
||||
@@ -288,14 +288,21 @@ 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-dev' as const,
|
||||
name: 'pdb-ihm' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: pdbDev,
|
||||
id: pdbIhm,
|
||||
encoding: 'bcif',
|
||||
},
|
||||
options: params.source.params.options,
|
||||
|
||||
@@ -111,8 +111,11 @@
|
||||
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.loadPdbDev(pdbDev);
|
||||
if (pdbDev) viewer.loadPdbIhm(pdbDev);
|
||||
|
||||
var emdb = getParam('emdb', '[^&]+').trim();
|
||||
if (emdb) viewer.loadEmdb(emdb);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -43,7 +44,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: ColorTheme.Category.Misc,
|
||||
category: ColorThemeCategory.Misc,
|
||||
factory: CustomColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: { },
|
||||
|
||||
31
src/examples/ihm-restraints/index.html
Normal file
31
src/examples/ihm-restraints/index.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!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>
|
||||
341
src/examples/ihm-restraints/index.tsx
Normal file
341
src/examples/ihm-restraints/index.tsx
Normal file
@@ -0,0 +1,341 @@
|
||||
/**
|
||||
* 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;
|
||||
37
src/examples/mvs-kinase-story/context.ts
Normal file
37
src/examples/mvs-kinase-story/context.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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];
|
||||
}
|
||||
118
src/examples/mvs-kinase-story/elements/snapshot-markdown.tsx
Normal file
118
src/examples/mvs-kinase-story/elements/snapshot-markdown.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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);
|
||||
113
src/examples/mvs-kinase-story/elements/viewer.tsx
Normal file
113
src/examples/mvs-kinase-story/elements/viewer.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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);
|
||||
59
src/examples/mvs-kinase-story/index.html
Normal file
59
src/examples/mvs-kinase-story/index.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!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>
|
||||
22
src/examples/mvs-kinase-story/index.tsx
Normal file
22
src/examples/mvs-kinase-story/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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;
|
||||
731
src/examples/mvs-kinase-story/kinase-story.ts
Normal file
731
src/examples/mvs-kinase-story/kinase-story.ts
Normal file
@@ -0,0 +1,731 @@
|
||||
/**
|
||||
* 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(),
|
||||
}
|
||||
};
|
||||
}
|
||||
50
src/examples/mvs-kinase-story/readme.md
Normal file
50
src/examples/mvs-kinase-story/readme.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 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.
|
||||
164
src/examples/mvs-kinase-story/styles.scss
Normal file
164
src/examples/mvs-kinase-story/styles.scss
Normal file
@@ -0,0 +1,164 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ 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);
|
||||
|
||||
@@ -102,7 +103,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: ColorTheme.Category.Symmetry,
|
||||
category: ColorThemeCategory.Symmetry,
|
||||
factory: AssemblySymmetryClusterColorTheme,
|
||||
getParams: getAssemblySymmetryClusterColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(AssemblySymmetryClusterColorThemeParams),
|
||||
|
||||
@@ -18,6 +18,7 @@ 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';
|
||||
|
||||
@@ -62,7 +63,7 @@ export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values
|
||||
export const ConfalPyramidsColorThemeProvider: ColorTheme.Provider<ConfalPyramidsColorThemeParams, 'confal-pyramids'> = {
|
||||
name: 'confal-pyramids',
|
||||
label: 'Confal Pyramids',
|
||||
category: ColorTheme.Category.Residue,
|
||||
category: ColorThemeCategory.Residue,
|
||||
factory: ConfalPyramidsColorTheme,
|
||||
getParams: getConfalPyramidsColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(ConfalPyramidsColorThemeParams),
|
||||
|
||||
@@ -18,6 +18,7 @@ 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';
|
||||
|
||||
@@ -87,7 +88,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: ColorTheme.Category.Residue,
|
||||
category: ColorThemeCategory.Residue,
|
||||
factory: NtCTubeColorTheme,
|
||||
getParams: getNtCTubeColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(NtCTubeColorThemeParams),
|
||||
|
||||
@@ -15,6 +15,7 @@ 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 = {
|
||||
@@ -87,7 +88,7 @@ export function PLDDTConfidenceColorTheme(ctx: ThemeDataContext, props: PD.Value
|
||||
export const PLDDTConfidenceColorThemeProvider: ColorTheme.Provider<PLDDTConfidenceColorThemeParams, 'plddt-confidence'> = {
|
||||
name: 'plddt-confidence',
|
||||
label: 'pLDDT Confidence',
|
||||
category: ColorTheme.Category.Validation,
|
||||
category: ColorThemeCategory.Validation,
|
||||
factory: PLDDTConfidenceColorTheme,
|
||||
getParams: getPLDDTConfidenceColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(getPLDDTConfidenceColorThemeParams({})),
|
||||
|
||||
@@ -12,6 +12,7 @@ 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);
|
||||
|
||||
@@ -71,7 +72,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: ColorTheme.Category.Validation,
|
||||
category: ColorThemeCategory.Validation,
|
||||
factory: QmeanScoreColorTheme,
|
||||
getParams: getQmeanScoreColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(getQmeanScoreColorThemeParams({})),
|
||||
|
||||
@@ -9,7 +9,7 @@ import { MVSData } from '../mvs-data';
|
||||
|
||||
|
||||
describe('MVSData', () => {
|
||||
it('MVSData functions work', async () => {
|
||||
it.skip('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('MVSData builder works', async () => {
|
||||
it.skip('MVSData builder works', async () => {
|
||||
const builder = MVSData.createBuilder();
|
||||
expect(builder).toBeTruthy();
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { Bond, StructureElement } from '../../../mol-model/structure';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import type { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import type { 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: ColorTheme.Category.Misc,
|
||||
category: 'Miscellaneous', // ColorTheme.Category.Misc can cause webpack build error due to import ordering
|
||||
factory: MVSAnnotationColorTheme,
|
||||
getParams: ctx => MVSAnnotationColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(MVSAnnotationColorThemeParams),
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
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';
|
||||
@@ -139,7 +140,7 @@ export function makeMultilayerColorThemeProvider(colorThemeRegistry: ColorTheme.
|
||||
return {
|
||||
name: MultilayerColorThemeName,
|
||||
label: 'MVS Multi-layer',
|
||||
category: ColorTheme.Category.Misc,
|
||||
category: ColorThemeCategory.Misc,
|
||||
factory: (ctx, props) => makeMultilayerColorTheme(ctx, props, colorThemeRegistry),
|
||||
getParams: (ctx: ThemeDataContext) => makeMultilayerColorThemeParams(colorThemeRegistry, ctx),
|
||||
defaultValues: DefaultMultilayerColorThemeProps,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2024-2025 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,12 +8,17 @@
|
||||
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';
|
||||
@@ -278,6 +283,15 @@ 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,
|
||||
@@ -291,6 +305,45 @@ 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);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -602,6 +655,86 @@ 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);
|
||||
@@ -646,17 +779,332 @@ 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;
|
||||
resolveBasePosition(context, params.position, labelPos);
|
||||
resolvePosition(context, params.position, PrimitiveLabelState.position, PrimitiveLabelState.sphere, undefined);
|
||||
|
||||
const group = groups.allocateSingle(node);
|
||||
groups.updateColor(group, params.label_color);
|
||||
groups.updateSize(group, params.label_size);
|
||||
|
||||
labels.add(params.text, labelPos[0], labelPos[1], labelPos[2], params.label_offset, 1, group);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Column } from '../../../mol-data/db';
|
||||
@@ -262,7 +263,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 } = MS.struct.atomProperty;
|
||||
const { macromolecular, ihm } = MS.struct.atomProperty;
|
||||
const propTests: Partial<Record<string, Expression>> = {};
|
||||
|
||||
if (isDefined(row.label_entity_id)) {
|
||||
@@ -280,11 +281,16 @@ export function rowToExpression(row: MVSAnnotationRow): Expression {
|
||||
}
|
||||
|
||||
const residueTests: Expression[] = [];
|
||||
if (isDefined(row.label_seq_id)) residueTests.push(eq([macromolecular.label_seq_id(), row.label_seq_id]));
|
||||
if (isDefined(row.label_seq_id)) {
|
||||
residueTests.push(ihm.hasSeqId({ 0: 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)) 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_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_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) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -125,6 +125,9 @@ 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. */
|
||||
@@ -142,7 +145,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() };
|
||||
const result: UpdateTarget = { ...target, selector: msResult, mvsDependencyRefs: new Set(), transformer, transformParams: params };
|
||||
target.targetManager.allTargets.push(result);
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
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 } from '../../mol-plugin-state/transforms/representation';
|
||||
import { StructureRepresentation3D, VolumeRepresentation3D } from '../../mol-plugin-state/transforms/representation';
|
||||
import { StateTransformer } from '../../mol-state';
|
||||
import { arrayDistinct } from '../../mol-util/array';
|
||||
import { canonicalJsonString } from '../../mol-util/json';
|
||||
@@ -27,6 +29,7 @@ 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[]);
|
||||
@@ -35,10 +38,6 @@ 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}`);
|
||||
@@ -285,19 +284,30 @@ 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);
|
||||
switch (node.params.type) {
|
||||
const params = node.params;
|
||||
switch (params.type) {
|
||||
case 'cartoon':
|
||||
return {
|
||||
type: { name: 'cartoon', params: { alpha } },
|
||||
type: { name: 'cartoon', params: { alpha, tubularHelices: params.tubular_helices } },
|
||||
sizeTheme: { name: 'uniform', params: { value: params.size_factor } },
|
||||
};
|
||||
case 'ball_and_stick':
|
||||
return {
|
||||
type: { name: 'ball-and-stick', params: { sizeFactor: 0.5, sizeAspectRatio: 0.5, alpha } },
|
||||
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 } },
|
||||
};
|
||||
case 'surface':
|
||||
return {
|
||||
type: { name: 'molecular-surface', params: { alpha } },
|
||||
sizeTheme: { name: 'physical', params: { scale: 1 } },
|
||||
type: { name: 'molecular-surface', params: { alpha, ignoreHydrogens: params.ignore_hydrogens } },
|
||||
sizeTheme: { name: 'physical', params: { scale: params.size_factor } },
|
||||
};
|
||||
default:
|
||||
throw new Error('NotImplementedError');
|
||||
@@ -305,7 +315,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'>): number {
|
||||
export function alphaForNode(node: MolstarSubtree<'representation' | 'volume_representation'>): number {
|
||||
const children = getChildren(node).filter(c => c.kind === 'opacity');
|
||||
if (children.length > 0) {
|
||||
return children[children.length - 1].params.opacity;
|
||||
@@ -313,8 +323,14 @@ export function alphaForNode(node: MolstarSubtree<'representation'>): number {
|
||||
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'] {
|
||||
export function colorThemeForNode(node: MolstarSubtree<'color' | 'color_from_uri' | 'color_from_source' | 'representation'> | undefined, context: MolstarLoadingContext): StateTransformer.Params<StructureRepresentation3D>['colorTheme'] | undefined {
|
||||
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) {
|
||||
@@ -322,12 +338,23 @@ 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 => ({ theme: colorThemeForNode(c, context), selection: componentPropsFromSelector(c.kind === 'color' ? c.params.selector : undefined) })
|
||||
);
|
||||
c => {
|
||||
const theme = colorThemeForNode(c, context);
|
||||
if (!theme) return undefined;
|
||||
return { theme, selection: componentPropsFromSelector(c.kind === 'color' ? c.params.selector : undefined) };
|
||||
}
|
||||
).filter(t => !!t);
|
||||
return {
|
||||
name: MultilayerColorThemeName,
|
||||
params: { layers },
|
||||
@@ -389,3 +416,36 @@ 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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -7,9 +7,11 @@
|
||||
*/
|
||||
|
||||
import { PluginStateSnapshotManager } from '../../mol-plugin-state/manager/snapshots';
|
||||
import { Download, ParseCif } from '../../mol-plugin-state/transforms/data';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { Download, ParseCcp4, 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 } from '../../mol-plugin-state/transforms/representation';
|
||||
import { ShapeRepresentation3D, StructureRepresentation3D, VolumeRepresentation3D } from '../../mol-plugin-state/transforms/representation';
|
||||
import { VolumeFromCcp4, VolumeFromDensityServerCif } from '../../mol-plugin-state/transforms/volume';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateObjectSelector } from '../../mol-state';
|
||||
@@ -24,7 +26,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 } from './load-helpers';
|
||||
import { AnnotationFromSourceKind, AnnotationFromUriKind, collectAnnotationReferences, collectAnnotationTooltips, collectInlineLabels, collectInlineTooltips, colorThemeForNode, componentFromXProps, componentPropsFromSelector, isPhantomComponent, labelFromXProps, makeNearestReprMap, prettyNameFromSelector, representationProps, structureProps, transformProps, volumeColorThemeForNode, volumeRepresentationProps } from './load-helpers';
|
||||
import { MVSData, SnapshotMetadata } from './mvs-data';
|
||||
import { validateTree } from './tree/generic/tree-schema';
|
||||
import { convertMvsToMolstar, mvsSanityCheck } from './tree/molstar/conversion';
|
||||
@@ -32,13 +34,24 @@ 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: { replaceExisting?: boolean, keepCamera?: boolean, extensions?: MolstarLoadingExtension<any>[], sanityChecks?: boolean, sourceUrl?: string, doNotReportErrors?: boolean } = {}) {
|
||||
export async function loadMVS(plugin: PluginContext, data: MVSData, options: MVSLoadOptions = {}) {
|
||||
plugin.errorContext.clear('mvs');
|
||||
try {
|
||||
const mvsExtensionLoaded = plugin.state.hasBehavior(MolViewSpec);
|
||||
@@ -113,13 +126,15 @@ async function loadMolstarTree(plugin: PluginContext, tree: MolstarTree, options
|
||||
}
|
||||
}
|
||||
|
||||
function molstarTreeToEntry(plugin: PluginContext, tree: MolstarTree, metadata: SnapshotMetadata & { previousTransitionDurationMs?: number }, options?: { replaceExisting?: boolean, keepCamera?: boolean }) {
|
||||
function molstarTreeToEntry(plugin: PluginContext, tree: MolstarTree, metadata: SnapshotMetadata & { previousTransitionDurationMs?: number }, options?: { replaceExisting?: boolean, keepCamera?: boolean, keepSnapshotCamera?: 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,
|
||||
};
|
||||
snapshot.camera = createPluginStateSnapshotCamera(plugin, context, metadata);
|
||||
if (!options?.keepSnapshotCamera) {
|
||||
snapshot.camera = createPluginStateSnapshotCamera(plugin, context, metadata);
|
||||
}
|
||||
snapshot.durationInMs = metadata.linger_duration_ms + (metadata.previousTransitionDurationMs ?? 0);
|
||||
|
||||
const entryParams: PluginStateSnapshotManager.EntryParams = {
|
||||
@@ -172,6 +187,8 @@ 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;
|
||||
@@ -272,6 +289,22 @@ 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`
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { omitObjectKeys, pickObjectKeys } from '../../../../mol-util/object';
|
||||
@@ -17,6 +18,7 @@ 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 } };
|
||||
|
||||
|
||||
@@ -71,6 +73,7 @@ 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) */
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { deepClone, pickObjectKeys } from '../../../../mol-util/object';
|
||||
@@ -151,6 +152,10 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -248,10 +253,37 @@ 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 */
|
||||
class Primitives extends _Base<'primitives'> implements FocusMixin {
|
||||
export 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 });
|
||||
@@ -267,6 +299,11 @@ 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 });
|
||||
@@ -277,6 +314,21 @@ 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');
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { bool, float, int, mapping, nullable, OptionalField, RequiredField, str } from '../generic/field-schema';
|
||||
import { bool, float, int, mapping, nullable, OptionalField, RequiredField, str, union } from '../generic/field-schema';
|
||||
import { SimpleParamsSchema, UnionParamsSchema } from '../generic/params-schema';
|
||||
import { ColorT, FloatList, IntList, PrimitivePositionT } from './param-types';
|
||||
import { ColorT, FloatList, IntList, PrimitivePositionT, Vector3 } from './param-types';
|
||||
|
||||
|
||||
const _TubeBase = {
|
||||
@@ -75,6 +75,39 @@ 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. */
|
||||
@@ -89,6 +122,37 @@ 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.'),
|
||||
@@ -102,6 +166,73 @@ 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',
|
||||
@@ -109,7 +240,12 @@ 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),
|
||||
},
|
||||
);
|
||||
|
||||
72
src/extensions/mvs/tree/mvs/mvs-tree-representations.ts
Normal file
72
src/extensions/mvs/tree/mvs/mvs-tree-representations.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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),
|
||||
},
|
||||
);
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -8,8 +8,9 @@
|
||||
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, RepresentationTypeT, SchemaFormatT, SchemaT, StrList, StructureTypeT, Vector3 } from './param-types';
|
||||
import { ColorT, ComponentExpressionT, ComponentSelectorT, Matrix, ParseFormatT, SchemaFormatT, SchemaT, StrList, StructureTypeT, Vector3 } from './param-types';
|
||||
|
||||
|
||||
const _DataFromUriParams = {
|
||||
@@ -42,6 +43,9 @@ 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',
|
||||
@@ -142,18 +146,29 @@ 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({
|
||||
/** Method of visual representation of the component. */
|
||||
type: RequiredField(RepresentationTypeT, 'Method of visual representation of the component.'),
|
||||
channel_id: OptionalField(nullable(str), null, 'Channel identifier (only applies when the input data contain multiple channels).'),
|
||||
}),
|
||||
},
|
||||
/** 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'],
|
||||
parent: ['representation', 'volume_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: 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"`).'),
|
||||
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"`).'),
|
||||
/** 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.'),
|
||||
}),
|
||||
@@ -181,7 +196,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'],
|
||||
parent: ['representation', 'volume_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.'),
|
||||
@@ -248,7 +263,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'],
|
||||
parent: ['root', 'component', 'component_from_uri', 'component_from_source', 'primitives', 'primitives_from_uri', 'volume', 'volume_representation'],
|
||||
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).'),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -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');
|
||||
export const ParseFormatT = literal('mmcif', 'bcif', 'pdb', 'map');
|
||||
export type ParseFormatT = ValueFor<typeof ParseFormatT>
|
||||
|
||||
/** `format` parameter values for `parse` node in Molstar tree */
|
||||
export const MolstarParseFormatT = literal('cif', 'pdb');
|
||||
export const MolstarParseFormatT = literal('cif', 'pdb', 'map');
|
||||
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');
|
||||
export const ComponentSelectorT = literal('all', 'polymer', 'protein', 'nucleic', 'branched', 'ligand', 'ion', 'water', 'coarse');
|
||||
|
||||
/** `selector` parameter values for `component` node in MVS tree */
|
||||
export const ComponentExpressionT = iots.partial({
|
||||
@@ -45,9 +45,6 @@ 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');
|
||||
|
||||
@@ -64,7 +61,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, list(PrimitiveComponentExpressionT)]);
|
||||
export const PrimitivePositionT = iots.union([Vector3, ComponentExpressionT, PrimitiveComponentExpressionT]);
|
||||
export type PrimitivePositionT = ValueFor<typeof PrimitivePositionT>
|
||||
|
||||
export const FloatList = list(float);
|
||||
@@ -93,6 +90,7 @@ 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 {
|
||||
|
||||
@@ -13,6 +13,7 @@ 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
|
||||
@@ -90,7 +91,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: ColorTheme.Category.Validation,
|
||||
category: ColorThemeCategory.Validation,
|
||||
factory: StructureQualityReportColorTheme,
|
||||
getParams: ctx => {
|
||||
const issueTypes = StructureQualityReport.getIssueTypes(ctx.structure);
|
||||
|
||||
@@ -12,6 +12,7 @@ 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);
|
||||
|
||||
@@ -70,7 +71,7 @@ export function DensityFitColorTheme(ctx: ThemeDataContext, props: {}): ColorThe
|
||||
export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationReport.Tag.DensityFit> = {
|
||||
name: ValidationReport.Tag.DensityFit,
|
||||
label: 'Density Fit',
|
||||
category: ColorTheme.Category.Validation,
|
||||
category: ColorThemeCategory.Validation,
|
||||
factory: DensityFitColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: PD.getDefaultValues({}),
|
||||
|
||||
@@ -15,6 +15,7 @@ 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);
|
||||
|
||||
@@ -108,7 +109,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: ColorTheme.Category.Validation,
|
||||
category: ColorThemeCategory.Validation,
|
||||
factory: GeometryQualityColorTheme,
|
||||
getParams: getGeometricQualityColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(getGeometricQualityColorThemeParams({})),
|
||||
|
||||
@@ -12,6 +12,7 @@ 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);
|
||||
|
||||
@@ -61,7 +62,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: ColorTheme.Category.Validation,
|
||||
category: ColorThemeCategory.Validation,
|
||||
factory: RandomCoilIndexColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: PD.getDefaultValues({}),
|
||||
|
||||
@@ -6,6 +6,7 @@ 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),
|
||||
@@ -133,7 +134,7 @@ PartialChargesThemeParams,
|
||||
> = {
|
||||
label: 'SB NCBR Partial Charges',
|
||||
name: 'sb-ncbr-partial-charges',
|
||||
category: ColorTheme.Category.Atom,
|
||||
category: ColorThemeCategory.Atom,
|
||||
factory: PartialChargesColorTheme,
|
||||
getParams: getPartialChargesThemeParams,
|
||||
defaultValues: PD.getDefaultValues(PartialChargesThemeParams),
|
||||
|
||||
@@ -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>
|
||||
@@ -392,8 +392,6 @@ namespace Canvas3D {
|
||||
let y = 0;
|
||||
let width = 128;
|
||||
let height = 128;
|
||||
let canvasScaleRatioX = 1;
|
||||
let canvasScaleRatioY = 1;
|
||||
|
||||
let forceNextRender = false;
|
||||
let currentTime = 0;
|
||||
@@ -647,7 +645,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 / canvasScaleRatioX, y / canvasScaleRatioY, cam);
|
||||
return webgl.isContextLost ? undefined : pickHelper.identify(x, y, cam);
|
||||
}
|
||||
|
||||
function commit(isSynchronous: boolean = false) {
|
||||
@@ -1160,12 +1158,6 @@ 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;
|
||||
@@ -1192,7 +1184,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 * canvasScaleRatioX, height * canvasScaleRatioY);
|
||||
Viewport.set(controls.viewport, x, y, width, height);
|
||||
hiZ.setViewport(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -130,6 +130,7 @@ 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)),
|
||||
@@ -152,6 +153,7 @@ 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));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 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>
|
||||
@@ -24,6 +24,8 @@ 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>,
|
||||
@@ -36,6 +38,9 @@ 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);
|
||||
@@ -103,6 +108,8 @@ 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)),
|
||||
@@ -131,6 +138,8 @@ 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)),
|
||||
@@ -233,6 +242,8 @@ 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)),
|
||||
@@ -255,6 +266,8 @@ 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)),
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -214,7 +214,7 @@ export namespace DirectVolume {
|
||||
const { bboxSize, bboxMin, bboxMax, gridDimension, transform: gridTransform } = directVolume;
|
||||
|
||||
const { instanceCount, groupCount } = locationIt;
|
||||
const positionIt = Utils.createPositionIterator(directVolume, transform);
|
||||
const positionIt = createPositionIterator(directVolume, transform);
|
||||
|
||||
const color = createColors(locationIt, positionIt, theme.color);
|
||||
const marker = props.instanceGranularity
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 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>
|
||||
*/
|
||||
@@ -23,6 +23,7 @@ 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'
|
||||
|
||||
@@ -105,7 +106,7 @@ export namespace Geometry {
|
||||
case 'direct-volume':
|
||||
return 1;
|
||||
case 'image':
|
||||
return arrayMax(geometry.groupTexture.ref.value.array) + 1;
|
||||
return arrayMaxPackedIntToRGB(geometry.groupTexture.ref.value.array, 4) + 1;
|
||||
case 'texture-mesh':
|
||||
return geometry.groupCount;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2025 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 } from '../../../mol-math/linear-algebra';
|
||||
import { Vec2, Vec4, Vec3, Quat, Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
@@ -59,16 +59,37 @@ 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 function create(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, image?: Image): 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 {
|
||||
return image ?
|
||||
update(imageTexture, corners, groupTexture, image) :
|
||||
fromData(imageTexture, corners, groupTexture);
|
||||
update(imageTexture, corners, groupTexture, valueTexture, trim, isoLevel, image) :
|
||||
fromData(imageTexture, corners, groupTexture, valueTexture, trim, isoLevel);
|
||||
}
|
||||
|
||||
function hashCode(image: Image) {
|
||||
@@ -77,7 +98,7 @@ namespace Image {
|
||||
]);
|
||||
}
|
||||
|
||||
function fromData(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>): Image {
|
||||
function fromData(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, valueTexture: TextureImage<Float32Array>, trim: Trim, isoLevel: number): Image {
|
||||
const boundingSphere = Sphere3D();
|
||||
let currentHash = -1;
|
||||
|
||||
@@ -90,6 +111,13 @@ 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) {
|
||||
@@ -103,7 +131,7 @@ namespace Image {
|
||||
return image;
|
||||
}
|
||||
|
||||
function update(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, image: Image): Image {
|
||||
function update(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, valueTexture: TextureImage<Float32Array>, trim: Trim, isoLevel: number, image: Image): Image {
|
||||
const width = imageTexture.width;
|
||||
const height = imageTexture.height;
|
||||
|
||||
@@ -111,6 +139,15 @@ 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;
|
||||
}
|
||||
|
||||
@@ -118,7 +155,9 @@ 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);
|
||||
return create(imageTexture, corners, groupTexture, image);
|
||||
const valueTexture = createTextureImage(0, 1, Float32Array);
|
||||
const trim = createEmptyTrim();
|
||||
return create(imageTexture, corners, groupTexture, valueTexture, trim, -1, image);
|
||||
}
|
||||
|
||||
export const Params = {
|
||||
@@ -136,12 +175,16 @@ namespace Image {
|
||||
updateBoundingSphere,
|
||||
createRenderableState,
|
||||
updateRenderableState,
|
||||
createPositionIterator: () => LocationIterator(1, 1, 1, () => NullLocation)
|
||||
createPositionIterator
|
||||
};
|
||||
|
||||
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 = Utils.createPositionIterator(image, transform);
|
||||
const positionIt = createPositionIterator(image, transform);
|
||||
|
||||
const color = createColors(locationIt, positionIt, theme.color);
|
||||
const marker = props.instanceGranularity
|
||||
@@ -186,6 +229,15 @@ 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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
25
src/mol-geo/geometry/mesh/builder/plane.ts
Normal file
25
src/mol-geo/geometry/mesh/builder/plane.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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>
|
||||
*/
|
||||
@@ -37,4 +37,53 @@ 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 };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -23,6 +23,15 @@ 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),
|
||||
};
|
||||
|
||||
@@ -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 Gianluca Tomasello <giagitom@gmail.com>
|
||||
@@ -203,6 +203,8 @@ 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'),
|
||||
@@ -323,6 +325,7 @@ export const BaseSchema = {
|
||||
uClipObjectPosition: UniformSpec('v3[]', 'material'),
|
||||
uClipObjectRotation: UniformSpec('v4[]', 'material'),
|
||||
uClipObjectScale: UniformSpec('v3[]', 'material'),
|
||||
uClipObjectTransform: UniformSpec('m4[]', 'material'),
|
||||
|
||||
aInstance: AttributeSpec('float32', 1, 1),
|
||||
/**
|
||||
|
||||
@@ -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 Gianluca Tomasello <giagitom@gmail.com>
|
||||
@@ -270,7 +270,6 @@ namespace Renderer {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: check what happens if sphere surrounds frustum fully
|
||||
if (!Frustum3D.intersectsSphere3D(frustum, r.values.boundingSphere.ref.value)) {
|
||||
return;
|
||||
}
|
||||
@@ -283,8 +282,13 @@ namespace Renderer {
|
||||
if (d - radius > maxDistance) return;
|
||||
}
|
||||
|
||||
const hasInstanceGrid = r.values.instanceGrid.ref.value.cellSize > 1;
|
||||
if (hasInstanceGrid || (hasInstanceGrid && r.values.lodLevels)) {
|
||||
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)) {
|
||||
r.cull(cameraPlane, frustum, isOccluded, ctx.stats);
|
||||
} else {
|
||||
r.uncull();
|
||||
|
||||
@@ -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>
|
||||
@@ -252,7 +252,8 @@ 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;
|
||||
opacityAverage += (1 - p.values.transparencyAverage.ref.value) * alpha * xray * fuzzy * text;
|
||||
const image = p.values.dGeometryType.ref.value === 'image' ? 0.5 : 1;
|
||||
opacityAverage += (1 - p.values.transparencyAverage.ref.value) * alpha * xray * fuzzy * text * image;
|
||||
count += 1;
|
||||
}
|
||||
return count > 0 ? opacityAverage / count : 0;
|
||||
@@ -272,7 +273,8 @@ 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 === 'text' ||
|
||||
p.values.dGeometryType.ref.value === 'image'
|
||||
) transparenyValues.push(0.5);
|
||||
if (p.values.transparencyMin.ref.value > 0) transparenyValues.push(p.values.transparencyMin.ref.value);
|
||||
transparencyMin = Math.min(transparencyMin, ...transparenyValues);
|
||||
|
||||
@@ -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) / 16777215.0;
|
||||
vPaletteV = ((vColor.r * 256.0 * 256.0 * 255.0 + vColor.g * 256.0 * 255.0 + vColor.b * 255.0) - 1.0) / PALETTE_SCALE;
|
||||
#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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
export const clip_instance = `
|
||||
#if defined(dClipVariant_instance) && dClipObjectCount != 0
|
||||
vec4 mCenter = uModel * aTransform * vec4(uInvariantBoundingSphere.xyz, 1.0);
|
||||
if (clipTest(vec4(mCenter.xyz, uInvariantBoundingSphere.w)))
|
||||
vec3 mCenter = (uModel * aTransform * vec4(uInvariantBoundingSphere.xyz, 1.0)).xyz;
|
||||
if (clipTest(mCenter)) {
|
||||
// move out of [ -w, +w ] to 'discard' in vert shader
|
||||
gl_Position.z = 2.0 * gl_Position.w;
|
||||
}
|
||||
#endif
|
||||
`;
|
||||
@@ -1,6 +1,6 @@
|
||||
export const clip_pixel = `
|
||||
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
|
||||
if (clipTest(vec4(vModelPosition, 0.0)))
|
||||
if (clipTest(vModelPosition))
|
||||
discard;
|
||||
#endif
|
||||
`;
|
||||
@@ -1,112 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2025 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 = `
|
||||
#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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
#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;
|
||||
}
|
||||
|
||||
#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 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
|
||||
|
||||
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;
|
||||
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
|
||||
|
||||
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));
|
||||
#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)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma unroll_loop_end
|
||||
return false;
|
||||
}
|
||||
`;
|
||||
@@ -15,6 +15,7 @@ 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)
|
||||
|
||||
@@ -19,6 +19,7 @@ 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;
|
||||
|
||||
@@ -42,6 +42,8 @@ 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2025 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(vec4(modelPosition, 0.0))) {
|
||||
if (clipTest(modelPosition)) {
|
||||
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(vec4(modelPosition, 0.0))) {
|
||||
if (clipTest(modelPosition)) {
|
||||
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(vec4(modelPosition, 0.0))) {
|
||||
if (clipTest(modelPosition)) {
|
||||
objectClipped = true;
|
||||
fragmentDepth = -1.0;
|
||||
#ifdef dSolidInterior
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -38,6 +38,8 @@ uniform float uIsOrtho;
|
||||
uniform vec3 uCameraDir;
|
||||
|
||||
void main() {
|
||||
int vertexId = VertexID;
|
||||
|
||||
#include assign_group
|
||||
#include assign_color_varying
|
||||
#include assign_marker_varying
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2025 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,6 +18,7 @@ precision highp int;
|
||||
uniform vec3 uClipObjectPosition[dClipObjectCount];
|
||||
uniform vec4 uClipObjectRotation[dClipObjectCount];
|
||||
uniform vec3 uClipObjectScale[dClipObjectCount];
|
||||
uniform mat4 uClipObjectTransform[dClipObjectCount];
|
||||
#endif
|
||||
#include common_clip
|
||||
|
||||
@@ -122,6 +123,7 @@ uniform mat4 uCartnToUnit;
|
||||
#endif
|
||||
|
||||
#ifdef dUsePalette
|
||||
uniform vec2 uPaletteDomain;
|
||||
uniform sampler2D tPalette;
|
||||
#endif
|
||||
|
||||
@@ -240,7 +242,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
|
||||
|
||||
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
|
||||
vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
|
||||
if (clipTest(vec4(vModelPosition, 0.0))) {
|
||||
if (clipTest(modelPosition)) {
|
||||
prevValue = value;
|
||||
pos += step;
|
||||
continue;
|
||||
@@ -271,7 +273,8 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
|
||||
#endif
|
||||
|
||||
#if defined(dColorType_direct) && defined(dUsePalette)
|
||||
material.rgb = texture2D(tPalette, vec2(value, 0.0)).rgb;
|
||||
float paletteValue = (value - uPaletteDomain[0]) / (uPaletteDomain[1] - uPaletteDomain[0]);
|
||||
material.rgb = texture2D(tPalette, vec2(clamp(paletteValue, 0.0, 1.0), 0.0)).rgb;
|
||||
#elif defined(dColorType_uniform)
|
||||
material.rgb = uColor;
|
||||
#elif defined(dColorType_instance)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -12,12 +12,46 @@ 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;
|
||||
@@ -25,6 +59,19 @@ 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
|
||||
@@ -91,36 +138,102 @@ varying float vInstance;
|
||||
#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)
|
||||
vec4 imageData = biCubic(tImageTex, vUv);
|
||||
#ifdef dUsePalette
|
||||
vec4 material = texture2D(tImageTex, vUv);
|
||||
if (material.rgb != vec3(1.0)) {
|
||||
material = biCubic(tImageTex, vUv);
|
||||
}
|
||||
#else
|
||||
vec4 material = biCubic(tImageTex, vUv);
|
||||
#endif
|
||||
#else
|
||||
vec4 imageData = texture2D(tImageTex, vUv);
|
||||
vec4 material = texture2D(tImageTex, vUv);
|
||||
#endif
|
||||
imageData.a = clamp(imageData.a, 0.0, 1.0);
|
||||
if (imageData.a > 0.9) imageData.a = 1.0;
|
||||
|
||||
imageData.a *= uAlpha;
|
||||
if (imageData.a < 0.05)
|
||||
discard;
|
||||
if (uIsoLevel >= 0.0) {
|
||||
if (texture2D(tValueTex, vUv).r < uIsoLevel) discard;
|
||||
|
||||
material.a = uAlpha;
|
||||
} else {
|
||||
if (material.a == 0.0) discard;
|
||||
|
||||
material.a *= uAlpha;
|
||||
}
|
||||
|
||||
float fragmentDepth = gl_FragCoord.z;
|
||||
|
||||
if ((uRenderMask == MaskOpaque && imageData.a < 1.0) ||
|
||||
(uRenderMask == MaskTransparent && imageData.a == 1.0)
|
||||
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)
|
||||
) {
|
||||
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 (imageData.a < 0.3)
|
||||
discard;
|
||||
if (group == -1.0) discard;
|
||||
|
||||
#include check_picking_alpha
|
||||
#ifdef requiredDrawBuffers
|
||||
gl_FragColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
|
||||
gl_FragData[1] = vec4(packIntToRGB(vInstance), 1.0);
|
||||
gl_FragData[2] = vec4(texture2D(tGroupTex, vUv).rgb, 1.0);
|
||||
gl_FragData[2] = vec4(packIntToRGB(group), 1.0);
|
||||
gl_FragData[3] = packDepthToRGBA(fragmentDepth);
|
||||
#else
|
||||
gl_FragColor = vColor;
|
||||
@@ -129,50 +242,61 @@ void main() {
|
||||
} else if (uPickType == 2) {
|
||||
gl_FragColor = vec4(packIntToRGB(vInstance), 1.0);
|
||||
} else {
|
||||
gl_FragColor = vec4(texture2D(tGroupTex, vUv).rgb, 1.0);
|
||||
gl_FragColor = vec4(packIntToRGB(group), 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, imageData.a);
|
||||
gl_FragColor = packDepthWithAlphaToRGBA(fragmentDepth, material.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 || imageData.a < 0.05)
|
||||
if (marker > 0.0)
|
||||
discard;
|
||||
gl_FragColor = packDepthToRGBA(fragmentDepth);
|
||||
} else {
|
||||
if (marker == 0.0 || imageData.a < 0.05)
|
||||
if (marker == 0.0)
|
||||
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;
|
||||
gl_FragColor = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0);
|
||||
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);
|
||||
}
|
||||
#elif defined(dRenderVariant_emissive)
|
||||
gl_FragColor = vec4(0.0);
|
||||
gl_FragColor = vec4(emissive);
|
||||
#elif defined(dRenderVariant_color) || defined(dRenderVariant_tracing)
|
||||
gl_FragColor = imageData;
|
||||
#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
|
||||
|
||||
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
|
||||
}
|
||||
// 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
|
||||
|
||||
gl_FragColor = material;
|
||||
#include apply_marker_color
|
||||
|
||||
#if defined(dRenderVariant_color)
|
||||
@@ -180,8 +304,8 @@ void main() {
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#elif defined(dRenderVariant_tracing)
|
||||
gl_FragData[1] = vec4(normalize(vViewPosition), 0.0);
|
||||
gl_FragData[2] = vec4(imageData.rgb, uDensity);
|
||||
gl_FragData[1] = vec4(normalize(vViewPosition), emissive);
|
||||
gl_FragData[2] = vec4(material.rgb, uDensity);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -19,6 +19,8 @@ varying vec2 vUv;
|
||||
varying float vInstance;
|
||||
|
||||
void main() {
|
||||
int vertexId = VertexID;
|
||||
|
||||
#include assign_position
|
||||
|
||||
vUv = aUv;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 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>
|
||||
*
|
||||
@@ -40,6 +40,7 @@ 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
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -32,6 +32,8 @@ attribute float aInstance;
|
||||
varying vec3 vNormal;
|
||||
|
||||
void main(){
|
||||
int vertexId = VertexID;
|
||||
|
||||
#include assign_group
|
||||
#include assign_marker_varying
|
||||
#include assign_clipping_varying
|
||||
@@ -40,7 +42,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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 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>
|
||||
*/
|
||||
@@ -24,6 +24,8 @@ attribute float aInstance;
|
||||
attribute float aGroup;
|
||||
|
||||
void main(){
|
||||
int vertexId = VertexID;
|
||||
|
||||
#include assign_group
|
||||
#include assign_color_varying
|
||||
#include assign_marker_varying
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2025 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(vec4(modelPos, 0.0))) {
|
||||
if (clipTest(modelPos)) {
|
||||
objectClipped = true;
|
||||
fragmentDepth = -1.0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -70,7 +70,9 @@ void main(void){
|
||||
mapping = vec2(1.0, -1.0);
|
||||
}
|
||||
|
||||
vec4 positionGroup = readFromTexture(tPositionGroup, VertexID / 6, uTexDim);
|
||||
int vertexId = VertexID / 6;
|
||||
|
||||
vec4 positionGroup = readFromTexture(tPositionGroup, vertexId, uTexDim);
|
||||
vec3 position = positionGroup.rgb;
|
||||
float group = positionGroup.a;
|
||||
|
||||
@@ -128,7 +130,7 @@ void main(void){
|
||||
}
|
||||
|
||||
#if defined(dClipPrimitive) && !defined(dClipVariant_instance) && dClipObjectCount != 0
|
||||
if (clipTest(vec4(vModelPosition.xyz, 0.0))) {
|
||||
if (clipTest(vModelPosition)) {
|
||||
// move out of [ -w, +w ] to 'discard' in vert shader
|
||||
gl_Position.z = 2.0 * gl_Position.w;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -38,6 +38,8 @@ varying vec2 vTexCoord;
|
||||
#include matrix_scale
|
||||
|
||||
void main(void){
|
||||
int vertexId = VertexID;
|
||||
|
||||
#include assign_group
|
||||
#include assign_color_varying
|
||||
#include assign_marker_varying
|
||||
|
||||
@@ -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 Gianluca Tomasello <giagitom@gmail.com>
|
||||
@@ -21,6 +21,10 @@ 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'
|
||||
@@ -311,10 +315,16 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (elementsBuffer) {
|
||||
instancedArrays.drawElementsInstanced(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount);
|
||||
} else {
|
||||
instancedArrays.drawArraysInstanced(glDrawMode, 0, drawCount, instanceCount);
|
||||
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 (isTimingMode) {
|
||||
stats.calls.drawInstanced += 1;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 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>
|
||||
*/
|
||||
|
||||
import { Mat3, Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
|
||||
import { Mat3, Mat4, Quat, 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,6 +18,7 @@ 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[]
|
||||
@@ -39,6 +40,7 @@ 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;
|
||||
@@ -77,6 +79,7 @@ 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;
|
||||
@@ -107,6 +110,7 @@ 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';
|
||||
}
|
||||
|
||||
@@ -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.399, IHM 1.27, MA 1.4.6.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.401, IHM 1.27, MA 1.4.7.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -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.399, IHM 1.27, MA 1.4.6.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.401, IHM 1.27, MA 1.4.7.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -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.399, IHM 1.27, MA 1.4.6.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.401, IHM 1.27, MA 1.4.7.
|
||||
*
|
||||
* @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' | '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' | 'pdb-dev' | '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
|
||||
* (PTO) for Phosphothreonine
|
||||
* (TPO) 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: str,
|
||||
db_name: Aliased<'BIOISIS' | 'BMCD' | 'BMRB' | 'EMDB' | 'NDB' | 'PDB' | 'PDB-Dev' | 'SASBDB' | 'TargetDB' | 'TargetTrack'>(str),
|
||||
/**
|
||||
* A description of the related entry.
|
||||
*/
|
||||
@@ -4939,25 +4939,19 @@ export const mmCIF_Schema = {
|
||||
*/
|
||||
ma_model_list: {
|
||||
/**
|
||||
* A unique identifier for the model / model group combination.
|
||||
* A unique identifier for the structural model being deposited.
|
||||
*/
|
||||
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.
|
||||
* 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.
|
||||
* This data item has been deprecated with dictionary version 1.4.7.
|
||||
* See ma_model_group category.
|
||||
*/
|
||||
model_group_id: int,
|
||||
/**
|
||||
@@ -4966,6 +4960,8 @@ 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,
|
||||
/**
|
||||
@@ -5131,11 +5127,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]' | 'PAE' | 'pTM' | 'ipTM' | 'contact probability' | 'other'>(str),
|
||||
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),
|
||||
/**
|
||||
* The mode of calculation of the QA metric.
|
||||
*/
|
||||
mode: Aliased<'local' | 'global' | 'local-pairwise'>(str),
|
||||
mode: Aliased<'local' | 'global' | 'local-pairwise' | 'per-feature' | 'per-feature-pair'>(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
|
||||
@@ -5154,7 +5150,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.model_id
|
||||
* This data item is a pointer to _ma_model_list.ordinal_id
|
||||
* in the MA_MODEL_LIST category.
|
||||
*/
|
||||
model_id: int,
|
||||
@@ -5172,6 +5168,10 @@ 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.model_id
|
||||
* This data item is a pointer to _ma_model_list.ordinal_id
|
||||
* in the MA_MODEL_LIST category.
|
||||
*/
|
||||
model_id: int,
|
||||
@@ -5219,6 +5219,15 @@ 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: {
|
||||
/**
|
||||
@@ -5227,7 +5236,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.model_id
|
||||
* This data item is a pointer to _ma_model_list.ordinal_id
|
||||
* in the MA_MODEL_LIST category.
|
||||
*/
|
||||
model_id: int,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -129,6 +129,12 @@ 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);
|
||||
@@ -218,6 +224,18 @@ 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 };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2022-2025 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, out, plane.normal, -distanceToPoint(plane, point));
|
||||
return Vec3.scaleAndAdd(out, point, plane.normal, -distanceToPoint(plane, point));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2025 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,6 +27,7 @@ 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 }
|
||||
@@ -282,6 +283,12 @@ 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;
|
||||
|
||||
@@ -367,6 +374,12 @@ 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];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2025 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) {
|
||||
// here, axis can be [0,0,0] but the rotation is a simple flip
|
||||
return Mat4.fromScaling(mat, negUnit);
|
||||
// choose arbitrary orthogonal axis
|
||||
return Mat4.fromRotation(mat, Math.PI, Math.abs(a[0]) < 0.9 ? Vec3.unitX : Vec3.unitZ);
|
||||
}
|
||||
const axis = cross(rotTemp, a, b);
|
||||
return Mat4.fromRotation(mat, by, axis);
|
||||
@@ -645,6 +645,11 @@ 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)}]`;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -16,6 +16,7 @@ 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,
|
||||
@@ -50,6 +51,7 @@ 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(),
|
||||
|
||||
@@ -15,6 +15,7 @@ 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.';
|
||||
@@ -76,7 +77,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: ColorTheme.Category.Residue,
|
||||
category: ColorThemeCategory.Residue,
|
||||
factory: AccessibleSurfaceAreaColorTheme,
|
||||
getParams: getAccessibleSurfaceAreaColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(AccessibleSurfaceAreaColorThemeParams),
|
||||
|
||||
@@ -15,6 +15,7 @@ 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.';
|
||||
@@ -110,7 +111,7 @@ export function InteractionTypeColorTheme(ctx: ThemeDataContext, props: PD.Value
|
||||
export const InteractionTypeColorThemeProvider: ColorTheme.Provider<InteractionTypeColorThemeParams, 'interaction-type'> = {
|
||||
name: 'interaction-type',
|
||||
label: 'Interaction Type',
|
||||
category: ColorTheme.Category.Misc,
|
||||
category: ColorThemeCategory.Misc,
|
||||
factory: InteractionTypeColorTheme,
|
||||
getParams: getInteractionTypeColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(InteractionTypeColorThemeParams),
|
||||
|
||||
@@ -11,6 +11,7 @@ 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`).';
|
||||
@@ -63,7 +64,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: ColorTheme.Category.Misc,
|
||||
category: ColorThemeCategory.Misc,
|
||||
factory: CrossLinkColorTheme,
|
||||
getParams: getCrossLinkColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(CrossLinkColorThemeParams),
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
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';
|
||||
@@ -82,7 +83,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: ColorTheme.Category.Residue,
|
||||
category: ColorThemeCategory.Residue,
|
||||
factory: SIFTSMappingColorTheme,
|
||||
getParams: getSIFTSMappingColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(SIFTSMappingColorThemeParams),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 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>
|
||||
*/
|
||||
@@ -31,4 +31,17 @@ export function isDataLocation(x: any): x is DataLocation {
|
||||
return !!x && x.kind === 'data-location';
|
||||
}
|
||||
|
||||
export type Location = StructureElement.Location | Bond.Location | ShapeGroup.Location | PositionLocation | DataLocation | NullLocation | Volume.Segment.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
|
||||
|
||||
@@ -9,6 +9,7 @@ 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 */
|
||||
@@ -60,7 +61,8 @@ export type CoarseElements = CoarsedElementKeys & CoarseElementData & CoarseRang
|
||||
export interface CoarseHierarchy {
|
||||
isDefined: boolean,
|
||||
spheres: CoarseElements,
|
||||
gaussians: CoarseElements
|
||||
gaussians: CoarseElements,
|
||||
index: CoarseIndex
|
||||
}
|
||||
|
||||
const EmptyCoarseElements: CoarseElements = {
|
||||
@@ -81,10 +83,39 @@ 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
|
||||
gaussians: EmptyCoarseElements,
|
||||
index: EmptyCoarseIndex,
|
||||
};
|
||||
}
|
||||
102
src/mol-model/structure/model/properties/utils/coarse-index.ts
Normal file
102
src/mol-model/structure/model/properties/utils/coarse-index.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2025 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) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_asym_id.value(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])),
|
||||
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) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_entity_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]))
|
||||
};
|
||||
|
||||
const coarse = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -8,6 +8,7 @@
|
||||
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 {
|
||||
@@ -76,6 +77,59 @@ 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 };
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -220,6 +220,18 @@ 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) {
|
||||
|
||||
@@ -63,13 +63,13 @@ const DownloadStructure = StateAction.build({
|
||||
}, { pivot: 'id' }),
|
||||
options
|
||||
}, { isFlat: true, label: 'PDB' }),
|
||||
'pdb-dev': PD.Group({
|
||||
'pdb-ihm': PD.Group({
|
||||
provider: PD.Group({
|
||||
id: PD.Text('PDBDEV_00000001', { label: 'PDB-Dev Id(s)', description: 'One or more comma/space separated ids.' }),
|
||||
id: PD.Text('8zzc', { label: 'PDB-IHM 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-Dev' }),
|
||||
}, { isFlat: true, label: 'PDB-IHM' }),
|
||||
'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-dev':
|
||||
case 'pdb-ihm':
|
||||
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-dev.wwpdb.org/bcif/${id.toLowerCase()}.bcif`
|
||||
: `https://pdb-dev.wwpdb.org/cif/${id.toLowerCase()}.cif`;
|
||||
? `https://pdb-ihm.org/bcif/${id.toLowerCase()}.bcif`
|
||||
: `https://pdb-ihm.org/cif/${id.toLowerCase()}.cif`;
|
||||
}
|
||||
const nId = map(id.toUpperCase());
|
||||
return src.params.provider.encoding === 'bcif'
|
||||
? `https://pdb-dev.wwpdb.org/bcif/${nId}.bcif`
|
||||
: `https://pdb-dev.wwpdb.org/cif/${nId}.cif`;
|
||||
? `https://pdb-ihm.org/bcif/${nId}.bcif`
|
||||
: `https://pdb-ihm.org/cif/${nId}.cif`;
|
||||
},
|
||||
id => { const nId = id.toUpperCase(); return nId.match(/^[1-9][A-Z0-9]{3}$/) ? `PDB-Dev: ${nId}` : map(nId); },
|
||||
id => { const nId = id.toUpperCase(); return nId.match(/^[1-9][A-Z0-9]{3}$/) ? `PDB-IHM: ${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.pdbjbk1.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.pdbjlc1.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']) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2025 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';
|
||||
@@ -121,7 +122,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);
|
||||
@@ -151,7 +152,7 @@ const empty = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-empty',
|
||||
display: { name: 'Empty', description: 'Removes all existing representations.' },
|
||||
async apply(ref, params, plugin) {
|
||||
return { };
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -396,12 +397,9 @@ const illustrative = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-illustrative',
|
||||
display: {
|
||||
name: 'Illustrative', group: 'Miscellaneous',
|
||||
description: '...'
|
||||
description: 'Show everything in spacefill representation with illustrative colors and ignore light.'
|
||||
},
|
||||
params: () => ({
|
||||
...CommonParams,
|
||||
showCarbohydrateSymbol: PD.Boolean(false)
|
||||
}),
|
||||
params: () => CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
@@ -416,7 +414,47 @@ 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' }, { tag: 'all' }),
|
||||
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' }),
|
||||
};
|
||||
await update.commit({ revertOnError: true });
|
||||
await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params);
|
||||
@@ -474,6 +512,7 @@ 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
Reference in New Issue
Block a user