Compare commits

...

11 Commits

Author SHA1 Message Date
dsehnal
2ecdc0eafa 5.3.0 2025-11-05 14:41:21 +01:00
dsehnal
dccfd35c7a changelog 2025-11-05 14:39:06 +01:00
Dominik Andrew Tichy
9e81a4f7a6 fix: validation and default params for primitives_from_uri (#1690)
* fix: primitives default param values

* chore: changelog

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2025-11-05 14:37:00 +01:00
midlik
6f6cc73ce9 MVS: tweak param type definitions to make type bundle smaller (#1692)
* MVS: tweak param type definitions to make type bundle smaller

* MVS: add docstring to all parameter definitions

* MVS: tweak param type definitions to make type bundle smaller 2 (palettes)

* add types
2025-11-05 14:21:54 +01:00
David Sehnal
c248ae11bf fix wheel scrolling edge case (#1696) 2025-11-05 13:27:14 +01:00
dsehnal
742be03901 add index file to mvs extension 2025-11-05 09:54:26 +01:00
midlik
00009ef198 MVS: coarse annotations bugfix (#1695) 2025-11-04 23:16:24 +01:00
David Sehnal
1cb617524d MVS: Fix coarse structure selection (#1694) 2025-11-04 23:07:47 +01:00
David Sehnal
e2e348240b MVS: topology format support (#1691)
* MVS: topology format support

* bugfix

* mvs: additional coordinate formats

* fix
2025-11-04 22:16:49 +01:00
Alexander Rose
b54908492c Add Canvas3D.setAttribs 2025-11-03 23:52:26 -08:00
dsehnal
33172862bd mvs stories loading message 2025-11-02 14:07:52 +01:00
25 changed files with 693 additions and 96 deletions

View File

@@ -5,6 +5,16 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v5.3.0] - 2025-11-5
- Update loading message in MVS Stories Viewer
- Add `Canvas3D.setAttribs`
- Fix `normalizeWheel` "spin" calculation fallback
- MolViewSpec
- Add support for "topology" formats (TOP, PRMTOP, PSF)
- Add support for additional "coordiates" formats (NCTRAJ, DCD, TRR)
- Fix coarse structure selection
- Fix missing default param values in `primitives_from_uri`
## [v5.2.0] - 2025-10-31
- Handle transparency updates on ImagePass
- Fix CIF parser edge case when the last token is escaped
@@ -15,7 +25,7 @@ Note that since we don't clearly distinguish between a public and private interf
- Add clipping support for primitives
- Support near camera distance
## [v5.1.0] - 2025-10-25
## [v5.1.2] - 2025-10-25
- Fix createColorScaleByType when offsets are available
- Get bond orders from non-standard CONECT records in PDB files
- Remove outdated `gl_FrontFacing` workaround for buggy drivers

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,264 @@
%VERSION VERSION_STAMP = V0001.000 DATE = 11/04/25 11:55:47
%FLAG TITLE
%FORMAT(20a4)
alanine-dipeptide.solvated.pdb
%FLAG POINTERS
%FORMAT(10I8)
22 7 12 9 25 11 39 19 0 0
99 3 9 11 19 7 11 20 0 0
0 0 0 0 0 0 0 1 10 0
0 1
%FLAG ATOM_NAME
%FORMAT(20a4)
H1 CH3 H2 H3 C O N H CA HA CB HB1 HB2 HB3 C O N H C H1
H2 H3
%FLAG ATOMIC_NUMBER
%FORMAT(10I8)
1 6 1 1 6 8 7 1 6 1
6 1 1 1 6 8 7 1 6 1
1 1
%FLAG RESIDUE_LABEL
%FORMAT(20a4)
ACE ALA NME
%FLAG RESIDUE_POINTER
%FORMAT(10I8)
1 7 17
%FLAG RESIDUE_NUMBER
%FORMAT(20I4)
1 2 3
%FLAG RESIDUE_ICODE
%FORMAT(20a4)
%FLAG RESIDUE_CHAINID
%FORMAT(20a4)
B B B
%FLAG SOLVENT_POINTERS
%FORMAT(3I8)
0 1 0
%FLAG ATOMS_PER_MOLECULE
%FORMAT(10I8)
22
%FLAG MASS
%FORMAT(5E16.8)
3.02400000E+00 5.96200000E+00 3.02400000E+00 3.02400000E+00 1.20100000E+01
1.60000000E+01 1.19940000E+01 3.02400000E+00 9.99400000E+00 3.02400000E+00
5.96200000E+00 3.02400000E+00 3.02400000E+00 3.02400000E+00 1.20100000E+01
1.60000000E+01 1.19940000E+01 3.02400000E+00 5.96200000E+00 3.02400000E+00
3.02400000E+00 3.02400000E+00
%FLAG CHARGE
%FORMAT(5E16.8)
2.04636429E+00 -6.67300626E+00 2.04636429E+00 2.04636429E+00 1.08823576E+01
-1.03484442E+01 -7.57501011E+00 4.95464337E+00 6.14091510E-01 1.49969529E+00
-3.32556975E+00 1.09880469E+00 1.09880469E+00 1.09880469E+00 1.08841798E+01
-1.03484442E+01 -7.57501011E+00 4.95464337E+00 -2.71512270E+00 1.77849648E+00
1.77849648E+00 1.77849648E+00
%FLAG AMBER_ATOM_TYPE
%FORMAT(20a4)
a0 a1 a0 a0 a2 a3 a4 a5 a1 a6 a1 a0 a0 a0 a2 a3 a4 a5 a1 a6
a6 a6
%FLAG ATOM_TYPE_INDEX
%FORMAT(10I8)
1 2 1 1 3 4 5 6 2 7
2 1 1 1 3 4 5 6 2 7
7 7
%FLAG NONBONDED_PARM_INDEX
%FORMAT(10I8)
1 2 4 7 11 16 22 2 3 5
8 12 17 23 4 5 6 9 13 18
24 7 8 9 10 14 19 25 11 12
13 14 15 20 26 16 17 18 19 20
21 27 22 23 24 25 26 27 28
%FLAG LENNARD_JONES_ACOEF
%FORMAT(5E16.8)
7.51607703E+03 9.71708117E+04 1.04308023E+06 8.61541883E+04 9.24822269E+05
8.19971662E+05 5.44261042E+04 6.47841732E+05 5.74393458E+05 3.79876399E+05
8.96776989E+04 9.95480466E+05 8.82619071E+05 6.06829343E+05 9.44293233E+05
1.07193645E+02 2.56678134E+03 2.27577560E+03 1.02595236E+03 2.12601181E+03
1.39982777E-01 4.98586847E+03 6.78771368E+04 6.01816484E+04 3.69471530E+04
6.20665998E+04 5.94667299E+01 3.25969625E+03
%FLAG LENNARD_JONES_BCOEF
%FORMAT(5E16.8)
2.17257828E+01 1.26919150E+02 6.75612247E+02 1.12529845E+02 5.99015525E+02
5.31102864E+02 1.11805549E+02 6.26720080E+02 5.55666449E+02 5.64885984E+02
1.36131731E+02 7.36907417E+02 6.53361429E+02 6.77220874E+02 8.01323529E+02
2.59456373E+00 2.06278363E+01 1.82891803E+01 1.53505284E+01 2.09604198E+01
9.37598976E-02 1.76949863E+01 1.06076943E+02 9.40505981E+01 9.21192137E+01
1.13252062E+02 1.93248820E+00 1.43076527E+01
%FLAG NUMBER_EXCLUDED_ATOMS
%FORMAT(10I8)
6 7 4 3 7 3 10 4 10 7
6 3 2 1 7 3 5 4 3 2
1 1
%FLAG EXCLUDED_ATOMS_LIST
%FORMAT(10I8)
2 3 4 5 6 7 3 4 5 6
7 8 9 4 5 6 7 5 6 7
6 7 8 9 10 11 15 7 8 9
8 9 10 11 12 13 14 15 16 17
9 10 11 15 10 11 12 13 14 15
16 17 18 19 11 12 13 14 15 16
17 12 13 14 15 16 17 13 14 15
14 15 15 16 17 18 19 20 21 22
17 18 19 18 19 20 21 22 19 20
21 22 20 21 22 21 22 22 0
%FLAG BOND_FORCE_CONSTANT
%FORMAT(5E16.8)
3.40000000E+02 4.34000000E+02 3.17000000E+02 5.70000000E+02 4.90000000E+02
3.37000000E+02 3.10000000E+02
%FLAG BOND_EQUIL_VALUE
%FORMAT(5E16.8)
1.09000000E+00 1.01000000E+00 1.52200000E+00 1.22900000E+00 1.33500000E+00
1.44900000E+00 1.52600000E+00
%FLAG BONDS_INC_HYDROGEN
%FORMAT(10I8)
0 3 1 3 6 1 3 9 1 18
21 2 24 27 1 30 33 1 30 36
1 30 39 1 48 51 2 54 57 1
54 60 1 54 63 1
%FLAG BONDS_WITHOUT_HYDROGEN
%FORMAT(10I8)
3 12 3 12 15 4 12 18 5 18
24 6 24 42 3 24 30 7 42 48
5 42 45 4 48 54 6
%FLAG ANGLE_FORCE_CONSTANT
%FORMAT(5E16.8)
3.50000000E+01 5.00000000E+01 5.00000000E+01 5.00000000E+01 8.00000000E+01
7.00000000E+01 5.00000000E+01 8.00000000E+01 8.00000000E+01 6.30000000E+01
6.30000000E+01
%FLAG ANGLE_EQUIL_VALUE
%FORMAT(5E16.8)
1.91113553E+00 1.91113553E+00 2.09439510E+00 2.06018665E+00 2.10137642E+00
2.03505391E+00 2.12755636E+00 2.14500965E+00 1.91462619E+00 1.92160751E+00
1.93906080E+00
%FLAG ANGLES_INC_HYDROGEN
%FORMAT(10I8)
0 3 6 1 0 3 9 1 0 3
12 2 6 3 9 1 6 3 12 2
9 3 12 2 12 18 21 3 18 24
27 2 21 18 24 4 24 30 33 2
24 30 36 2 24 30 39 2 27 24
30 2 27 24 42 2 33 30 36 1
33 30 39 1 36 30 39 1 42 48
51 3 48 54 57 2 48 54 60 2
48 54 63 2 51 48 54 4 57 54
60 1 57 54 63 1 60 54 63 1
%FLAG ANGLES_WITHOUT_HYDROGEN
%FORMAT(10I8)
3 12 15 5 3 12 18 6 12 18
24 7 15 12 18 8 18 24 30 9
18 24 42 10 24 42 45 5 24 42
48 6 30 24 42 11 42 48 54 7
45 42 48 8
%FLAG DIHEDRAL_FORCE_CONSTANT
%FORMAT(5E16.8)
8.00000000E-01 8.00000000E-02 2.50000000E+00 2.50000000E+00 2.00000000E+00
1.55555556E-01 1.10000000E+00 0.00000000E+00 0.00000000E+00 8.00000000E-01
1.80000000E+00 4.20000000E-01 2.70000000E-01 5.50000000E-01 1.58000000E+00
4.50000000E-01 4.00000000E-01 2.00000000E-01 2.00000000E-01 1.05000000E+01
%FLAG DIHEDRAL_PERIODICITY
%FORMAT(5E16.8)
1.00000000E+00 3.00000000E+00 2.00000000E+00 2.00000000E+00 1.00000000E+00
3.00000000E+00 2.00000000E+00 1.00000000E+00 1.00000000E+00 3.00000000E+00
2.00000000E+00 3.00000000E+00 2.00000000E+00 3.00000000E+00 2.00000000E+00
1.00000000E+00 3.00000000E+00 2.00000000E+00 1.00000000E+00 2.00000000E+00
%FLAG DIHEDRAL_PHASE
%FORMAT(5E16.8)
0.00000000E+00 3.14159265E+00 3.14159265E+00 3.14159265E+00 0.00000000E+00
0.00000000E+00 3.14159265E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00
0.00000000E+00 0.00000000E+00 0.00000000E+00 3.14159265E+00 3.14159265E+00
3.14159265E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 3.14159265E+00
%FLAG SCEE_SCALE_FACTOR
%FORMAT(5E16.8)
1.20000000E+00 0.00000000E+00 1.20000000E+00 1.20000000E+00 0.00000000E+00
1.20000000E+00 0.00000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00
0.00000000E+00 1.20000000E+00 0.00000000E+00 1.20000000E+00 0.00000000E+00
0.00000000E+00 1.20000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00
%FLAG SCNB_SCALE_FACTOR
%FORMAT(5E16.8)
2.00000000E+00 0.00000000E+00 2.00000000E+00 2.00000000E+00 0.00000000E+00
2.00000000E+00 0.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00
0.00000000E+00 2.00000000E+00 0.00000000E+00 2.00000000E+00 0.00000000E+00
0.00000000E+00 2.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00
%FLAG DIHEDRALS_INC_HYDROGEN
%FORMAT(10I8)
0 3 12 15 1 0 3 -12 15 2
3 12 18 21 3 6 3 12 15 1
6 3 -12 15 2 9 3 12 15 1
9 3 -12 15 2 15 12 18 21 4
15 12 -18 21 5 18 24 30 33 6
18 24 30 36 6 18 24 30 39 6
24 42 48 51 3 27 24 30 33 6
27 24 30 36 6 27 24 30 39 6
27 24 42 45 1 27 24 -42 45 2
42 24 30 33 6 42 24 30 36 6
42 24 30 39 6 45 42 48 51 4
45 42 -48 51 5 21 18 -24 -12 7
51 48 -54 -42 7 51 48 54 60 8
21 18 24 30 8 42 48 54 57 8
6 3 12 18 9 42 48 54 63 8
51 48 54 57 8 21 18 24 42 8
0 3 12 18 9 42 48 54 60 8
27 24 42 48 8 21 18 24 27 8
51 48 54 63 8 9 3 12 18 9
12 18 24 27 8
%FLAG DIHEDRALS_WITHOUT_HYDROGEN
%FORMAT(10I8)
3 12 18 24 3 12 18 24 30 10
12 18 -24 30 11 12 18 -24 30 5
12 18 24 42 12 12 18 -24 42 13
15 12 18 24 3 18 24 42 48 14
18 24 -42 48 15 18 24 -42 48 16
24 42 48 54 3 30 24 42 48 17
30 24 -42 48 18 30 24 -42 48 19
45 42 48 54 3 15 12 -18 -3 20
45 42 -48 -24 20 18 24 42 45 8
30 24 42 45 8
%FLAG SOLTY
%FORMAT(5E16.8)
%FLAG HBOND_ACOEF
%FORMAT(5E16.8)
%FLAG HBOND_BCOEF
%FORMAT(5E16.8)
%FLAG HBCUT
%FORMAT(5E16.8)
%FLAG TREE_CHAIN_CLASSIFICATION
%FORMAT(20a4)
BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA
BLA BLA
%FLAG JOIN_ARRAY
%FORMAT(10I8)
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0
%FLAG IROTAT
%FORMAT(10I8)
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0
%FLAG BOX_DIMENSIONS
%FORMAT(5E16.8)
9.00000000E+01 3.00000000E+01 3.00000000E+01 3.00000000E+01
%FLAG RADIUS_SET
%FORMAT(1a80)
0
%FLAG RADII
%FORMAT(5E16.8)
0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00
0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00
0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00
0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00
0.00000000E+00 0.00000000E+00
%FLAG SCREEN
%FORMAT(5E16.8)
0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00
0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00
0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00
0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00
0.00000000E+00 0.00000000E+00
%FLAG IPOL
%FORMAT(1I8)
0

View File

@@ -0,0 +1,26 @@
CRYST1 30.000 30.000 30.000 90.00 90.00 90.00 P 1 1
ATOM 1 H1 ACE A 1 2.000 1.000 -0.000 0.00 0.00 H
ATOM 2 CH3 ACE A 1 2.000 2.090 0.000 0.00 0.00 C
ATOM 3 H2 ACE A 1 1.486 2.454 0.890 0.00 0.00 H
ATOM 4 H3 ACE A 1 1.486 2.454 -0.890 0.00 0.00 H
ATOM 5 C ACE A 1 3.427 2.641 -0.000 0.00 0.00 C
ATOM 6 O ACE A 1 4.391 1.877 -0.000 0.00 0.00 O
ATOM 7 N ALA A 2 3.555 3.970 -0.000 0.00 0.00 N
ATOM 8 H ALA A 2 2.733 4.556 -0.000 0.00 0.00 H
ATOM 9 CA ALA A 2 4.853 4.614 -0.000 0.00 0.00 C
ATOM 10 HA ALA A 2 5.408 4.316 0.890 0.00 0.00 H
ATOM 11 CB ALA A 2 5.661 4.221 -1.232 0.00 0.00 C
ATOM 12 HB1 ALA A 2 5.123 4.521 -2.131 0.00 0.00 H
ATOM 13 HB2 ALA A 2 6.630 4.719 -1.206 0.00 0.00 H
ATOM 14 HB3 ALA A 2 5.809 3.141 -1.241 0.00 0.00 H
ATOM 15 C ALA A 2 4.713 6.129 0.000 0.00 0.00 C
ATOM 16 O ALA A 2 3.601 6.653 0.000 0.00 0.00 O
ATOM 17 N NME A 3 5.846 6.835 0.000 0.00 0.00 N
ATOM 18 H NME A 3 6.737 6.359 -0.000 0.00 0.00 H
ATOM 19 C NME A 3 5.846 8.284 0.000 0.00 0.00 C
ATOM 20 H1 NME A 3 4.819 8.648 0.000 0.00 0.00 H
ATOM 21 H2 NME A 3 6.360 8.648 0.890 0.00 0.00 H
ATOM 22 H3 NME A 3 6.360 8.648 -0.890 0.00 0.00 H
TER 23 NME A 3
CONECT 5 7
CONECT 15 17

View File

@@ -0,0 +1,14 @@
alanine-dipeptide.solvated.pdb
22
0.7494821 1.2436848 0.8743532 1.0856344 2.2423820 0.5955986
0.4304414 2.9747953 1.0671825 1.0497815 2.3544810 -0.4880289
2.5015950 2.4471725 1.0820421 3.1003812 1.5343071 1.6479120
3.0220696 3.6519467 0.8741013 2.4411554 4.3533213 0.4373955
4.3920715 4.0500473 1.2160543 4.7674596 3.4172266 2.0202454
5.2805058 3.8202998 -0.0180103 4.9565949 4.4537317 -0.8438106
6.3180425 4.0583459 0.2164072 5.2327259 2.7740601 -0.3200050
4.4431625 5.5106563 1.7135265 3.4307644 6.2198007 1.6891606
5.6170320 5.9613562 2.1744082 6.3997462 5.3231585 2.1616313
5.8784762 7.3296314 2.6320299 5.1056278 8.0184146 2.2908769
5.9253575 7.3544224 3.7207393 6.8360338 7.6745804 2.2419090
30.0000000 30.0000000 30.0000000 90.0000000 90.0000000 90.0000000

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "molstar",
"version": "5.2.0",
"version": "5.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "molstar",
"version": "5.2.0",
"version": "5.3.0",
"license": "MIT",
"dependencies": {
"@types/argparse": "^2.0.17",

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "5.2.0",
"version": "5.3.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {

View File

@@ -12,7 +12,7 @@ import { useBehavior } from '../../../mol-plugin-ui/hooks/use-behavior';
import { createRoot } from 'react-dom/client';
import { PluginStateSnapshotManager } from '../../../mol-plugin-state/manager/snapshots';
import { PluginReactContext } from '../../../mol-plugin-ui/base';
import { CSSProperties } from 'react';
import { CSSProperties, useEffect, useState } from 'react';
import { Markdown } from '../../../mol-plugin-ui/controls/markdown';
export class MVSStoriesSnapshotMarkdownModel extends PluginComponent {
@@ -70,6 +70,28 @@ export class MVSStoriesSnapshotMarkdownModel extends PluginComponent {
}
}
function Loading() {
return <div>
<div style={{ marginBottom: 16 }}><i>Loading times may vary depending on the story size, your internet connection, and device performance</i></div>
<div>Fetching data<Dots /></div>
<div>Generating animations<Dots /></div>
<div>Preparing visuals<Dots /></div>
</div>;
}
function Dots() {
const [dots, setDots] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setDots(d => (d + 1) % 4);
}, Math.random() * 500 + 300);
return () => clearInterval(interval);
}, []);
return <span>{'.'.repeat(dots)}</span>;
}
export function MVSStoriesSnapshotMarkdownUI({ model }: { model: MVSStoriesSnapshotMarkdownModel }) {
const state = useBehavior(model.state);
const isLoading = useBehavior(model.context.state.isLoading);
@@ -79,7 +101,8 @@ export function MVSStoriesSnapshotMarkdownUI({ model }: { model: MVSStoriesSnaps
if (isLoading) {
return <div style={style} className={className}>
<i>Loading...</i>
<h3>The story will be ready momentarily</h3>
<Loading />
</div>;
}

View File

@@ -94,8 +94,8 @@
</div>
<div id="links">
<span id="open-in-stories"><a href="#" id="open-in-stories-link" target="_blank" rel="noopener noreferrer" title="Open and edit the story in the MolViewStories app">Edit in MolViewStories</a>&nbsp;<span class="sep"></span></span>
<span id="open-in-molstar"><a href="#" id="open-in-molstar-link" target="_blank" rel="noopener noreferrer" title="Open the story in the Mol* Viewer app. Enables exporting an animation.">Open in Mol* Viewer</a>&nbsp;<span class="sep"></span></span>
<span id="open-in-stories" style="display: none;"><a href="#" id="open-in-stories-link" target="_blank" rel="noopener noreferrer" title="Open and edit the story in the MolViewStories app">Edit in MolViewStories</a>&nbsp;<span class="sep"></span></span>
<span id="open-in-molstar" style="display: none;"><a href="#" id="open-in-molstar-link" target="_blank" rel="noopener noreferrer" title="Open the story in the Mol* Viewer app. Enables exporting an animation.">Open in Mol* Viewer</a>&nbsp;<span class="sep"></span></span>
<a href="#" id="mvs-data" title="MolViewSpec State for this story. Can be opened in the Mol* app.">Download MVS</a> <span class="sep"></span> <a href="https://github.com/molstar/molstar/tree/master/src/apps/mvs-stories" id="mvs-data" target="_blank" rel="noopener noreferrer">Source Code</a>
</div>

View File

@@ -41,6 +41,7 @@ import { capitalize } from '../../../mol-util/string';
import { rowsToExpression, rowToExpression } from '../helpers/selections';
import { collectMVSReferences, decodeColor, isDefined } from '../helpers/utils';
import { addParamDefaults } from '../tree/generic/params-schema';
import { treeValidationIssues } from '../tree/generic/tree-validation';
import { MolstarNode, MolstarNodeParams, MolstarSubtree } from '../tree/molstar/molstar-tree';
import { MVSNode, MVSTreeSchema } from '../tree/mvs/mvs-tree';
import { isComponentExpression, isPrimitiveComponentExpressions, isVector3, PrimitivePositionT } from '../tree/mvs/param-types';
@@ -82,15 +83,35 @@ export const MVSDownloadPrimitiveData = MVSTransform({
const url = Asset.getUrlAsset(plugin.managers.asset, params.uri);
const asset = await plugin.managers.asset.resolve(url, 'string').runInContext(ctx);
const node = JSON.parse(StringLike.toString(asset.data)) as MolstarSubtree<'primitives'>;
const validationIssues = treeValidationIssues(MVSTreeSchema, node, { anyRoot: true });
if (validationIssues) {
throw new Error(`Invalid primitive data from ${params.uri}:\n${validationIssues.join('\n')}`);
}
if (node.kind !== 'primitives') {
throw new Error(`Expected primitives node from ${params.uri}, got ${node.kind}`);
}
const nodeWithDefaults: MolstarSubtree<'primitives'> = {
...node,
params: addParamDefaults(MVSTreeSchema.nodes.primitives.params, node.params || {}),
children: node.children?.map((child: any) => {
if (child.kind === 'primitive') {
return {
...child,
params: addParamDefaults(MVSTreeSchema.nodes.primitive.params, child.params || {})
};
}
return child;
})
};
(cache as any).asset = asset;
return new MVSPrimitivesData({
node,
node: nodeWithDefaults,
defaultStructure: SO.Molecule.Structure.is(a) ? a.data : undefined,
structureRefs: {},
primitives: getPrimitives(node),
options: { ...node.params },
primitives: getPrimitives(nodeWithDefaults),
options: { ...nodeWithDefaults.params },
positionCache: new Map(),
instances: getInstances(node.params),
instances: getInstances(nodeWithDefaults.params),
}, { label: 'Primitive Data' });
});
},

View File

@@ -14,7 +14,7 @@ import { MVSTransform } from './annotation-structure-component';
export const MVSTrajectoryWithCoordinates = MVSTransform({
name: 'trajectory-with-coordinates',
display: { name: 'Trajectory with Coordinates', description: 'Create a trajectory from existing model and the provided coordinates.' },
from: PluginStateObject.Molecule.Model,
from: [PluginStateObject.Molecule.Model, PluginStateObject.Molecule.Topology],
to: PluginStateObject.Molecule.Trajectory,
params: {
coordinatesRef: ParamDefinition.Text('', { isHidden: true }),

View File

@@ -208,6 +208,7 @@ function getQualifyingResidues(model: Model, row: MVSAnnotationRow, indices: Ato
}
arrayExtend(result, residuesHere);
}
sortIfNeeded(result, (a, b) => a - b);
return result;
}
@@ -419,7 +420,7 @@ function getQualifyingCoarseElements(coarseElements: CoarseElements, row: MVSAnn
// This implementation can yield some elements even when queryStart>queryEnd (e.g. { beg_label_seq_id: 70, end_label_seq_id: 58, label_seq_id: 60 } -> sphere 51-100 qualifies ).
// This is on purpose, to have the same behavior as MolScript.
}
sortIfNeeded(result, iElem => iElem);
sortIfNeeded(result, (a, b) => a - b);
return result;
}

View File

@@ -8,7 +8,6 @@
import { hashString } from '../../../mol-data/util';
import { StateObject } from '../../../mol-state';
import { Color } from '../../../mol-util/color';
import { ColorNames } from '../../../mol-util/color/names';
import { decodeColor as _decodeColor } from '../../../mol-util/color/utils';
@@ -107,29 +106,6 @@ export function decodeColor(colorString: string | number | undefined | null): Co
return _decodeColor(colorString);
}
/** Regular expression matching a hexadecimal color string, e.g. '#FF1100' or '#f10' */
const hexColorRegex = /^#([0-9A-F]{3}){1,2}$/i;
/** Hexadecimal color string, e.g. '#FF1100' (the type matches more than just valid HexColor strings) */
export type HexColor = `#${string}`
export const HexColor = {
/** Decide if a string is a valid hexadecimal color string (6-digit or 3-digit, e.g. '#FF1100' or '#f10') */
is(str: any): str is HexColor {
return typeof str === 'string' && hexColorRegex.test(str);
},
};
/** Named color string, e.g. 'red' */
export type ColorName = keyof ColorNames
export const ColorName = {
/** Decide if a string is a valid named color string */
is(str: any): str is ColorName {
return str in ColorNames;
},
};
export function collectMVSReferences<T extends StateObject.Ctor>(type: T[], dependencies: Record<string, StateObject>): Record<string, StateObject.From<T>['data']> {
const ret: any = {};

View File

@@ -0,0 +1,8 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
export * from './mvs-data';
export * from './load';

View File

@@ -8,8 +8,8 @@
import { PluginStateSnapshotManager } from '../../mol-plugin-state/manager/snapshots';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { Download, ParseCcp4, ParseCif, ParseDx } from '../../mol-plugin-state/transforms/data';
import { CoordinatesFromLammpstraj, CoordinatesFromXtc, CustomModelProperties, CustomStructureProperties, ModelFromTrajectory, StructureComponent, StructureFromModel, TrajectoryFromGRO, TrajectoryFromLammpsTrajData, TrajectoryFromMmCif, TrajectoryFromMOL, TrajectoryFromMOL2, TrajectoryFromPDB, TrajectoryFromSDF, TrajectoryFromXYZ } from '../../mol-plugin-state/transforms/model';
import { Download, ParseCcp4, ParseCif, ParseDx, ParsePrmtop, ParsePsf, ParseTop } from '../../mol-plugin-state/transforms/data';
import { CoordinatesFromDcd, CoordinatesFromLammpstraj, CoordinatesFromNctraj, CoordinatesFromTrr, CoordinatesFromXtc, CustomModelProperties, CustomStructureProperties, ModelFromTrajectory, StructureComponent, StructureFromModel, TopologyFromPrmtop, TopologyFromPsf, TopologyFromTop, TrajectoryFromGRO, TrajectoryFromLammpsTrajData, TrajectoryFromMmCif, TrajectoryFromMOL, TrajectoryFromMOL2, TrajectoryFromPDB, TrajectoryFromSDF, TrajectoryFromXYZ } from '../../mol-plugin-state/transforms/model';
import { StructureRepresentation3D, VolumeRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { VolumeFromCcp4, VolumeFromDensityServerCif, VolumeFromDx } from '../../mol-plugin-state/transforms/volume';
import { PluginCommands } from '../../mol-plugin/commands';
@@ -246,7 +246,16 @@ const MolstarLoadingActions: LoadingActions<MolstarTree, MolstarLoadingContext>
case 'mol2':
case 'xtc':
case 'lammpstrj':
case 'dcd':
case 'nctraj':
case 'trr':
return updateParent;
case 'psf':
return UpdateTarget.apply(updateParent, ParsePsf, {});
case 'prmtop':
return UpdateTarget.apply(updateParent, ParsePrmtop, {});
case 'top':
return UpdateTarget.apply(updateParent, ParseTop, {});
case 'map':
return UpdateTarget.apply(updateParent, ParseCcp4, {});
case 'dx':
@@ -260,6 +269,12 @@ const MolstarLoadingActions: LoadingActions<MolstarTree, MolstarLoadingContext>
coordinates(updateParent: UpdateTarget, node: MolstarNode<'coordinates'>): UpdateTarget | undefined {
const format = node.params.format;
switch (format) {
case 'nctraj':
return UpdateTarget.apply(updateParent, CoordinatesFromNctraj);
case 'dcd':
return UpdateTarget.apply(updateParent, CoordinatesFromDcd);
case 'trr':
return UpdateTarget.apply(updateParent, CoordinatesFromTrr);
case 'xtc':
return UpdateTarget.apply(updateParent, CoordinatesFromXtc);
case 'lammpstrj':
@@ -303,6 +318,28 @@ const MolstarLoadingActions: LoadingActions<MolstarTree, MolstarLoadingContext>
});
return UpdateTarget.setMvsDependencies(result, [node.params.coordinates_ref]);
},
topology_with_coordinates(updateParent: UpdateTarget, node: MolstarNode<'topology_with_coordinates'>): UpdateTarget | undefined {
let parsed: UpdateTarget;
const format = node.params.format;
switch (format) {
case 'psf':
parsed = UpdateTarget.apply(updateParent, TopologyFromPsf, {});
break;
case 'prmtop':
parsed = UpdateTarget.apply(updateParent, TopologyFromPrmtop, {});
break;
case 'top':
parsed = UpdateTarget.apply(updateParent, TopologyFromTop, {});
break;
default:
console.error(`Unknown format in "topology_with_coordinates" node: "${format}"`);
return undefined;
}
const result = UpdateTarget.apply(parsed, MVSTrajectoryWithCoordinates, {
coordinatesRef: node.params.coordinates_ref,
});
return UpdateTarget.setMvsDependencies(result, [node.params.coordinates_ref]);
},
model(updateParent: UpdateTarget, node: MolstarSubtree<'model'>, context: MolstarLoadingContext): UpdateTarget {
const annotations = collectAnnotationReferences(node, context);
const model = UpdateTarget.apply(updateParent, ModelFromTrajectory, {

View File

@@ -9,7 +9,15 @@ import { SimpleParamsSchema, UnionParamsSchema } from '../generic/params-schema'
import { NodeFor, ParamsOfKind, SubtreeOfKind, TreeFor, TreeSchema } from '../generic/tree-schema';
import { ColorT, ContinuousPalette, DiscretePalette, Matrix, Vector3 } from '../mvs/param-types';
const Easing = literal(
type Easing =
| 'linear'
| 'bounce-in' | 'bounce-out' | 'bounce-in-out'
| 'circle-in' | 'circle-out' | 'circle-in-out'
| 'cubic-in' | 'cubic-out' | 'cubic-in-out'
| 'exp-in' | 'exp-out' | 'exp-in-out'
| 'quad-in' | 'quad-out' | 'quad-in-out'
| 'sin-in' | 'sin-out' | 'sin-in-out'
const Easing = literal<Easing>(
'linear',
'bounce-in', 'bounce-out', 'bounce-in-out',
'circle-in', 'circle-out', 'circle-in-out',
@@ -22,23 +30,31 @@ const Easing = literal(
export type MVSAnimationEasing = ValueFor<typeof Easing>;
const _Noise = {
/** Magnitude of the noise to apply to the interpolated value. */
noise_magnitude: OptionalField(float, 0, 'Magnitude of the noise to apply to the interpolated value.')
// support cummulative noise?
};
const _Common = {
/** Reference to the node. */
target_ref: RequiredField(str, 'Reference to the node.'),
/** Value accessor. */
property: RequiredField(union(str, list(union(str, int))), 'Value accessor.'),
/** Start time of the transition in milliseconds. */
start_ms: OptionalField(float, 0, 'Start time of the transition in milliseconds.'),
/** Duration of the transition in milliseconds. */
duration_ms: RequiredField(float, 'Duration of the transition in milliseconds.'),
};
const _Frequency = {
/** Determines how many times the interpolation loops. Current T = frequency * t mod 1. */
frequency: OptionalField(int, 1, 'Determines how many times the interpolation loops. Current T = frequency * t mod 1.'),
/** Whether to alternate the direction of the interpolation for frequency > 1. */
alternate_direction: OptionalField(bool, false, 'Whether to alternate the direction of the interpolation for frequency > 1.'),
};
const _Easing = {
/** Easing function to use for the transition. */
easing: OptionalField(Easing, 'linear', 'Easing function to use for the transition.'),
};
@@ -46,8 +62,11 @@ const ScalarInterpolation = {
..._Common,
..._Frequency,
..._Easing,
/** Start value. If a list of values is provided, each element will be interpolated separately. If unset, parent state value is used. */
start: OptionalField(nullable(union(float, list(float))), null, 'Start value. If a list of values is provided, each element will be interpolated separately. If unset, parent state value is used.'),
/** End value. If a list of values is provided, each element will be interpolated separately. If unset, only noise is applied. */
end: OptionalField(nullable(union(float, list(float))), null, 'End value. If a list of values is provided, each element will be interpolated separately. If unset, only noise is applied.'),
/** Whether to round the values to the closest integer. Useful for example for trajectory animation. */
discrete: OptionalField(bool, false, 'Whether to round the values to the closest integer. Useful for example for trajectory animation.'),
..._Noise,
};
@@ -56,8 +75,11 @@ const Vec3Interpolation = {
..._Common,
..._Frequency,
..._Easing,
/** Start value. If unset, parent state value is used. Must be array of length 3N (x1, y1, z1, x2, y2, z2, ...). */
start: OptionalField(nullable(list(float)), null, 'Start value. If unset, parent state value is used. Must be array of length 3N (x1, y1, z1, x2, y2, z2, ...).'),
/** End value. Must be array of length 3N (x1, y1, z1, x2, y2, z2, ...). If unset, only noise is applied. */
end: OptionalField(nullable(list(float)), null, 'End value. Must be array of length 3N (x1, y1, z1, x2, y2, z2, ...). If unset, only noise is applied.'),
/** Whether to use spherical interpolation. */
spherical: OptionalField(bool, false, 'Whether to use spherical interpolation.'),
..._Noise,
};
@@ -66,7 +88,9 @@ const RotationMatrixInterpolation = {
..._Common,
..._Frequency,
..._Easing,
/** Start value. If unset, parent state value is used. */
start: OptionalField(nullable(Matrix), null, 'Start value. If unset, parent state value is used.'),
/** End value. If unset, only noise is applied. */
end: OptionalField(nullable(Matrix), null, 'End value. If unset, only noise is applied.'),
..._Noise,
};
@@ -75,31 +99,53 @@ const ColorInterpolation = {
..._Common,
..._Frequency,
..._Easing,
/** Start value. If unset, parent state value is used. */
start: OptionalField(union(nullable(ColorT), dict(union(int, str), ColorT)), null, 'Start value. If unset, parent state value is used.'),
/** End value. */
end: OptionalField(union(nullable(ColorT), dict(union(int, str), ColorT)), null, 'End value.'),
/** Palette to sample colors from. Overrides start and end values. */
palette: OptionalField(nullable(union(DiscretePalette, ContinuousPalette)), null, 'Palette to sample colors from. Overrides start and end values.'),
};
const TransformationMatrixInterpolation = {
..._Common,
/** Pivot point for rotation and scale. */
pivot: OptionalField(nullable(Vector3), null, 'Pivot point for rotation and scale.'),
/** Start rotation value. If unset, parent state value is used. */
rotation_start: OptionalField(nullable(Matrix), null, 'Start rotation value. If unset, parent state value is used.'),
/** End rotation value. If unset, only noise is applied */
rotation_end: OptionalField(nullable(Matrix), null, 'End rotation value. If unset, only noise is applied.'),
/** Magnitude of the noise to apply to the rotation. */
rotation_noise_magnitude: OptionalField(float, 0, 'Magnitude of the noise to apply to the rotation.'),
/** Easing function to use for the rotation. */
rotation_easing: OptionalField(Easing, 'linear', 'Easing function to use for the rotation.'),
/** Determines how many times the rotation interpolation loops. Current T = frequency * t mod 1. */
rotation_frequency: OptionalField(int, 1, 'Determines how many times the rotation interpolation loops. Current T = frequency * t mod 1.'),
/** Whether to alternate the direction of the interpolation for frequency > 1. */
rotation_alternate_direction: OptionalField(bool, false, 'Whether to alternate the direction of the interpolation for frequency > 1.'),
/** Start translation value. If unset, parent state value is used. */
translation_start: OptionalField(nullable(Vector3), null, 'Start translation value. If unset, parent state value is used.'),
/** End translation value. If unset, only noise is applied. */
translation_end: OptionalField(nullable(Vector3), null, 'End translation value. If unset, only noise is applied.'),
/** Magnitude of the noise to apply to the translation. */
translation_noise_magnitude: OptionalField(float, 0, 'Magnitude of the noise to apply to the translation.'),
/** Easing function to use for the translation. */
translation_easing: OptionalField(Easing, 'linear', 'Easing function to use for the translation.'),
/** Determines how many times the translation interpolation loops. Current T = frequency * t mod 1. */
translation_frequency: OptionalField(int, 1, 'Determines how many times the translation interpolation loops. Current T = frequency * t mod 1.'),
/** Whether to alternate the direction of the interpolation for frequency > 1. */
translation_alternate_direction: OptionalField(bool, false, 'Whether to alternate the direction of the interpolation for frequency > 1.'),
/** Start scale value. If unset, parent state value is used. */
scale_start: OptionalField(nullable(Vector3), null, 'Start scale value. If unset, parent state value is used.'),
/** End scale value. If unset, only noise is applied. */
scale_end: OptionalField(nullable(Vector3), null, 'End scale value. If unset, only noise is applied.'),
/** Magnitude of the noise to apply to the scale. */
scale_noise_magnitude: OptionalField(float, 0, 'Magnitude of the noise to apply to the scale.'),
/** Easing function to use for the scale. */
scale_easing: OptionalField(Easing, 'linear', 'Easing function to use for the scale.'),
/** Determines how many times the scale interpolation loops. Current T = frequency * t mod 1. */
scale_frequency: OptionalField(int, 1, 'Determines how many times the scale interpolation loops. Current T = frequency * t mod 1.'),
/** Whether to alternate the direction of the interpolation for frequency > 1. */
scale_alternate_direction: OptionalField(bool, false, 'Whether to alternate the direction of the interpolation for frequency > 1.'),
};
@@ -110,12 +156,18 @@ export const MVSAnimationSchema = TreeSchema({
description: 'Animation root node',
parent: [],
params: SimpleParamsSchema({
frame_time_ms: OptionalField(float, 1000 / 60, 'Frame time in milliseconds'),
/** Frame time in milliseconds. */
frame_time_ms: OptionalField(float, 1000 / 60, 'Frame time in milliseconds.'),
/** Total duration of the animation. If not specified, computed as maximum of all transitions. */
duration_ms: OptionalField(nullable(float), null, 'Total duration of the animation. If not specified, computed as maximum of all transitions.'),
autoplay: OptionalField(bool, true, 'Determines whether the animation should autoplay when a snapshot is loaded'),
loop: OptionalField(bool, false, 'Determines whether the animation should loop when it reaches the end'),
include_camera: OptionalField(bool, false, 'Determines whether the camera state should be included in the animation'),
include_canvas: OptionalField(bool, false, 'Determines whether the canvas state should be included in the animation'),
/** Determines whether the animation should autoplay when a snapshot is loaded */
autoplay: OptionalField(bool, true, 'Determines whether the animation should autoplay when a snapshot is loaded.'),
/** Determines whether the animation should loop when it reaches the end. */
loop: OptionalField(bool, false, 'Determines whether the animation should loop when it reaches the end.'),
/** Determines whether the camera state should be included in the animation. */
include_camera: OptionalField(bool, false, 'Determines whether the camera state should be included in the animation.'),
/** Determines whether the canvas state should be included in the animation. */
include_canvas: OptionalField(bool, false, 'Determines whether the canvas state should be included in the animation.'),
}),
},
interpolate: {

View File

@@ -78,7 +78,16 @@ export function nullable<V>(type: iots.Type<V>): iots.Type<V | null> {
return union(type, iots.null);
}
/** Type definition for literal types, e.g. `literal('red', 'green', 'blue')` means 'red' or 'green' or 'blue' */
/** Type definition for literal types, e.g. `literal('red', 'green', 'blue')` means 'red' or 'green' or 'blue'.
*
* Example usage:
* ```
* export type MyColor = 'red' | 'green' | 'blue';
* export const MyColor = literal<MyColor>('red', 'green', 'blue');
* ```
*
* (it looks stupid to repeat the list of values but it will result in nicer type bundle (for MolViewStories))
*/
export function literal<V extends string | number | boolean>(...values: V[]) {
if (values.length === 0) {
throw new Error(`literal type must have at least one value`);

View File

@@ -28,12 +28,22 @@ export const ParseFormatMvsToMolstar = {
lammpstrj: { format: 'lammpstrj', is_binary: false },
// coordinates
xtc: { format: 'xtc', is_binary: true },
nctraj: { format: 'nctraj', is_binary: true },
dcd: { format: 'dcd', is_binary: true },
trr: { format: 'trr', is_binary: true },
// topology
psf: { format: 'psf', is_binary: false },
prmtop: { format: 'prmtop', is_binary: false },
top: { format: 'top', is_binary: false },
// maps
map: { format: 'map', is_binary: true },
dx: { format: 'dx', is_binary: false },
dxbin: { format: 'dxbin', is_binary: true },
} satisfies { [p in ParseFormatT]: { format: MolstarParseFormatT, is_binary: boolean } };
const TopologyFormats = new Set<ParseFormatT>(['psf', 'prmtop', 'top']);
/** Conversion rules for conversion from `MVSTree` (with all parameter values) to `MolstarTree` */
const mvsToMolstarConversionRules: ConversionRules<FullMVSTree, MolstarTree> = {
'download': node => ({ subtree: [] }),
@@ -68,7 +78,18 @@ const mvsToMolstarConversionRules: ConversionRules<FullMVSTree, MolstarTree> = {
if (parent?.kind !== 'parse') throw new Error(`Parent of "structure" must be "parse", not "${parent?.kind}".`);
const { format } = ParseFormatMvsToMolstar[parent.params.format];
if (node.params.coordinates_ref) {
if (TopologyFormats.has(parent.params.format)) {
if (!node.params.coordinates_ref) {
throw new Error(`"structure" node with topology format "${parent.params.format}" must have "coordinates_ref" parameter.`);
}
return {
subtree: [
{ kind: 'topology_with_coordinates', params: { format, coordinates_ref: node.params.coordinates_ref } },
{ kind: 'model', params: pickObjectKeys(node.params, ['model_index']) },
{ kind: 'structure', params: omitObjectKeys(node.params, ['block_header', 'block_index', 'model_index', 'coordinates_ref']), custom: node.custom, ref: node.ref },
] satisfies MolstarNode[]
};
} else if (node.params.coordinates_ref) {
return {
subtree: [
{ kind: 'trajectory', params: { format, ...pickObjectKeys(node.params, ['block_header', 'block_index']) } },
@@ -124,6 +145,13 @@ const StructureFormatExtensions: Record<ParseFormatT, (FileExtension | '*')[]> =
lammpstrj: ['.lammpstrj'],
// coordinates
xtc: ['.xtc'],
nctraj: ['.nc', '.nctraj'],
dcd: ['.dcd'],
trr: ['.trr'],
// topology
psf: ['.psf'],
prmtop: ['.prmtop', '.parm7'],
top: ['.top'],
// volumes
map: ['.map', '.ccp4', '.mrc', '.mrcs'],
dx: ['.dx'],

View File

@@ -21,12 +21,14 @@ export const MolstarTreeSchema = TreeSchema({
...FullMVSTreeSchema.nodes.download,
params: SimpleParamsSchema({
...FullMVSTreeSchema.nodes.download.params.fields,
/** Specifies whether file is downloaded as bytes array or string */
is_binary: RequiredField(bool, 'Specifies whether file is downloaded as bytes array or string'),
}),
},
parse: {
...FullMVSTreeSchema.nodes.parse,
params: SimpleParamsSchema({
/** File format */
format: RequiredField(MolstarParseFormatT, 'File format'),
}),
},
@@ -35,6 +37,7 @@ export const MolstarTreeSchema = TreeSchema({
description: "Auxiliary node corresponding to Molstar's CoordinatesFrom*.",
parent: ['parse'],
params: SimpleParamsSchema({
/** File format */
format: RequiredField(MolstarParseFormatT, 'File format'),
}),
},
@@ -43,6 +46,7 @@ export const MolstarTreeSchema = TreeSchema({
description: "Auxiliary node corresponding to Molstar's TrajectoryFrom*.",
parent: ['parse'],
params: SimpleParamsSchema({
/** File format */
format: RequiredField(MolstarParseFormatT, 'File format'),
...pickObjectKeys(FullMVSTreeSchema.nodes.structure.params.fields, ['block_header', 'block_index'] as const),
}),
@@ -52,13 +56,22 @@ export const MolstarTreeSchema = TreeSchema({
description: 'Auxiliary node corresponding to assigning a separate coordinates to a trajectory.',
parent: ['model'],
params: SimpleParamsSchema({
/** Coordinates reference */
coordinates_ref: RequiredField(str, 'Coordinates reference'),
}),
},
topology_with_coordinates: {
description: 'Auxiliary node corresponding to assigning a separate coordinates to a topology.',
parent: ['parse'],
params: SimpleParamsSchema({
format: RequiredField(MolstarParseFormatT, 'File format'),
coordinates_ref: RequiredField(str, 'Coordinates reference'),
}),
},
/** Auxiliary node corresponding to Molstar's ModelFromTrajectory. */
model: {
description: "Auxiliary node corresponding to Molstar's ModelFromTrajectory.",
parent: ['trajectory', 'trajectory_with_coordinates'],
parent: ['trajectory', 'trajectory_with_coordinates', 'topology_with_coordinates'],
params: SimpleParamsSchema(
pickObjectKeys(FullMVSTreeSchema.nodes.structure.params.fields, ['model_index'] as const)
),

View File

@@ -8,9 +8,9 @@
import { bool, dict, float, int, list, literal, nullable, OptionalField, RequiredField, str, tuple, union } from '../generic/field-schema';
import { SimpleParamsSchema } from '../generic/params-schema';
import { NodeFor, ParamsOfKind, SubtreeOfKind, TreeFor, TreeSchema, TreeSchemaWithAllRequired } from '../generic/tree-schema';
import { MVSClipParams, MVSRepresentationParams, MVSVolumeRepresentationParams } from './mvs-tree-representations';
import { MVSPrimitiveParams } from './mvs-tree-primitives';
import { ColorT, ComponentExpressionT, ComponentSelectorT, Matrix, Palette, ParseFormatT, SchemaFormatT, SchemaT, StrList, StructureTypeT, Vector3 } from './param-types';
import { MVSClipParams, MVSRepresentationParams, MVSVolumeRepresentationParams } from './mvs-tree-representations';
import { ColorT, ComponentExpressionT, ComponentSelectorT, LabelAttachments, Matrix, Palette, ParseFormatT, SchemaFormatT, SchemaT, StrList, StructureTypeT, Vector3 } from './param-types';
const _DataFromUriParams = {
@@ -50,8 +50,6 @@ const _DataFromSourceParams = {
/** Color to be used e.g. for representations without 'color' node */
export const DefaultColor = 'white';
const LabelAttachments = literal('bottom-left', 'bottom-center', 'bottom-right', 'middle-left', 'middle-center', 'middle-right', 'top-left', 'top-center', 'top-right');
const TransformParams = SimpleParamsSchema({
/** Rotation matrix (3x3 matrix flattened in column major format (j*3+i indexing), this is equivalent to Fortran-order in numpy). This matrix will multiply the structure coordinates from the left. The default value is the identity matrix (corresponds to no rotation). */
rotation: OptionalField(Matrix, [1, 0, 0, 0, 1, 0, 0, 0, 1], 'Rotation matrix (3x3 matrix flattened in column major format (j*3+i indexing), this is equivalent to Fortran-order in numpy). This matrix will multiply the structure coordinates from the left. The default value is the identity matrix (corresponds to no rotation).'),
@@ -179,6 +177,7 @@ export const MVSTreeSchema = TreeSchema({
description: 'This node instructs to create a volume from a parsed data resource. "Volume" refers to an internal representation of volumetric data without any visual representation.',
parent: ['parse'],
params: SimpleParamsSchema({
/** Channel identifier (only applies when the input data contain multiple channels). */
channel_id: OptionalField(nullable(str), null, 'Channel identifier (only applies when the input data contain multiple channels).'),
}),
},

View File

@@ -6,12 +6,20 @@
*/
import * as iots from 'io-ts';
import { ColorName, HexColor } from '../../helpers/utils';
import { ColorNames } from '../../../../mol-util/color/names';
import { ValueFor, bool, dict, float, int, list, literal, nullable, object, partial, str, tuple, union } from '../generic/field-schema';
/** `format` parameter values for `parse` node in MVS tree */
export const ParseFormatT = literal(
export type ParseFormatT =
// trajectory
| 'mmcif' | 'bcif' | 'pdb' | 'pdbqt' | 'gro' | 'xyz' | 'mol' | 'sdf' | 'mol2' | 'lammpstrj'
// coordinates
| 'xtc' | 'nctraj' | 'dcd' | 'trr'
// topology
| 'psf' | 'prmtop' | 'top'
// volumes
| 'map' | 'dx' | 'dxbin'
export const ParseFormatT = literal<ParseFormatT>(
// trajectory
'mmcif',
'bcif', // +volumes
@@ -25,15 +33,30 @@ export const ParseFormatT = literal(
'lammpstrj', // + coordinates
// coordinates
'xtc',
'nctraj',
'dcd',
'trr',
// topology
'psf',
'prmtop',
'top',
// volumes
'map',
'dx',
'dxbin',
);
export type ParseFormatT = ValueFor<typeof ParseFormatT>
/** `format` parameter values for `parse` node in Molstar tree */
export const MolstarParseFormatT = literal(
export type MolstarParseFormatT =
// trajectory
| 'cif' | 'pdb' | 'pdbqt' | 'gro' | 'xyz' | 'mol' | 'sdf' | 'mol2' | 'lammpstrj'
// coordinates
| 'xtc' | 'nctraj' | 'dcd' | 'trr'
// topology
| 'psf' | 'prmtop' | 'top'
// volumes
| 'map' | 'dx' | 'dxbin'
export const MolstarParseFormatT = literal<MolstarParseFormatT>(
// trajectory
'cif', // +volumes
'pdb',
@@ -46,21 +69,29 @@ export const MolstarParseFormatT = literal(
'lammpstrj',
// coordinates
'xtc',
'nctraj',
'dcd',
'trr',
// topology
'psf',
'prmtop',
'top',
// volumes
'map',
'dx',
'dxbin',
);
export type MolstarParseFormatT = ValueFor<typeof MolstarParseFormatT>
/** `kind` parameter values for `structure` node in MVS tree */
export const StructureTypeT = literal('model', 'assembly', 'symmetry', 'symmetry_mates');
export type StructureTypeT = 'model' | 'assembly' | 'symmetry' | 'symmetry_mates';
export const StructureTypeT = literal<StructureTypeT>('model', 'assembly', 'symmetry', 'symmetry_mates');
/** `selector` parameter values for `component` node in MVS tree */
export const ComponentSelectorT = literal('all', 'polymer', 'protein', 'nucleic', 'branched', 'ligand', 'ion', 'water', 'coarse');
export type ComponentSelectorT = 'all' | 'polymer' | 'protein' | 'nucleic' | 'branched' | 'ligand' | 'ion' | 'water' | 'coarse';
export const ComponentSelectorT = literal<ComponentSelectorT>('all', 'polymer', 'protein', 'nucleic', 'branched', 'ligand', 'ion', 'water', 'coarse');
/** `selector` parameter values for `component` node in MVS tree */
export const ComponentExpressionT = partial({
const _ComponentExpressionT = partial({
label_entity_id: str,
label_asym_id: str,
auth_asym_id: str,
@@ -84,24 +115,39 @@ export const ComponentExpressionT = partial({
* like 'ASM-X0-1' for assemblies or '1_555' for crystals */
instance_id: str,
});
export type ComponentExpressionT = ValueFor<typeof ComponentExpressionT>
/** `selector` parameter values for `component` node in MVS tree */
export interface ComponentExpressionT extends ValueFor<typeof _ComponentExpressionT> { }
export const ComponentExpressionT: iots.Type<ComponentExpressionT> = _ComponentExpressionT;
/** `schema` parameter values for `*_from_uri` and `*_from_source` nodes in MVS tree */
export const SchemaT = literal('whole_structure', 'entity', 'chain', 'auth_chain', 'residue', 'auth_residue', 'residue_range', 'auth_residue_range', 'atom', 'auth_atom', 'all_atomic');
export type SchemaT = 'whole_structure' | 'entity' | 'chain' | 'auth_chain' | 'residue' | 'auth_residue' | 'residue_range' | 'auth_residue_range' | 'atom' | 'auth_atom' | 'all_atomic';
export const SchemaT = literal<SchemaT>('whole_structure', 'entity', 'chain', 'auth_chain', 'residue', 'auth_residue', 'residue_range', 'auth_residue_range', 'atom', 'auth_atom', 'all_atomic');
/** `format` parameter values for `*_from_uri` nodes in MVS tree */
export const SchemaFormatT = literal('cif', 'bcif', 'json');
export type SchemaFormatT = 'cif' | 'bcif' | 'json';
export const SchemaFormatT = literal<SchemaFormatT>('cif', 'bcif', 'json');
/** Parameter values for vector params, e.g. `position` */
export const Vector3 = tuple([float, float, float]);
export type Vector3 = ValueFor<typeof Vector3>
export type Vector3 = [number, number, number];
export const Vector3: iots.Type<Vector3> = tuple([float, float, float]);
/** Parameter values for matrix params, e.g. `rotation` */
export const Matrix = list(float);
export const Matrix = list(float); // TODO impl custom types Matrix3x3 and Matrix4x4
export type LabelAttachments = 'bottom-left' | 'bottom-center' | 'bottom-right' | 'middle-left' | 'middle-center' | 'middle-right' | 'top-left' | 'top-center' | 'top-right';
export const LabelAttachments = literal<LabelAttachments>('bottom-left', 'bottom-center', 'bottom-right', 'middle-left', 'middle-center', 'middle-right', 'top-left', 'top-center', 'top-right');
/** Primitives-related types */
export const PrimitiveComponentExpressionT = partial({ structure_ref: str, expression_schema: SchemaT, expressions: list(ComponentExpressionT) });
export type PrimitiveComponentExpressionT = ValueFor<typeof PrimitiveComponentExpressionT>
const _PrimitiveComponentExpressionT = partial({
structure_ref: str,
expression_schema: SchemaT,
expressions: list(ComponentExpressionT),
});
/** Primitives-related types */
export interface PrimitiveComponentExpressionT extends ValueFor<typeof _PrimitiveComponentExpressionT> { }
export const PrimitiveComponentExpressionT: iots.Type<PrimitiveComponentExpressionT> = _PrimitiveComponentExpressionT;
export const PrimitivePositionT = union(Vector3, ComponentExpressionT, PrimitiveComponentExpressionT);
export type PrimitivePositionT = ValueFor<typeof PrimitivePositionT>
@@ -110,27 +156,61 @@ export const IntList = list(int);
export const StrList = list(str);
/** `color` parameter values for `color` node in MVS tree */
export const HexColorT = new iots.Type<HexColor>(
/** Hexadecimal color string, e.g. '#FF1100' (the type matches more than just valid HexColor strings) */
export type HexColorT = `#${string}`;
export const HexColorT = new iots.Type<HexColorT>(
'HexColor',
((value: any) => typeof value === 'string') as any,
(value, ctx) => HexColor.is(value) ? { _tag: 'Right', right: value } : { _tag: 'Left', left: [{ value: value, context: ctx, message: `"${value}" is not a valid hex color string` }] },
(value, ctx) => isHexColorT(value) ? { _tag: 'Right', right: value } : { _tag: 'Left', left: [{ value: value, context: ctx, message: `"${value}" is not a valid hex color string` }] },
value => value
);
/** Regular expression matching a hexadecimal color string, e.g. '#FF1100' or '#f10' */
const hexColorRegex = /^#([0-9A-F]{3}){1,2}$/i;
/** Decide if a string is a valid hexadecimal color string (6-digit or 3-digit, e.g. '#FF1100' or '#f10') */
function isHexColorT(str: any): str is HexColorT {
return typeof str === 'string' && hexColorRegex.test(str);
}
/** `color` parameter values for `color` node in MVS tree */
export const ColorNameT = new iots.Type<ColorName>(
/** Named color string (e.g. 'red') for `color` parameter values for `color` node in MVS tree */
export const ColorNameT = new iots.Type<ColorNameT>(
'ColorName',
((value: any) => typeof value === 'string') as any,
(value, ctx) => ColorName.is(value) ? { _tag: 'Right', right: value } : { _tag: 'Left', left: [{ value: value, context: ctx, message: `"${value}" is not a valid color name` }] },
(value, ctx) => isColorNameT(value) ? { _tag: 'Right', right: value } : { _tag: 'Left', left: [{ value: value, context: ctx, message: `"${value}" is not a valid color name` }] },
value => value
);
export type ColorNameT =
| 'aliceblue' | 'antiquewhite' | 'aqua' | 'aquamarine' | 'azure' | 'beige' | 'bisque' | 'black'
| 'blanchedalmond' | 'blue' | 'blueviolet' | 'brown' | 'burlywood' | 'cadetblue' | 'chartreuse'
| 'chocolate' | 'coral' | 'cornflower' | 'cornflowerblue' | 'cornsilk' | 'crimson' | 'cyan' | 'darkblue'
| 'darkcyan' | 'darkgoldenrod' | 'darkgray' | 'darkgreen' | 'darkgrey' | 'darkkhaki' | 'darkmagenta'
| 'darkolivegreen' | 'darkorange' | 'darkorchid' | 'darkred' | 'darksalmon' | 'darkseagreen' | 'darkslateblue'
| 'darkslategray' | 'darkslategrey' | 'darkturquoise' | 'darkviolet' | 'deeppink' | 'deepskyblue' | 'dimgray'
| 'dimgrey' | 'dodgerblue' | 'firebrick' | 'floralwhite' | 'forestgreen' | 'fuchsia' | 'gainsboro'
| 'ghostwhite' | 'gold' | 'goldenrod' | 'gray' | 'green' | 'greenyellow' | 'grey' | 'honeydew' | 'hotpink'
| 'indianred' | 'indigo' | 'ivory' | 'khaki' | 'laserlemon' | 'lavender' | 'lavenderblush' | 'lawngreen'
| 'lemonchiffon' | 'lightblue' | 'lightcoral' | 'lightcyan' | 'lightgoldenrod' | 'lightgoldenrodyellow'
| 'lightgray' | 'lightgreen' | 'lightgrey' | 'lightpink' | 'lightsalmon' | 'lightseagreen' | 'lightskyblue'
| 'lightslategray' | 'lightslategrey' | 'lightsteelblue' | 'lightyellow' | 'lime' | 'limegreen' | 'linen'
| 'magenta' | 'maroon' | 'maroon2' | 'maroon3' | 'mediumaquamarine' | 'mediumblue' | 'mediumorchid' | 'mediumpurple'
| 'mediumseagreen' | 'mediumslateblue' | 'mediumspringgreen' | 'mediumturquoise' | 'mediumvioletred' | 'midnightblue'
| 'mintcream' | 'mistyrose' | 'moccasin' | 'navajowhite' | 'navy' | 'oldlace' | 'olive' | 'olivedrab' | 'orange'
| 'orangered' | 'orchid' | 'palegoldenrod' | 'palegreen' | 'paleturquoise' | 'palevioletred' | 'papayawhip'
| 'peachpuff' | 'peru' | 'pink' | 'plum' | 'powderblue' | 'purple' | 'purple2' | 'purple3' | 'rebeccapurple'
| 'red' | 'rosybrown' | 'royalblue' | 'saddlebrown' | 'salmon' | 'sandybrown' | 'seagreen' | 'seashell' | 'sienna'
| 'silver' | 'skyblue' | 'slateblue' | 'slategray' | 'slategrey' | 'snow' | 'springgreen' | 'steelblue' | 'tan'
| 'teal' | 'thistle' | 'tomato' | 'turquoise' | 'violet' | 'wheat' | 'white' | 'whitesmoke' | 'yellow' | 'yellowgreen'
/** Decide if a string is a valid named color string */
function isColorNameT(str: any): str is ColorNameT {
return str in ColorNames;
}
/** `color` parameter values for `color` node in MVS tree */
export const ColorT = union(ColorNameT, HexColorT);
export type ColorT = ValueFor<typeof ColorT>
export type ColorT = ColorNameT | HexColorT;
export const ColorT: iots.Type<ColorT> = union(ColorNameT, HexColorT);
// Type helpers
/** Type helpers */
export function isVector3(x: any): x is Vector3 {
return !!x && Array.isArray(x) && x.length === 3 && typeof x[0] === 'number';
}
@@ -144,7 +224,23 @@ export function isComponentExpression(x: any): x is ComponentExpressionT {
}
export const ColorListNameT = literal(
export type ColorListNameT =
// Color lists from https://observablehq.com/@d3/color-schemes (definitions: https://colorbrewer2.org/export/colorbrewer.js)
// Sequential single-hue
| 'Reds' | 'Oranges' | 'Greens' | 'Blues' | 'Purples' | 'Greys'
// Sequential multi-hue
| 'OrRd' | 'BuGn' | 'PuBuGn' | 'GnBu' | 'PuBu' | 'BuPu' | 'RdPu' | 'PuRd' | 'YlOrRd' | 'YlOrBr' | 'YlGn' | 'YlGnBu'
| 'Magma' | 'Inferno' | 'Plasma' | 'Viridis' | 'Cividis' | 'Turbo' | 'Warm' | 'Cool' | 'CubehelixDefault'
// Cyclical
| 'Rainbow' | 'Sinebow'
// Diverging
| 'RdBu' | 'RdGy' | 'PiYG' | 'BrBG' | 'PRGn' | 'PuOr' | 'RdYlGn' | 'RdYlBu' | 'Spectral'
// Categorical
| 'Category10' | 'Observable10' | 'Tableau10'
| 'Set1' | 'Set2' | 'Set3' | 'Pastel1' | 'Pastel2' | 'Dark2' | 'Paired' | 'Accent'
// Additional lists, not standard for visualization in general, but commonly used for structures
| 'Chainbow'
export const ColorListNameT = literal<ColorListNameT>(
// Color lists from https://observablehq.com/@d3/color-schemes (definitions: https://colorbrewer2.org/export/colorbrewer.js)
// Sequential single-hue
'Reds', 'Oranges', 'Greens', 'Blues', 'Purples', 'Greys',
@@ -162,13 +258,12 @@ export const ColorListNameT = literal(
// Additional lists, not standard for visualization in general, but commonly used for structures
'Chainbow',
);
export type ColorListNameT = ValueFor<typeof ColorListNameT>;
export const ColorDictNameT = literal('ElementSymbol', 'ResidueName', 'ResidueProperties', 'SecondaryStructure');
export type ColorDictNameT = ValueFor<typeof ColorDictNameT>;
export type ColorDictNameT = 'ElementSymbol' | 'ResidueName' | 'ResidueProperties' | 'SecondaryStructure';
export const ColorDictNameT = literal<ColorDictNameT>('ElementSymbol', 'ResidueName', 'ResidueProperties', 'SecondaryStructure');
export const CategoricalPalette = object(
const _CategoricalPalette = object(
{
kind: literal('categorical'),
},
@@ -192,7 +287,8 @@ export const CategoricalPalette = object(
missing_color: nullable(ColorT),
}
);
export type CategoricalPalette = ValueFor<typeof CategoricalPalette>;
export interface CategoricalPalette extends ValueFor<typeof _CategoricalPalette> { }
export const CategoricalPalette: iots.Type<CategoricalPalette> = _CategoricalPalette;
export const CategoricalPaletteDefaults: Required<CategoricalPalette> = {
kind: 'categorical',
@@ -205,7 +301,7 @@ export const CategoricalPaletteDefaults: Required<CategoricalPalette> = {
};
export const DiscretePalette = object(
const _DiscretePalette = object(
{
kind: literal('discrete'),
},
@@ -231,7 +327,8 @@ export const DiscretePalette = object(
value_domain: tuple([nullable(float), nullable(float)]),
}
);
export type DiscretePalette = ValueFor<typeof DiscretePalette>;
export interface DiscretePalette extends ValueFor<typeof _DiscretePalette> { }
export const DiscretePalette: iots.Type<DiscretePalette> = _DiscretePalette;
export const DiscretePaletteDefaults: Required<DiscretePalette> = {
kind: 'discrete',
@@ -242,10 +339,9 @@ export const DiscretePaletteDefaults: Required<DiscretePalette> = {
};
export const ContinuousPalette = object(
const _ContinuousPalette = object(
{
kind: literal('continuous'),
},
// Optionals:
{
@@ -269,7 +365,8 @@ export const ContinuousPalette = object(
overflow_color: nullable(union(literal('auto'), ColorT)),
}
);
export type ContinuousPalette = ValueFor<typeof ContinuousPalette>;
export interface ContinuousPalette extends ValueFor<typeof _ContinuousPalette> { }
export const ContinuousPalette: iots.Type<ContinuousPalette> = _ContinuousPalette;
export const ContinuousPaletteDefaults: Required<ContinuousPalette> = {
kind: 'continuous',

View File

@@ -124,6 +124,9 @@ export const DefaultCanvas3DAttribs = {
xr: DefaultXRManagerAttribs,
};
export type Canvas3DAttribs = typeof DefaultCanvas3DAttribs
export type PartialCanvas3DAttribs = {
[K in keyof Canvas3DAttribs]?: Canvas3DAttribs[K] extends { name: string, params: any } ? Canvas3DAttribs[K] : Partial<Canvas3DAttribs[K]>
}
export { Canvas3DContext };
@@ -373,6 +376,7 @@ interface Canvas3D {
readonly boundingSphere: Readonly<Sphere3D>
readonly boundingSphereVisible: Readonly<Sphere3D>
setProps(props: PartialCanvas3DProps | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void), doNotRequestDraw?: boolean /* = false */): void
setAttribs(attribs: PartialCanvas3DAttribs): void
getImagePass(props: Partial<ImageProps>): ImagePass
getRenderObjects(): GraphicsRenderObject[]
@@ -515,7 +519,7 @@ namespace Canvas3D {
}
}
const xrManager = new XRManager(webgl, input, scene, camera, stereoCamera, helper.pointer, interactionHelper);
const xrManager = new XRManager(webgl, input, scene, camera, stereoCamera, helper.pointer, interactionHelper, p.xr, a.xr);
const xr = {
request: async () => {
@@ -1358,6 +1362,10 @@ namespace Canvas3D {
requestDraw();
}
},
setAttribs: (attribs: PartialCanvas3DAttribs) => {
if (attribs.trackball) controls.setAttribs(attribs.trackball);
if (attribs.xr) xrManager.setAttribs(attribs.xr);
},
getImagePass: (props: Partial<ImageProps> = {}) => {
return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, props);
},
@@ -1371,7 +1379,10 @@ namespace Canvas3D {
return getProps();
},
get attribs() {
return a;
return {
trackball: controls.attribs,
xr: xrManager.attribs,
};
},
get input() {
return input;

View File

@@ -90,11 +90,16 @@ export class XRManager {
private hit: Vec3 | undefined = undefined;
readonly props: XRManagerProps;
readonly attribs: XRManagerAttribs;
setProps(props: Partial<XRManagerProps>) {
Object.assign(this.props, props);
}
setAttribs(attribs: Partial<XRManagerAttribs>) {
Object.assign(this.attribs, attribs);
}
private intersect(camera: ICamera, view: Mat4, plane: Plane3D, targetRayPose: XRPose): { point: Vec3, screen: Vec2 } | undefined {
const point = Vec3();
const ray = getRayFromPose(targetRayPose, view);
@@ -310,6 +315,7 @@ export class XRManager {
constructor(private webgl: WebGLContext, private input: InputObserver, private scene: Scene, private camera: Camera, private stereoCamera: StereoCamera, private pointerHelper: PointerHelper, private interactionHelper: Canvas3dInteractionHelper, props: Partial<XRManagerProps> = {}, attribs: Partial<XRManagerAttribs> = {}) {
this.props = { ...PD.getDefaultValues(XRManagerParams), ...props };
this.attribs = { ...DefaultXRManagerAttribs, ...attribs };
this.hoverSub = this.interactionHelper.events.hover.subscribe(({ position }) => {
this.hit = position;
@@ -323,9 +329,9 @@ export class XRManager {
this.checkSupported();
navigator.xr?.addEventListener('devicechange', this.checkSupported);
const b = { ...DefaultXRManagerBindings, ...attribs.bindings };
this.keyUpSub = input.keyUp.subscribe(({ code, modifiers, key }) => {
const b = this.attribs.bindings;
if (Binding.matchKey(b.exit, code, modifiers, key)) {
this.end();
}
@@ -336,6 +342,8 @@ export class XRManager {
});
this.gestureSub = input.gesture.subscribe(({ scale, button, modifiers }) => {
const b = this.attribs.bindings;
if (Binding.match(b.gestureScale, button, modifiers)) {
this.setScaleFactor(scale);
}

View File

@@ -1259,8 +1259,8 @@ export function normalizeWheel(event: any) {
}
// Fall-back if spin cannot be determined
if (dx && !spinX) { spinX = (dx < 1) ? -1 : 1; }
if (dy && !spinY) { spinY = (dy < 1) ? -1 : 1; }
if (dx && !spinX) { spinX = (dx < 0) ? -1 : 1; }
if (dy && !spinY) { spinY = (dy < 0) ? -1 : 1; }
return { spinX, spinY, dx, dy, dz };
}