mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 22:54:22 +08:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ac6f5c202 | ||
|
|
5726515707 | ||
|
|
f2ee7d1470 | ||
|
|
4140412e06 | ||
|
|
44ed142521 | ||
|
|
1ae0bbc150 | ||
|
|
8213611293 | ||
|
|
2697634a9f | ||
|
|
d7ba9e0c61 | ||
|
|
c99c4342b7 | ||
|
|
f410e27d1a | ||
|
|
e6d54412cf | ||
|
|
6238684819 | ||
|
|
ea07cd89de | ||
|
|
a7330f40d7 | ||
|
|
92c55ffe35 | ||
|
|
c21ba08fc7 | ||
|
|
ba3a716900 | ||
|
|
3133dc1543 | ||
|
|
fe2541f9e8 | ||
|
|
27af73f97f | ||
|
|
e9a442ca6e | ||
|
|
e86e282bb4 | ||
|
|
213506dff0 | ||
|
|
bc7aa7c9aa | ||
|
|
b234bf8890 | ||
|
|
36b4dcf7a8 |
34
CHANGELOG.md
34
CHANGELOG.md
@@ -5,6 +5,40 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v5.5.0] - 2025-12-22
|
||||
- Viewer app
|
||||
- Move viewer extensions, options, and presets to a separate file
|
||||
- Add `molstar.lib` export providing access to a wide range of functionality previously not available from the compiled bundle
|
||||
- Add `Viewer.subscribe` method that keeps track of subscribed plugin events and disposes them together with the parent viewer
|
||||
- Add `Viewer.structureInteractivity` that makes it easy to highlight/select elements on the loaded structure
|
||||
- Add `viewportBackgroundColor` and `viewportFocusBehavior` options
|
||||
- Add `mvs.html` example to showcase the new functionality combined with MolViewSpec
|
||||
- Add dark and blue color theme support (import `theme/dark.css` or `theme/blue.css` instead of the default `molstar.css`)
|
||||
- MolViewSpec extension
|
||||
- Add `tryGetPrimitivesFromLoci` that makes it easier to access primitive element data from hover/click interactions
|
||||
- Add `getCurrentMVSSnapshot` to obtain source data for the currently displayed snapshot
|
||||
- Add TM-align structure-based protein alignment algorithm
|
||||
- New `TMAlign` namespace in `mol-math/linear-algebra/3d/tm-align.ts`
|
||||
- New `tmAlign` function in `mol-model/structure/structure/util/tm-align.ts`
|
||||
- Returns TM-score, RMSD, alignment mapping, and transformation matrix
|
||||
- Molecular Surface
|
||||
- Fix "auto" quality params not hidden
|
||||
- Fix calculation when probe diameter is smaller then resolution
|
||||
- Fix webgl1 shader syntax
|
||||
- Fix program not compiled for sync picking
|
||||
- Fix missing `gl.flush` for async picking (needed for Safari)
|
||||
- Add Residue Charge color scheme (#1722)
|
||||
- Add dropdown indicator for mapped parameter definitions and adjust "more options" icon
|
||||
- Fix `flipSided` for meshes
|
||||
- [Breaking] Interior coloring
|
||||
- Remove global `interiorDarkening`, `interiorColorFlag`, `interiorColor`
|
||||
- Add per-geometry `interiorColor`, `interiorSubstance`
|
||||
- Add `label/auth_comp_id` to `StructureProperties.residue`
|
||||
- Previously, this has been only been present on `.atom` (since residue name can alter on per-atom basis), but this has been a bit confusing for the general use-case
|
||||
- Move canvas "checkered background" logic to `canvas3d.ts` and only apply it when `transparentBackground` is on
|
||||
- This prevents ugly flickering during plugin initialization
|
||||
- Fix unit hash collision issues (#1721)
|
||||
|
||||
## [v5.4.2] - 2025-12-07
|
||||
- Fix postprocessing issues with SSAO and outlines for large structures (#1387)
|
||||
- Reduce automatic quality on standalone HMD devices
|
||||
|
||||
1
breaking-v6-changes.md
Normal file
1
breaking-v6-changes.md
Normal file
@@ -0,0 +1 @@
|
||||
- Remove `checkeredCanvasBackground` from `PluginContext` and `PluginContainer`
|
||||
@@ -15,10 +15,24 @@ There are 4 basic ways of instantiating the Mol* plugin.
|
||||
|
||||
## ``Viewer`` wrapper
|
||||
|
||||
- The most basic usage is to use the ``Viewer`` wrapper. This is best suited for use cases that do not require much custom behavior and are mostly about just displaying a structure.
|
||||
- See ``Viewer`` class is defined in [src/apps/viewer/app.ts](https://github.com/molstar/molstar/blob/master/src/apps/viewer/app.ts) for available methods and options.
|
||||
- The most basic usage is to use the ``Viewer`` wrapper. This is best suited for use cases that do not require custom behavior and are mostly about just displaying a structure.
|
||||
- See ``Viewer`` class is defined in [src/apps/viewer/app.ts](https://github.com/molstar/molstar/blob/master/src/apps/viewer/app.ts) for available methods
|
||||
- See [options.ts](https://github.com/molstar/molstar/blob/master/src/apps/viewer/options.ts) for available plugin options
|
||||
- See [embedded.html](https://github.com/molstar/molstar/blob/master/src/apps/viewer/embedded.html) and [mvs.html](https://github.com/molstar/molstar/blob/master/src/apps/viewer/mvs.html) for example usage
|
||||
- Importing `molstar.js` will expose `molstar.lib` namespace that allow accessing various functionality without a bundler such as WebPack or esbuild. See the `mvs` example above for basic usage.
|
||||
- Alternative color themes can be used by importing `theme/dark.css` (or `light/blue`) instead of `molstar.css`
|
||||
|
||||
Example usage without using WebPack:
|
||||
### molstar.js and molstar.css sources
|
||||
|
||||
- Download `molstar` NPM package and use the files from `build/viewer` diractory
|
||||
- Use `jsdelivr` CDN
|
||||
- `<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.js" />`
|
||||
- `<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.css" />`
|
||||
- `@latest` can be replaced by a specific Mol* version, e.g., `@5.4.2`
|
||||
- Clone & build the GitHub repository
|
||||
- This option allows for quite straightforward extension customization, e.g., not including movie export, which reduces the bundle size by ~0.5MB
|
||||
|
||||
### Example
|
||||
|
||||
```HTML
|
||||
<style>
|
||||
@@ -35,7 +49,7 @@ Example usage without using WebPack:
|
||||
- the folder build/viewer after cloning and building the molstar package
|
||||
- from the build/viewer folder in the Mol* NPM package
|
||||
-->
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
<link rel="stylesheet" type="text/css" href="./molstar.css" />
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
|
||||
<div id="app"></div>
|
||||
@@ -62,13 +76,15 @@ Example usage without using WebPack:
|
||||
</script>
|
||||
```
|
||||
|
||||
When using WebPack (or possibly other build tool) with the Mol* NPM package installed, the viewer class can be imported using
|
||||
### Using WebPack/esbuild/...
|
||||
|
||||
When using WebPack (or other bundler) with the Mol* NPM package installed, the viewer class can be imported using
|
||||
|
||||
```ts
|
||||
import { Viewer } from 'molstar/build/viewer/molstar'
|
||||
import { Viewer } from 'molstar/lib/apps/viewer/app'
|
||||
|
||||
function initViewer(target: string | HTMLElement) {
|
||||
return new Viewer(target, { /* options */})
|
||||
return Viewer.create(target, { /* options */}) // returns a Promise
|
||||
}
|
||||
```
|
||||
|
||||
@@ -139,6 +155,8 @@ export function MolStarWrapper() {
|
||||
// In debug mode of react's strict mode, this code will
|
||||
// be called twice in a row, which might result in unexpected behavior.
|
||||
useEffect(() => {
|
||||
// By default, react will call each useEffect twice if using Strict mode in
|
||||
// debug build, it is recommended to disable strict mode for this reason if possible
|
||||
async function init() {
|
||||
window.molstar = await createPluginUI({
|
||||
target: parent.current as HTMLDivElement,
|
||||
|
||||
152
docs/docs/plugin/superposition.md
Normal file
152
docs/docs/plugin/superposition.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# Structure Superposition
|
||||
|
||||
Mol* provides utilities for superposing protein structures, including both sequence-independent (RMSD-based) and structure-based (TM-align) methods.
|
||||
|
||||
## RMSD-based Superposition
|
||||
|
||||
The basic superposition method uses the Kabsch algorithm to minimize RMSD between corresponding atoms:
|
||||
|
||||
```typescript
|
||||
import { superpose } from 'molstar/lib/mol-model/structure/structure/util/superposition';
|
||||
import { StructureSelection, QueryContext } from 'molstar/lib/mol-model/structure';
|
||||
import { compile } from 'molstar/lib/mol-script/runtime/query/compiler';
|
||||
import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder';
|
||||
|
||||
// Create a query for C-alpha atoms
|
||||
const caQuery = compile<StructureSelection>(MS.struct.generator.atomGroups({
|
||||
'atom-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_atom_id(), 'CA'])
|
||||
}));
|
||||
|
||||
// Get selections from two structures
|
||||
const sel1 = StructureSelection.toLociWithCurrentUnits(caQuery(new QueryContext(structure1)));
|
||||
const sel2 = StructureSelection.toLociWithCurrentUnits(caQuery(new QueryContext(structure2)));
|
||||
|
||||
// Compute superposition (returns transformation matrices)
|
||||
const transforms = superpose([sel1, sel2]);
|
||||
|
||||
// transforms[0].bTransform contains the Mat4 to superpose structure2 onto structure1
|
||||
```
|
||||
|
||||
## TM-align Superposition
|
||||
|
||||
TM-align is a structure-based alignment algorithm that produces the TM-score, a length-independent metric for comparing protein structures. Unlike RMSD, TM-score is normalized to [0, 1] and is more robust for comparing proteins of different sizes.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { tmAlign } from 'molstar/lib/mol-model/structure/structure/util/tm-align';
|
||||
import { StructureElement } from 'molstar/lib/mol-model/structure';
|
||||
|
||||
// Get C-alpha Loci from two structures (see selection examples above)
|
||||
const loci1: StructureElement.Loci = /* ... */;
|
||||
const loci2: StructureElement.Loci = /* ... */;
|
||||
|
||||
// Run TM-align
|
||||
const result = tmAlign(loci1, loci2);
|
||||
|
||||
console.log('TM-score (normalized by structure 1):', result.tmScoreA);
|
||||
console.log('TM-score (normalized by structure 2):', result.tmScoreB);
|
||||
console.log('RMSD:', result.rmsd);
|
||||
console.log('Aligned residues:', result.alignedLength);
|
||||
|
||||
// result.bTransform is a Mat4 to transform structure2 onto structure1
|
||||
```
|
||||
|
||||
### TM-align Result
|
||||
|
||||
The `tmAlign` function returns a `TMAlignResult` object with the following properties:
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `bTransform` | `Mat4` | Transformation matrix to superpose structure B onto A |
|
||||
| `tmScoreA` | `number` | TM-score normalized by length of structure A |
|
||||
| `tmScoreB` | `number` | TM-score normalized by length of structure B |
|
||||
| `rmsd` | `number` | RMSD of aligned residue pairs (in Angstroms) |
|
||||
| `alignedLength` | `number` | Number of aligned residue pairs |
|
||||
| `sequenceIdentity` | `number` | Sequence identity of aligned residues (0-1) |
|
||||
| `alignmentA` | `number[]` | Indices of aligned residues in structure A |
|
||||
| `alignmentB` | `number[]` | Indices of aligned residues in structure B |
|
||||
|
||||
### Understanding TM-score
|
||||
|
||||
The TM-score is calculated as:
|
||||
|
||||
$$\text{TM-score} = \frac{1}{L} \sum_{i=1}^{L_{ali}} \frac{1}{1 + (d_i/d_0)^2}$$
|
||||
|
||||
Where:
|
||||
- $L$ is the length of the reference protein
|
||||
- $L_{ali}$ is the number of aligned residues
|
||||
- $d_i$ is the distance between the $i$-th pair of aligned residues after superposition
|
||||
- $d_0 = 1.24 \sqrt[3]{L - 15} - 1.8$ is a length-dependent normalization factor
|
||||
|
||||
**TM-score interpretation:**
|
||||
- TM-score > 0.5: Generally indicates proteins with the same fold
|
||||
- TM-score > 0.17: Generally indicates proteins with random structural similarity
|
||||
|
||||
### Low-level API
|
||||
|
||||
For direct coordinate-based alignment without structures, use the `TMAlign` namespace:
|
||||
|
||||
```typescript
|
||||
import { TMAlign } from 'molstar/lib/mol-math/linear-algebra/3d/tm-align';
|
||||
|
||||
// Create position arrays
|
||||
const posA = TMAlign.Positions.empty(lengthA);
|
||||
const posB = TMAlign.Positions.empty(lengthB);
|
||||
|
||||
// Fill in coordinates
|
||||
for (let i = 0; i < lengthA; i++) {
|
||||
posA.x[i] = /* x coordinate */;
|
||||
posA.y[i] = /* y coordinate */;
|
||||
posA.z[i] = /* z coordinate */;
|
||||
}
|
||||
// ... similarly for posB
|
||||
|
||||
// Compute alignment
|
||||
const result = TMAlign.compute({ a: posA, b: posB });
|
||||
```
|
||||
|
||||
### Complete Example: Aligning Two PDB Structures
|
||||
|
||||
```typescript
|
||||
import { PluginContext } from 'molstar/lib/mol-plugin/context';
|
||||
import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder';
|
||||
import { compile } from 'molstar/lib/mol-script/runtime/query/compiler';
|
||||
import { StructureSelection, QueryContext, StructureElement } from 'molstar/lib/mol-model/structure';
|
||||
import { tmAlign } from 'molstar/lib/mol-model/structure/structure/util/tm-align';
|
||||
import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms';
|
||||
import { Mat4 } from 'molstar/lib/mol-math/linear-algebra';
|
||||
|
||||
async function alignStructures(plugin: PluginContext, structure1: any, structure2: any) {
|
||||
// Query for C-alpha atoms in chain A
|
||||
const caQuery = compile<StructureSelection>(MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), 'A']),
|
||||
'atom-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_atom_id(), 'CA'])
|
||||
}));
|
||||
|
||||
// Get structure data
|
||||
const data1 = structure1.cell?.obj?.data;
|
||||
const data2 = structure2.cell?.obj?.data;
|
||||
|
||||
// Create selections
|
||||
const sel1 = StructureSelection.toLociWithCurrentUnits(caQuery(new QueryContext(data1)));
|
||||
const sel2 = StructureSelection.toLociWithCurrentUnits(caQuery(new QueryContext(data2)));
|
||||
|
||||
// Run TM-align
|
||||
const result = tmAlign(sel1, sel2);
|
||||
|
||||
// Apply transformation to structure2
|
||||
const b = plugin.state.data.build().to(structure2)
|
||||
.insert(StateTransforms.Model.TransformStructureConformation, {
|
||||
transform: { name: 'matrix', params: { data: result.bTransform, transpose: false } }
|
||||
});
|
||||
await plugin.runTask(plugin.state.data.updateTree(b));
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- Zhang Y, Skolnick J. "TM-align: a protein structure alignment algorithm based on the TM-score." *Nucleic Acids Research* 33, 2302-2309 (2005). DOI: [10.1093/nar/gki524](https://doi.org/10.1093/nar/gki524)
|
||||
- Kabsch W. "A solution for the best rotation to relate two sets of vectors." *Acta Crystallographica* A32, 922-923 (1976).
|
||||
@@ -33,6 +33,7 @@ nav:
|
||||
- Examples: plugin/examples.md
|
||||
- Custom Library: 'plugin/custom-library.md'
|
||||
- Selections: 'plugin/selections.md'
|
||||
- Superposition: 'plugin/superposition.md'
|
||||
- Viewer State: 'plugin/viewer-state.md'
|
||||
- Data State: 'plugin/data-state.md'
|
||||
- File Formats: 'plugin/file-formats.md'
|
||||
|
||||
412
package-lock.json
generated
412
package-lock.json
generated
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "molstar",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.17",
|
||||
"@types/benchmark": "^2.1.5",
|
||||
"@types/compression": "1.8.1",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^20.19.25",
|
||||
"@types/node": "^20.19.27",
|
||||
"@types/node-fetch": "^2.6.13",
|
||||
"@types/swagger-ui-dist": "3.30.6",
|
||||
"argparse": "^2.0.1",
|
||||
@@ -28,7 +28,7 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"swagger-ui-dist": "^5.30.3",
|
||||
"swagger-ui-dist": "^5.31.0",
|
||||
"tslib": "^2.8.1",
|
||||
"util.promisify": "^1.1.3"
|
||||
},
|
||||
@@ -50,26 +50,26 @@
|
||||
"@types/gl": "^6.0.5",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"@types/react": "^18.3.26",
|
||||
"@types/react": "^18.3.27",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@types/webxr": "^0.5.24",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.50.0",
|
||||
"@typescript-eslint/parser": "^8.50.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^9.2.1",
|
||||
"cpx2": "^8.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"esbuild": "^0.27.1",
|
||||
"esbuild": "^0.27.2",
|
||||
"esbuild-jest-transform": "^2.0.1",
|
||||
"esbuild-sass-plugin": "^3.3.1",
|
||||
"eslint": "^9.39.1",
|
||||
"fs-extra": "^11.3.2",
|
||||
"eslint": "^9.39.2",
|
||||
"fs-extra": "^11.3.3",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^30.2.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"sass": "^1.94.2",
|
||||
"sass": "^1.97.1",
|
||||
"simple-git": "^3.30.0",
|
||||
"tsc-alias": "^1.8.16",
|
||||
"typescript": "^5.9.3"
|
||||
@@ -663,9 +663,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz",
|
||||
"integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -680,9 +680,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz",
|
||||
"integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -697,9 +697,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz",
|
||||
"integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -714,9 +714,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz",
|
||||
"integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -731,9 +731,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz",
|
||||
"integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -748,9 +748,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz",
|
||||
"integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -765,9 +765,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz",
|
||||
"integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -782,9 +782,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz",
|
||||
"integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -799,9 +799,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz",
|
||||
"integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -816,9 +816,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz",
|
||||
"integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -833,9 +833,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz",
|
||||
"integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -850,9 +850,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz",
|
||||
"integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
|
||||
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -867,9 +867,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz",
|
||||
"integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
|
||||
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -884,9 +884,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz",
|
||||
"integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -901,9 +901,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz",
|
||||
"integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
|
||||
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -918,9 +918,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz",
|
||||
"integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
|
||||
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -935,9 +935,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz",
|
||||
"integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -952,9 +952,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz",
|
||||
"integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -969,9 +969,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz",
|
||||
"integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -986,9 +986,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz",
|
||||
"integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1003,9 +1003,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz",
|
||||
"integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1020,9 +1020,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz",
|
||||
"integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1037,9 +1037,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz",
|
||||
"integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1054,9 +1054,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz",
|
||||
"integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1071,9 +1071,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz",
|
||||
"integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -1088,9 +1088,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz",
|
||||
"integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1199,9 +1199,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
|
||||
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
|
||||
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1211,7 +1211,7 @@
|
||||
"globals": "^14.0.0",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"strip-json-comments": "^3.1.1"
|
||||
},
|
||||
@@ -1257,9 +1257,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
|
||||
"integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
|
||||
"version": "9.39.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
|
||||
"integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2629,9 +2629,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
|
||||
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
|
||||
"version": "20.19.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz",
|
||||
"integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
@@ -2676,14 +2676,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
|
||||
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
|
||||
"version": "18.3.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
||||
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
@@ -2759,18 +2759,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz",
|
||||
"integrity": "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==",
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz",
|
||||
"integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.48.1",
|
||||
"@typescript-eslint/type-utils": "8.48.1",
|
||||
"@typescript-eslint/utils": "8.48.1",
|
||||
"@typescript-eslint/visitor-keys": "8.48.1",
|
||||
"graphemer": "^1.4.0",
|
||||
"@typescript-eslint/scope-manager": "8.50.0",
|
||||
"@typescript-eslint/type-utils": "8.50.0",
|
||||
"@typescript-eslint/utils": "8.50.0",
|
||||
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
@@ -2783,23 +2782,23 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"@typescript-eslint/parser": "^8.50.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.1.tgz",
|
||||
"integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==",
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz",
|
||||
"integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.48.1",
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"@typescript-eslint/typescript-estree": "8.48.1",
|
||||
"@typescript-eslint/visitor-keys": "8.48.1",
|
||||
"@typescript-eslint/scope-manager": "8.50.0",
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2815,14 +2814,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz",
|
||||
"integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==",
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz",
|
||||
"integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.48.1",
|
||||
"@typescript-eslint/types": "^8.48.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.50.0",
|
||||
"@typescript-eslint/types": "^8.50.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2837,14 +2836,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz",
|
||||
"integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==",
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz",
|
||||
"integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"@typescript-eslint/visitor-keys": "8.48.1"
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"@typescript-eslint/visitor-keys": "8.50.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2855,9 +2854,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz",
|
||||
"integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==",
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz",
|
||||
"integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2872,15 +2871,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz",
|
||||
"integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==",
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz",
|
||||
"integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"@typescript-eslint/typescript-estree": "8.48.1",
|
||||
"@typescript-eslint/utils": "8.48.1",
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||
"@typescript-eslint/utils": "8.50.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@@ -2897,9 +2896,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz",
|
||||
"integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==",
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz",
|
||||
"integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2911,16 +2910,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz",
|
||||
"integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==",
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz",
|
||||
"integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.48.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.48.1",
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"@typescript-eslint/visitor-keys": "8.48.1",
|
||||
"@typescript-eslint/project-service": "8.50.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.50.0",
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||
"debug": "^4.3.4",
|
||||
"minimatch": "^9.0.4",
|
||||
"semver": "^7.6.0",
|
||||
@@ -2939,16 +2938,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz",
|
||||
"integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==",
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz",
|
||||
"integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.48.1",
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"@typescript-eslint/typescript-estree": "8.48.1"
|
||||
"@typescript-eslint/scope-manager": "8.50.0",
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"@typescript-eslint/typescript-estree": "8.50.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2963,13 +2962,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz",
|
||||
"integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==",
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz",
|
||||
"integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4441,9 +4440,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.1.tgz",
|
||||
"integrity": "sha512-98XGutrXoh75MlgLihlNxAGbUuFQc7l1cqcnEZlLNKc0UrVdPndgmaDmYTDDh929VS/eqTZV0rozmhu2qqT1/g==",
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
@@ -4900,9 +4899,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz",
|
||||
"integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
|
||||
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -4914,32 +4913,32 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.1",
|
||||
"@esbuild/android-arm": "0.27.1",
|
||||
"@esbuild/android-arm64": "0.27.1",
|
||||
"@esbuild/android-x64": "0.27.1",
|
||||
"@esbuild/darwin-arm64": "0.27.1",
|
||||
"@esbuild/darwin-x64": "0.27.1",
|
||||
"@esbuild/freebsd-arm64": "0.27.1",
|
||||
"@esbuild/freebsd-x64": "0.27.1",
|
||||
"@esbuild/linux-arm": "0.27.1",
|
||||
"@esbuild/linux-arm64": "0.27.1",
|
||||
"@esbuild/linux-ia32": "0.27.1",
|
||||
"@esbuild/linux-loong64": "0.27.1",
|
||||
"@esbuild/linux-mips64el": "0.27.1",
|
||||
"@esbuild/linux-ppc64": "0.27.1",
|
||||
"@esbuild/linux-riscv64": "0.27.1",
|
||||
"@esbuild/linux-s390x": "0.27.1",
|
||||
"@esbuild/linux-x64": "0.27.1",
|
||||
"@esbuild/netbsd-arm64": "0.27.1",
|
||||
"@esbuild/netbsd-x64": "0.27.1",
|
||||
"@esbuild/openbsd-arm64": "0.27.1",
|
||||
"@esbuild/openbsd-x64": "0.27.1",
|
||||
"@esbuild/openharmony-arm64": "0.27.1",
|
||||
"@esbuild/sunos-x64": "0.27.1",
|
||||
"@esbuild/win32-arm64": "0.27.1",
|
||||
"@esbuild/win32-ia32": "0.27.1",
|
||||
"@esbuild/win32-x64": "0.27.1"
|
||||
"@esbuild/aix-ppc64": "0.27.2",
|
||||
"@esbuild/android-arm": "0.27.2",
|
||||
"@esbuild/android-arm64": "0.27.2",
|
||||
"@esbuild/android-x64": "0.27.2",
|
||||
"@esbuild/darwin-arm64": "0.27.2",
|
||||
"@esbuild/darwin-x64": "0.27.2",
|
||||
"@esbuild/freebsd-arm64": "0.27.2",
|
||||
"@esbuild/freebsd-x64": "0.27.2",
|
||||
"@esbuild/linux-arm": "0.27.2",
|
||||
"@esbuild/linux-arm64": "0.27.2",
|
||||
"@esbuild/linux-ia32": "0.27.2",
|
||||
"@esbuild/linux-loong64": "0.27.2",
|
||||
"@esbuild/linux-mips64el": "0.27.2",
|
||||
"@esbuild/linux-ppc64": "0.27.2",
|
||||
"@esbuild/linux-riscv64": "0.27.2",
|
||||
"@esbuild/linux-s390x": "0.27.2",
|
||||
"@esbuild/linux-x64": "0.27.2",
|
||||
"@esbuild/netbsd-arm64": "0.27.2",
|
||||
"@esbuild/netbsd-x64": "0.27.2",
|
||||
"@esbuild/openbsd-arm64": "0.27.2",
|
||||
"@esbuild/openbsd-x64": "0.27.2",
|
||||
"@esbuild/openharmony-arm64": "0.27.2",
|
||||
"@esbuild/sunos-x64": "0.27.2",
|
||||
"@esbuild/win32-arm64": "0.27.2",
|
||||
"@esbuild/win32-ia32": "0.27.2",
|
||||
"@esbuild/win32-x64": "0.27.2"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-jest-transform": {
|
||||
@@ -4998,9 +4997,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.39.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
|
||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||
"version": "9.39.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@@ -5011,7 +5010,7 @@
|
||||
"@eslint/config-helpers": "^0.4.2",
|
||||
"@eslint/core": "^0.17.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.39.1",
|
||||
"@eslint/js": "9.39.2",
|
||||
"@eslint/plugin-kit": "^0.4.1",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
@@ -5635,9 +5634,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "11.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz",
|
||||
"integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==",
|
||||
"version": "11.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
|
||||
"integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5972,13 +5971,6 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/graphemer": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
|
||||
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/h264-mp4-encoder": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/h264-mp4-encoder/-/h264-mp4-encoder-1.0.12.tgz",
|
||||
@@ -10246,9 +10238,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.94.2",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz",
|
||||
"integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==",
|
||||
"version": "1.97.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz",
|
||||
"integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11321,9 +11313,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-ui-dist": {
|
||||
"version": "5.30.3",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.3.tgz",
|
||||
"integrity": "sha512-giQl7/ToPxCqnUAx2wpnSnDNGZtGzw1LyUw6ZitIpTmdrvpxKFY/94v1hihm0zYNpgp1/VY0jTDk//R0BBgnRQ==",
|
||||
"version": "5.31.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz",
|
||||
"integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scarf/scarf": "=1.4.0"
|
||||
|
||||
23
package.json
23
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -123,7 +123,8 @@
|
||||
"Chetan Mishra <chetan.s115@gmail.com>",
|
||||
"Zach Charlop-Powers <zach.charlop.powers@gmail.com>",
|
||||
"Kim Juho <juho_kim@outlook.com>",
|
||||
"Victoria Doshchenko <doshchenko.victoria@gmail.com>"
|
||||
"Victoria Doshchenko <doshchenko.victoria@gmail.com>",
|
||||
"Diego del Alamo <diego.delalamo@gmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
@@ -131,26 +132,26 @@
|
||||
"@types/gl": "^6.0.5",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"@types/react": "^18.3.26",
|
||||
"@types/react": "^18.3.27",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@types/webxr": "^0.5.24",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.50.0",
|
||||
"@typescript-eslint/parser": "^8.50.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^9.2.1",
|
||||
"cpx2": "^8.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"esbuild": "^0.27.1",
|
||||
"esbuild": "^0.27.2",
|
||||
"esbuild-jest-transform": "^2.0.1",
|
||||
"esbuild-sass-plugin": "^3.3.1",
|
||||
"eslint": "^9.39.1",
|
||||
"fs-extra": "^11.3.2",
|
||||
"eslint": "^9.39.2",
|
||||
"fs-extra": "^11.3.3",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^30.2.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"sass": "^1.94.2",
|
||||
"sass": "^1.97.1",
|
||||
"simple-git": "^3.30.0",
|
||||
"tsc-alias": "^1.8.16",
|
||||
"typescript": "^5.9.3"
|
||||
@@ -160,7 +161,7 @@
|
||||
"@types/benchmark": "^2.1.5",
|
||||
"@types/compression": "1.8.1",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^20.19.25",
|
||||
"@types/node": "^20.19.27",
|
||||
"@types/node-fetch": "^2.6.13",
|
||||
"@types/swagger-ui-dist": "3.30.6",
|
||||
"argparse": "^2.0.1",
|
||||
@@ -175,7 +176,7 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"swagger-ui-dist": "^5.30.3",
|
||||
"swagger-ui-dist": "^5.31.0",
|
||||
"tslib": "^2.8.1",
|
||||
"util.promisify": "^1.1.3"
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as os from 'os';
|
||||
|
||||
const Apps = [
|
||||
// Apps
|
||||
{ kind: 'app', name: 'viewer' },
|
||||
{ kind: 'app', name: 'viewer', themes: ['light', 'dark', 'blue'] },
|
||||
{ kind: 'app', name: 'docking-viewer' },
|
||||
{ kind: 'app', name: 'mesoscale-explorer' },
|
||||
{ kind: 'app', name: 'mvs-stories', globalName: 'mvsStories', filename: 'mvs-stories.js' },
|
||||
@@ -132,7 +132,6 @@ function getPaths(app) {
|
||||
async function createBundle(app) {
|
||||
const { name, kind } = app;
|
||||
const { prefix, entry, outfile } = getPaths(app);
|
||||
const NODE_ENV_PRD = isProduction || process.env.NODE_ENV === 'production';
|
||||
|
||||
const ctx = await esbuild.context({
|
||||
entryPoints: [entry],
|
||||
@@ -173,6 +172,41 @@ async function createBundle(app) {
|
||||
if (!isProduction) await ctx.watch();
|
||||
}
|
||||
|
||||
async function createTheme(appName, themeName) {
|
||||
// const { prefix, entry, outfile } = getPaths(app);
|
||||
|
||||
const ctx = await esbuild.context({
|
||||
entryPoints: [resolveEntryPath(`./src/apps/${appName}/theme/${themeName}.ts`)],
|
||||
tsconfig: './tsconfig.json',
|
||||
bundle: true,
|
||||
minify: isProduction,
|
||||
minifyIdentifiers: false,
|
||||
sourcemap: false,
|
||||
outfile: `./build/${appName}/theme/${themeName}.js`,
|
||||
plugins: [
|
||||
// fileLoaderPlugin({ out: prefix }),
|
||||
sassPlugin({
|
||||
type: 'css',
|
||||
silenceDeprecations: ['import'],
|
||||
logger: {
|
||||
warn: (msg) => console.warn(msg),
|
||||
debug: () => { },
|
||||
}
|
||||
}),
|
||||
],
|
||||
color: true,
|
||||
logLevel: 'info',
|
||||
define: {
|
||||
'process.env.NODE_ENV': JSON.stringify(NODE_ENV_PRD ? 'production' : 'development'),
|
||||
'process.env.DEBUG': JSON.stringify(process.env.DEBUG || false),
|
||||
},
|
||||
});
|
||||
|
||||
await ctx.rebuild();
|
||||
|
||||
if (!isProduction) await ctx.watch();
|
||||
}
|
||||
|
||||
function findBrowserTests(names) {
|
||||
const dir = path.resolve('./src', 'tests', 'browser');
|
||||
let files = fs.readdirSync(dir).filter(file => file.endsWith('.ts')).map(file => file.replace('.ts', ''));
|
||||
@@ -230,6 +264,7 @@ const args = argParser.parse_args();
|
||||
const isProduction = !!args.prd;
|
||||
const includeSourceMap = !args.no_src_map;
|
||||
|
||||
const NODE_ENV_PRD = isProduction || process.env.NODE_ENV === 'production';
|
||||
const VERSION = isProduction ? JSON.parse(fs.readFileSync('./package.json', 'utf8')).version : '(dev build)';
|
||||
const TIMESTAMP = Date.now();
|
||||
|
||||
@@ -261,7 +296,14 @@ async function main() {
|
||||
const promises = [];
|
||||
console.log(isProduction ? 'Building apps...' : 'Initial build...');
|
||||
|
||||
for (const app of apps) promises.push(createBundle(app));
|
||||
for (const app of apps) {
|
||||
promises.push(createBundle(app));
|
||||
if (app.themes) {
|
||||
for (const theme of app.themes) {
|
||||
promises.push(createTheme(app.name, theme));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const example of examples) promises.push(createBundle(example));
|
||||
for (const browserTest of browserTests) promises.push(createBundle(browserTest));
|
||||
|
||||
|
||||
@@ -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>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
@@ -36,6 +36,12 @@ function getSpacefillParams(color: Color, sizeFactor: number, graphics: Graphics
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
visuals: [merge ? 'structure-element-sphere' : 'element-sphere'],
|
||||
interior: {
|
||||
color: Color.fromNormalizedRgb(0, 0, 0),
|
||||
colorStrength: 0.15,
|
||||
substance: { metalness: 0.0, roughness: 1.0, bumpiness: 0.0 },
|
||||
substanceStrength: 1,
|
||||
}
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -50,6 +50,12 @@ function getSpacefillParams(color: Color, sizeFactor: number, graphics: Graphics
|
||||
clipPrimitive: true,
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
interior: {
|
||||
color: Color.fromNormalizedRgb(0, 0, 0),
|
||||
colorStrength: 0.15,
|
||||
substance: { metalness: 0.0, roughness: 1.0, bumpiness: 0.0 },
|
||||
substanceStrength: 1,
|
||||
}
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -40,6 +40,12 @@ function getSpacefillParams(color: Color, scaleFactor: number, graphics: Graphic
|
||||
clipPrimitive: true,
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
interior: {
|
||||
color: Color.fromNormalizedRgb(0, 0, 0),
|
||||
colorStrength: 0.15,
|
||||
substance: { metalness: 0.0, roughness: 1.0, bumpiness: 0.0 },
|
||||
substanceStrength: 1,
|
||||
}
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2024 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>
|
||||
*/
|
||||
@@ -35,6 +35,12 @@ function getSpacefillParams(color: Color, graphics: GraphicsMode) {
|
||||
clipPrimitive: true,
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
interior: {
|
||||
color: Color.fromNormalizedRgb(0, 0, 0),
|
||||
colorStrength: 0.15,
|
||||
substance: { metalness: 0.0, roughness: 1.0, bumpiness: 0.0 },
|
||||
substanceStrength: 1,
|
||||
}
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2024 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>
|
||||
*/
|
||||
@@ -46,8 +46,6 @@ function adjustPluginProps(ctx: PluginContext) {
|
||||
dimColor: Color(0xffffff),
|
||||
dimStrength: 1,
|
||||
markerPriority: 2,
|
||||
interiorColorFlag: false,
|
||||
interiorDarkening: 0.15,
|
||||
exposure: 1.1,
|
||||
xrayEdgeFalloff: 3,
|
||||
},
|
||||
|
||||
@@ -7,34 +7,20 @@
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
import { Backgrounds } from '../../extensions/backgrounds';
|
||||
import { DnatcoNtCs } from '../../extensions/dnatco';
|
||||
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
|
||||
import { GeometryExport } from '../../extensions/geo-export';
|
||||
import { MAQualityAssessment, MAQualityAssessmentConfig, QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
|
||||
import { ModelExport } from '../../extensions/model-export';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { MolViewSpec } from '../../extensions/mvs/behavior';
|
||||
import { AssemblySymmetryConfig } from '../../extensions/assembly-symmetry';
|
||||
import { loadMVSData, loadMVSX } from '../../extensions/mvs/components/formats';
|
||||
import { loadMVS, MolstarLoadingExtension } from '../../extensions/mvs/load';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { AssemblySymmetry, AssemblySymmetryConfig } from '../../extensions/assembly-symmetry';
|
||||
import { SbNcbrPartialCharges, SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider, SbNcbrTunnels } from '../../extensions/sb-ncbr';
|
||||
import { wwPDBChemicalComponentDictionary } from '../../extensions/wwpdb/ccd/behavior';
|
||||
import { wwPDBStructConnExtensionFunctions } from '../../extensions/wwpdb/struct-conn';
|
||||
import { ZenodoImport } from '../../extensions/zenodo';
|
||||
import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
|
||||
import { StringLike } from '../../mol-io/common/string-like';
|
||||
import { Structure, StructureElement, StructureSelection } from '../../mol-model/structure';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { OpenFiles } from '../../mol-plugin-state/actions/file';
|
||||
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { PluginComponent } from '../../mol-plugin-state/component';
|
||||
import { BuiltInCoordinatesFormat } from '../../mol-plugin-state/formats/coordinates';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { BuiltInTopologyFormat } from '../../mol-plugin-state/formats/topology';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
|
||||
@@ -42,98 +28,37 @@ import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { renderReact18 } from '../../mol-plugin-ui/react18';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginBehaviors } from '../../mol-plugin/behavior';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig, PluginConfigItem } from '../../mol-plugin/config';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
|
||||
import { MolScriptBuilder } from '../../mol-script/language/builder';
|
||||
import { Expression } from '../../mol-script/language/expression';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { StateObjectSelector } from '../../mol-state';
|
||||
import { Task } from '../../mol-task';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { OpenFiles } from '../../mol-plugin-state/actions/file';
|
||||
import { StringLike } from '../../mol-io/common/string-like';
|
||||
import { ExtensionMap } from './extensions';
|
||||
import { DefaultViewerOptions, ViewerOptions } from './options';
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { consoleStats, setDebugMode, setProductionMode, setTimingMode, isProductionMode, isDebugMode, isTimingMode } from '../../mol-util/debug';
|
||||
export { consoleStats, isDebugMode, isProductionMode, isTimingMode, setDebugMode, setProductionMode, setTimingMode } from '../../mol-util/debug';
|
||||
|
||||
const CustomFormats = [
|
||||
['g3d', G3dProvider] as const
|
||||
];
|
||||
|
||||
export const ExtensionMap = {
|
||||
'backgrounds': PluginSpec.Behavior(Backgrounds),
|
||||
'dnatco-ntcs': PluginSpec.Behavior(DnatcoNtCs),
|
||||
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
'assembly-symmetry': PluginSpec.Behavior(AssemblySymmetry),
|
||||
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
|
||||
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
|
||||
'g3d': PluginSpec.Behavior(G3DFormat),
|
||||
'model-export': PluginSpec.Behavior(ModelExport),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
'geo-export': PluginSpec.Behavior(GeometryExport),
|
||||
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
|
||||
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
|
||||
'sb-ncbr-partial-charges': PluginSpec.Behavior(SbNcbrPartialCharges),
|
||||
'wwpdb-chemical-component-dictionary': PluginSpec.Behavior(wwPDBChemicalComponentDictionary),
|
||||
'mvs': PluginSpec.Behavior(MolViewSpec),
|
||||
'tunnels': PluginSpec.Behavior(SbNcbrTunnels),
|
||||
};
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
customFormats: CustomFormats as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(ExtensionMap),
|
||||
disabledExtensions: [] as string[],
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
collapseLeftPanel: false,
|
||||
collapseRightPanel: false,
|
||||
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
|
||||
pixelScale: PluginConfig.General.PixelScale.defaultValue,
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
transparency: PluginConfig.General.Transparency.defaultValue,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
|
||||
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
|
||||
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
|
||||
resolutionMode: PluginConfig.General.ResolutionMode.defaultValue,
|
||||
illumination: false,
|
||||
|
||||
viewportShowReset: PluginConfig.Viewport.ShowReset.defaultValue,
|
||||
viewportShowScreenshotControls: PluginConfig.Viewport.ShowScreenshotControls.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowToggleFullscreen: PluginConfig.Viewport.ShowToggleFullscreen.defaultValue,
|
||||
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
|
||||
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
|
||||
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
|
||||
viewportShowTrajectoryControls: PluginConfig.Viewport.ShowTrajectoryControls.defaultValue,
|
||||
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
|
||||
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
|
||||
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
|
||||
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
|
||||
rcsbAssemblySymmetryDefaultServerType: AssemblySymmetryConfig.DefaultServerType.defaultValue,
|
||||
rcsbAssemblySymmetryDefaultServerUrl: AssemblySymmetryConfig.DefaultServerUrl.defaultValue,
|
||||
rcsbAssemblySymmetryApplyColors: AssemblySymmetryConfig.ApplyColors.defaultValue,
|
||||
|
||||
config: [] as [PluginConfigItem, any][],
|
||||
};
|
||||
type ViewerOptions = typeof DefaultViewerOptions;
|
||||
import '../../mol-util/polyfill';
|
||||
import { ViewerAutoPreset } from './presets';
|
||||
import { decodeColor } from '../../mol-util/color/utils';
|
||||
|
||||
export class Viewer {
|
||||
constructor(public plugin: PluginUIContext) {
|
||||
private _events = new PluginComponent();
|
||||
public readonly plugin: PluginUIContext;
|
||||
|
||||
constructor(plugin: PluginUIContext) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
|
||||
@@ -148,11 +73,22 @@ export class Viewer {
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const disabledExtension = new Set(o.disabledExtensions ?? []);
|
||||
let baseBehaviors = defaultSpec.behaviors;
|
||||
|
||||
if (o.viewportFocusBehavior === 'disabled') {
|
||||
baseBehaviors = baseBehaviors.filter(b =>
|
||||
b.transformer !== PluginBehaviors.Camera.FocusLoci
|
||||
&& b.transformer !== PluginBehaviors.Representation.FocusLoci
|
||||
);
|
||||
}
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
canvas3d: {
|
||||
...defaultSpec.canvas3d,
|
||||
},
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
...defaultSpec.behaviors,
|
||||
...baseBehaviors,
|
||||
...o.extensions.filter(e => !disabledExtension.has(e)).map(e => ExtensionMap[e]),
|
||||
],
|
||||
animations: [...defaultSpec.animations || []],
|
||||
@@ -228,10 +164,23 @@ export class Viewer {
|
||||
plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
|
||||
}
|
||||
});
|
||||
|
||||
plugin.canvas3d?.setProps({ illumination: { enabled: o.illumination } });
|
||||
if (o.viewportBackgroundColor) {
|
||||
const backgroundColor = decodeColor(o.viewportBackgroundColor);
|
||||
if (typeof backgroundColor === 'number') {
|
||||
plugin.canvas3d?.setProps({ renderer: { backgroundColor } });
|
||||
}
|
||||
}
|
||||
return new Viewer(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows subscribing to rxjs observables in the context of the viewer.
|
||||
* All subscriptions will be disposed of when the viewer is destroyed.
|
||||
*/
|
||||
subscribe = this._events.subscribe.bind(this._events);
|
||||
|
||||
setRemoteSnapshot(id: string) {
|
||||
const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
|
||||
return PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
|
||||
@@ -567,7 +516,51 @@ export class Viewer {
|
||||
this.plugin.layout.events.updated.next(void 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers structure element selection or highlighting based on the provided
|
||||
* MolScript expression or StructureElement schema.
|
||||
*
|
||||
* If neither `expression` nor `elements` are provided, all selections/highlights
|
||||
* will be cleared based on the specified `action`.
|
||||
*/
|
||||
structureInteractivity({ expression, elements, action, applyGranularity = false, filterStructure }: {
|
||||
expression?: (queryBuilder: typeof MolScriptBuilder) => Expression,
|
||||
elements?: StructureElement.Schema,
|
||||
action: 'highlight' | 'select',
|
||||
applyGranularity?: boolean,
|
||||
filterStructure?: (structure: Structure) => boolean
|
||||
}) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
if (!expression && !elements) {
|
||||
if (action === 'select') {
|
||||
plugin.managers.interactivity.lociSelects.deselectAll();
|
||||
} else if (action === 'highlight') {
|
||||
plugin.managers.interactivity.lociHighlights.clearHighlights();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const structures = this.plugin.state.data.selectQ(Q => Q.rootsOfType(PluginStateObject.Molecule.Structure));
|
||||
for (const s of structures) {
|
||||
if (!s.obj?.data) continue;
|
||||
|
||||
if (filterStructure && !filterStructure(s.obj.data)) continue;
|
||||
|
||||
const loci = expression
|
||||
? StructureSelection.toLociWithSourceUnits(Script.getStructureSelection(expression, s.obj.data))
|
||||
: StructureElement.Schema.toLoci(s.obj.data, elements!);
|
||||
|
||||
if (action === 'select') {
|
||||
plugin.managers.interactivity.lociSelects.select({ loci }, applyGranularity);
|
||||
} else if (action === 'highlight') {
|
||||
plugin.managers.interactivity.lociHighlights.highlight({ loci }, applyGranularity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._events.dispose();
|
||||
this.plugin.dispose();
|
||||
}
|
||||
}
|
||||
@@ -594,44 +587,4 @@ export interface LoadTrajectoryParams {
|
||||
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array<ArrayBuffer>, format: BuiltInCoordinatesFormat },
|
||||
coordinatesLabel?: string,
|
||||
preset?: keyof PresetTrajectoryHierarchy
|
||||
}
|
||||
|
||||
export const ViewerAutoPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-viewer-auto',
|
||||
display: {
|
||||
name: 'Automatic (w/ Annotation)', group: 'Annotation',
|
||||
description: 'Show standard automatic representation but colored by quality assessment (if available in the model).'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return (
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) ||
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))
|
||||
);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) {
|
||||
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
|
||||
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
|
||||
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
|
||||
} else if (!!structure.models.some(m => SbNcbrPartialChargesPropertyProvider.isApplicable(m))) {
|
||||
return await SbNcbrPartialChargesPreset.apply(ref, params, plugin);
|
||||
} else {
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const PluginExtensions = {
|
||||
wwPDBStructConn: wwPDBStructConnExtensionFunctions,
|
||||
mvs: { MVSData, loadMVS, loadMVSData },
|
||||
modelArchive: {
|
||||
qualityAssessment: {
|
||||
config: MAQualityAssessmentConfig
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
69
src/apps/viewer/extensions.ts
Normal file
69
src/apps/viewer/extensions.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
import { AssemblySymmetry } from '../../extensions/assembly-symmetry';
|
||||
import { Backgrounds } from '../../extensions/backgrounds';
|
||||
import { DnatcoNtCs } from '../../extensions/dnatco';
|
||||
import { G3DFormat } from '../../extensions/g3d/format';
|
||||
import { GeometryExport } from '../../extensions/geo-export';
|
||||
import { MAQualityAssessment, MAQualityAssessmentConfig } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { ModelExport } from '../../extensions/model-export';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { loadMVS } from '../../extensions/mvs';
|
||||
import { MolViewSpec } from '../../extensions/mvs/behavior';
|
||||
import { loadMVSData } from '../../extensions/mvs/components/formats';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { SbNcbrPartialCharges, SbNcbrTunnels } from '../../extensions/sb-ncbr';
|
||||
import { wwPDBChemicalComponentDictionary } from '../../extensions/wwpdb/ccd/behavior';
|
||||
import { wwPDBStructConnExtensionFunctions } from '../../extensions/wwpdb/struct-conn';
|
||||
import { ZenodoImport } from '../../extensions/zenodo';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
import * as MVSUtil from '../../extensions/mvs/util';
|
||||
|
||||
export const ExtensionMap = {
|
||||
// Mol* built-in extensions
|
||||
'mvs': PluginSpec.Behavior(MolViewSpec),
|
||||
'backgrounds': PluginSpec.Behavior(Backgrounds),
|
||||
'model-export': PluginSpec.Behavior(ModelExport),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
'geo-export': PluginSpec.Behavior(GeometryExport),
|
||||
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
|
||||
'wwpdb-chemical-component-dictionary': PluginSpec.Behavior(wwPDBChemicalComponentDictionary),
|
||||
|
||||
// 3rd party extensions
|
||||
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
'dnatco-ntcs': PluginSpec.Behavior(DnatcoNtCs),
|
||||
'assembly-symmetry': PluginSpec.Behavior(AssemblySymmetry),
|
||||
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
|
||||
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
|
||||
'g3d': PluginSpec.Behavior(G3DFormat), // TODO: consider removing this for Mol* 6.0
|
||||
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
|
||||
'sb-ncbr-partial-charges': PluginSpec.Behavior(SbNcbrPartialCharges),
|
||||
'tunnels': PluginSpec.Behavior(SbNcbrTunnels),
|
||||
};
|
||||
|
||||
export const PluginExtensions = {
|
||||
wwPDBStructConn: wwPDBStructConnExtensionFunctions,
|
||||
mvs: {
|
||||
MVSData,
|
||||
createBuilder: MVSData.createBuilder,
|
||||
loadMVS,
|
||||
loadMVSData,
|
||||
util: {
|
||||
...MVSUtil
|
||||
}
|
||||
},
|
||||
modelArchive: {
|
||||
qualityAssessment: {
|
||||
config: MAQualityAssessmentConfig
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,12 +1,16 @@
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
|
||||
import './mvs.html';
|
||||
import './embedded.html';
|
||||
import './favicon.ico';
|
||||
import './index.html';
|
||||
import '../../mol-plugin-ui/skin/light.scss';
|
||||
export * from './lib';
|
||||
export * from './extensions';
|
||||
export * from './app';
|
||||
export * from './presets';
|
||||
|
||||
58
src/apps/viewer/lib.ts
Normal file
58
src/apps/viewer/lib.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as Structure from '../../mol-model/structure';
|
||||
import { DataLoci, EveryLoci, Loci } from '../../mol-model/loci';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { Shape, ShapeGroup } from '../../mol-model/shape';
|
||||
import * as LinearAlgebra3D from '../../mol-math/linear-algebra/3d';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior';
|
||||
import { DefaultPluginSpec, PluginSpec } from '../../mol-plugin/spec';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginStateObject, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { StateActions } from '../../mol-plugin-state/actions';
|
||||
import { PluginExtensions } from './extensions';
|
||||
|
||||
export const lib = {
|
||||
structure: {
|
||||
...Structure,
|
||||
},
|
||||
volume: {
|
||||
Volume,
|
||||
},
|
||||
shape: {
|
||||
Shape,
|
||||
ShapeGroup,
|
||||
},
|
||||
loci: {
|
||||
Loci,
|
||||
DataLoci,
|
||||
EveryLoci,
|
||||
},
|
||||
math: {
|
||||
LinearAlgebra: {
|
||||
...LinearAlgebra3D,
|
||||
}
|
||||
},
|
||||
plugin: {
|
||||
PluginContext,
|
||||
PluginConfig,
|
||||
PluginBehavior,
|
||||
PluginSpec,
|
||||
PluginStateObject,
|
||||
PluginStateTransform,
|
||||
StateTransforms,
|
||||
StateActions,
|
||||
DefaultPluginSpec,
|
||||
DefaultPluginUISpec,
|
||||
},
|
||||
extensions: {
|
||||
...PluginExtensions
|
||||
}
|
||||
};
|
||||
170
src/apps/viewer/mvs.html
Normal file
170
src/apps/viewer/mvs.html
Normal file
@@ -0,0 +1,170 @@
|
||||
<!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">
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<title>Mol* Viewer MolViewSpec Example</title>
|
||||
<style>
|
||||
body {
|
||||
background: #111318;
|
||||
}
|
||||
|
||||
#app {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: sans-serif;
|
||||
gap: 8px;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
z-index: 10;
|
||||
background-color: #111318;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="theme/dark.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="controls">
|
||||
<button onclick="selectResidues()">Select Residues 10-50</button>
|
||||
<button onclick="clearSelection()">Clear Selection</button>
|
||||
<div id="selection-info"></div>
|
||||
</div>
|
||||
<script type="text/javascript" src="molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
function selectResidues() {
|
||||
viewer.structureInteractivity({
|
||||
elements: { beg_auth_seq_id: 10, end_auth_seq_id: 50 },
|
||||
action: 'select',
|
||||
});
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
viewer.structureInteractivity({ action: 'select' });
|
||||
}
|
||||
|
||||
molstar.Viewer.create('app', {
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: false,
|
||||
layoutShowLeftPanel: true,
|
||||
|
||||
viewportShowExpand: true,
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
viewportFocusBehavior: 'disabled',
|
||||
viewportBackgroundColor: 'black',
|
||||
|
||||
pdbProvider: 'rcsb',
|
||||
emdbProvider: 'rcsb',
|
||||
}).then(viewer => {
|
||||
// Make the viewer accessible globally for the demo buttons
|
||||
window.viewer = viewer;
|
||||
|
||||
// Build MVS state
|
||||
const builder = molstar.lib.extensions.mvs.createBuilder();
|
||||
const structure = builder
|
||||
.download({ url: 'https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif' })
|
||||
.parse({ format: 'bcif' })
|
||||
.modelStructure({});
|
||||
structure
|
||||
.component({ selector: 'polymer' })
|
||||
.representation({ type: 'cartoon' })
|
||||
.color({ color: 'green' });
|
||||
structure
|
||||
.component({ selector: 'ligand' })
|
||||
.representation({ type: 'ball_and_stick' })
|
||||
.color({ color: '#cc3399' });
|
||||
|
||||
// Extra data can be passed to the MVS snapshot via custom state
|
||||
// and later accessed it using getCurrentMVSSnapshot() (see hover handler below)
|
||||
// Each node can have custom data as well, but generally could be harder to access
|
||||
// This example is a little contrived to demonstrate the concept
|
||||
builder.extendRootCustomState({
|
||||
extraResidueAnnotations: {
|
||||
'REA': 'Ligand'
|
||||
}
|
||||
})
|
||||
|
||||
builder.canvas({
|
||||
background_color: "#111318",
|
||||
})
|
||||
|
||||
structure.primitives()
|
||||
.sphere({
|
||||
center: { label_comp_id: 'REA' },
|
||||
radius: 3,
|
||||
custom: { action: 'Action 1' },
|
||||
})
|
||||
.label({
|
||||
text: '1',
|
||||
position: { label_comp_id: 'REA' },
|
||||
label_size: 2.5,
|
||||
label_color: 'blue',
|
||||
});
|
||||
|
||||
structure.primitives()
|
||||
.sphere({
|
||||
center: { label_seq_id: 2 },
|
||||
radius: 3,
|
||||
custom: { action: 'Action 2' },
|
||||
})
|
||||
.label({
|
||||
text: '2',
|
||||
position: { label_seq_id: 2 },
|
||||
label_size: 2.5,
|
||||
label_color: 'blue',
|
||||
});
|
||||
|
||||
const mvsData = builder.getState();
|
||||
|
||||
viewer.loadMvsData(mvsData, 'mvsj');
|
||||
|
||||
// Show current residue interaction
|
||||
viewer.subscribe(viewer.plugin.behaviors.interaction.hover, e => {
|
||||
const infoElement = document.getElementById('selection-info');
|
||||
if (!infoElement) return;
|
||||
|
||||
if (molstar.lib.structure.StructureElement.Loci.is(e.current.loci)) {
|
||||
molstar.lib.structure.StructureElement.Loci.forEachLocation(e.current.loci, location => {
|
||||
const props = molstar.lib.structure.StructureProperties;
|
||||
let label = `Hovered Residue: ${props.chain.label_asym_id(location)} ${props.residue.label_seq_id(location)}`;
|
||||
|
||||
const compId = props.residue.label_comp_id(location);
|
||||
const snapshot = molstar.lib.extensions.mvs.util.getCurrentMVSSnapshot(viewer.plugin);
|
||||
if (snapshot && snapshot.root.custom && snapshot.root.custom.extraResidueAnnotations) {
|
||||
const extra = snapshot.root.custom.extraResidueAnnotations[compId];
|
||||
if (extra) label += ` (${extra})`;
|
||||
}
|
||||
|
||||
infoElement.innerText = label;
|
||||
});
|
||||
} else {
|
||||
infoElement.innerText = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Show clicked primitive action
|
||||
viewer.subscribe(viewer.plugin.behaviors.interaction.click, e => {
|
||||
const nodes = molstar.lib.extensions.mvs.util.tryGetPrimitivesFromLoci(e.current.loci);
|
||||
if (nodes?.length) {
|
||||
alert('Clicked on: ' + (nodes[0].custom?.action || 'unknown'));
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
69
src/apps/viewer/options.ts
Normal file
69
src/apps/viewer/options.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
|
||||
import { AssemblySymmetryConfig } from '../../extensions/assembly-symmetry';
|
||||
import { G3dProvider } from '../../extensions/g3d/format';
|
||||
import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { PluginConfig, PluginConfigItem } from '../../mol-plugin/config';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { ExtensionMap } from './extensions';
|
||||
|
||||
const CustomFormats: [string, DataFormatProvider][] = [
|
||||
['g3d', G3dProvider] as const
|
||||
];
|
||||
|
||||
export const DefaultViewerOptions = {
|
||||
customFormats: CustomFormats as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(ExtensionMap),
|
||||
disabledExtensions: [] as string[],
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
collapseLeftPanel: false,
|
||||
collapseRightPanel: false,
|
||||
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
|
||||
pixelScale: PluginConfig.General.PixelScale.defaultValue,
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
transparency: PluginConfig.General.Transparency.defaultValue,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
|
||||
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
|
||||
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
|
||||
resolutionMode: PluginConfig.General.ResolutionMode.defaultValue,
|
||||
illumination: false,
|
||||
|
||||
viewportShowReset: PluginConfig.Viewport.ShowReset.defaultValue,
|
||||
viewportShowScreenshotControls: PluginConfig.Viewport.ShowScreenshotControls.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowToggleFullscreen: PluginConfig.Viewport.ShowToggleFullscreen.defaultValue,
|
||||
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
|
||||
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
|
||||
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
|
||||
viewportShowTrajectoryControls: PluginConfig.Viewport.ShowTrajectoryControls.defaultValue,
|
||||
viewportFocusBehavior: 'default' as 'default' | 'disabled',
|
||||
viewportBackgroundColor: undefined as string | undefined,
|
||||
|
||||
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
|
||||
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
|
||||
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
|
||||
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
|
||||
rcsbAssemblySymmetryDefaultServerType: AssemblySymmetryConfig.DefaultServerType.defaultValue,
|
||||
rcsbAssemblySymmetryDefaultServerUrl: AssemblySymmetryConfig.DefaultServerUrl.defaultValue,
|
||||
rcsbAssemblySymmetryApplyColors: AssemblySymmetryConfig.ApplyColors.defaultValue,
|
||||
|
||||
config: [] as [PluginConfigItem, any][],
|
||||
};
|
||||
export type ViewerOptions = typeof DefaultViewerOptions;
|
||||
42
src/apps/viewer/presets.ts
Normal file
42
src/apps/viewer/presets.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 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>
|
||||
*/
|
||||
|
||||
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
|
||||
import { SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider } from '../../extensions/sb-ncbr';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
|
||||
export const ViewerAutoPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-viewer-auto',
|
||||
display: {
|
||||
name: 'Automatic (w/ Annotation)', group: 'Annotation',
|
||||
description: 'Show standard automatic representation but colored by quality assessment (if available in the model).'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return (
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) ||
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))
|
||||
);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) {
|
||||
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
|
||||
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
|
||||
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
|
||||
} else if (!!structure.models.some(m => SbNcbrPartialChargesPropertyProvider.isApplicable(m))) {
|
||||
return await SbNcbrPartialChargesPreset.apply(ref, params, plugin);
|
||||
} else {
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
7
src/apps/viewer/theme/blue.ts
Normal file
7
src/apps/viewer/theme/blue.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import '../../../mol-plugin-ui/skin/blue.scss';
|
||||
7
src/apps/viewer/theme/dark.ts
Normal file
7
src/apps/viewer/theme/dark.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import '../../../mol-plugin-ui/skin/dark.scss';
|
||||
7
src/apps/viewer/theme/light.ts
Normal file
7
src/apps/viewer/theme/light.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import '../../../mol-plugin-ui/skin/light.scss';
|
||||
@@ -21,7 +21,8 @@ import { StripedResidues } from './coloring';
|
||||
import { CustomToastMessage } from './controls';
|
||||
import { CustomColorThemeProvider } from './custom-theme';
|
||||
import './index.html';
|
||||
import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition';
|
||||
import './tm-align.html';
|
||||
import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData, tmAlignStructures, loadStructuresNoAlignment, sequenceAlignStructures } from './superposition';
|
||||
import '../../mol-plugin-ui/skin/light.scss';
|
||||
|
||||
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
|
||||
@@ -190,6 +191,45 @@ class BasicWrapper {
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-2' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Run TM-align on two structures
|
||||
* @param pdbId1 - PDB ID of first structure (reference)
|
||||
* @param chain1 - Chain ID of first structure
|
||||
* @param pdbId2 - PDB ID of second structure (mobile)
|
||||
* @param chain2 - Chain ID of second structure
|
||||
* @param color1 - Optional color for first structure (hex, default blue)
|
||||
* @param color2 - Optional color for second structure (hex, default red)
|
||||
*/
|
||||
tmAlign(pdbId1: string, chain1: string, pdbId2: string, chain2: string, color1?: number, color2?: number) {
|
||||
return tmAlignStructures(this.plugin, pdbId1, chain1, pdbId2, chain2, color1, color2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load two structures without alignment
|
||||
* @param pdbId1 - PDB ID of first structure
|
||||
* @param chain1 - Chain ID of first structure
|
||||
* @param pdbId2 - PDB ID of second structure
|
||||
* @param chain2 - Chain ID of second structure
|
||||
* @param color1 - Optional color for first structure (hex, default blue)
|
||||
* @param color2 - Optional color for second structure (hex, default red)
|
||||
*/
|
||||
loadStructures(pdbId1: string, chain1: string, pdbId2: string, chain2: string, color1?: number, color2?: number) {
|
||||
return loadStructuresNoAlignment(this.plugin, pdbId1, chain1, pdbId2, chain2, color1, color2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Align two structures using sequence alignment
|
||||
* @param pdbId1 - PDB ID of first structure (reference)
|
||||
* @param chain1 - Chain ID of first structure
|
||||
* @param pdbId2 - PDB ID of second structure (mobile)
|
||||
* @param chain2 - Chain ID of second structure
|
||||
* @param color1 - Optional color for first structure (hex, default blue)
|
||||
* @param color2 - Optional color for second structure (hex, default red)
|
||||
*/
|
||||
sequenceAlign(pdbId1: string, chain1: string, pdbId2: string, chain2: string, color1?: number, color2?: number) {
|
||||
return sequenceAlignStructures(this.plugin, pdbId1, chain1, pdbId2, chain2, color1, color2);
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).BasicMolStarWrapper = new BasicWrapper();
|
||||
@@ -5,8 +5,9 @@
|
||||
*/
|
||||
|
||||
import { Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { QueryContext, StructureSelection } from '../../mol-model/structure';
|
||||
import { superpose } from '../../mol-model/structure/structure/util/superposition';
|
||||
import { QueryContext, StructureSelection, StructureElement } from '../../mol-model/structure';
|
||||
import { superpose, alignAndSuperpose } from '../../mol-model/structure/structure/util/superposition';
|
||||
import { tmAlign } from '../../mol-model/structure/structure/util/tm-align';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
@@ -116,4 +117,217 @@ function transform(plugin: PluginContext, s: StateObjectRef<PSO.Molecule.Structu
|
||||
const b = plugin.state.data.build().to(s)
|
||||
.insert(StateTransforms.Model.TransformStructureConformation, { transform: { name: 'matrix', params: { data: matrix, transpose: false } } });
|
||||
return plugin.runTask(plugin.state.data.updateTree(b));
|
||||
}
|
||||
|
||||
export interface TMAlignResult {
|
||||
tmScoreA: number;
|
||||
tmScoreB: number;
|
||||
rmsd: number;
|
||||
alignedLength: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* TM-align superposition: aligns two structures using TM-align algorithm
|
||||
* @param plugin - Mol* plugin context
|
||||
* @param pdbId1 - PDB ID of first structure (reference)
|
||||
* @param chain1 - Chain ID of first structure
|
||||
* @param pdbId2 - PDB ID of second structure (mobile)
|
||||
* @param chain2 - Chain ID of second structure
|
||||
* @param color1 - Optional color for first structure (hex, default blue)
|
||||
* @param color2 - Optional color for second structure (hex, default red)
|
||||
*/
|
||||
export async function tmAlignStructures(
|
||||
plugin: PluginContext,
|
||||
pdbId1: string,
|
||||
chain1: string,
|
||||
pdbId2: string,
|
||||
chain2: string,
|
||||
color1: number = 0x3498db,
|
||||
color2: number = 0xe74c3c
|
||||
): Promise<TMAlignResult | undefined> {
|
||||
await plugin.clear();
|
||||
|
||||
const url1 = `https://files.rcsb.org/download/${pdbId1}.pdb`;
|
||||
const url2 = `https://files.rcsb.org/download/${pdbId2}.pdb`;
|
||||
const label1 = `${pdbId1} Chain ${chain1}`;
|
||||
const label2 = `${pdbId2} Chain ${chain2}`;
|
||||
|
||||
// Load structures
|
||||
const struct1 = await loadStructure(plugin, url1, 'pdb');
|
||||
const struct2 = await loadStructure(plugin, url2, 'pdb');
|
||||
|
||||
// Build query for C-alpha atoms from specified chains
|
||||
const caQuery1 = compile<StructureSelection>(MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), chain1]),
|
||||
'atom-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_atom_id(), 'CA'])
|
||||
}));
|
||||
const caQuery2 = compile<StructureSelection>(MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), chain2]),
|
||||
'atom-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_atom_id(), 'CA'])
|
||||
}));
|
||||
|
||||
const structure1Data = struct1.structure.cell?.obj?.data;
|
||||
const structure2Data = struct2.structure.cell?.obj?.data;
|
||||
|
||||
if (!structure1Data || !structure2Data) {
|
||||
console.error('Failed to load structures');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sel1 = StructureSelection.toLociWithCurrentUnits(caQuery1(new QueryContext(structure1Data)));
|
||||
const sel2 = StructureSelection.toLociWithCurrentUnits(caQuery2(new QueryContext(structure2Data)));
|
||||
|
||||
const loci1 = StructureElement.Loci.is(sel1) ? sel1 : StructureElement.Loci.none(structure1Data);
|
||||
const loci2 = StructureElement.Loci.is(sel2) ? sel2 : StructureElement.Loci.none(structure2Data);
|
||||
|
||||
if (StructureElement.Loci.size(loci1) === 0 || StructureElement.Loci.size(loci2) === 0) {
|
||||
console.error('Empty selection - cannot run TM-align');
|
||||
// Still show the structures without alignment
|
||||
await addChainRepresentation(plugin, struct1.structure, chain1, label1, color1);
|
||||
await addChainRepresentation(plugin, struct2.structure, chain2, label2, color2);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Run TM-align
|
||||
const result = tmAlign(loci1, loci2);
|
||||
|
||||
console.log('TM-score (structure 1):', result.tmScoreA.toFixed(5));
|
||||
console.log('TM-score (structure 2):', result.tmScoreB.toFixed(5));
|
||||
console.log('RMSD:', result.rmsd.toFixed(2), 'A');
|
||||
console.log('Aligned residues:', result.alignedLength);
|
||||
|
||||
// Apply the transformation to superimpose structure 2 onto structure 1
|
||||
await transform(plugin, struct2.structure, result.bTransform);
|
||||
|
||||
// Add cartoon representations
|
||||
await addChainRepresentation(plugin, struct1.structure, chain1, label1, color1);
|
||||
await addChainRepresentation(plugin, struct2.structure, chain2, label2, color2);
|
||||
|
||||
return {
|
||||
tmScoreA: result.tmScoreA,
|
||||
tmScoreB: result.tmScoreB,
|
||||
rmsd: result.rmsd,
|
||||
alignedLength: result.alignedLength
|
||||
};
|
||||
}
|
||||
|
||||
async function addChainRepresentation(
|
||||
plugin: PluginContext,
|
||||
structure: StateObjectRef<PSO.Molecule.Structure>,
|
||||
chain: string,
|
||||
label: string,
|
||||
color: number
|
||||
) {
|
||||
const component = await plugin.builders.structure.tryCreateComponentFromExpression(
|
||||
structure,
|
||||
chainSelection(chain),
|
||||
label
|
||||
);
|
||||
if (component) {
|
||||
await plugin.builders.structure.representation.addRepresentation(component, {
|
||||
type: 'cartoon',
|
||||
color: 'uniform',
|
||||
colorParams: { value: color }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and display two structures without any alignment
|
||||
* @param plugin - Mol* plugin context
|
||||
* @param pdbId1 - PDB ID of first structure
|
||||
* @param chain1 - Chain ID of first structure
|
||||
* @param pdbId2 - PDB ID of second structure
|
||||
* @param chain2 - Chain ID of second structure
|
||||
* @param color1 - Optional color for first structure (hex, default blue)
|
||||
* @param color2 - Optional color for second structure (hex, default red)
|
||||
*/
|
||||
export async function loadStructuresNoAlignment(
|
||||
plugin: PluginContext,
|
||||
pdbId1: string,
|
||||
chain1: string,
|
||||
pdbId2: string,
|
||||
chain2: string,
|
||||
color1: number = 0x3498db,
|
||||
color2: number = 0xe74c3c
|
||||
): Promise<void> {
|
||||
await plugin.clear();
|
||||
|
||||
const url1 = `https://files.rcsb.org/download/${pdbId1}.pdb`;
|
||||
const url2 = `https://files.rcsb.org/download/${pdbId2}.pdb`;
|
||||
const label1 = `${pdbId1} Chain ${chain1}`;
|
||||
const label2 = `${pdbId2} Chain ${chain2}`;
|
||||
|
||||
const struct1 = await loadStructure(plugin, url1, 'pdb');
|
||||
const struct2 = await loadStructure(plugin, url2, 'pdb');
|
||||
|
||||
await addChainRepresentation(plugin, struct1.structure, chain1, label1, color1);
|
||||
await addChainRepresentation(plugin, struct2.structure, chain2, label2, color2);
|
||||
|
||||
console.log('Loaded structures - NO ALIGNMENT');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequence-based superposition: aligns two structures using sequence alignment + RMSD minimization
|
||||
* @param plugin - Mol* plugin context
|
||||
* @param pdbId1 - PDB ID of first structure (reference)
|
||||
* @param chain1 - Chain ID of first structure
|
||||
* @param pdbId2 - PDB ID of second structure (mobile)
|
||||
* @param chain2 - Chain ID of second structure
|
||||
* @param color1 - Optional color for first structure (hex, default blue)
|
||||
* @param color2 - Optional color for second structure (hex, default red)
|
||||
*/
|
||||
export async function sequenceAlignStructures(
|
||||
plugin: PluginContext,
|
||||
pdbId1: string,
|
||||
chain1: string,
|
||||
pdbId2: string,
|
||||
chain2: string,
|
||||
color1: number = 0x3498db,
|
||||
color2: number = 0xe74c3c
|
||||
): Promise<{ rmsd: number }> {
|
||||
await plugin.clear();
|
||||
|
||||
const url1 = `https://files.rcsb.org/download/${pdbId1}.pdb`;
|
||||
const url2 = `https://files.rcsb.org/download/${pdbId2}.pdb`;
|
||||
const label1 = `${pdbId1} Chain ${chain1}`;
|
||||
const label2 = `${pdbId2} Chain ${chain2}`;
|
||||
|
||||
const struct1 = await loadStructure(plugin, url1, 'pdb');
|
||||
const struct2 = await loadStructure(plugin, url2, 'pdb');
|
||||
|
||||
// Build queries for C-alpha atoms from specified chains
|
||||
const caQuery1 = compile<StructureSelection>(MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), chain1]),
|
||||
'atom-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_atom_id(), 'CA'])
|
||||
}));
|
||||
const caQuery2 = compile<StructureSelection>(MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), chain2]),
|
||||
'atom-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_atom_id(), 'CA'])
|
||||
}));
|
||||
|
||||
const structure1Data = struct1.structure.cell?.obj?.data;
|
||||
const structure2Data = struct2.structure.cell?.obj?.data;
|
||||
|
||||
if (!structure1Data || !structure2Data) {
|
||||
console.error('Failed to load structures');
|
||||
return { rmsd: 0 };
|
||||
}
|
||||
|
||||
const sel1 = StructureSelection.toLociWithCurrentUnits(caQuery1(new QueryContext(structure1Data)));
|
||||
const sel2 = StructureSelection.toLociWithCurrentUnits(caQuery2(new QueryContext(structure2Data)));
|
||||
|
||||
// Run sequence alignment + superposition
|
||||
const transforms = alignAndSuperpose([sel1, sel2]);
|
||||
|
||||
// Apply the transformation to superimpose structure 2 onto structure 1
|
||||
await transform(plugin, struct2.structure, transforms[0].bTransform);
|
||||
|
||||
// Add cartoon representations
|
||||
await addChainRepresentation(plugin, struct1.structure, chain1, label1, color1);
|
||||
await addChainRepresentation(plugin, struct2.structure, chain2, label2, color2);
|
||||
|
||||
console.log('RMSD:', transforms[0].rmsd.toFixed(2), 'A');
|
||||
|
||||
return { rmsd: transforms[0].rmsd };
|
||||
}
|
||||
39
src/examples/basic-wrapper/tm-align.html
Normal file
39
src/examples/basic-wrapper/tm-align.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!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>TM-align Superposition</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#app {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
// Initialize and automatically run TM-align superposition
|
||||
BasicMolStarWrapper.init('app').then(() => {
|
||||
BasicMolStarWrapper.setBackground(0xffffff);
|
||||
BasicMolStarWrapper.tests.tmAlignSuperposition();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -67,6 +67,12 @@ export function getPrimitiveStructureRefs(primitives: MolstarSubtree<'primitives
|
||||
export class MVSPrimitivesData extends SO.Create<PrimitiveBuilderContext>({ name: 'Primitive Data', typeClass: 'Object' }) { }
|
||||
export class MVSPrimitiveShapes extends SO.Create<{ mesh?: Shape<Mesh>, labels?: Shape<Text> }>({ name: 'Primitive Shapes', typeClass: 'Object' }) { }
|
||||
|
||||
export interface MVSPrimitiveShapeSourceData {
|
||||
kind: 'mvs-primitives',
|
||||
node: MVSNode<'primitives'>,
|
||||
groupToNode: Map<number, MVSNode<'primitive'>>,
|
||||
}
|
||||
|
||||
export type MVSDownloadPrimitiveData = typeof MVSDownloadPrimitiveData
|
||||
export const MVSDownloadPrimitiveData = MVSTransform({
|
||||
name: 'mvs-download-primitive-data',
|
||||
@@ -605,7 +611,7 @@ function buildPrimitiveMesh(context: PrimitiveBuilderContext, prev?: Mesh): Shap
|
||||
kind: 'mvs-primitives',
|
||||
node: context.node,
|
||||
groupToNode: state.groups.groupToNodeMap,
|
||||
},
|
||||
} satisfies MVSPrimitiveShapeSourceData,
|
||||
MeshBuilder.getMesh(meshBuilder),
|
||||
(g) => colors.get(g) as Color ?? color,
|
||||
(g) => 1,
|
||||
@@ -638,7 +644,7 @@ function buildPrimitiveLines(context: PrimitiveBuilderContext, prev?: Lines): Sh
|
||||
kind: 'mvs-primitives',
|
||||
node: context.node,
|
||||
groupToNode: state.groups.groupToNodeMap,
|
||||
},
|
||||
} satisfies MVSPrimitiveShapeSourceData,
|
||||
linesBuilder.getLines(),
|
||||
(g) => colors.get(g) as Color ?? color,
|
||||
(g) => sizes.get(g) ?? 1,
|
||||
@@ -673,7 +679,7 @@ function buildPrimitiveLabels(context: PrimitiveBuilderContext, prev: Text | und
|
||||
kind: 'mvs-primitives',
|
||||
node: context.node,
|
||||
groupToNode: state.groups.groupToNodeMap,
|
||||
},
|
||||
} satisfies MVSPrimitiveShapeSourceData,
|
||||
labelsBuilder.getText(),
|
||||
(g) => colors.get(g) as Color ?? color,
|
||||
(g) => sizes.get(g) ?? 1,
|
||||
|
||||
@@ -95,7 +95,10 @@ async function _loadMVS(ctx: RuntimeContext, plugin: PluginContext, data: MVSDat
|
||||
options
|
||||
);
|
||||
await assignStateTransition(ctx, plugin, entry, snapshot, options, i, multiData.snapshots.length);
|
||||
entries.push(entry);
|
||||
entries.push({
|
||||
...entry,
|
||||
_transientData: { sourceMvsSnapshot: snapshot }
|
||||
});
|
||||
|
||||
if (ctx.shouldUpdate) {
|
||||
await ctx.update({ message: 'Loading MVS...', current: i, max: multiData.snapshots.length });
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { Loci } from '../../mol-model/loci';
|
||||
import { ShapeGroup } from '../../mol-model/shape';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateObjectSelector, StateTree } from '../../mol-state';
|
||||
import type { MVSPrimitiveShapeSourceData } from './components/primitives';
|
||||
import type { Snapshot } from './mvs-data';
|
||||
import type { MVSNode } from './tree/mvs/mvs-tree';
|
||||
|
||||
|
||||
/**
|
||||
@@ -33,4 +39,26 @@ export function createMVSRefMap(plugin: PluginContext) {
|
||||
});
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
export function tryGetPrimitivesFromLoci(loci: Loci | undefined): MVSNode<'primitive'>[] | undefined {
|
||||
if (!ShapeGroup.isLoci(loci)) return undefined;
|
||||
|
||||
const srcData = loci.shape.sourceData as MVSPrimitiveShapeSourceData;
|
||||
if (srcData?.kind !== 'mvs-primitives') return undefined;
|
||||
|
||||
const nodes: MVSNode<'primitive'>[] = [];
|
||||
for (const group of loci.groups) {
|
||||
OrderedSet.forEach(group.ids, id => {
|
||||
const node = srcData.groupToNode.get(id);
|
||||
if (node) nodes.push(node);
|
||||
});
|
||||
}
|
||||
return nodes.length > 0 ? nodes : undefined;
|
||||
}
|
||||
|
||||
// Retrieves the MVS snapshot associated with the current snapshot of the plugin
|
||||
// This will only work if the current state was created from an MVS snapshot
|
||||
export function getCurrentMVSSnapshot(plugin: PluginContext): Snapshot | undefined {
|
||||
return plugin.managers.snapshot.current?._transientData?.sourceMvsSnapshot;
|
||||
}
|
||||
@@ -96,6 +96,7 @@ export const Canvas3DParams = {
|
||||
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
|
||||
sceneRadiusFactor: PD.Numeric(1, { min: 1, max: 10, step: 0.1 }),
|
||||
transparentBackground: PD.Boolean(false),
|
||||
checkeredTransparentBackground: PD.Boolean(false),
|
||||
dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }),
|
||||
pickPadding: PD.Numeric(3, { min: 0, max: 10, step: 1 }, { description: 'Extra pixels to around target to check in case target is empty.' }),
|
||||
userInteractionReleaseMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time before the user is not considered interacting anymore.' }),
|
||||
@@ -405,6 +406,22 @@ const cancelAnimationFrame = typeof window !== 'undefined'
|
||||
? window.cancelAnimationFrame
|
||||
: (handle: number) => clearImmediate(handle as unknown as NodeJS.Immediate);
|
||||
|
||||
function syncCanvasBackground(canvas: HTMLCanvasElement, canvasProps: Canvas3DProps) {
|
||||
if (canvasProps.transparentBackground && canvasProps.checkeredTransparentBackground) {
|
||||
Object.assign(canvas.style, {
|
||||
'background-image': 'linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey)',
|
||||
'background-size': '60px 60px',
|
||||
'background-position': '0 0, 30px 30px'
|
||||
});
|
||||
} else {
|
||||
Object.assign(canvas.style, {
|
||||
'background-image': '',
|
||||
'background-size': '',
|
||||
'background-position': ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
namespace Canvas3D {
|
||||
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
|
||||
export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 }
|
||||
@@ -435,6 +452,7 @@ namespace Canvas3D {
|
||||
let forceNextRender = false;
|
||||
let currentTime = 0;
|
||||
|
||||
syncCanvasBackground(canvas!, p);
|
||||
updateViewport();
|
||||
const scene = Scene.create(webgl, passes.draw.transparency, {
|
||||
dColorMarker: p.renderer.colorMarker,
|
||||
@@ -1061,6 +1079,7 @@ namespace Canvas3D {
|
||||
cameraResetDurationMs: p.cameraResetDurationMs,
|
||||
sceneRadiusFactor: p.sceneRadiusFactor,
|
||||
transparentBackground: p.transparentBackground,
|
||||
checkeredTransparentBackground: p.checkeredTransparentBackground,
|
||||
dpoitIterations: p.dpoitIterations,
|
||||
pickPadding: p.pickPadding,
|
||||
userInteractionReleaseMs: p.userInteractionReleaseMs,
|
||||
@@ -1311,6 +1330,7 @@ namespace Canvas3D {
|
||||
}
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs;
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground;
|
||||
if (props.checkeredTransparentBackground !== undefined) p.checkeredTransparentBackground = props.checkeredTransparentBackground;
|
||||
if (props.dpoitIterations !== undefined) p.dpoitIterations = props.dpoitIterations;
|
||||
if (props.pickPadding !== undefined) {
|
||||
p.pickPadding = props.pickPadding;
|
||||
@@ -1357,6 +1377,12 @@ namespace Canvas3D {
|
||||
p.camera.stereo.name = 'off';
|
||||
}
|
||||
|
||||
if ('transparentBackground' in props
|
||||
|| 'checkeredTransparentBackground' in props
|
||||
|| (props.renderer && 'backgroundColor' in props.renderer)) {
|
||||
syncCanvasBackground(canvas!, p);
|
||||
}
|
||||
|
||||
shaderManager.updateRequired(p);
|
||||
if (!doNotRequestDraw) {
|
||||
requestDraw();
|
||||
|
||||
@@ -154,6 +154,7 @@ export class PickHelper {
|
||||
|
||||
if (this.dirty) {
|
||||
if (isTimingMode) this.webgl.timer.mark('PickHelper.identify');
|
||||
this.webgl.resources.finalizePrograms(['pick'], true);
|
||||
this.render(camera);
|
||||
this.buffers.read();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('PickHelper.identify');
|
||||
|
||||
@@ -390,7 +390,7 @@ export class PickBuffers {
|
||||
|
||||
this.fenceTimestamp = now();
|
||||
this.fenceSync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
// gl.flush();
|
||||
gl.flush();
|
||||
|
||||
this.ready = false;
|
||||
if (isTimingMode) this.webgl.timer.markEnd('PickBuffers.asyncRead');
|
||||
|
||||
@@ -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>
|
||||
@@ -28,6 +28,7 @@ import { CylindersValues } from '../../../mol-gl/renderable/cylinders';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { createEmptySubstance } from '../substance-data';
|
||||
import { createEmptyEmissive } from '../emissive-data';
|
||||
import { getInteriorColor, getInteriorParam, getInteriorSubstance } from '../interior';
|
||||
|
||||
export interface Cylinders {
|
||||
readonly kind: 'cylinders',
|
||||
@@ -174,6 +175,7 @@ export namespace Cylinders {
|
||||
solidInterior: PD.Boolean(true, BaseGeometry.ShadingCategory),
|
||||
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
interior: getInteriorParam(),
|
||||
colorMode: PD.Select('default', PD.arrayToOptions(['default', 'interpolate'] as const), BaseGeometry.ShadingCategory)
|
||||
};
|
||||
export type Params = typeof Params
|
||||
@@ -266,6 +268,8 @@ export namespace Cylinders {
|
||||
dSolidInterior: ValueCell.create(props.solidInterior),
|
||||
uBumpFrequency: ValueCell.create(props.bumpFrequency),
|
||||
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
|
||||
uInteriorColor: ValueCell.create(getInteriorColor(props.interior, Vec4())),
|
||||
uInteriorSubstance: ValueCell.create(getInteriorSubstance(props.interior, Vec4())),
|
||||
dDualColor: ValueCell.create(props.colorMode === 'interpolate'),
|
||||
};
|
||||
}
|
||||
@@ -287,6 +291,8 @@ export namespace Cylinders {
|
||||
ValueCell.updateIfChanged(values.dSolidInterior, props.solidInterior);
|
||||
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
|
||||
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
|
||||
ValueCell.update(values.uInteriorColor, getInteriorColor(props.interior, values.uInteriorColor.ref.value));
|
||||
ValueCell.update(values.uInteriorSubstance, getInteriorSubstance(props.interior, values.uInteriorSubstance.ref.value));
|
||||
ValueCell.updateIfChanged(values.dDualColor, props.colorMode === 'interpolate');
|
||||
}
|
||||
|
||||
|
||||
39
src/mol-geo/geometry/interior.ts
Normal file
39
src/mol-geo/geometry/interior.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec4 } from '../../mol-math/linear-algebra/3d/vec4';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { Material } from '../../mol-util/material';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
export function getInteriorParam() {
|
||||
return PD.Group({
|
||||
color: PD.Color(Color.fromRgb(76, 76, 76)),
|
||||
colorStrength: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
substance: Material.getParam(),
|
||||
substanceStrength: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
});
|
||||
}
|
||||
export type InteriorProp = ReturnType<typeof getInteriorParam>['defaultValue'];
|
||||
|
||||
export function areInteriorPropsEquals(a: InteriorProp, b: InteriorProp): boolean {
|
||||
return a.color === b.color
|
||||
&& a.colorStrength === b.colorStrength
|
||||
&& Material.areEqual(a.substance, b.substance)
|
||||
&& a.substanceStrength === b.substanceStrength;
|
||||
}
|
||||
|
||||
export function getInteriorColor(props: InteriorProp, out: Vec4): Vec4 {
|
||||
Color.toArrayNormalized(props.color, out, 0);
|
||||
out[3] = props.colorStrength;
|
||||
return out;
|
||||
}
|
||||
|
||||
export function getInteriorSubstance(props: InteriorProp, out: Vec4): Vec4 {
|
||||
Material.toArrayNormalized(props.substance, out, 0);
|
||||
out[3] = props.substanceStrength;
|
||||
return out;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -29,6 +29,7 @@ import { arraySetAdd } from '../../../mol-util/array';
|
||||
import { degToRad } from '../../../mol-math/misc';
|
||||
import { createEmptySubstance } from '../substance-data';
|
||||
import { createEmptyEmissive } from '../emissive-data';
|
||||
import { getInteriorColor, getInteriorParam, getInteriorSubstance } from '../interior';
|
||||
|
||||
export interface Mesh {
|
||||
readonly kind: 'mesh',
|
||||
@@ -633,6 +634,7 @@ export namespace Mesh {
|
||||
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque'] as const), BaseGeometry.ShadingCategory),
|
||||
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
interior: getInteriorParam(),
|
||||
};
|
||||
export type Params = typeof Params
|
||||
|
||||
@@ -719,6 +721,8 @@ export namespace Mesh {
|
||||
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
|
||||
uBumpFrequency: ValueCell.create(props.bumpFrequency),
|
||||
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
|
||||
uInteriorColor: ValueCell.create(getInteriorColor(props.interior, Vec4())),
|
||||
uInteriorSubstance: ValueCell.create(getInteriorSubstance(props.interior, Vec4())),
|
||||
|
||||
meta: ValueCell.create(mesh.meta),
|
||||
};
|
||||
@@ -741,6 +745,8 @@ export namespace Mesh {
|
||||
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
|
||||
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
|
||||
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
|
||||
ValueCell.update(values.uInteriorColor, getInteriorColor(props.interior, values.uInteriorColor.ref.value));
|
||||
ValueCell.update(values.uInteriorSubstance, getInteriorSubstance(props.interior, values.uInteriorSubstance.ref.value));
|
||||
}
|
||||
|
||||
function updateBoundingSphere(values: MeshValues, mesh: Mesh) {
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -27,6 +27,7 @@ import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { createEmptySubstance } from '../substance-data';
|
||||
import { createEmptyEmissive } from '../emissive-data';
|
||||
import { getInteriorColor, getInteriorParam, getInteriorSubstance } from '../interior';
|
||||
|
||||
export interface Spheres {
|
||||
readonly kind: 'spheres',
|
||||
@@ -256,6 +257,7 @@ export namespace Spheres {
|
||||
alphaThickness: PD.Numeric(0, { min: 0, max: 20, step: 1 }, { ...BaseGeometry.ShadingCategory, description: 'If not zero, adjusts alpha for radius.' }),
|
||||
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
interior: getInteriorParam(),
|
||||
lodLevels: PD.ObjectList({
|
||||
minDistance: PD.Numeric(0),
|
||||
maxDistance: PD.Numeric(0),
|
||||
@@ -356,6 +358,8 @@ export namespace Spheres {
|
||||
uAlphaThickness: ValueCell.create(props.alphaThickness),
|
||||
uBumpFrequency: ValueCell.create(props.bumpFrequency),
|
||||
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
|
||||
uInteriorColor: ValueCell.create(getInteriorColor(props.interior, Vec4())),
|
||||
uInteriorSubstance: ValueCell.create(getInteriorSubstance(props.interior, Vec4())),
|
||||
|
||||
lodLevels: spheres.shaderData.lodLevels,
|
||||
centerBuffer: spheres.centerBuffer,
|
||||
@@ -383,6 +387,8 @@ export namespace Spheres {
|
||||
ValueCell.updateIfChanged(values.uAlphaThickness, props.alphaThickness);
|
||||
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
|
||||
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
|
||||
ValueCell.update(values.uInteriorColor, getInteriorColor(props.interior, values.uInteriorColor.ref.value));
|
||||
ValueCell.update(values.uInteriorSubstance, getInteriorSubstance(props.interior, values.uInteriorSubstance.ref.value));
|
||||
|
||||
const lodLevels = getLodLevels(values.lodLevels.ref.value as LodLevelsValue);
|
||||
if (!areLodLevelsEqual(props.lodLevels, lodLevels)) {
|
||||
|
||||
@@ -28,6 +28,7 @@ import { createEmptySubstance } from '../substance-data';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
import { createEmptyEmissive } from '../emissive-data';
|
||||
import { getInteriorColor, getInteriorParam, getInteriorSubstance } from '../interior';
|
||||
|
||||
export interface TextureMesh {
|
||||
readonly kind: 'texture-mesh',
|
||||
@@ -128,6 +129,7 @@ export namespace TextureMesh {
|
||||
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque'] as const), BaseGeometry.ShadingCategory),
|
||||
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
interior: getInteriorParam(),
|
||||
};
|
||||
export type Params = typeof Params
|
||||
|
||||
@@ -244,6 +246,8 @@ export namespace TextureMesh {
|
||||
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
|
||||
uBumpFrequency: ValueCell.create(props.bumpFrequency),
|
||||
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
|
||||
uInteriorColor: ValueCell.create(getInteriorColor(props.interior, Vec4())),
|
||||
uInteriorSubstance: ValueCell.create(getInteriorSubstance(props.interior, Vec4())),
|
||||
|
||||
meta: ValueCell.create(textureMesh.meta),
|
||||
};
|
||||
@@ -266,6 +270,8 @@ export namespace TextureMesh {
|
||||
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
|
||||
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
|
||||
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
|
||||
ValueCell.update(values.uInteriorColor, getInteriorColor(props.interior, values.uInteriorColor.ref.value));
|
||||
ValueCell.update(values.uInteriorSubstance, getInteriorSubstance(props.interior, values.uInteriorSubstance.ref.value));
|
||||
}
|
||||
|
||||
function updateBoundingSphere(values: TextureMeshValues, textureMesh: TextureMesh) {
|
||||
|
||||
@@ -32,6 +32,8 @@ export const CylindersSchema = {
|
||||
dSolidInterior: DefineSpec('boolean'),
|
||||
uBumpFrequency: UniformSpec('f', 'material'),
|
||||
uBumpAmplitude: UniformSpec('f', 'material'),
|
||||
uInteriorColor: UniformSpec('v4'),
|
||||
uInteriorSubstance: UniformSpec('v4'),
|
||||
dDualColor: DefineSpec('boolean'),
|
||||
};
|
||||
export type CylindersSchema = typeof CylindersSchema
|
||||
|
||||
@@ -27,6 +27,8 @@ export const MeshSchema = {
|
||||
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
|
||||
uBumpFrequency: UniformSpec('f', 'material'),
|
||||
uBumpAmplitude: UniformSpec('f', 'material'),
|
||||
uInteriorColor: UniformSpec('v4'),
|
||||
uInteriorSubstance: UniformSpec('v4'),
|
||||
meta: ValueSpec('unknown')
|
||||
} as const;
|
||||
export type MeshSchema = typeof MeshSchema
|
||||
|
||||
@@ -162,10 +162,6 @@ export const GlobalUniformSchema = {
|
||||
|
||||
uPickingAlphaThreshold: UniformSpec('f'),
|
||||
|
||||
uInteriorDarkening: UniformSpec('f'),
|
||||
uInteriorColorFlag: UniformSpec('b'),
|
||||
uInteriorColor: UniformSpec('v3'),
|
||||
|
||||
uHighlightColor: UniformSpec('v3'),
|
||||
uSelectColor: UniformSpec('v3'),
|
||||
uDimColor: UniformSpec('v3'),
|
||||
|
||||
@@ -30,6 +30,8 @@ export const SpheresSchema = {
|
||||
uAlphaThickness: UniformSpec('f'),
|
||||
uBumpFrequency: UniformSpec('f', 'material'),
|
||||
uBumpAmplitude: UniformSpec('f', 'material'),
|
||||
uInteriorColor: UniformSpec('v4'),
|
||||
uInteriorSubstance: UniformSpec('v4'),
|
||||
|
||||
lodLevels: ValueSpec('unknown'),
|
||||
centerBuffer: ValueSpec('float32'),
|
||||
|
||||
@@ -27,6 +27,8 @@ export const TextureMeshSchema = {
|
||||
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
|
||||
uBumpFrequency: UniformSpec('f', 'material'),
|
||||
uBumpAmplitude: UniformSpec('f', 'material'),
|
||||
uInteriorColor: UniformSpec('v4'),
|
||||
uInteriorSubstance: UniformSpec('v4'),
|
||||
meta: ValueSpec('unknown')
|
||||
};
|
||||
export type TextureMeshSchema = typeof TextureMeshSchema
|
||||
|
||||
@@ -95,10 +95,6 @@ export const RendererParams = {
|
||||
|
||||
pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
|
||||
|
||||
interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
interiorColorFlag: PD.Boolean(true, { label: 'Use Interior Color' }),
|
||||
interiorColor: PD.Color(Color.fromNormalizedRgb(0.3, 0.3, 0.3)),
|
||||
|
||||
colorMarker: PD.Boolean(true, { description: 'Enable color marker' }),
|
||||
highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
|
||||
selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
|
||||
@@ -269,10 +265,6 @@ namespace Renderer {
|
||||
|
||||
uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
|
||||
|
||||
uInteriorDarkening: ValueCell.create(p.interiorDarkening),
|
||||
uInteriorColorFlag: ValueCell.create(p.interiorColorFlag),
|
||||
uInteriorColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.interiorColor)),
|
||||
|
||||
uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)),
|
||||
uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)),
|
||||
uDimColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.dimColor)),
|
||||
@@ -849,19 +841,6 @@ namespace Renderer {
|
||||
ValueCell.update(globalUniforms.uPickingAlphaThreshold, p.pickingAlphaThreshold);
|
||||
}
|
||||
|
||||
if (props.interiorDarkening !== undefined && props.interiorDarkening !== p.interiorDarkening) {
|
||||
p.interiorDarkening = props.interiorDarkening;
|
||||
ValueCell.update(globalUniforms.uInteriorDarkening, p.interiorDarkening);
|
||||
}
|
||||
if (props.interiorColorFlag !== undefined && props.interiorColorFlag !== p.interiorColorFlag) {
|
||||
p.interiorColorFlag = props.interiorColorFlag;
|
||||
ValueCell.update(globalUniforms.uInteriorColorFlag, p.interiorColorFlag);
|
||||
}
|
||||
if (props.interiorColor !== undefined && props.interiorColor !== p.interiorColor) {
|
||||
p.interiorColor = props.interiorColor;
|
||||
ValueCell.update(globalUniforms.uInteriorColor, Color.toVec3Normalized(globalUniforms.uInteriorColor.ref.value, p.interiorColor));
|
||||
}
|
||||
|
||||
if (props.colorMarker !== undefined && props.colorMarker !== p.colorMarker) {
|
||||
p.colorMarker = props.colorMarker;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
export const apply_interior_color = `
|
||||
if (interior) {
|
||||
if (uInteriorColorFlag) {
|
||||
gl_FragColor.rgb = uInteriorColor;
|
||||
} else {
|
||||
gl_FragColor.rgb *= 1.0 - uInteriorDarkening;
|
||||
}
|
||||
material.rgb = mix(material.rgb, uInteriorColor.rgb, uInteriorColor.a);
|
||||
|
||||
float isf = clamp(uInteriorSubstance.a, 0.0, 0.99); // clamp to avoid artifacts
|
||||
metalness = mix(metalness, uInteriorSubstance.r, isf);
|
||||
roughness = mix(roughness, uInteriorSubstance.g, isf);
|
||||
bumpiness = mix(bumpiness, uInteriorSubstance.b, isf);
|
||||
|
||||
#ifdef dTransparentBackfaces_opaque
|
||||
gl_FragColor.a = 1.0;
|
||||
material.a = 1.0;
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
@@ -72,9 +72,6 @@ uniform float uPickingAlphaThreshold;
|
||||
uniform bool uTransparentBackground;
|
||||
|
||||
uniform bool uDoubleSided;
|
||||
uniform float uInteriorDarkening;
|
||||
uniform bool uInteriorColorFlag;
|
||||
uniform vec3 uInteriorColor;
|
||||
bool interior;
|
||||
|
||||
uniform float uXrayEdgeFalloff;
|
||||
|
||||
@@ -23,6 +23,9 @@ uniform vec3 uCameraDir;
|
||||
uniform vec3 uCameraPosition;
|
||||
uniform mat4 uInvView;
|
||||
|
||||
uniform vec4 uInteriorColor;
|
||||
uniform vec4 uInteriorSubstance;
|
||||
|
||||
#include common
|
||||
#include common_frag_params
|
||||
#include color_frag_params
|
||||
@@ -177,6 +180,13 @@ bool CylinderImpostor(
|
||||
if (!objectClipped) {
|
||||
fragmentDepth = 0.0 + (0.0000002 / vSize);
|
||||
cameraNormal = -rayDir;
|
||||
|
||||
// intersection of ray in model space with near plane in camera space
|
||||
vec3 cameraRayOrigin = (uView * vec4(rayOrigin, 1.0)).xyz;
|
||||
vec3 cameraRayDir = (uView * vec4(rayDir, 0.0)).xyz;
|
||||
float nearT = - (uNear + cameraRayOrigin.z) / cameraRayDir.z;
|
||||
viewPosition = cameraRayOrigin + nearT * cameraRayDir;
|
||||
modelPosition = (uInvView * vec4(viewPosition, 1.0)).xyz;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
@@ -197,6 +207,13 @@ bool CylinderImpostor(
|
||||
if (!objectClipped) {
|
||||
fragmentDepth = 0.0 + (0.0000002 / vSize);
|
||||
cameraNormal = -rayDir;
|
||||
|
||||
// intersection of ray in model space with near plane in camera space
|
||||
vec3 cameraRayOrigin = (uView * vec4(rayOrigin, 1.0)).xyz;
|
||||
vec3 cameraRayDir = (uView * vec4(rayDir, 0.0)).xyz;
|
||||
float nearT = - (uNear + cameraRayOrigin.z) / cameraRayDir.z;
|
||||
viewPosition = cameraRayOrigin + nearT * cameraRayDir;
|
||||
modelPosition = (uInvView * vec4(viewPosition, 1.0)).xyz;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
@@ -216,6 +233,13 @@ bool CylinderImpostor(
|
||||
if (!objectClipped) {
|
||||
fragmentDepth = 0.0 + (0.0000002 / vSize);
|
||||
cameraNormal = -rayDir;
|
||||
|
||||
// intersection of ray in model space with near plane in camera space
|
||||
vec3 cameraRayOrigin = (uView * vec4(rayOrigin, 1.0)).xyz;
|
||||
vec3 cameraRayDir = (uView * vec4(rayDir, 0.0)).xyz;
|
||||
float nearT = - (uNear + cameraRayOrigin.z) / cameraRayDir.z;
|
||||
viewPosition = cameraRayOrigin + nearT * cameraRayDir;
|
||||
modelPosition = (uInvView * vec4(viewPosition, 1.0)).xyz;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
@@ -274,8 +298,8 @@ void main() {
|
||||
#elif defined(dRenderVariant_emissive)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color) || defined(dRenderVariant_tracing)
|
||||
#include apply_light_color
|
||||
#include apply_interior_color
|
||||
#include apply_light_color
|
||||
#include apply_marker_color
|
||||
|
||||
#if defined(dRenderVariant_color)
|
||||
|
||||
@@ -17,15 +17,14 @@ precision highp int;
|
||||
#include normal_frag_params
|
||||
#include common_clip
|
||||
|
||||
uniform vec4 uInteriorColor;
|
||||
uniform vec4 uInteriorSubstance;
|
||||
|
||||
void main() {
|
||||
#include fade_lod
|
||||
#include clip_pixel
|
||||
|
||||
#if defined(dFlipSided)
|
||||
interior = gl_FrontFacing;
|
||||
#else
|
||||
interior = !gl_FrontFacing;
|
||||
#endif
|
||||
interior = !gl_FrontFacing;
|
||||
|
||||
float fragmentDepth = gl_FragCoord.z;
|
||||
|
||||
@@ -38,6 +37,10 @@ void main() {
|
||||
vec3 normal = -normalize(vNormal);
|
||||
if (uDoubleSided) normal *= float(gl_FrontFacing) * 2.0 - 1.0;
|
||||
#endif
|
||||
|
||||
#if defined(dFlipSided)
|
||||
normal *= -1.0;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include assign_material_color
|
||||
@@ -60,8 +63,8 @@ void main() {
|
||||
#elif defined(dRenderVariant_emissive)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color) || defined(dRenderVariant_tracing)
|
||||
#include apply_light_color
|
||||
#include apply_interior_color
|
||||
#include apply_light_color
|
||||
#include apply_marker_color
|
||||
|
||||
#if defined(dRenderVariant_color)
|
||||
|
||||
@@ -19,6 +19,9 @@ precision highp int;
|
||||
uniform mat4 uInvView;
|
||||
uniform float uAlphaThickness;
|
||||
|
||||
uniform vec4 uInteriorColor;
|
||||
uniform vec4 uInteriorSubstance;
|
||||
|
||||
varying float vRadius;
|
||||
varying vec3 vPoint;
|
||||
varying vec3 vPointViewPosition;
|
||||
@@ -73,6 +76,11 @@ bool SphereImpostor(out vec3 modelPos, out vec3 cameraPos, out vec3 cameraNormal
|
||||
if (!objectClipped) {
|
||||
fragmentDepth = 0.0 + (0.0000001 / vRadius);
|
||||
cameraNormal = -mix(normalize(vPoint), vec3(0.0, 0.0, -1.0), uIsOrtho);
|
||||
|
||||
// intersection of ray with near plane
|
||||
float nearT = - (uNear + dot(rayOrigin, vec3(0.0, 0.0, 1.0))) / dot(rayDirection, vec3(0.0, 0.0, 1.0));
|
||||
cameraPos = rayDirection * nearT + rayOrigin;
|
||||
modelPos = (uInvView * vec4(cameraPos, 1.0)).xyz;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
@@ -147,8 +155,8 @@ void main(void){
|
||||
#elif defined(dRenderVariant_emissive)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color) || defined(dRenderVariant_tracing)
|
||||
#include apply_light_color
|
||||
#include apply_interior_color
|
||||
#include apply_light_color
|
||||
#include apply_marker_color
|
||||
|
||||
#if defined(dRenderVariant_color)
|
||||
|
||||
@@ -78,7 +78,7 @@ void main(void) {
|
||||
|
||||
float sum = 0.0;
|
||||
float kernelSum = 0.0;
|
||||
int halfKernelSize = dOcclusionKernelSize / 2;
|
||||
const int halfKernelSize = dOcclusionKernelSize / 2;
|
||||
// only if kernelSize is odd
|
||||
for (int i = -halfKernelSize; i <= halfKernelSize; i++) {
|
||||
if (abs(float(i)) > 1.0 && abs(float(i)) * pixelSize > 0.8) continue;
|
||||
|
||||
@@ -235,7 +235,7 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
|
||||
|
||||
use: () => {
|
||||
// console.log('use', programId)
|
||||
if (isDebugMode && !finalized) throw new Error('program not finalized');
|
||||
if (isDebugMode && !finalized) throw new Error(`program not finalized: ${variant}`);
|
||||
state.currentProgramId = programId;
|
||||
gl.useProgram(program);
|
||||
},
|
||||
|
||||
@@ -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.408, IHM 1.28, MA 1.4.8.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.409, IHM 1.28, MA 1.4.8.
|
||||
*
|
||||
* @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.408, IHM 1.28, MA 1.4.8.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.409, IHM 1.28, MA 1.4.8.
|
||||
*
|
||||
* @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.408, IHM 1.28, MA 1.4.8.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.409, IHM 1.28, MA 1.4.8.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -119,11 +119,12 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
const vx = px[j], vy = py[j], vz = pz[j];
|
||||
const rad = radius[j];
|
||||
const rSq = rad * rad;
|
||||
const extended = ngPoints > 0;
|
||||
|
||||
lookup3d.find(vx, vy, vz, rad);
|
||||
|
||||
// Number of grid points, round this up...
|
||||
const ng = Math.ceil(rad * scaleFactor);
|
||||
const ng = Math.ceil(rad * scaleFactor) + ngPoints;
|
||||
|
||||
// Center of the atom, mapped to grid points (take floor)
|
||||
const iax = Math.floor(scaleFactor * (vx - minX));
|
||||
@@ -153,12 +154,9 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
const dz = gridz[zi] - vz;
|
||||
const dSq = dxySq + dz * dz;
|
||||
|
||||
if (dSq < rSq) {
|
||||
if (extended || dSq < rSq) {
|
||||
const idx = zi + xyIdx;
|
||||
|
||||
// if unvisited, make positive
|
||||
if (data[idx] < 0.0) data[idx] *= -1;
|
||||
|
||||
// Project on to the surface of the sphere
|
||||
// sp is the projected point ( dx, dy, dz ) * ( ra / d )
|
||||
const d = Math.sqrt(dSq);
|
||||
@@ -167,12 +165,22 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
const spy = dy * ap + vy;
|
||||
const spz = dz * ap + vz;
|
||||
|
||||
if (obscured(spx, spy, spz, j, -1) === -1) {
|
||||
const dd = rad - d;
|
||||
if (dd < data[idx]) {
|
||||
data[idx] = dd;
|
||||
idData[idx] = id[i];
|
||||
const obs = obscured(spx, spy, spz, j, -1);
|
||||
|
||||
if (dSq < rSq) {
|
||||
// if unvisited, make positive
|
||||
if (data[idx] < 0.0) data[idx] *= -1;
|
||||
|
||||
if (obs === -1) {
|
||||
const dd = rad - d;
|
||||
if (dd < data[idx]) {
|
||||
data[idx] = dd;
|
||||
idData[idx] = id[i];
|
||||
}
|
||||
}
|
||||
} else if (extended && obs === -1) {
|
||||
const dd = rad - d;
|
||||
if (dd > data[idx]) data[idx] = dd;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,7 +326,8 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
// console.time('MolecularSurface createState')
|
||||
const { resolution, probeRadius, probePositions } = props;
|
||||
const scaleFactor = 1 / resolution;
|
||||
const ngTorus = Math.max(5, 2 + Math.floor(probeRadius * scaleFactor));
|
||||
const ngTorus = 2 + Math.floor(probeRadius * scaleFactor);
|
||||
const ngPoints = probeRadius < (resolution * 2) ? 1 : 0;
|
||||
|
||||
const cellSize = Vec3.create(maxRadius, maxRadius, maxRadius);
|
||||
Vec3.scale(cellSize, cellSize, 2);
|
||||
|
||||
1012
src/mol-math/linear-algebra/3d/tm-align.ts
Normal file
1012
src/mol-math/linear-algebra/3d/tm-align.ts
Normal file
File diff suppressed because it is too large
Load Diff
308
src/mol-math/linear-algebra/_spec/tm-align.spec.ts
Normal file
308
src/mol-math/linear-algebra/_spec/tm-align.spec.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Diego del Alamo <diego.delalamo@gmail.com>
|
||||
*/
|
||||
|
||||
import { TMAlign } from '../3d/tm-align';
|
||||
import { Vec3 } from '../3d/vec3';
|
||||
|
||||
// Reference data from US-align for 6F34 vs 3TT3 chain A
|
||||
const REFERENCE_6F34_3TT3 = {
|
||||
structure1Length: 458,
|
||||
structure2Length: 501,
|
||||
alignedLength: 409,
|
||||
rmsd: 4.10,
|
||||
tmScore1: 0.72566, // normalized by structure 1 (6F34)
|
||||
tmScore2: 0.67133, // normalized by structure 2 (3TT3)
|
||||
sequenceIdentity: 0.147
|
||||
};
|
||||
|
||||
describe('TMAlign', () => {
|
||||
// Helper to create positions from coordinate arrays
|
||||
function makePositions(coords: number[][]): TMAlign.Positions {
|
||||
const n = coords.length;
|
||||
const pos = TMAlign.Positions.empty(n);
|
||||
for (let i = 0; i < n; i++) {
|
||||
pos.x[i] = coords[i][0];
|
||||
pos.y[i] = coords[i][1];
|
||||
pos.z[i] = coords[i][2];
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
describe('calculateD0', () => {
|
||||
it('returns 0.5 for short proteins (L <= 21)', () => {
|
||||
expect(TMAlign.calculateD0(10)).toBe(0.5);
|
||||
expect(TMAlign.calculateD0(21)).toBe(0.5);
|
||||
});
|
||||
|
||||
it('returns correct d0 for longer proteins', () => {
|
||||
// d0 = 1.24 * (L - 15)^(1/3) - 1.8
|
||||
const d0_100 = 1.24 * Math.pow(100 - 15, 1 / 3) - 1.8;
|
||||
expect(TMAlign.calculateD0(100)).toBeCloseTo(d0_100, 5);
|
||||
|
||||
const d0_200 = 1.24 * Math.pow(200 - 15, 1 / 3) - 1.8;
|
||||
expect(TMAlign.calculateD0(200)).toBeCloseTo(d0_200, 5);
|
||||
});
|
||||
|
||||
it('matches reference d0 values', () => {
|
||||
// From reference: L=458, d0=7.65; L=501, d0=7.95
|
||||
expect(TMAlign.calculateD0(458)).toBeCloseTo(7.65, 1);
|
||||
expect(TMAlign.calculateD0(501)).toBeCloseTo(7.95, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('compute - basic cases', () => {
|
||||
it('returns identity transform and TM-score=1 for identical structures', () => {
|
||||
const coords = [
|
||||
[0, 0, 0],
|
||||
[3.8, 0, 0],
|
||||
[7.6, 0, 0],
|
||||
[11.4, 0, 0],
|
||||
[15.2, 0, 0],
|
||||
];
|
||||
const pos = makePositions(coords);
|
||||
|
||||
const result = TMAlign.compute({ a: pos, b: pos });
|
||||
|
||||
// Identical structures should have perfect TM-score
|
||||
expect(result.tmScoreA).toBeCloseTo(1.0, 2);
|
||||
expect(result.tmScoreB).toBeCloseTo(1.0, 2);
|
||||
expect(result.rmsd).toBeCloseTo(0, 5);
|
||||
expect(result.alignedLength).toBe(5);
|
||||
});
|
||||
|
||||
it('handles empty inputs', () => {
|
||||
const empty = makePositions([]);
|
||||
const result = TMAlign.compute({ a: empty, b: empty });
|
||||
|
||||
expect(result.tmScoreA).toBe(0);
|
||||
expect(result.tmScoreB).toBe(0);
|
||||
expect(result.alignedLength).toBe(0);
|
||||
});
|
||||
|
||||
it('aligns translated structures correctly', () => {
|
||||
// Structure A: simple helix-like
|
||||
const coordsA = [
|
||||
[0, 0, 0],
|
||||
[1.5, 0, 1.0],
|
||||
[3.0, 0, 0],
|
||||
[4.5, 0, 1.0],
|
||||
[6.0, 0, 0],
|
||||
[7.5, 0, 1.0],
|
||||
[9.0, 0, 0],
|
||||
[10.5, 0, 1.0],
|
||||
];
|
||||
|
||||
// Structure B: same structure translated by (10, 20, 30)
|
||||
const translation = [10, 20, 30];
|
||||
const coordsB = coordsA.map(c => [
|
||||
c[0] + translation[0],
|
||||
c[1] + translation[1],
|
||||
c[2] + translation[2]
|
||||
]);
|
||||
|
||||
const posA = makePositions(coordsA);
|
||||
const posB = makePositions(coordsB);
|
||||
|
||||
const result = TMAlign.compute({ a: posA, b: posB });
|
||||
|
||||
// Should align well since they're identical structures
|
||||
expect(result.tmScoreA).toBeGreaterThan(0.9);
|
||||
expect(result.rmsd).toBeLessThan(1.0);
|
||||
});
|
||||
|
||||
it('aligns rotated structures correctly', () => {
|
||||
// Structure A
|
||||
const coordsA = [
|
||||
[0, 0, 0],
|
||||
[3.8, 0, 0],
|
||||
[7.6, 0, 0],
|
||||
[11.4, 0, 0],
|
||||
[15.2, 0, 0],
|
||||
[19.0, 0, 0],
|
||||
];
|
||||
|
||||
// Structure B: rotated 90 degrees around Z axis
|
||||
const coordsB = coordsA.map(c => [-c[1], c[0], c[2]]);
|
||||
|
||||
const posA = makePositions(coordsA);
|
||||
const posB = makePositions(coordsB);
|
||||
|
||||
const result = TMAlign.compute({ a: posA, b: posB });
|
||||
|
||||
// Should align perfectly since it's just a rotation
|
||||
expect(result.tmScoreA).toBeGreaterThan(0.9);
|
||||
expect(result.rmsd).toBeLessThan(0.5);
|
||||
});
|
||||
|
||||
it('returns lower TM-score for dissimilar structures', () => {
|
||||
// Structure A: extended chain
|
||||
const coordsA: number[][] = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
coordsA.push([i * 3.8, 0, 0]);
|
||||
}
|
||||
|
||||
// Structure B: helix-like with different geometry
|
||||
const coordsB: number[][] = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const angle = i * 100 * Math.PI / 180;
|
||||
coordsB.push([
|
||||
5 * Math.cos(angle),
|
||||
5 * Math.sin(angle),
|
||||
i * 1.5
|
||||
]);
|
||||
}
|
||||
|
||||
const posA = makePositions(coordsA);
|
||||
const posB = makePositions(coordsB);
|
||||
|
||||
const result = TMAlign.compute({ a: posA, b: posB });
|
||||
|
||||
// Different structures should have lower TM-score
|
||||
expect(result.tmScoreA).toBeLessThan(0.5);
|
||||
});
|
||||
|
||||
it('produces valid transformation matrix', () => {
|
||||
const coordsA = [
|
||||
[0, 0, 0],
|
||||
[3.8, 0, 0],
|
||||
[7.6, 1, 0],
|
||||
[11.4, 0, 0],
|
||||
[15.2, 1, 0],
|
||||
];
|
||||
|
||||
const coordsB = [
|
||||
[5, 5, 5],
|
||||
[8.8, 5, 5],
|
||||
[12.6, 6, 5],
|
||||
[16.4, 5, 5],
|
||||
[20.2, 6, 5],
|
||||
];
|
||||
|
||||
const posA = makePositions(coordsA);
|
||||
const posB = makePositions(coordsB);
|
||||
|
||||
const result = TMAlign.compute({ a: posA, b: posB });
|
||||
|
||||
// Transform should be a valid 4x4 matrix
|
||||
expect(result.bTransform).toBeDefined();
|
||||
|
||||
// Apply transform to B and check alignment improves
|
||||
const transformedB: number[][] = [];
|
||||
for (let i = 0; i < coordsB.length; i++) {
|
||||
const v = Vec3.create(coordsB[i][0], coordsB[i][1], coordsB[i][2]);
|
||||
Vec3.transformMat4(v, v, result.bTransform);
|
||||
transformedB.push([v[0], v[1], v[2]]);
|
||||
}
|
||||
|
||||
// After transformation, structures should be closer
|
||||
let totalDistAfter = 0;
|
||||
for (let i = 0; i < coordsA.length; i++) {
|
||||
const dx = transformedB[i][0] - coordsA[i][0];
|
||||
const dy = transformedB[i][1] - coordsA[i][1];
|
||||
const dz = transformedB[i][2] - coordsA[i][2];
|
||||
totalDistAfter += Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
expect(totalDistAfter / coordsA.length).toBeLessThan(2.0);
|
||||
});
|
||||
|
||||
it('handles different length structures', () => {
|
||||
const coordsA: number[][] = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
coordsA.push([i * 3.8, Math.sin(i * 0.5), 0]);
|
||||
}
|
||||
|
||||
const coordsB: number[][] = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
coordsB.push([i * 3.8, Math.sin(i * 0.5), 0]);
|
||||
}
|
||||
|
||||
const posA = makePositions(coordsA);
|
||||
const posB = makePositions(coordsB);
|
||||
|
||||
const result = TMAlign.compute({ a: posA, b: posB });
|
||||
|
||||
// Should still produce valid results
|
||||
expect(result.tmScoreA).toBeGreaterThan(0);
|
||||
expect(result.tmScoreB).toBeGreaterThan(0);
|
||||
expect(result.alignedLength).toBeGreaterThan(0);
|
||||
expect(result.alignedLength).toBeLessThanOrEqual(Math.min(50, 30));
|
||||
});
|
||||
});
|
||||
|
||||
describe('TM-score properties', () => {
|
||||
it('TM-score is length-normalized', () => {
|
||||
// Create two identical small structures
|
||||
const coordsSmall: number[][] = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
coordsSmall.push([i * 3.8, Math.sin(i * 0.3) * 2, Math.cos(i * 0.3) * 2]);
|
||||
}
|
||||
|
||||
// Create longer version with same pattern
|
||||
const coordsLong: number[][] = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
coordsLong.push([i * 3.8, Math.sin(i * 0.3) * 2, Math.cos(i * 0.3) * 2]);
|
||||
}
|
||||
|
||||
const posSmall = makePositions(coordsSmall);
|
||||
const posLong = makePositions(coordsLong);
|
||||
|
||||
const result = TMAlign.compute({ a: posLong, b: posSmall });
|
||||
|
||||
// TM-score normalized by longer structure should be lower
|
||||
// than TM-score normalized by shorter structure
|
||||
expect(result.tmScoreA).toBeLessThan(result.tmScoreB);
|
||||
});
|
||||
|
||||
it('TM-score is between 0 and 1 for normalized length', () => {
|
||||
const coordsA: number[][] = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
coordsA.push([i * 3.8, Math.random() * 10, Math.random() * 10]);
|
||||
}
|
||||
|
||||
const coordsB: number[][] = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
coordsB.push([i * 3.8, Math.random() * 10, Math.random() * 10]);
|
||||
}
|
||||
|
||||
const posA = makePositions(coordsA);
|
||||
const posB = makePositions(coordsB);
|
||||
|
||||
const result = TMAlign.compute({ a: posA, b: posB });
|
||||
|
||||
expect(result.tmScoreA).toBeGreaterThanOrEqual(0);
|
||||
expect(result.tmScoreA).toBeLessThanOrEqual(1);
|
||||
expect(result.tmScoreB).toBeGreaterThanOrEqual(0);
|
||||
expect(result.tmScoreB).toBeLessThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Reference comparison (6F34 vs 3TT3 chain A expected ranges)', () => {
|
||||
// These tests verify that our implementation produces results
|
||||
// in the expected ballpark for known protein pairs
|
||||
// Exact values may differ due to algorithm implementation details
|
||||
|
||||
it('d0 calculation matches reference implementation', () => {
|
||||
// Reference values from US-align output
|
||||
const d0_458 = TMAlign.calculateD0(REFERENCE_6F34_3TT3.structure1Length);
|
||||
const d0_501 = TMAlign.calculateD0(REFERENCE_6F34_3TT3.structure2Length);
|
||||
|
||||
// Should be within 5% of reference values
|
||||
expect(d0_458).toBeCloseTo(7.65, 0);
|
||||
expect(d0_501).toBeCloseTo(7.95, 0);
|
||||
});
|
||||
|
||||
it('TM-score interpretation thresholds', () => {
|
||||
// According to TM-align literature:
|
||||
// TM-score > 0.5: same fold
|
||||
// TM-score > 0.17: statistically significant
|
||||
// 6F34 vs 3TT3: TM-scores ~0.73 and ~0.67 indicate same fold
|
||||
|
||||
// The reference TM-scores indicate same fold
|
||||
expect(REFERENCE_6F34_3TT3.tmScore1).toBeGreaterThan(0.5); // same fold
|
||||
expect(REFERENCE_6F34_3TT3.tmScore2).toBeGreaterThan(0.5); // same fold
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
@@ -88,6 +88,8 @@ const residue = {
|
||||
key: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.residueIndex[l.element]),
|
||||
|
||||
group_PDB: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
|
||||
label_comp_id: p(compId),
|
||||
auth_comp_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_comp_id.value(l.element)),
|
||||
label_seq_id: p(seqId),
|
||||
auth_seq_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
|
||||
pdbx_PDB_ins_code: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])),
|
||||
|
||||
@@ -134,6 +134,7 @@ namespace StructureSymmetry {
|
||||
for (let j = 0, _j = au.length; j < _j; j++) {
|
||||
if (au[j].conformation !== bu[j].conformation) return false;
|
||||
}
|
||||
if (!SortedArray.areEqual(a[i].elements, b[i].elements)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
73
src/mol-model/structure/structure/util/tm-align.ts
Normal file
73
src/mol-model/structure/structure/util/tm-align.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Diego del Alamo <diego.delalamo@gmail.com>
|
||||
*
|
||||
* Structure-level TM-align wrapper
|
||||
*/
|
||||
|
||||
import { Mat4 } from '../../../../mol-math/linear-algebra/3d/mat4';
|
||||
import { TMAlign } from '../../../../mol-math/linear-algebra/3d/tm-align';
|
||||
import { StructureElement } from '../element';
|
||||
import { getPositionTable } from './superposition';
|
||||
|
||||
export { tmAlign, tmAlignMultiple };
|
||||
|
||||
export type TMAlignResult = TMAlign.Result;
|
||||
|
||||
/**
|
||||
* Perform TM-align on two structure element loci.
|
||||
* Aligns structure B onto structure A (A is the reference).
|
||||
*
|
||||
* @param a Reference structure loci (will not be transformed)
|
||||
* @param b Mobile structure loci (transformation returned)
|
||||
* @returns TM-align result with transformation, scores, and alignment
|
||||
*/
|
||||
function tmAlign(a: StructureElement.Loci, b: StructureElement.Loci): TMAlignResult {
|
||||
const lenA = StructureElement.Loci.size(a);
|
||||
const lenB = StructureElement.Loci.size(b);
|
||||
|
||||
if (lenA === 0 || lenB === 0) {
|
||||
return {
|
||||
bTransform: Mat4.identity(),
|
||||
tmScoreA: 0,
|
||||
tmScoreB: 0,
|
||||
rmsd: 0,
|
||||
alignedLength: 0,
|
||||
sequenceIdentity: 0,
|
||||
alignmentA: [],
|
||||
alignmentB: []
|
||||
};
|
||||
}
|
||||
|
||||
const posA = getPositionTable(a, lenA);
|
||||
const posB = getPositionTable(b, lenB);
|
||||
|
||||
return TMAlign.compute({ a: posA, b: posB });
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform TM-align on multiple structure element loci.
|
||||
* The first structure is used as the reference; all others are aligned to it.
|
||||
*
|
||||
* @param xs Array of structure element loci (first is reference)
|
||||
* @returns Array of TM-align results (length = xs.length - 1)
|
||||
*/
|
||||
function tmAlignMultiple(xs: StructureElement.Loci[]): TMAlignResult[] {
|
||||
const results: TMAlignResult[] = [];
|
||||
if (xs.length < 2) return results;
|
||||
|
||||
const refLoci = xs[0];
|
||||
const lenRef = StructureElement.Loci.size(refLoci);
|
||||
const posRef = getPositionTable(refLoci, lenRef);
|
||||
|
||||
for (let i = 1; i < xs.length; i++) {
|
||||
const mobileLoci = xs[i];
|
||||
const lenMobile = StructureElement.Loci.size(mobileLoci);
|
||||
const posMobile = getPositionTable(mobileLoci, lenMobile);
|
||||
|
||||
results.push(TMAlign.compute({ a: posRef, b: posMobile }));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export class StructureUnitTransforms {
|
||||
private groupUnitTransforms: Float32Array[] = [];
|
||||
/** maps unit.id to offset of transform in unitTransforms */
|
||||
private unitOffsetMap = IntMap.Mutable<number>();
|
||||
private groupIndexMap = IntMap.Mutable<number>();
|
||||
private groupIndexMap = new Map<Unit.SymmetryGroup, number>();
|
||||
private size: number;
|
||||
|
||||
private _isIdentity: boolean | undefined = undefined;
|
||||
@@ -30,7 +30,7 @@ export class StructureUnitTransforms {
|
||||
let groupOffset = 0;
|
||||
for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
|
||||
const g = structure.unitSymmetryGroups[i];
|
||||
this.groupIndexMap.set(g.hashCode, i);
|
||||
this.groupIndexMap.set(g, i);
|
||||
const groupTransforms = this.unitTransforms.subarray(groupOffset, groupOffset + g.units.length * 16);
|
||||
this.groupUnitTransforms.push(groupTransforms);
|
||||
for (let j = 0, jl = g.units.length; j < jl; ++j) {
|
||||
@@ -71,6 +71,6 @@ export class StructureUnitTransforms {
|
||||
}
|
||||
|
||||
getSymmetryGroupTransforms(group: Unit.SymmetryGroup): Float32Array {
|
||||
return this.groupUnitTransforms[this.groupIndexMap.get(group.hashCode)];
|
||||
return this.groupUnitTransforms[this.groupIndexMap.get(group)!];
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export class PluginComponent {
|
||||
private _ev: RxEventHelper | undefined;
|
||||
private subs: Subscription[] | undefined = void 0;
|
||||
|
||||
protected subscribe<T>(obs: Observable<T> | undefined, action: (v: T) => void) {
|
||||
subscribe<T>(obs: Observable<T> | undefined, action: (v: T) => void) {
|
||||
if (!obs) return { unsubscribe: () => {} };
|
||||
if (typeof this.subs === 'undefined') this.subs = [];
|
||||
|
||||
|
||||
@@ -265,6 +265,16 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<StateManagerSta
|
||||
async getStateSnapshot(options?: { name?: string, description?: string, playOnLoad?: boolean, params?: PluginState.SnapshotParams }): Promise<PluginStateSnapshotManager.StateSnapshot> {
|
||||
await this.syncCurrent(options);
|
||||
|
||||
const entries = this.state.entries.valueSeq().toArray();
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
if (!entries[i]._transientData) continue;
|
||||
|
||||
// Clone the entry to avoid modifying the original
|
||||
entries[i] = { ...entries[i] };
|
||||
// Remove any transient data before serialization
|
||||
delete entries[i]._transientData;
|
||||
}
|
||||
|
||||
return {
|
||||
timestamp: +new Date(),
|
||||
version: PLUGIN_VERSION,
|
||||
@@ -275,7 +285,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<StateManagerSta
|
||||
isPlaying: !!(options && options.playOnLoad),
|
||||
nextSnapshotDelayInMs: this.state.nextSnapshotDelayInMs
|
||||
},
|
||||
entries: this.state.entries.valueSeq().toArray()
|
||||
entries,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -459,7 +469,9 @@ namespace PluginStateSnapshotManager {
|
||||
|
||||
export interface Entry extends EntryParams {
|
||||
timestamp: number,
|
||||
snapshot: PluginState.Snapshot
|
||||
snapshot: PluginState.Snapshot,
|
||||
/** Extra data that is not serialized */
|
||||
_transientData?: any
|
||||
}
|
||||
|
||||
export function Entry(snapshot: PluginState.Snapshot, params: EntryParams): Entry {
|
||||
|
||||
@@ -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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -15,7 +15,7 @@ import { StateBuilder, StateObjectRef, StateTransformer } from '../../../mol-sta
|
||||
import { Task } from '../../../mol-task';
|
||||
import { ColorTheme } from '../../../mol-theme/color';
|
||||
import { SizeTheme } from '../../../mol-theme/size';
|
||||
import { shallowEqual, UUID } from '../../../mol-util';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { ColorNames } from '../../../mol-util/color/names';
|
||||
import { objectForEach } from '../../../mol-util/object';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
@@ -35,6 +35,7 @@ import { setStructureSubstance } from '../../helpers/structure-substance';
|
||||
import { Material } from '../../../mol-util/material';
|
||||
import { Clip } from '../../../mol-util/clip';
|
||||
import { setStructureEmissive } from '../../helpers/structure-emissive';
|
||||
import { areInteriorPropsEquals, getInteriorParam } from '../../../mol-geo/geometry/interior';
|
||||
|
||||
export { StructureComponentManager };
|
||||
|
||||
@@ -82,20 +83,21 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
|
||||
p.ignoreLight = options.ignoreLight;
|
||||
p.material = options.materialStyle;
|
||||
p.clip = options.clipObjects;
|
||||
p.interior = options.interior;
|
||||
});
|
||||
if (interactionChanged) await this.updateInterationProps();
|
||||
});
|
||||
}
|
||||
|
||||
private updateReprParams(update: StateBuilder.Root, component: StructureComponentRef) {
|
||||
const { hydrogens, visualQuality: quality, ignoreLight, materialStyle: material, clipObjects: clip } = this.state.options;
|
||||
const { hydrogens, visualQuality: quality, ignoreLight, materialStyle: material, clipObjects: clip, interior } = this.state.options;
|
||||
const ignoreHydrogens = hydrogens !== 'all';
|
||||
const ignoreHydrogensVariant = hydrogens === 'only-polar' ? 'non-polar' : 'all';
|
||||
for (const r of component.representations) {
|
||||
if (r.cell.transform.transformer !== StructureRepresentation3D) continue;
|
||||
|
||||
const params = r.cell.transform.params as StateTransformer.Params<StructureRepresentation3D>;
|
||||
if (!!params.type.params.ignoreHydrogens !== ignoreHydrogens || params.type.params.ignoreHydrogensVariant !== ignoreHydrogensVariant || params.type.params.quality !== quality || params.type.params.ignoreLight !== ignoreLight || !shallowEqual(params.type.params.material, material) || !PD.areEqual(Clip.Params, params.type.params.clip, clip)) {
|
||||
if (!!params.type.params.ignoreHydrogens !== ignoreHydrogens || params.type.params.ignoreHydrogensVariant !== ignoreHydrogensVariant || params.type.params.quality !== quality || params.type.params.ignoreLight !== ignoreLight || !Material.areEqual(params.type.params.material, material) || !PD.areEqual(Clip.Params, params.type.params.clip, clip) || !areInteriorPropsEquals(params.type.params.interior, interior)) {
|
||||
update.to(r.cell).update(old => {
|
||||
old.type.params.ignoreHydrogens = ignoreHydrogens;
|
||||
old.type.params.ignoreHydrogensVariant = ignoreHydrogensVariant;
|
||||
@@ -103,6 +105,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
|
||||
old.type.params.ignoreLight = ignoreLight;
|
||||
old.type.params.material = material;
|
||||
old.type.params.clip = clip;
|
||||
old.type.params.interior = interior;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -321,10 +324,10 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
|
||||
addRepresentation(components: ReadonlyArray<StructureComponentRef>, type: string) {
|
||||
if (components.length === 0) return;
|
||||
|
||||
const { hydrogens, visualQuality: quality, ignoreLight, materialStyle: material, clipObjects: clip } = this.state.options;
|
||||
const { hydrogens, visualQuality: quality, ignoreLight, materialStyle: material, clipObjects: clip, interior } = this.state.options;
|
||||
const ignoreHydrogens = hydrogens !== 'all';
|
||||
const ignoreHydrogensVariant = hydrogens === 'only-polar' ? 'non-polar' : 'all';
|
||||
const typeParams = { ignoreHydrogens, ignoreHydrogensVariant, quality, ignoreLight, material, clip };
|
||||
const typeParams = { ignoreHydrogens, ignoreHydrogensVariant, quality, ignoreLight, material, clip, interior };
|
||||
|
||||
return this.plugin.dataTransaction(async () => {
|
||||
for (const component of components) {
|
||||
@@ -359,10 +362,10 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
|
||||
const xs = structures || this.currentStructures;
|
||||
if (xs.length === 0) return;
|
||||
|
||||
const { hydrogens, visualQuality: quality, ignoreLight, materialStyle: material, clipObjects: clip } = this.state.options;
|
||||
const { hydrogens, visualQuality: quality, ignoreLight, materialStyle: material, clipObjects: clip, interior } = this.state.options;
|
||||
const ignoreHydrogens = hydrogens !== 'all';
|
||||
const ignoreHydrogensVariant = hydrogens === 'only-polar' ? 'non-polar' : 'all';
|
||||
const typeParams = { ignoreHydrogens, ignoreHydrogensVariant, quality, ignoreLight, material, clip };
|
||||
const typeParams = { ignoreHydrogens, ignoreHydrogensVariant, quality, ignoreLight, material, clip, interior };
|
||||
|
||||
const componentKey = UUID.create22();
|
||||
for (const s of xs) {
|
||||
@@ -483,6 +486,7 @@ namespace StructureComponentManager {
|
||||
materialStyle: Material.getParam(),
|
||||
clipObjects: PD.Group(Clip.Params),
|
||||
interactions: PD.Group(InteractionsProvider.defaultParams, { label: 'Non-covalent Interactions' }),
|
||||
interior: getInteriorParam(),
|
||||
};
|
||||
export type Options = PD.Values<typeof OptionsParams>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import { PluginUIContext } from '../context';
|
||||
import { ActionMenu } from './action-menu';
|
||||
import { ColorOptions, ColorValueOption, CombinedColorControl } from './color';
|
||||
import { Button, ControlGroup, ControlRow, ExpandGroup, IconButton, TextInput, ToggleButton } from './common';
|
||||
import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg, WarningSvg } from './icons';
|
||||
import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, TuneSvg, WarningSvg } from './icons';
|
||||
import { legendFor } from './legend';
|
||||
import { LineGraphComponent } from './line-graph/line-graph-component';
|
||||
import { Slider, Slider2 } from './slider';
|
||||
@@ -526,7 +526,7 @@ export class SelectControl extends React.PureComponent<ParamProps<PD.Select<stri
|
||||
: void 0;
|
||||
|
||||
return <ToggleButton disabled={this.props.isDisabled} style={{ textAlign, overflow: 'hidden', textOverflow: 'ellipsis' }}
|
||||
label={label} title={label as string} icon={icon} toggle={toggle} isSelected={this.state.showOptions} />;
|
||||
label={label} title={label as string} icon={icon} toggle={toggle} isSelected={this.state.showOptions} className='msp-select-toggle' />;
|
||||
}
|
||||
|
||||
renderAddOn() {
|
||||
@@ -1192,7 +1192,7 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
|
||||
if (!this.state.isExpanded) {
|
||||
return <div className='msp-mapped-parameter-group'>
|
||||
{ctrl}
|
||||
<IconButton svg={MoreHorizSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`More Options`} />
|
||||
<IconButton svg={TuneSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`More Options`} style={{ opacity: 0.7 }} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -1203,7 +1203,7 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
|
||||
|
||||
return <div className='msp-mapped-parameter-group'>
|
||||
{ctrl}
|
||||
<IconButton svg={MoreHorizSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`More Options`} />
|
||||
<IconButton svg={TuneSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`More Options`} />
|
||||
<div className='msp-control-offset'>
|
||||
{this.pivotedPresets()}
|
||||
<ParameterControls params={filtered} onEnter={this.props.onEnter} values={this.props.value} onChange={this.onChangeParam} isDisabled={this.props.isDisabled} />
|
||||
@@ -1312,7 +1312,7 @@ export class MappedControl extends React.PureComponent<ParamProps<PD.Mapped<any>
|
||||
if (!this.areParamsEmpty(param.params)) {
|
||||
return <div className='msp-mapped-parameter-group'>
|
||||
{Select}
|
||||
<IconButton svg={MoreHorizSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`${label} Properties`} />
|
||||
<IconButton svg={TuneSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`${label} Properties`} style={{ opacity: this.state.isExpanded ? undefined : 0.7 }} />
|
||||
{this.state.isExpanded && <GroupControl inMapped param={param} value={value.params} name={value.name} onChange={this.onChangeParam} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} />}
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -286,6 +286,27 @@
|
||||
color: $msp-btn-commit-on-hover-font-color;
|
||||
}
|
||||
|
||||
.msp-select-toggle::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 7px solid $hover-font-color;
|
||||
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.msp-select-toggle:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.msp-btn-action {
|
||||
height: $row-height;
|
||||
line-height: $row-height;
|
||||
|
||||
@@ -22,27 +22,6 @@
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.msp-viewport-host3d {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
-webkit-touch-callout: none;
|
||||
touch-action: manipulation;
|
||||
|
||||
>canvas {
|
||||
background-color: $default-background;
|
||||
background-image: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey),
|
||||
linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey);
|
||||
background-size: 60px 60px;
|
||||
background-position: 0 0, 30px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.msp-viewport-controls {
|
||||
position: absolute;
|
||||
right: $control-spacing;
|
||||
|
||||
@@ -11,6 +11,7 @@ import { SIFTSMapping } from '../../mol-model-props/sequence/sifts-mapping';
|
||||
import { QueryContext, Structure, StructureElement, StructureProperties, StructureSelection } from '../../mol-model/structure';
|
||||
import { alignAndSuperpose, superpose } from '../../mol-model/structure/structure/util/superposition';
|
||||
import { alignAndSuperposeWithSIFTSMapping } from '../../mol-model/structure/structure/util/superposition-sifts-mapping';
|
||||
import { tmAlign } from '../../mol-model/structure/structure/util/tm-align';
|
||||
import { StructureSelectionQueries } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/structure/selection';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
@@ -61,7 +62,7 @@ const SuperpositionTag = 'SuperpositionTransform';
|
||||
|
||||
type SuperpositionControlsState = {
|
||||
isBusy: boolean,
|
||||
action?: 'byChains' | 'byAtoms' | 'options',
|
||||
action?: 'byChains' | 'byAtoms' | 'byTMAlign' | 'options',
|
||||
canUseDb?: boolean,
|
||||
options: StructureSuperpositionOptions
|
||||
}
|
||||
@@ -222,6 +223,32 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
|
||||
}
|
||||
};
|
||||
|
||||
superposeTMAlign = async () => {
|
||||
const { query } = this.state.options.traceOnly ? StructureSelectionQueries.trace : StructureSelectionQueries.polymer;
|
||||
const entries = this.chainEntries;
|
||||
|
||||
const locis = entries.map(e => {
|
||||
const s = StructureElement.Loci.toStructure(e.loci);
|
||||
const loci = StructureSelection.toLociWithSourceUnits(query(new QueryContext(s)));
|
||||
return StructureElement.Loci.remap(loci, this.getRootStructure(e.loci.structure));
|
||||
});
|
||||
|
||||
const pivot = this.plugin.managers.structure.hierarchy.findStructure(locis[0]?.structure);
|
||||
const coordinateSystem = pivot?.transform?.cell.obj?.data.coordinateSystem;
|
||||
|
||||
const eA = entries[0];
|
||||
for (let i = 1, il = locis.length; i < il; ++i) {
|
||||
const eB = entries[i];
|
||||
const result = tmAlign(locis[0], locis[i]);
|
||||
const { bTransform, tmScoreA, tmScoreB, rmsd, alignedLength } = result;
|
||||
await this.transform(eB.cell, bTransform, coordinateSystem);
|
||||
const labelA = stripTags(eA.label);
|
||||
const labelB = stripTags(eB.label);
|
||||
this.plugin.log.info(`TM-align [${labelA}] and [${labelB}]: TM-score=${tmScoreA.toFixed(4)}/${tmScoreB.toFixed(4)}, RMSD=${rmsd.toFixed(2)} Å, aligned ${alignedLength} residues.`);
|
||||
}
|
||||
await this.cameraReset();
|
||||
};
|
||||
|
||||
async cameraReset() {
|
||||
await new Promise(res => requestAnimationFrame(res));
|
||||
PluginCommands.Camera.Reset(this.plugin);
|
||||
@@ -229,6 +256,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
|
||||
|
||||
toggleByChains = () => this.setState({ action: this.state.action === 'byChains' ? void 0 : 'byChains' });
|
||||
toggleByAtoms = () => this.setState({ action: this.state.action === 'byAtoms' ? void 0 : 'byAtoms' });
|
||||
toggleByTMAlign = () => this.setState({ action: this.state.action === 'byTMAlign' ? void 0 : 'byTMAlign' });
|
||||
toggleOptions = () => this.setState({ action: this.state.action === 'options' ? void 0 : 'options' });
|
||||
|
||||
highlight(loci: StructureElement.Loci) {
|
||||
@@ -361,6 +389,21 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
|
||||
</>;
|
||||
}
|
||||
|
||||
addByTMAlign() {
|
||||
const entries = this.chainEntries;
|
||||
return <>
|
||||
{entries.length > 0 && <div className='msp-control-offset'>
|
||||
{entries.map((e, i) => this.lociEntry(e, i))}
|
||||
</div>}
|
||||
{entries.length < 2 && <div className='msp-control-offset msp-help-text'>
|
||||
<div className='msp-help-description'><Icon svg={HelpOutlineSvg} inline />Add 2 or more selections{this.toggleHint()} from separate structures. Selections must be limited to single polymer chains. TM-align performs structure-based alignment independent of sequence.</div>
|
||||
</div>}
|
||||
{entries.length > 1 && <Button title='Superpose structures using TM-align (structure-based alignment).' className='msp-btn-commit msp-btn-commit-on' onClick={this.superposeTMAlign} style={{ marginTop: '1px' }}>
|
||||
TM-align Superpose
|
||||
</Button>}
|
||||
</>;
|
||||
}
|
||||
|
||||
superposeByDbMapping() {
|
||||
return <>
|
||||
<Button icon={SuperposeChainsSvg} title='Superpose structures using intersection of residues from SIFTS UNIPROT mapping.' className='msp-btn msp-btn-block' onClick={this.superposeDb} style={{ marginTop: '1px' }} disabled={this.state.isBusy}>
|
||||
@@ -378,11 +421,13 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
|
||||
<div className='msp-flex-row'>
|
||||
<ToggleButton icon={SuperposeChainsSvg} label='Chains' toggle={this.toggleByChains} isSelected={this.state.action === 'byChains'} disabled={this.state.isBusy} />
|
||||
<ToggleButton icon={SuperposeAtomsSvg} label='Atoms' toggle={this.toggleByAtoms} isSelected={this.state.action === 'byAtoms'} disabled={this.state.isBusy} />
|
||||
<ToggleButton icon={SuperposeChainsSvg} label='TM-align' toggle={this.toggleByTMAlign} isSelected={this.state.action === 'byTMAlign'} disabled={this.state.isBusy} />
|
||||
{this.state.canUseDb && this.superposeByDbMapping()}
|
||||
<ToggleButton icon={TuneSvg} label='' title='Options' toggle={this.toggleOptions} isSelected={this.state.action === 'options'} disabled={this.state.isBusy} style={{ flex: '0 0 40px', padding: 0 }} />
|
||||
</div>
|
||||
{this.state.action === 'byChains' && this.addByChains()}
|
||||
{this.state.action === 'byAtoms' && this.addByAtoms()}
|
||||
{this.state.action === 'byTMAlign' && this.addByTMAlign()}
|
||||
{this.state.action === 'options' && <div className='msp-control-offset'>
|
||||
<ParameterControls params={StructureSuperpositionParams} values={this.state.options} onChangeValues={this.setOptions} isDisabled={this.state.isBusy} />
|
||||
</div>}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 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>
|
||||
@@ -20,6 +20,7 @@ import { PluginCommands } from '../../../commands';
|
||||
import { PluginContext } from '../../../context';
|
||||
import { Material } from '../../../../mol-util/material';
|
||||
import { Clip } from '../../../../mol-util/clip';
|
||||
import { getInteriorParam } from '../../../../mol-geo/geometry/interior';
|
||||
|
||||
const StructureFocusRepresentationParams = (plugin: PluginContext) => {
|
||||
const reprParams = StateTransforms.Representation.StructureRepresentation3D.definition.params!(void 0, plugin) as PD.Params;
|
||||
@@ -56,6 +57,7 @@ const StructureFocusRepresentationParams = (plugin: PluginContext) => {
|
||||
ignoreLight: PD.Boolean(false),
|
||||
material: Material.getParam(),
|
||||
clip: PD.Group(Clip.Params),
|
||||
interior: getInteriorParam(),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -81,7 +83,7 @@ class StructureFocusRepresentationBehavior extends PluginBehavior.WithSubscriber
|
||||
...reprParams,
|
||||
type: {
|
||||
name: reprParams.type.name,
|
||||
params: { ...reprParams.type.params, ignoreHydrogens: this.params.ignoreHydrogens, ignoreHydrogensVariant: this.params.ignoreHydrogensVariant, ignoreLight: this.params.ignoreLight, material: this.params.material, clip: this.params.clip }
|
||||
params: { ...reprParams.type.params, ignoreHydrogens: this.params.ignoreHydrogens, ignoreHydrogensVariant: this.params.ignoreHydrogensVariant, ignoreLight: this.params.ignoreLight, material: this.params.material, clip: this.params.clip, interior: this.params.interior }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2024-25 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -19,6 +19,10 @@ export class PluginContainer {
|
||||
this.parent.parentElement?.removeChild(this.parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* options.checkeredCanvasBackground has no effect. Use canvas3d.checkeredTransparentBackground instead.
|
||||
* TODO: remove in v6
|
||||
*/
|
||||
constructor(public options?: { checkeredCanvasBackground?: boolean, canvas?: HTMLCanvasElement }) {
|
||||
const parent = document.createElement('div');
|
||||
Object.assign(parent.style, {
|
||||
@@ -36,13 +40,6 @@ export class PluginContainer {
|
||||
let canvas = options?.canvas;
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas');
|
||||
if (options?.checkeredCanvasBackground) {
|
||||
Object.assign(canvas.style, {
|
||||
'background-image': 'linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey)',
|
||||
'background-size': '60px 60px',
|
||||
'background-position': '0 0, 30px 30px'
|
||||
});
|
||||
}
|
||||
parent.appendChild(canvas);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -246,6 +246,9 @@ export class PluginContext {
|
||||
if (!this._initViewer(container.canvas, container.parent, options?.canvas3dContext)) {
|
||||
return false;
|
||||
}
|
||||
if (options?.checkeredCanvasBackground) {
|
||||
this.canvas3d?.setProps({ checkeredTransparentBackground: true });
|
||||
}
|
||||
this.container = container;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -28,6 +28,12 @@ import { StructureGroup } from './visual/util/common';
|
||||
import { Substance } from '../../mol-theme/substance';
|
||||
import { LocationCallback } from '../util';
|
||||
import { Emissive } from '../../mol-theme/emissive';
|
||||
import { HashMap } from '../../mol-util/map';
|
||||
|
||||
function createVisualsMap<P extends StructureParams>() {
|
||||
return new HashMap<Unit.SymmetryGroup, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>(group => group.hashCode, Unit.SymmetryGroup.areInvariantElementsEqual);
|
||||
}
|
||||
|
||||
|
||||
export interface UnitsVisual<P extends StructureParams> extends Visual<StructureGroup, P> { }
|
||||
|
||||
@@ -39,7 +45,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
const renderObjects: GraphicsRenderObject[] = [];
|
||||
const geometryState = new Representation.GeometryState();
|
||||
const _state = StructureRepresentationStateBuilder.create();
|
||||
let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>();
|
||||
let visuals = createVisualsMap<P>();
|
||||
|
||||
let _structure: Structure;
|
||||
let _groups: ReadonlyArray<Unit.SymmetryGroup>;
|
||||
@@ -67,7 +73,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
|
||||
if (promise) await promise;
|
||||
setVisualState(visual, group, _state); // current state for new visual
|
||||
visuals.set(group.hashCode, { visual, group });
|
||||
visuals.set(group, { visual, group });
|
||||
if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length });
|
||||
}
|
||||
} else if (structure && (!Structure.areUnitIdsAndIndicesEqual(structure, _structure) || structure.child !== _structure.child)) {
|
||||
@@ -77,10 +83,10 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
_groups = structure.unitSymmetryGroups;
|
||||
// const newGroups: Unit.SymmetryGroup[] = []
|
||||
const oldVisuals = visuals;
|
||||
visuals = new Map();
|
||||
visuals = createVisualsMap<P>();
|
||||
for (let i = 0; i < _groups.length; i++) {
|
||||
const group = _groups[i];
|
||||
const visualGroup = oldVisuals.get(group.hashCode);
|
||||
const visualGroup = oldVisuals.get(group);
|
||||
if (visualGroup) {
|
||||
// console.log(label, 'found visualGroup to reuse');
|
||||
// console.log('old', visualGroup.group)
|
||||
@@ -96,8 +102,8 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
|
||||
if (promise) await promise;
|
||||
}
|
||||
visuals.set(group.hashCode, { visual, group });
|
||||
oldVisuals.delete(group.hashCode);
|
||||
visuals.set(group, { visual, group });
|
||||
oldVisuals.delete(group);
|
||||
|
||||
// Remove highlight
|
||||
// TODO: remove selection too??
|
||||
@@ -112,7 +118,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
|
||||
if (promise) await promise;
|
||||
setVisualState(visual, group, _state); // current state for new visual
|
||||
visuals.set(group.hashCode, { visual, group });
|
||||
visuals.set(group, { visual, group });
|
||||
}
|
||||
if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length });
|
||||
}
|
||||
@@ -130,7 +136,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
// console.log('old', _structure.unitSymmetryGroups)
|
||||
for (let i = 0; i < _groups.length; i++) {
|
||||
const group = _groups[i];
|
||||
const visualGroup = visuals.get(group.hashCode);
|
||||
const visualGroup = visuals.get(group);
|
||||
if (visualGroup) {
|
||||
let { visual } = visualGroup;
|
||||
if (visual.mustRecreate?.({ group, structure }, _props, ctx.webgl)) {
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
|
||||
import { MolecularSurfaceCalculationParams } from '../../../mol-math/geometry/molecular-surface';
|
||||
import { VisualContext } from '../../visual';
|
||||
import { Unit, Structure } from '../../../mol-model/structure';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { computeStructureMolecularSurface, computeUnitMolecularSurface } from './util/molecular-surface';
|
||||
import { CommonMolecularSurfaceCalculationParams, computeStructureMolecularSurface, computeUnitMolecularSurface } from './util/molecular-surface';
|
||||
import { computeMarchingCubesMesh } from '../../../mol-geo/util/marching-cubes/algorithm';
|
||||
import { ElementIterator, getElementLoci, eachElement, getSerialElementLoci, eachSerialElement } from './util/element';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
@@ -21,16 +20,10 @@ import { MeshValues } from '../../../mol-gl/renderable/mesh';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing';
|
||||
import { BaseGeometry, ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
|
||||
import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { ComplexMeshVisual, ComplexVisual } from '../complex-visual';
|
||||
|
||||
const CommonMolecularSurfaceCalculationParams = {
|
||||
...MolecularSurfaceCalculationParams,
|
||||
resolution: { ...MolecularSurfaceCalculationParams.resolution, ...BaseGeometry.CustomQualityParamInfo },
|
||||
probePositions: { ...MolecularSurfaceCalculationParams.probePositions, ...BaseGeometry.CustomQualityParamInfo },
|
||||
};
|
||||
|
||||
export const MolecularSurfaceMeshParams = {
|
||||
...UnitsMeshParams,
|
||||
...CommonMolecularSurfaceCalculationParams,
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 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>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { UnitsVisual, UnitsLinesVisual, UnitsLinesParams } from '../units-visual';
|
||||
import { MolecularSurfaceCalculationParams } from '../../../mol-math/geometry/molecular-surface';
|
||||
import { VisualContext } from '../../visual';
|
||||
import { Unit, Structure } from '../../../mol-model/structure';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { Lines } from '../../../mol-geo/geometry/lines/lines';
|
||||
import { computeUnitMolecularSurface, MolecularSurfaceProps } from './util/molecular-surface';
|
||||
import { CommonMolecularSurfaceCalculationParams, computeUnitMolecularSurface, MolecularSurfaceProps } from './util/molecular-surface';
|
||||
import { computeMarchingCubesLines } from '../../../mol-geo/util/marching-cubes/algorithm';
|
||||
import { ElementIterator, getElementLoci, eachElement } from './util/element';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
@@ -20,7 +19,7 @@ import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
export const MolecularSurfaceWireframeParams = {
|
||||
...UnitsLinesParams,
|
||||
...MolecularSurfaceCalculationParams,
|
||||
...CommonMolecularSurfaceCalculationParams,
|
||||
...CommonSurfaceParams,
|
||||
sizeFactor: PD.Numeric(1.5, { min: 0, max: 10, step: 0.1 }),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 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>
|
||||
*/
|
||||
@@ -8,10 +8,17 @@ import { Unit, Structure } from '../../../../mol-model/structure';
|
||||
import { Task, RuntimeContext } from '../../../../mol-task';
|
||||
import { getUnitConformationAndRadius, CommonSurfaceProps, ensureReasonableResolution, getStructureConformationAndRadius } from './common';
|
||||
import { PositionData, DensityData, Box3D } from '../../../../mol-math/geometry';
|
||||
import { MolecularSurfaceCalculationProps, calcMolecularSurface } from '../../../../mol-math/geometry/molecular-surface';
|
||||
import { MolecularSurfaceCalculationParams, MolecularSurfaceCalculationProps, calcMolecularSurface } from '../../../../mol-math/geometry/molecular-surface';
|
||||
import { OrderedSet } from '../../../../mol-data/int';
|
||||
import { Boundary } from '../../../../mol-math/geometry/boundary';
|
||||
import { SizeTheme } from '../../../../mol-theme/size';
|
||||
import { BaseGeometry } from '../../../../mol-geo/geometry/base';
|
||||
|
||||
export const CommonMolecularSurfaceCalculationParams = {
|
||||
...MolecularSurfaceCalculationParams,
|
||||
resolution: { ...MolecularSurfaceCalculationParams.resolution, ...BaseGeometry.CustomQualityParamInfo },
|
||||
probePositions: { ...MolecularSurfaceCalculationParams.probePositions, ...BaseGeometry.CustomQualityParamInfo },
|
||||
};
|
||||
|
||||
export type MolecularSurfaceProps = MolecularSurfaceCalculationProps & CommonSurfaceProps
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { MoleculeTypeColorThemeProvider } from './color/molecule-type';
|
||||
import { PolymerIdColorThemeProvider } from './color/polymer-id';
|
||||
import { PolymerIndexColorThemeProvider } from './color/polymer-index';
|
||||
import { ResidueNameColorThemeProvider } from './color/residue-name';
|
||||
import { ResidueChargeColorThemeProvider } from './color/residue-charge';
|
||||
import { SecondaryStructureColorThemeProvider } from './color/secondary-structure';
|
||||
import { SequenceIdColorThemeProvider } from './color/sequence-id';
|
||||
import { ShapeGroupColorThemeProvider } from './color/shape-group';
|
||||
@@ -199,6 +200,7 @@ namespace ColorTheme {
|
||||
'partial-charge': PartialChargeColorThemeProvider,
|
||||
'polymer-id': PolymerIdColorThemeProvider,
|
||||
'polymer-index': PolymerIndexColorThemeProvider,
|
||||
'residue-charge': ResidueChargeColorThemeProvider,
|
||||
'residue-name': ResidueNameColorThemeProvider,
|
||||
'secondary-structure': SecondaryStructureColorThemeProvider,
|
||||
'sequence-id': SequenceIdColorThemeProvider,
|
||||
|
||||
178
src/mol-theme/color/residue-charge.ts
Normal file
178
src/mol-theme/color/residue-charge.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Lukáš Polák <admin@lukaspolak.cz>
|
||||
*/
|
||||
|
||||
import { Color, ColorMap } from '../../mol-util/color';
|
||||
import { StructureElement, Unit, Bond, ElementIndex } from '../../mol-model/structure';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import type { ColorTheme } from '../color';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ThemeDataContext } from '../theme';
|
||||
import { TableLegend } from '../../mol-util/legend';
|
||||
import { getAdjustedColorMap } from '../../mol-util/color/color';
|
||||
import { getColorMapParams } from '../../mol-util/color/params';
|
||||
import { ColorThemeCategory } from './categories';
|
||||
|
||||
// Colors for charged residues (by-name)
|
||||
export const ChargedResidueColors = ColorMap({
|
||||
// standard amino acids (charged)
|
||||
'ARG': 0x0000FF,
|
||||
'ASP': 0xFF0000,
|
||||
'GLU': 0xFF0000,
|
||||
'HIS': 0x33C3F9,
|
||||
'LYS': 0x0000FF,
|
||||
|
||||
// standard amino acids (uncharged)
|
||||
'ALA': 0xFFFFFF,
|
||||
'ASN': 0xFFFFFF,
|
||||
'CYS': 0xFFFFFF,
|
||||
'GLN': 0xFFFFFF,
|
||||
'GLY': 0xFFFFFF,
|
||||
'ILE': 0xFFFFFF,
|
||||
'LEU': 0xFFFFFF,
|
||||
'MET': 0xFFFFFF,
|
||||
'PHE': 0xFFFFFF,
|
||||
'PRO': 0xFFFFFF,
|
||||
'SER': 0xFFFFFF,
|
||||
'THR': 0xFFFFFF,
|
||||
'TRP': 0xFFFFFF,
|
||||
'TYR': 0xFFFFFF,
|
||||
'VAL': 0xFFFFFF,
|
||||
|
||||
// common from CCD
|
||||
'MSE': 0xFFFFFF,
|
||||
'SEP': 0xFFFFFF,
|
||||
'TPO': 0xFFFFFF,
|
||||
'PTR': 0xFFFFFF,
|
||||
'PCA': 0xFFFFFF,
|
||||
'HYP': 0xFFFFFF,
|
||||
|
||||
// charmm ff
|
||||
'HSD': 0xFFFFFF,
|
||||
'HSE': 0xFFFFFF,
|
||||
'HSP': 0x0000FF,
|
||||
'LSN': 0xFFFFFF,
|
||||
'ASPP': 0xFFFFFF,
|
||||
'GLUP': 0xFFFFFF,
|
||||
|
||||
// amber ff
|
||||
'HID': 0xFFFFFF,
|
||||
'HIE': 0xFFFFFF,
|
||||
'HIP': 0x0000FF,
|
||||
'LYN': 0xFFFFFF,
|
||||
'ASH': 0xFFFFFF,
|
||||
'GLH': 0xFFFFFF,
|
||||
|
||||
// rna bases
|
||||
'A': 0xFFFFFF,
|
||||
'G': 0xFFFFFF,
|
||||
'I': 0xFFFFFF,
|
||||
'C': 0xFFFFFF,
|
||||
'T': 0xFFFFFF,
|
||||
'U': 0xFFFFFF,
|
||||
|
||||
// dna bases
|
||||
'DA': 0xFFFFFF,
|
||||
'DG': 0xFFFFFF,
|
||||
'DI': 0xFFFFFF,
|
||||
'DC': 0xFFFFFF,
|
||||
'DT': 0xFFFFFF,
|
||||
'DU': 0xFFFFFF,
|
||||
|
||||
// peptide bases
|
||||
'APN': 0xFFFFFF,
|
||||
'GPN': 0xFFFFFF,
|
||||
'CPN': 0xFFFFFF,
|
||||
'TPN': 0xFFFFFF,
|
||||
});
|
||||
export type ChargedResidueColors = typeof ChargedResidueColors
|
||||
|
||||
const DefaultResidueChargeColor = Color(0xFF00FF);
|
||||
const Description = 'Assigns a color to every residue based on its charge state.';
|
||||
|
||||
export const ResidueChargeColorThemeParams = {
|
||||
method: PD.MappedStatic('by-name', {
|
||||
'by-name': PD.Group({
|
||||
saturation: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
|
||||
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
|
||||
colors: PD.MappedStatic('default', {
|
||||
'default': PD.EmptyGroup(),
|
||||
'custom': PD.Group(getColorMapParams(ChargedResidueColors)),
|
||||
})
|
||||
}, { isFlat: true })
|
||||
})
|
||||
};
|
||||
export type ResidueChargeColorThemeParams = typeof ResidueChargeColorThemeParams;
|
||||
export function getResidueChargeColorThemeParams(ctx: ThemeDataContext) {
|
||||
return PD.clone(ResidueChargeColorThemeParams);
|
||||
}
|
||||
|
||||
function getAtomicCompId(unit: Unit.Atomic, element: ElementIndex) {
|
||||
return unit.model.atomicHierarchy.atoms.label_comp_id.value(element);
|
||||
}
|
||||
|
||||
function getCoarseCompId(unit: Unit.Spheres | Unit.Gaussians, element: ElementIndex) {
|
||||
const seqIdBegin = unit.coarseElements.seq_id_begin.value(element);
|
||||
const seqIdEnd = unit.coarseElements.seq_id_end.value(element);
|
||||
if (seqIdBegin === seqIdEnd) {
|
||||
const entityKey = unit.coarseElements.entityKey[element];
|
||||
const seq = unit.model.sequence.byEntityKey[entityKey].sequence;
|
||||
return seq.compId.value(seqIdBegin - 1); // 1-indexed
|
||||
}
|
||||
}
|
||||
|
||||
export function residueChargeColor(colorMap: ColorMap<Record<string, Color>>, residueName: string): Color {
|
||||
const c = colorMap[residueName];
|
||||
return c === undefined ? DefaultResidueChargeColor : c;
|
||||
}
|
||||
|
||||
export function ResidueChargeColorTheme(ctx: ThemeDataContext, props: PD.Values<ResidueChargeColorThemeParams>): ColorTheme<ResidueChargeColorThemeParams> {
|
||||
const { saturation, lightness, colors } = props.method.params;
|
||||
const colorMap = getAdjustedColorMap(props.method.params.colors.name === 'default' ? ChargedResidueColors : colors.params, saturation, lightness);
|
||||
|
||||
function color(location: Location): Color {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
if (Unit.isAtomic(location.unit)) {
|
||||
const compId = getAtomicCompId(location.unit, location.element);
|
||||
return residueChargeColor(colorMap, compId);
|
||||
} else {
|
||||
const compId = getCoarseCompId(location.unit, location.element);
|
||||
if (compId) return residueChargeColor(colorMap, compId);
|
||||
}
|
||||
} else if (Bond.isLocation(location)) {
|
||||
if (Unit.isAtomic(location.aUnit)) {
|
||||
const compId = getAtomicCompId(location.aUnit, location.aUnit.elements[location.aIndex]);
|
||||
return residueChargeColor(colorMap, compId);
|
||||
} else {
|
||||
const compId = getCoarseCompId(location.aUnit, location.aUnit.elements[location.aIndex]);
|
||||
if (compId) return residueChargeColor(colorMap, compId);
|
||||
}
|
||||
}
|
||||
return DefaultResidueChargeColor;
|
||||
}
|
||||
|
||||
return {
|
||||
factory: ResidueChargeColorTheme,
|
||||
granularity: 'group',
|
||||
preferSmoothing: true,
|
||||
color,
|
||||
props,
|
||||
description: Description,
|
||||
legend: TableLegend(Object.keys(colorMap).map(name => {
|
||||
return [name, (colorMap as any)[name] as Color] as [string, Color];
|
||||
}).concat([['Unknown', DefaultResidueChargeColor]]))
|
||||
};
|
||||
}
|
||||
|
||||
export const ResidueChargeColorThemeProvider: ColorTheme.Provider<ResidueChargeColorThemeParams, 'residue-charge'> = {
|
||||
name: 'residue-charge',
|
||||
label: 'Residue Charge',
|
||||
category: ColorThemeCategory.Residue,
|
||||
factory: ResidueChargeColorTheme,
|
||||
getParams: getResidueChargeColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(ResidueChargeColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
|
||||
};
|
||||
@@ -44,4 +44,77 @@ export function arrayMapAdd<K, V>(map: Map<K, V[]>, key: K, value: V) {
|
||||
} else {
|
||||
map.set(key, [value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class HashMap<K, V> {
|
||||
private buckets = new Map<number, Array<{ key: K, value: V }>>();
|
||||
|
||||
set(key: K, value: V): void {
|
||||
const hashCode = this.hashCode(key);
|
||||
let bucket = this.buckets.get(hashCode);
|
||||
|
||||
if (!bucket) {
|
||||
bucket = [];
|
||||
this.buckets.set(hashCode, bucket);
|
||||
}
|
||||
|
||||
for (let i = 0; i < bucket.length; i++) {
|
||||
if (this.areEqual(bucket[i].key, key)) {
|
||||
bucket[i].value = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
bucket.push({ key, value });
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
const hashCode = this.hashCode(key);
|
||||
const bucket = this.buckets.get(hashCode);
|
||||
|
||||
if (!bucket) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const entry of bucket) {
|
||||
if (this.areEqual(entry.key, key)) {
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
delete(key: K): boolean {
|
||||
const hashCode = this.hashCode(key);
|
||||
const bucket = this.buckets.get(hashCode);
|
||||
if (!bucket) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < bucket.length; i++) {
|
||||
if (this.areEqual(bucket[i].key, key)) {
|
||||
bucket.splice(i, 1);
|
||||
if (bucket.length === 0) {
|
||||
this.buckets.delete(hashCode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
forEach(cb: (value: V, key: K) => void): void {
|
||||
for (const bucket of this.buckets.values()) {
|
||||
for (const entry of bucket) {
|
||||
cb(entry.value, entry.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.buckets.clear();
|
||||
}
|
||||
|
||||
constructor(private hashCode: (key: K) => number, private areEqual: (a: K, b: K) => boolean) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2021-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -30,6 +30,17 @@ export namespace Material {
|
||||
return array;
|
||||
}
|
||||
|
||||
export function toArrayNormalized<T extends NumberArray>(material: Material, array: T, offset: number) {
|
||||
array[offset] = material.metalness;
|
||||
array[offset + 1] = material.roughness;
|
||||
array[offset + 2] = material.bumpiness;
|
||||
return array;
|
||||
}
|
||||
|
||||
export function areEqual(a: Material, b: Material): boolean {
|
||||
return a.metalness === b.metalness && a.roughness === b.roughness && a.bumpiness === b.bumpiness;
|
||||
}
|
||||
|
||||
export function toString({ metalness, roughness, bumpiness }: Material) {
|
||||
return `M ${metalness.toFixed(2)} | R ${roughness.toFixed(2)} | B ${bumpiness.toFixed(2)}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user