Compare commits

...

596 Commits

Author SHA1 Message Date
Alexander Rose
d98350cfb2 4.2.0 2024-05-04 18:52:14 -07:00
Alexander Rose
ae96b6cdac changelog 2024-05-04 18:49:40 -07:00
Alexander Rose
7ba6f5bf4c package updates 2024-05-04 18:49:03 -07:00
Alexander Rose
cede59c282 Merge pull request #1083 from molstar/emissive-bloom
Emissive & Bloom
2024-05-04 18:46:11 -07:00
Alexander Rose
87db52c339 Merge branch 'master' of https://github.com/molstar/molstar into emissive-bloom 2024-05-04 11:22:22 -07:00
Alexander Rose
b3d4500505 Merge pull request #1101 from midlik/load-mvs-keep-camera
Load mvs keep camera
2024-05-04 11:20:17 -07:00
Alexander Rose
2935f1340d Merge branch 'master' into load-mvs-keep-camera 2024-05-04 11:20:02 -07:00
Alexander Rose
fda3481d15 fix bumpiness artifact with xrayShaded 2024-05-04 10:50:36 -07:00
Alexander Rose
24ad3c875a handle identical sets cases in SetUTils 2024-05-04 10:48:27 -07:00
Alexander Rose
dd31c3041b fix bloom & transparent background
- need to blur alpha channel
2024-05-04 10:45:34 -07:00
Alexander Rose
8f3a3dd2be fix variable redeclaration 2024-05-04 10:43:56 -07:00
Alexander Rose
d9d378b249 Merge pull request #1109 from molstar/bond-assignment-fixes
bond assignment fixes
2024-04-29 22:13:51 -07:00
Alexander Rose
98119787e9 Merge branch 'master' into bond-assignment-fixes 2024-04-29 22:13:42 -07:00
Alexander Rose
b00f650066 Merge pull request #1110 from molstar/fix-bumpiness-1107
fix bumpiness artifact
2024-04-29 22:12:45 -07:00
dsehnal
24e7456d6d lint 2024-04-29 14:51:23 +02:00
Alexander Rose
0bb596e255 expand render-shape in tests/browser
- toggling spheres visibility
2024-04-28 18:29:15 -07:00
Alexander Rose
31a6eef1a4 expand render-shape in tests/browser 2024-04-28 18:22:13 -07:00
Alexander Rose
1deead40a5 schema updates 2024-04-28 14:38:06 -07:00
Alexander Rose
628f72903d package updates 2024-04-28 14:37:42 -07:00
Alexander Rose
5d8a569aef fix bumpiness artifact 2024-04-27 18:52:57 -07:00
Alexander Rose
9604b89ee0 workaround bloom artifacts
- getImageData: no undo of pre-multiplied alpha when bloom pass is disabled
2024-04-27 18:20:34 -07:00
Alexander Rose
4627d436a6 Merge branch 'master' of https://github.com/molstar/molstar into emissive-bloom 2024-04-27 15:37:46 -07:00
Alexander Rose
10cdd6a1f0 Merge pull request #1104 from molstar/ssao-artifacts
Fix SSAO artifacts (@corredD, #1082)
2024-04-27 15:36:00 -07:00
Alexander Rose
db9cb955b0 bond assignment fixes 2024-04-27 12:44:09 -07:00
Adam Midlik
00ad11dd6b MVS: camera positioning uses sceneRadiusFactor to avoid cropping 2024-04-26 16:18:46 +01:00
Ryan DiRisio
932b59d62c return selectors for each StateTransform in each measurement (#1087)
* return selectors for each StateTransform in each measurement

* update changelog, modify file header, add name to contributors

* consistently name return selections and labels

---------

Co-authored-by: Ryan DiRisio <rdirisio@treeline.bio>
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2024-04-25 16:10:21 +02:00
Alexander Rose
86b889d48b Merge branch 'master' of https://github.com/molstar/molstar into emissive-bloom 2024-04-24 21:33:19 -07:00
Alexander Rose
9521d47b4e Fix SSAO artifacts (@corredD, #1082) 2024-04-24 21:13:19 -07:00
Alexander Rose
032cf3ad8b typo 2024-04-24 20:48:54 -07:00
Alexander Rose
b39f9a772d changelog 2024-04-24 20:39:51 -07:00
Alexander Rose
ebe727ba24 fix render-spheres example
- wrong step size in sphere geometry boundingSphere & groupmapping
- handle empty instanceGridin renderer & renderable
2024-04-24 20:38:29 -07:00
Adam Midlik
b8551824bf Add missing await 2024-04-23 17:08:06 +01:00
Adam Midlik
8340fc0a98 MVS: fixed aggressive clipping when setting camera 2024-04-23 17:00:50 +01:00
dsehnal
10a3f92697 update gha 2024-04-22 16:10:44 +02:00
David Sehnal
da9f5927d8 Merge docs from molstar/docs (#1090)
* merge docs

* tweak actions
2024-04-22 16:00:26 +02:00
Alexander Rose
990dac6f8d handle xray-shaded in transparency check 2024-04-21 19:04:15 -07:00
Alexander Rose
1aa9c2fb22 disable emissive for transparency 2024-04-20 13:21:38 -07:00
Alexander Rose
77d5c71e61 fix bloom with transparent background 2024-04-20 13:20:13 -07:00
Adam Midlik
ae1d1a44f6 MVS: keepCamera parameter 2024-04-19 15:21:16 +01:00
Alexander Rose
904a409665 changelog 2024-04-14 16:32:53 -07:00
Alexander Rose
ab70536820 add emissive to mesoscale explorer 2024-04-14 16:16:05 -07:00
Alexander Rose
c9ef0171b4 add emissive to glb exporter 2024-04-14 16:15:57 -07:00
Alexander Rose
682b551b6e add bloom postprocessing effect 2024-04-14 16:15:38 -07:00
Alexander Rose
49cd8f7904 add emissive material support 2024-04-14 16:15:09 -07:00
Alexander Rose
a9e1373203 Merge pull request #1075 from molstar/transparency-render-refactor
refactor transparency rendering
2024-04-06 10:29:59 -07:00
Alexander Rose
368fd3a1d5 changelog 2024-04-06 10:20:48 -07:00
Alexander Rose
51ba661e54 cleanup 2024-04-06 10:20:04 -07:00
Alexander Rose
eb86b83aa5 Merge branch 'master' of https://github.com/molstar/molstar into transparency-render-refactor 2024-04-06 10:15:34 -07:00
Alexander Rose
16d5299fbd 4.1.0 2024-03-31 23:01:57 -07:00
Alexander Rose
c85aa6f979 changelog 2024-03-31 22:59:43 -07:00
Alexander Rose
dfab137696 schema updates 2024-03-31 22:59:05 -07:00
Alexander Rose
629fc65b81 package updates
- fix types
- fix webpack browserify issue
2024-03-31 22:56:54 -07:00
Alexander Rose
f06064a0c6 Merge pull request #1072 from hui2000ji/master
Reproduce pymol's surface_ramp_above_mode=1
2024-03-31 22:25:30 -07:00
Alexander Rose
fa70a0e5eb Merge branch 'master' into master 2024-03-31 22:20:44 -07:00
Alexander Rose
f1a2c5c4ee doc tweaks 2024-03-31 22:19:46 -07:00
hui2000ji
169d6323ca add Vec3.transformDirectionOffset; renam param in external-volume 2024-04-01 10:21:40 +08:00
Alexander Rose
c5b3db9960 refactor transparency rendering
- more uniform behavior for blended, wboit, dpoit
- fix issues with text & image geometry
2024-03-30 15:06:09 -07:00
Alexander Rose
fefed0a5f0 removed unused 2024-03-30 15:01:27 -07:00
Alexander Rose
d04311a989 fix type 2024-03-30 15:01:12 -07:00
Alexander Rose
0273df2c16 add rotation param to skybox 2024-03-30 08:19:16 -07:00
hui2000ji
05494953df add "normal" property to position location; add "Above Mode" to external-volume 2024-03-29 11:57:53 +08:00
midlik
d6b79de86d MVS docs reorganization (#1073) 2024-03-27 19:34:14 +01:00
hui2000ji
20327871a8 update mesh positionIt 2024-03-27 17:18:08 +08:00
Alexander Rose
dd0845038d Merge pull request #1069 from molstar/transparency-fix
Fix transparency rendering fallback
2024-03-26 22:07:18 -07:00
Alexander Rose
d5d3977506 Merge branch 'master' into transparency-fix 2024-03-26 22:07:08 -07:00
Alexander Rose
1675e18f57 Merge pull request #1070 from molstar/ssao-sampler-fix
Fix SSAO broken when `OES_texture_float_linear` is unavailable
2024-03-24 15:36:02 -07:00
Alexander Rose
d8bfe78b30 Fix SSAO broken when OES_texture_float_linear is unavailable 2024-03-24 14:56:24 -07:00
Alexander Rose
589d3465ef Merge pull request #1056 from pechersky/volume-transform
VolumeTransform to rotate a volume, like TransformStructureConformation
2024-03-24 10:14:11 -07:00
Alexander Rose
ebc63099b4 Merge branch 'master' into volume-transform 2024-03-24 10:13:57 -07:00
Alexander Rose
512a099a28 Fix transparency rendering fallback 2024-03-23 10:10:13 -07:00
midlik
d056bf3549 Fixed text rendering in headless mode (#1068)
* Fixed text rendering in headless mode

* lint
2024-03-21 06:46:46 +01:00
Alexander Rose
e2c929fa33 fix wrong width/height in InputObserver 2024-03-18 18:44:11 -07:00
David Sehnal
a619ae6687 BinaryCIF encoder fixes (#1057) 2024-03-04 17:10:02 +01:00
Yakov Pechersky
e40fc34a6f move changes to unreleased section 2024-03-04 09:07:49 -05:00
Yakov Pechersky
2734377d0a remove extra line 2024-03-01 15:51:03 -05:00
Yakov Pechersky
86575a7a15 Changelog 2024-03-01 15:50:09 -05:00
Yakov Pechersky
31227d2050 authors header 2024-03-01 15:50:03 -05:00
Yakov Pechersky
2e73a37de2 VolumeTransform to rotate a volume, like TransformStructureConformation 2024-03-01 15:27:37 -05:00
midlik
32c3ef0801 MVS: OpenApi schema in docs (#1051) 2024-02-19 18:04:06 +01:00
dsehnal
b70f2e073d 4.0.1 2024-02-19 12:56:41 +01:00
dsehnal
03b4aa1b33 changelog 2024-02-19 12:54:01 +01:00
Alexander Rose
0a4643ebe4 Revert "require WEBGL_depth_texture extension" 2024-02-17 11:26:55 -08:00
Alexander Rose
8dc3dae81e revert renderbuffer removal 2024-02-17 11:24:29 -08:00
David Sehnal
a1e2b0e5ae Bcif decoder fix (#1048)
* Fix bcif decoder

* update integerPackingSigned typing

* fix type fix
2024-02-14 18:23:22 +01:00
midlik
46af7d03bf MVSX (#1041)
* MVSX format provider

* MVS: Integration examples

* MVS: drag-and-drop support for MVSX

* MVS: support for URL param mvs-format=mvsx

* MVS: docs for MVSX

* MVS: mvs-render supports MVSX

* Update README
2024-02-14 18:22:27 +01:00
Alexander Rose
60550cfea1 4.0.0 2024-02-04 15:51:28 -08:00
Alexander Rose
5de4569ad9 changelog 2024-02-04 15:49:04 -08:00
Alexander Rose
908fb0eba9 me: fix state loading 2024-02-03 10:53:25 -08:00
Alexander Rose
e7e191a907 Merge pull request #1019 from molstar/v4-dev
V4 dev
2024-02-03 10:26:26 -08:00
Alexander Rose
de46c82c78 add mesoscale-explorer to deploy script 2024-02-03 10:17:20 -08:00
Alexander Rose
d8f3ab767e changelog 2024-02-03 10:08:46 -08:00
Alexander Rose
d34a9be20f package updates 2024-02-03 10:08:34 -08:00
Alexander Rose
78628c217f Merge branch 'master' of https://github.com/molstar/molstar into v4-dev 2024-02-03 10:06:11 -08:00
Alexander Rose
d29bf2eec2 3.45.0 2024-02-03 10:02:48 -08:00
Alexander Rose
5db60c2882 changelog 2024-02-03 10:00:17 -08:00
Alexander Rose
737846e093 schema updates 2024-02-03 09:59:52 -08:00
Alexander Rose
2ad551bdc7 package updates 2024-02-03 09:52:14 -08:00
Alexander Rose
46fa581f07 Merge pull request #1036 from russellp17/master
Move most headless context deps to optional peer dependencies and remove `@types/jpeg-js`
2024-02-03 09:43:27 -08:00
Alexander Rose
17dbe4b60e make jpeg-js a dev and optional peer dependency 2024-02-03 09:35:20 -08:00
Russell Parker
aeaf2e799a update changelog 2024-01-30 11:18:08 -05:00
Russell Parker
ef16b718c4 Move most headless context deps to optional peer deps and remove @types/jpeg-js 2024-01-30 11:17:54 -05:00
Alexander Rose
4b4f6d34a3 Merge branch 'master' of https://github.com/molstar/molstar into v4-dev 2024-01-29 22:52:55 -08:00
Alexander Rose
ab4130d42d Merge pull request #1032 from molstar/interactive-snapshots
Interative labels & related changes
2024-01-29 22:52:05 -08:00
dsehnal
9e73de89fb changelog 2024-01-28 05:32:17 +01:00
dsehnal
02cec6f8e6 support multiline PD.text and use markdown to render multi-line tooltips 2024-01-28 05:26:46 +01:00
dsehnal
e97a02473f label snapshot key & tooltip support 2024-01-28 04:35:01 +01:00
Alexander Rose
827e75ce0e Merge branch 'master' of https://github.com/molstar/molstar into v4-dev 2024-01-15 09:42:36 -08:00
Alexander Rose
22e5c9d65b Merge pull request #800 from giagitom/2-colors-cylinders
Implementing 2 colors interpolation on impostor cylinders
2024-01-15 09:06:15 -08:00
Alexander Rose
10d9120d37 hide colorMode for cartoon repr 2024-01-13 11:38:45 -08:00
Alexander Rose
e6c20d35bd Merge branch 'master' into 2-colors-cylinders 2024-01-13 10:19:54 -08:00
Alexander Rose
27bf66038d location-iterator tweaks
- consolidate BondIterator.fromGroup/.fromStructure
- use options argument in PolymerLocationIterator.fromGroup
- remove 'any' type
2024-01-13 10:16:28 -08:00
Alexander Rose
3ce6d89521 color-mode docs 2024-01-13 10:14:06 -08:00
Alexander Rose
24608ac355 stricter param types 2024-01-13 10:13:11 -08:00
Alexander Rose
da034d9502 move cylinder color mixing to vertex shader 2024-01-13 10:11:13 -08:00
Alexander Rose
581673fb9b Merge pull request #1022 from midlik/is-mvs-model-prop
check applicability of mvs repr/color 2
2024-01-10 22:11:37 -08:00
Adam Midlik
29cf97e6cf Revert package-lock.json 2024-01-10 11:04:07 +00:00
Adam Midlik
f65773d654 MVS extension: MolViewSpec components are applicable only when the model has been loaded from MolViewSpec 2024-01-09 11:50:57 +00:00
giagitom
d11c8c166a Fix 2024-01-07 19:55:06 +01:00
giagitom
ae6bd743a8 Fix nucleotide atomic bonds 2024-01-07 19:34:09 +01:00
giagitom
c1654574d0 Merge branch 'master' into 2-colors-cylinders 2024-01-07 19:09:36 +01:00
giagitom
6f506351cd Merge branch 'master' of https://github.com/molstar/molstar 2024-01-07 19:06:01 +01:00
giagitom
db83b97ff9 Use only when necessary 2024-01-07 18:41:06 +01:00
Alexander Rose
5c818b35ad remove rcsb prefix 2024-01-06 17:24:01 -08:00
Alexander Rose
b1ce20f5f5 4.0.0-beta.0 2024-01-06 10:27:59 -08:00
Alexander Rose
d1cbebf8a7 changelog & readme 2024-01-06 10:25:29 -08:00
Alexander Rose
c771cfef99 Merge pull request #1017 from molstar/mesoscale-explorer
Mesoscale explorer
2024-01-06 10:18:41 -08:00
Alexander Rose
6ee72d5e8c changelog 2024-01-06 10:12:23 -08:00
Alexander Rose
b859b3c597 fix merge issues 2024-01-06 10:07:45 -08:00
Alexander Rose
8281909620 Merge branch 'v4-dev' of https://github.com/molstar/molstar into mesoscale-explorer 2024-01-06 09:54:20 -08:00
Alexander Rose
f3c30ae4b3 fix type 2024-01-06 09:44:29 -08:00
Alexander Rose
9ad53eb0d5 set minimal node.js version to 18 2024-01-06 09:33:27 -08:00
Alexander Rose
a15e9158b4 Merge pull request #1013 from molstar/assembly-symmetry
Generalize rcsb/assembly-symmetry/ extension
2024-01-06 09:28:22 -08:00
Alexander Rose
6bfedc038e Merge branch 'v4-dev' of https://github.com/molstar/molstar into assembly-symmetry 2024-01-06 09:28:07 -08:00
Alexander Rose
966293994f Merge branch 'v4-dev' of https://github.com/molstar/molstar into v4-dev 2024-01-06 09:26:22 -08:00
Alexander Rose
09f4c857de Merge branch 'master' of https://github.com/molstar/molstar into v4-dev 2024-01-06 09:25:46 -08:00
Alexander Rose
e05bd5f0c5 3.44.0 2024-01-06 09:01:15 -08:00
Alexander Rose
a3cdc2844e changelog 2024-01-06 08:58:31 -08:00
Alexander Rose
a51947637c package updates 2024-01-06 08:58:15 -08:00
David Sehnal
72ee428f51 Update createPluginUI (#1014)
* update createPluginUI

* fix example
2024-01-06 14:34:27 +01:00
Alexander Rose
4fe203d3ea Merge pull request #1015 from molstar/webgl-depth-rt
Require `WEBGL_depth_texture` webgl extension
2024-01-03 22:22:14 -08:00
Alexander Rose
2ec32641ee remove renderbuffer use 2024-01-03 22:02:52 -08:00
Alexander Rose
8d5198f15c require WEBGL_depth_texture extension 2024-01-03 18:26:24 -08:00
Alexander Rose
d5154c7391 Merge branch 'v4-dev' of https://github.com/molstar/molstar into assembly-symmetry 2024-01-02 15:19:31 -08:00
Alexander Rose
6ee750e6bd Merge pull request #825 from molstar/immer-es6
Immer, es6 & beyond
2024-01-02 15:15:55 -08:00
Alexander Rose
ea8aa10704 Merge branch 'v4-dev' of https://github.com/molstar/molstar into immer-es6 2024-01-02 15:07:56 -08:00
Alexander Rose
19ba76a1f1 Generalize rcsb/assembly-symmetry/ extension
- Move to assembly-symmetry/
- Remove RCSB specific dependencies and prefixes
2024-01-02 14:04:56 -08:00
Alexander Rose
fc28046df4 cleanup 2024-01-02 12:55:44 -08:00
Alexander Rose
20f0416a8c Merge branch 'v4-dev' of https://github.com/molstar/molstar into mesoscale-explorer 2024-01-02 12:52:47 -08:00
Alexander Rose
1298baf60d typo 2024-01-02 12:41:55 -08:00
Alexander Rose
999b86ab81 Merge branch 'master' of https://github.com/molstar/molstar into v4-dev 2024-01-02 12:41:29 -08:00
Alexander Rose
75f2acd8eb Merge pull request #1006 from molstar/lod-cull-hiz
culling & lod support
2024-01-02 12:40:36 -08:00
Alexander Rose
1541ba10f1 tweaks 2024-01-02 12:40:15 -08:00
Christian Domínguez
00ca25ffd7 Fixed drag and drop overlay on WebKit and Safari (#1011)
* Fixed drag and drop overlay on webkit/safari. Closes #1010

* Use dataTransfer.types when dataTransfer.items is not available/empty

* Updated package.json and header for contribution
2024-01-02 21:03:54 +01:00
giagitom
302e1c659f Merge branch 'master' into 2-colors-cylinders 2023-12-26 16:43:09 +01:00
giagitom
8b4d987f94 Merge branch 'master' of https://github.com/molstar/molstar 2023-12-26 16:38:35 +01:00
Alexander Rose
c383012fd0 add stochstic/dithered transparency to fade LODs in and out 2023-12-25 12:24:29 -08:00
Alexander Rose
66b6ddc527 Merge branch 'v4-dev' of https://github.com/molstar/molstar into lod-cull-hiz 2023-12-25 12:16:17 -08:00
Alexander Rose
15d60c51a9 fix ArrayMapping calls 2023-12-25 12:16:00 -08:00
Alexander Rose
fe82cd4f2c Merge branch 'v4-dev' of https://github.com/molstar/molstar into lod-cull-hiz 2023-12-25 12:08:13 -08:00
Alexander Rose
b3e792cbc3 Merge branch 'master' of https://github.com/molstar/molstar into v4-dev 2023-12-25 12:07:46 -08:00
Alexander Rose
2f0230dc84 avoid showing (and calculating) inter-unit bonds for huge structures 2023-12-25 12:03:14 -08:00
Alexander Rose
45522ad410 add LightbulbOnOutline and Serach icons 2023-12-25 11:54:00 -08:00
giagitom
3fe80fe61a Merge branch 'master' of https://github.com/molstar/molstar 2023-12-22 00:24:16 +01:00
Gianluca Tomasello
ede1a8da07 Cartoon nucleic with sugar visual (#727)
* add handlers to MeshBuilder

* Add ring fill visual

* Add nucleotide ring bond visual

* Add nucleotide ring element visual

* Update cartoon representation

* Fix imports

* Smooth normals

* Lint fix

* Update headers and Changelog

* Fix sugar ring mid point

* rename ring -> atomic

* refactor shared nucleotide helpers

* thicknessFactor for nucleic ring/block/fill visuals

* changelog

---------

Co-authored-by: Alexander Rose <alexander.rose@weirdbyte.de>
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2023-12-21 12:02:33 +01:00
Yakov Pechersky
0199afd5f3 Expose explicit bond orders from struct_conn in mmcif (#999)
* Expose explicit bond orders from struct_conn in mmcif

StructConn was referencing the wrong column name, it was using auth_seq_id instead of label_seq_id
The latter is mandatory by https://mmcif.wwpdb.org/dictionaries/mmcif_pdbx_v50.dic/Categories/struct_conn.html
This was causing no matches found during `getEntriesFromStructConn`
for building the bond lookup.

* update CHANGELOG and file headers

* Prefer auth_seq_id, fallback to label_seq_id

* case on presence instead of

* clarify changes in CHANGELOG

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2023-12-21 11:59:26 +01:00
midlik
f8bda3617f Mvs extension - yet another pull request (#1002)
* MVS extension: support all X11 colors

* MVS extension: nicer validation messages for literal types

* MVS extension: no need to call HexColor in builder

* MVS extension: Support relative URIs

* MVS extension: docs

* MVS extension: docs - fix markdown

* MVS extension: docs

* MVS extension: docs for selectors

* MVS extension: docs for selectors

* Support for label rendering in HeadlessPluginContext

* MVS extension: CLI utils

* MVS extension: nicer component node labels

* MVS extension: labels applied in one node

* MVS extension: labels applied in one node - fixed label colors

* MVS extensions: removed unused params from "Custom Label"

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2023-12-21 11:58:10 +01:00
Sebastian Bittrich
bffd7d75e0 ModelServer SDF/MOL2 ligand export: fix atom indices for atoms not present in the CCD (#1007)
* ModelServer: fix atom indices upon additional hydrogen atoms

* cl

* ignore all non-CCD atoms
2023-12-21 11:54:54 +01:00
Alexander Rose
e1315b6642 culling & lod support
- Add culling support
    - Cull per-object and per-instance
    - Cull based on frustum and camera distance
- Add LOD support
    - Visibility based on camera distance
    - Special mode for spheres with automatic LOD levels
- Add occlusion culling (only WebGL2)
    - Hi-Z pass
    - Cull based on previous frame's Hi-Z buffer
2023-12-17 17:37:04 -08:00
Alexander Rose
205413e696 Merge branch 'v4-dev' of https://github.com/molstar/molstar into mesoscale-explorer 2023-12-17 17:03:15 -08:00
Alexander Rose
2c0a486531 Merge branch 'master' of https://github.com/molstar/molstar into v4-dev 2023-12-17 16:51:31 -08:00
Alexander Rose
90a4e019ac remove offsetZ default from measurement label 2023-12-17 12:24:08 -08:00
Alexander Rose
640426ae16 me: wip, improve selection handling 2023-12-10 21:57:35 -08:00
Alexander Rose
d1e620eed1 me: support exact filter matches 2023-12-10 21:56:29 -08:00
Alexander Rose
1a2bad7c77 me: add animation export ui 2023-12-10 21:55:36 -08:00
Alexander Rose
b6e4d7721e Merge branch 'v4-dev' of https://github.com/molstar/molstar into mesoscale-explorer 2023-12-09 10:23:48 -08:00
Alexander Rose
e5902d15e2 Merge branch 'v4-dev' of https://github.com/molstar/molstar into v4-dev 2023-12-09 10:22:38 -08:00
Alexander Rose
4e2b7b5ffc Merge branch 'master' of https://github.com/molstar/molstar into v4-dev 2023-12-09 10:21:54 -08:00
Alexander Rose
bcbab9cb9d Merge pull request #21 from arose/manifest
Manifest
2023-12-09 10:21:02 -08:00
Alexander Rose
9a4b0116eb me: add selection-info panel 2023-12-09 10:20:02 -08:00
Alexander Rose
60d0de8d88 add search icon 2023-12-09 10:19:24 -08:00
Alexander Rose
6a520d2877 me: occlusion/culling tweaks 2023-12-09 10:18:55 -08:00
dsehnal
6edbae80db Fix changelog date 2023-12-04 16:30:41 +01:00
dsehnal
aac0abed32 3.43.1 2023-12-04 16:28:02 +01:00
dsehnal
6805dd7947 Fix react-markdown dependency 2023-12-04 16:25:23 +01:00
Alexander Rose
6c9254f2b6 3.43.0 2023-12-02 13:25:52 -08:00
Alexander Rose
897d443873 changelog 2023-12-02 13:23:17 -08:00
Alexander Rose
f276ea2258 schema updates 2023-12-02 13:22:26 -08:00
Alexander Rose
58d735996e type fix 2023-12-02 13:21:47 -08:00
Alexander Rose
72b66367f3 package updates 2023-12-02 13:21:35 -08:00
midlik
c592a3b93d MVS extension - additional work (#991)
* MVS extension: deterministic transform refs, updated metadata structure

* Perf-test for `sortIfNeeded`

* MVS extension: README

* MVS extension: show loading errors in the Mol* console

* MVS extension: auto-fix rotation matrix imprecisions

* MVS extension: data format provider

* MVS extension: Updated README

* MVS extension: rename deletePrevious -> replaceExisting, default to false in "Load MVS Data" to allow loading multiple files

* Perf-test for sortIfNeeded uses Benchmark.js
2023-11-29 17:21:02 +01:00
giagitom
3e6d0c8c62 Merge branch 'master' of https://github.com/molstar/molstar 2023-11-26 21:38:09 +01:00
Alexander Rose
60ed853c02 Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into manifest 2023-11-26 09:57:21 -08:00
Alexander Rose
ba58b8e559 Merge branch 'v4-dev' of https://github.com/molstar/molstar into mesoscale-explorer 2023-11-26 09:56:50 -08:00
Alexander Rose
2c3d122820 Merge pull request #992 from molstar/canvas3dcontext-runtime-props
add Canvas3DContext runtime props
2023-11-26 09:51:16 -08:00
Alexander Rose
47f4b3a051 Merge branch 'v4-dev' into canvas3dcontext-runtime-props 2023-11-26 09:45:59 -08:00
Alexander Rose
daed91e1ea simplify TransparencyMode to type 2023-11-26 09:43:19 -08:00
Alexander Rose
3d0b9c98e4 change default compile target to es2018 2023-11-25 22:28:15 -08:00
Alexander Rose
966e8e764f add Canvas3DContext runtime props
- pixelScale, pickScale, transparency (blended, wboit, dpoit)
- replacing instantiation-time attribs
2023-11-25 22:09:40 -08:00
Alexander Rose
14441a4ee3 Merge branch 'master' of https://github.com/molstar/molstar into v4-dev 2023-11-25 17:07:10 -08:00
Alexander Rose
e773824fb5 improve disposal to aid GC 2023-11-25 17:06:53 -08:00
Alexander Rose
cc6a990b85 me: improve asset & app disposal 2023-11-25 16:55:05 -08:00
Alexander Rose
ffa7d66c66 improve disposal to aid GC 2023-11-25 16:25:35 -08:00
Alexander Rose
abc29ceefb Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into manifest 2023-11-25 10:11:27 -08:00
Alexander Rose
e2f137a495 Merge branch 'v4-dev' of https://github.com/molstar/molstar into mesoscale-explorer 2023-11-25 10:11:00 -08:00
Alexander Rose
ebbe78fb1d Merge branch 'master' of https://github.com/molstar/molstar into v4-dev 2023-11-25 10:07:41 -08:00
Alexander Rose
5d139b6db8 optimize LociSelectManager.selectOnly
- avoid superfluous loci set operations
2023-11-25 10:07:00 -08:00
Alexander Rose
8d5aad2be1 Merge pull request #986 from molstar/symop-memop
reduce memory usage of SymmetryOperator.ArrayMapping
2023-11-25 10:01:23 -08:00
Alexander Rose
e8601d3f5e Merge branch 'symop-memop' of https://github.com/molstar/molstar into symop-memop 2023-11-25 09:49:29 -08:00
Alexander Rose
c63622677a more ArrayMapping improvements
- add identity specialization
- reuse x/y/z methods
2023-11-25 09:49:26 -08:00
Alexander Rose
163cee6d66 me: move instance transforms out of params 2023-11-25 09:18:30 -08:00
Alexander Rose
cd1d2010da me: dispose zip file asset after loading 2023-11-25 09:16:53 -08:00
Alexander Rose
8f0ab2fdf9 me: add transformer update methods 2023-11-25 09:14:24 -08:00
Alexander Rose
7dbc8ab4f1 optimize LociSelectManager.selectOnly 2023-11-24 23:23:56 -08:00
Alexander Rose
6a730a6ce7 Merge branch 'v4-dev' into symop-memop 2023-11-24 18:27:55 -08:00
Alexander Rose
ca606284b3 Merge branch 'master' of https://github.com/molstar/molstar into v4-dev 2023-11-24 18:18:13 -08:00
Alexander Rose
396c530e13 tweak array mapping & add perf test 2023-11-24 18:00:30 -08:00
Alexander Rose
4cc3072165 Merge branch 'master' of https://github.com/molstar/molstar into symop-memop 2023-11-24 15:50:12 -08:00
Alexander Rose
4598841ddc add transforms & label params to ShapeFromPly 2023-11-24 15:41:38 -08:00
Alexander Rose
728414366d fix tryGetCellData data check 2023-11-24 15:40:46 -08:00
Alexander Rose
1b7b38b47e fix bump scaling with ignoreLight enabled 2023-11-24 15:39:24 -08:00
Alexander Rose
dd15b1ed29 me: expose pattern/bump params for shapes 2023-11-24 15:37:06 -08:00
Alexander Rose
446ea9173b fix bump scaling with ignoreLight enabled 2023-11-24 14:53:30 -08:00
Alexander Rose
70f8515e51 me: add support for entities from ply files 2023-11-24 13:44:12 -08:00
Alexander Rose
90fac967f2 add transforms & label params to ShapeFromPly 2023-11-24 13:42:33 -08:00
Alexander Rose
1796dfd73a me: guard against no assemblies 2023-11-24 13:24:58 -08:00
Alexander Rose
a73cb1648d me: fix state loading 2023-11-24 13:22:24 -08:00
Alexander Rose
b715205a04 fix tryGetCellData 2023-11-24 13:08:09 -08:00
Yakov Pechersky
ef17cb2cca Custom sequence viewer as a plugin spec (#988)
Factored out of #936

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2023-11-24 14:39:22 +01:00
midlik
b7b52f5c7d MVS extension (#976)
* Moved MVS extension from mol-view-spec repo

* Viewer supports URL params mvs-url, mvs-data, mvs-format

* Tests

* MVS sanity checks

* MVS extension: drag-and-drop support

* mvs-render try1

* Example CLI utility mvs-render

* Example CLI utility mvs-validate

* MVS extension: renaming

* MVS extension: fixed FOV in mvs-render

* Moved stuff to mol-util/array.ts

* Moved stuff to mol-util/object.ts

* MVS extension: renamed `additions` to `components`

* MVS extension: trying plugin.managers.camera.focusSphere

* MVS extension: refactor focus

* MVS extension: fixed label color once again

* MVS extension: camera position adjustment (compensate FOV differences)

* Fixed formula for camera focus in orthographic mode

* Moved Choice to mol-util/param-choice.ts

* Moved stuff to mol-util/json.ts

* Object.hasOwn polyfill

* MVS extension: small refactor

* Fixed bug in hashString
2023-11-24 14:38:08 +01:00
David Sehnal
79ed8e7de4 Snapshot improvements (#977)
* Snapshot improvements

* improve key UX

* markdown descriptions

* tweak button

* drag overlay fix

* package lock
2023-11-20 12:01:19 +01:00
Alexander Rose
60a2eb8a22 me: add app.loadUrl 2023-11-19 23:30:41 -08:00
Alexander Rose
b833d41c1e Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into manifest 2023-11-19 18:02:12 -08:00
Alexander Rose
85d2ed0132 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-11-19 18:01:34 -08:00
Alexander Rose
2552a5218f fix seconday-structure.ts file name 2023-11-19 17:29:19 -08:00
Alexander Rose
c4a4562d82 package updates 2023-11-19 17:24:37 -08:00
Alexander Rose
a3b9d30c7c target ES2018 2023-11-19 17:19:26 -08:00
Alexander Rose
a3572961ac Merge branch 'master' of https://github.com/molstar/molstar into immer-es6 2023-11-19 17:11:39 -08:00
Alexander Rose
3118a46250 reduce memory usage of SymmetryOperator.ArrayMapping 2023-11-19 17:01:50 -08:00
Alexander Rose
d238b8aee9 Merge branch 'master' of https://github.com/molstar/molstar 2023-11-19 16:38:05 -08:00
Alexander Rose
22a57d8f48 add structure-element-sphere visual to spacefill repr 2023-11-19 16:37:26 -08:00
Alexander Rose
119e8f63eb Fix missing await in HeadlessPluginContext.saveStateSnapshot 2023-11-19 15:27:04 -08:00
Alexander Rose
e6a4122d1c add no-constant-binary-expression to eslint 2023-11-18 13:30:42 -08:00
Alexander Rose
9ba5f1f540 tweak distinctColors 2023-11-18 13:26:08 -08:00
Alexander Rose
0a0ac7ee63 guard against empty cif files 2023-11-18 13:25:57 -08:00
Alexander Rose
7670df04ae use isTimingMode for UserTiming 2023-11-18 13:24:58 -08:00
Alexander Rose
98744af872 fix for compiling with es6+ 2023-11-18 13:24:46 -08:00
Alexander Rose
f4eb509887 only change camera.target for flyMode and pointerLock 2023-11-18 13:24:21 -08:00
Alexander Rose
cbb8c0536d me: wip, trajectory support 2023-11-16 23:17:44 -08:00
Alexander Rose
d7d798746c me: wip, trajectory support 2023-11-16 23:17:13 -08:00
Alexander Rose
c39d092fc1 me: no clustering step for distinctColors 2023-11-16 23:15:31 -08:00
Alexander Rose
102b69961c tweak distinctColors 2023-11-16 23:14:53 -08:00
Alexander Rose
681d64dcd9 me: flexible binary transform in generic loader 2023-11-15 22:00:57 -08:00
Alexander Rose
2083407a84 me: fix bcif loading 2023-11-15 21:30:55 -08:00
Alexander Rose
a07f90a405 me: cache some state selections 2023-11-15 21:30:23 -08:00
Alexander Rose
c7ca528778 guard against empty cif files 2023-11-15 21:28:20 -08:00
Alexander Rose
ca36264cf8 use isTimingMode for UserTiming 2023-11-15 21:27:55 -08:00
Alexander Rose
7e5b181d92 fix for compiling with es6+ 2023-11-15 21:25:27 -08:00
Alexander Rose
c538e1d0c8 wip, support binary transforms 2023-11-08 22:30:15 -08:00
Alexander Rose
75e23d73b0 me: fix MesoscaleExplorerState handling 2023-11-07 21:55:55 -08:00
Alexander Rose
39f077b066 me: add drag-and-drop support 2023-11-07 21:04:19 -08:00
Alexander Rose
78874c0024 fix return type of State.tryGetCellData 2023-11-07 20:41:10 -08:00
Alexander Rose
7069760d07 me: improve generic loader (with @corredD)
- support assemblies
- support quaternions and transformation matrices
2023-11-07 20:34:47 -08:00
Alexander Rose
df0af2b1ca fix return type of State.tryGetCellData 2023-11-07 20:32:18 -08:00
Alexander Rose
edd6419b2b me: fix MesoscaleExplorerState handling 2023-11-07 20:30:30 -08:00
giagitom
c6210ae1a0 Merge branch 'master' of https://github.com/molstar/molstar 2023-11-06 07:36:00 +01:00
Alexander Rose
446455494e Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-11-05 20:04:03 -08:00
Alexander Rose
c4bc16fe5d 3.42.0 2023-11-05 20:00:47 -08:00
Alexander Rose
988cee0047 changelog 2023-11-05 19:56:47 -08:00
Alexander Rose
39fa185f3d me: use clipPrimitive 2023-11-05 17:02:11 -08:00
Alexander Rose
cb0484bca1 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-11-05 17:00:35 -08:00
Alexander Rose
bf0707a2aa schema updates 2023-11-05 13:25:57 -08:00
Alexander Rose
9a2191e1cc package updates 2023-11-05 13:25:43 -08:00
Alexander Rose
36ead9dda3 Merge pull request #966 from molstar/clip-primitive
add clipPrimitive option to spheres geometry
2023-11-05 11:51:16 -08:00
Alexander Rose
71e1bb849e Merge branch 'master' into clip-primitive 2023-11-05 11:51:05 -08:00
David Sehnal
975cceed77 Add DragAndDropManager (#968)
* Add PluginContext.customDragAndDropHandlers

* refactor to manager
2023-11-05 14:46:51 +01:00
Alexander Rose
82065dc5b7 typo 2023-11-04 19:24:05 -07:00
Alexander Rose
39ae03ff8c skip empty multiDrawBase data items 2023-11-04 19:22:32 -07:00
Alexander Rose
45ab88f0a1 fix batched camera distance culling 2023-11-04 19:21:14 -07:00
Alexander Rose
f0d649f265 Merge pull request #972 from JonStargaryen/condensed
Add `options` support for default bond labels
2023-11-04 15:06:11 -07:00
Sebastian Bittrich
44ce5df136 cl 2023-11-03 16:01:34 -07:00
Sebastian Bittrich
b00bce69fd make bond labels honor options 2023-11-03 16:00:03 -07:00
Alexander Rose
e2e9e5f6fc add clipPrimitive option to spheres geometry
- clip whole spheres instead of cutting them
2023-10-28 22:59:18 -07:00
giagitom
36cf2853b2 Merge branch 'master' of https://github.com/molstar/molstar 2023-10-22 15:15:38 +02:00
Alexander Rose
efc322a375 lod params tweaks 2023-10-21 23:46:23 -07:00
Alexander Rose
7e792a4f90 add model info, ui tweaks 2023-10-21 16:33:54 -07:00
Alexander Rose
44b3a9b121 more coloring improvements and fixes 2023-10-21 14:09:25 -07:00
Alexander Rose
55f72e65dc Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-10-21 13:49:21 -07:00
Alexander Rose
745746f243 handle altId & insCode for bonds from PDB files 2023-10-21 13:40:37 -07:00
Alexander Rose
b5f229ba6d improve distinctColors function
- Add `sort` and `sampleCountFactor` parameters
- Fix clustering issues
2023-10-21 12:50:03 -07:00
Alexander Rose
d5a47e617a fix de-/saturate of colors with no hue 2023-10-21 12:27:58 -07:00
Alexander Rose
a200ca5b21 Merge pull request #946 from molstar/fix-pdb-insCode-labelSeq
fix handling of pdb files with insertion codes
2023-10-21 12:11:59 -07:00
Alexander Rose
65b52c8ecd Merge branch 'master' into fix-pdb-insCode-labelSeq 2023-10-21 12:11:39 -07:00
Alexander Rose
203fb2f7fe missing prop 2023-10-15 15:47:32 -07:00
Alexander Rose
492494033f type fixes 2023-10-15 15:45:14 -07:00
Alexander Rose
9de6d86a0f typo 2023-10-15 15:40:02 -07:00
Alexander Rose
23fdbdea54 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-10-15 15:30:42 -07:00
Alexander Rose
8fa9c19346 add ui for loading entries from pdb & pdb-dev 2023-10-15 15:29:16 -07:00
Alexander Rose
ab10007d60 fix hiz error with zero size viewport 2023-10-15 15:28:19 -07:00
Alexander Rose
7a6094298b typo 2023-10-15 14:13:58 -07:00
dsehnal
ab34a59677 3.41.0 2023-10-15 13:23:58 +02:00
dsehnal
7a96cdd52d changelog 2023-10-15 13:20:01 +02:00
midlik
65cad5ea4d Assembly Symmetry extension customization (#950)
* Show assembly symmetry from PDBe API (quick and dirty)

* Treat 404 from PDBe API as success

* RCSBAssemblySymmetry extension: make defaults configurable via plugin config items

* RCSBAssemblySymmetry: revert configs to default values

* RCSBAssemblySymmetry: correctly handle non-assembly structure with PDBe API
2023-10-15 13:05:50 +02:00
Alexander Rose
fe433fc7b0 load example, pdb, pdbdev via get parameter 2023-10-14 21:53:12 -07:00
Alexander Rose
ba173978d3 treat water as separate type 2023-10-14 21:49:51 -07:00
Alexander Rose
b3aed2ead7 fix clipping for merged mmcif structures 2023-10-14 21:49:11 -07:00
Alexander Rose
c57774a62e coloring improvements & tweaks 2023-10-14 21:48:16 -07:00
Alexander Rose
199b767bbd ignore shift for highlight with pointer lock 2023-10-14 21:43:14 -07:00
Alexander Rose
0c3131ff56 add ultra graphics mode 2023-10-14 21:42:11 -07:00
Alexander Rose
7433fe07f1 type fixes 2023-10-14 21:39:34 -07:00
Alexander Rose
a765ba8e3b Merge pull request #949 from JonStargaryen/master
Fix layout typo
2023-10-10 21:49:30 -07:00
Sebastian Bittrich
8594ce80a9 cl 2023-10-10 10:04:23 -07:00
Sebastian Bittrich
915797c4a4 fix layout typo 2023-10-10 09:49:24 -07:00
giagitom
70ebdc6b80 Merge branch 'fix-pdb-insCode-labelSeq' of https://github.com/molstar/molstar 2023-10-09 11:37:28 +02:00
Alexander Rose
44c69f538b fix partial polymer trace sec-struc type 2023-10-08 22:06:35 -07:00
Alexander Rose
b53a52b04d fix handling of pdb files with insertion codes 2023-10-07 21:48:17 -07:00
Alexander Rose
e4396039fd SetUtils performance tweaks 2023-10-07 13:44:02 -07:00
Alexander Rose
296b6902b1 Improve SetUtils performance
- use ES6 features
- add setutils perf test
2023-10-07 13:42:23 -07:00
Alexander Rose
0795f06f28 Merge branch 'master' of https://github.com/molstar/molstar into immer-es6 2023-10-07 11:12:46 -07:00
Alexander Rose
a01f159889 add transparency to advanced simple-settings 2023-10-07 11:01:56 -07:00
Alexander Rose
4ea2eadb78 reorder bottom instance grid for performance 2023-10-07 11:01:29 -07:00
David Sehnal
e548a3ed85 add PluginContext.initialized promise (#935) 2023-10-02 19:46:02 +02:00
Alexander Rose
0e326bd4de Merge pull request #20 from arose/upscaling
[WIP] added various props
2023-09-30 13:19:48 -07:00
Alexander Rose
c1de7df9fb tweak graphics mode 2023-09-30 13:18:49 -07:00
Alexander Rose
3addc567a9 changelog 2023-09-30 12:10:15 -07:00
Alexander Rose
4d2d127dcc Merge branch 'master' of https://github.com/molstar/molstar into upscaling 2023-09-30 12:09:51 -07:00
Alexander Rose
fc44e66b26 3.40.1 2023-09-30 10:53:23 -07:00
Alexander Rose
98f3f5a23b changelog 2023-09-30 10:50:23 -07:00
Alexander Rose
f2f10d0cb5 3.40.0 2023-09-30 10:47:30 -07:00
Alexander Rose
aed1056d6c Merge pull request #932 from molstar/sharpening
Sharpening
2023-09-30 10:42:59 -07:00
Alexander Rose
be47ac09c9 schema updates 2023-09-30 10:37:27 -07:00
Alexander Rose
d5e7797a40 package updates 2023-09-30 10:33:44 -07:00
Alexander Rose
0aeac628c7 Merge pull request #929 from molstar/better-bounding-spheres
fix bounding sphere calculations for "element-like" visuals
2023-09-30 10:21:03 -07:00
Alexander Rose
668d617cd7 Merge branch 'master' into better-bounding-spheres 2023-09-30 10:20:46 -07:00
Alexander Rose
62ed993f0d changelog 2023-09-30 10:19:23 -07:00
Alexander Rose
aa0a008a41 gracefully handle missing HTMLImageElement 2023-09-30 10:02:12 -07:00
Alexander Rose
89f01f202d add sharpening postprocessing pass 2023-09-30 09:39:15 -07:00
Alexander Rose
733190f7a0 scale outline by pixelRatio 2023-09-30 09:38:44 -07:00
Alexander Rose
50429aacfa fix setSize not always applied to passes 2023-09-30 09:37:58 -07:00
David Sehnal
fa541bdbd3 hide right panel (#922) 2023-09-29 14:51:48 +02:00
Sebastian Bittrich
77d173afed Update RCSB PDB validation report URL (#930)
* update RCSB PDB valrep URL

* cl

* https & more tweaks
2023-09-29 14:51:07 +02:00
Alexander Rose
1415a21c09 fix spec 2023-09-28 22:17:04 -07:00
Alexander Rose
dd2593475a refactor transparency graphics variant handling 2023-09-28 21:55:28 -07:00
dsehnal
a934001ae8 fix bounding sphere calculations 2023-09-28 18:18:48 +02:00
midlik
e5d4606437 Add blockIndex parameter to TrajectoryFromMmCif (#928) 2023-09-28 16:45:19 +02:00
Alexander Rose
350dddd83a wip, add canvas3dcontext props
- transparency (replacing enableWboit/enableDpoit)
- add canvas3dcontext to snapshot
2023-09-27 22:19:36 -07:00
Alexander Rose
7566dd4e9a wip, add canvas3dcontext props
- pixelScale
- pickScale
- move pickPadding to canvas3d props
2023-09-26 21:48:54 -07:00
Alexander Rose
cb04c74c60 wip, support changing pixel-scale 2023-09-24 22:02:45 -07:00
Alexander Rose
ad937f8fe5 make hi-Z min-level a parameter 2023-09-24 22:02:00 -07:00
Alexander Rose
66017cf166 top/bottom level instance-grid 2023-09-24 22:01:19 -07:00
Alexander Rose
898f0d6462 add sharpening postprocessing 2023-09-16 22:15:37 -07:00
Alexander Rose
cf90418f89 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-09-16 22:06:30 -07:00
Alexander Rose
fb16cd0070 typo 2023-09-16 22:06:08 -07:00
Alexander Rose
5ca126be94 also call hiZ.render with multiSample enabled 2023-09-16 22:02:48 -07:00
Alexander Rose
7a8de81e6f me: improve lod transparency with alphaThickness 2023-09-16 22:01:23 -07:00
Alexander Rose
143760f7db Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-09-16 21:50:17 -07:00
Alexander Rose
c427549b8d add support for webgl extensions
- EXT_conservative_depth
- WEBGL_stencil_texturing
- EXT_clip_control
2023-09-16 21:47:08 -07:00
Alexander Rose
310300bde8 add alphaThickness parameter for spheres 2023-09-16 12:13:34 -07:00
Alexander Rose
11604b9e8f add MultiSampleParams.reduceFlicker 2023-09-16 12:08:38 -07:00
Alexander Rose
973b326157 hi-z: support non-canvas viewports 2023-09-10 14:35:26 -07:00
Alexander Rose
c37c63d990 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-09-09 23:28:46 -07:00
Alexander Rose
cc1bf482f2 add support for WEBGL_clip_cull_distance 2023-09-09 12:27:22 -07:00
Alexander Rose
61a351b3d4 Merge pull request #906 from JonStargaryen/atomcount
ModelServer ligand queries: fix atom count reported by SDF/MOL/MOL2 export
2023-09-09 10:58:06 -07:00
Alexander Rose
9e91a242bf Merge branch 'master' into atomcount 2023-09-09 10:57:53 -07:00
Alexander Rose
c3daa1a162 Merge pull request #907 from JonStargaryen/ccd-aromatic
CCD extension: Make visuals for aromatic bonds configurable
2023-09-09 10:56:35 -07:00
Alexander Rose
fe086fb62e Merge branch 'master' into ccd-aromatic 2023-09-09 10:56:23 -07:00
Alexander Rose
c2217829a3 improve Canvas3DContext types 2023-09-09 10:54:14 -07:00
Alexander Rose
f30686917f hi-z improvements
- support orthographic camera
- check near plane
- math optimizations
- debug support
- disable for non-canvas viewports
- support pixelScale
2023-09-09 10:51:37 -07:00
Alexander Rose
7b18545e1d improve Canvas3DContext types 2023-09-09 10:46:45 -07:00
David Sehnal
6333c8073f Add optional CifFile to MmcifFormat.data (#912)
* Add optional CifFile to MmcifFormat.data

* fix
2023-09-08 19:24:18 +02:00
Sebastian Bittrich
2801bcf111 CCD extension: Make visuals for aromatic bonds configurable 2023-09-06 15:20:45 -07:00
Sebastian Bittrich
8a2461e157 global cl 2023-09-05 14:09:21 -07:00
Sebastian Bittrich
0a081e2a8a ModelServer version 2023-09-05 14:07:19 -07:00
Sebastian Bittrich
700a3fe95c ligand queries: fix atom count reported by SDF/MOL/MOL2 export 2023-09-05 14:06:07 -07:00
dsehnal
febc634d8b fix changelog 2023-09-05 18:49:52 +02:00
David Sehnal
0105f75bb6 InputObserver tap => click (#901) 2023-09-05 18:47:52 +02:00
David Sehnal
4cc2073eaa fix updateFocusRepr (#903) 2023-09-05 06:01:47 +02:00
Alexander Rose
b657fa15f5 add stats.culled 2023-09-04 20:46:12 -07:00
Alexander Rose
72446d06ed disable hi-Z for orthogrpahic camera 2023-09-02 20:56:02 -07:00
Alexander Rose
dd7499b7f6 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-09-02 12:19:31 -07:00
Alexander Rose
9ac204cb6e 3.39.0 2023-09-02 12:16:11 -07:00
Alexander Rose
73378bbe9d schema updates 2023-09-02 12:08:52 -07:00
Alexander Rose
9b5fd2595c package updates 2023-09-02 12:06:21 -07:00
Alexander Rose
bca2073ed0 changelog 2023-09-02 11:34:26 -07:00
Alexander Rose
f264e4d6b8 webgl tweaks
- export getBuffer
- add glEnumToString helper
2023-09-02 11:30:09 -07:00
Alexander Rose
c86c00c958 hi-z improvements
- clear hi-z buffer on scene & size changes
- check red/float reading support
- add max frame lag
- mark  hi-z buffer as ready for use
2023-09-02 11:27:49 -07:00
Alexander Rose
bbce807d5e webgl tweaks
- export getBuffer
- add glEnumToString helper
2023-09-02 11:11:05 -07:00
Sebastian Bittrich
795222b5b4 Allow toggling of hydrogens as part of LabelTextVisual (#900)
* Allow toggling of hydrogens as part of `LabelTextVisual`

* fix typo
2023-08-30 10:51:51 +02:00
Alexander Rose
25eb4450ad support iv2/3/4 uniforms 2023-08-27 12:08:57 -07:00
Alexander Rose
140df13dae improve texture print helper 2023-08-27 12:08:39 -07:00
Alexander Rose
8b132d599e add hi-z occlusion culling 2023-08-27 12:04:48 -07:00
Alexander Rose
cbaf5129f9 tweak graphics mode props 2023-08-27 11:45:37 -07:00
Alexander Rose
117e2b2a1a improve texture print helper 2023-08-27 09:56:04 -07:00
Alexander Rose
301b9287ea improve MultiDrawBaseData setup/update 2023-08-27 09:52:37 -07:00
Alexander Rose
81a738c6a6 support iv2/3/4 uniforms 2023-08-27 09:50:26 -07:00
Alexander Rose
f4d15a5a31 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-08-27 09:43:21 -07:00
Alexander Rose
792cd513a8 faster bounding rectangle for imposter spheres 2023-08-27 09:40:52 -07:00
Shinn
14e6172c33 add some elements name for guessElementSymbolString function (#883) 2023-08-03 16:25:47 +02:00
Alexander Rose
17e278cefb Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-07-30 13:10:19 -07:00
Alexander Rose
911433e056 3.38.3 2023-07-29 23:10:31 -07:00
Alexander Rose
6b585cf0d6 fix imposter spheres not updating 2023-07-29 23:07:18 -07:00
Alexander Rose
cc737192ca Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-07-09 20:49:34 -07:00
Alexander Rose
11aeb6daf4 improve handling of cellpack membrane models 2023-07-09 18:44:36 -07:00
Alexander Rose
daec7e31ff add structure-element-sphere visual 2023-07-09 18:44:09 -07:00
Alexander Rose
c98cdc9360 improve cellpack mmcif detection 2023-07-09 18:42:17 -07:00
Alexander Rose
250013570d tweak graphics-mode handling 2023-07-08 11:56:26 -07:00
Alexander Rose
35739ab71b handle petworld using asymId for multiple entities 2023-07-04 19:48:32 -07:00
Alexander Rose
bb765968f1 fix missing uFog uniform 2023-07-03 23:15:57 -07:00
Alexander Rose
eb25d66359 add graphics mode to me app 2023-07-03 22:56:41 -07:00
Alexander Rose
3db28708c3 add option to approximate sphere impostors 2023-07-03 22:54:50 -07:00
Alexander Rose
4fb18b9afd properly switch-off fog 2023-07-03 22:53:28 -07:00
Alexander Rose
28693177cd more unit merging; split petworld membrane 2023-07-01 10:50:50 -07:00
Alexander Rose
cd2ac77469 merge non-instanced units in mmcif loader 2023-06-25 19:29:34 -07:00
Alexander Rose
8777f907ee reduce Spheres memory usage
- derive mapping from VertexID
- pull position and group from texture
2023-06-24 15:17:59 -07:00
Alexander Rose
5a6ac41b28 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-06-21 21:03:53 -07:00
Alexander Rose
2ac34f96f5 fix cellpack cif detection 2023-06-19 22:17:23 -07:00
Alexander Rose
de5d60f4d2 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-06-18 16:24:37 -07:00
Alexander Rose
72e3a31750 fix issues with wboit/dpoit in large scenes
- remove unneeded depth check (depth texture support required for wboit/dpoit)
2023-06-18 16:07:10 -07:00
Alexander Rose
5bcb0b19e5 support more structure formats 2023-06-18 12:56:52 -07:00
Alexander Rose
0277581684 generic loader improvements 2023-06-17 23:53:03 -07:00
Alexander Rose
d508988ba4 use inverted-xray 2023-06-17 00:17:03 -07:00
Alexander Rose
7352799270 Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into generic-loader 2023-06-16 23:57:26 -07:00
Alexander Rose
7b524dda7c Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-06-16 23:56:47 -07:00
Alexander Rose
86dcf22728 ux improvements, mmcif support 2023-06-11 10:51:55 -07:00
Alexander Rose
f47823577d Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into generic-loader 2023-06-07 23:13:13 -07:00
Alexander Rose
671a982463 get entities/groups tweaks 2023-06-07 23:12:36 -07:00
Alexander Rose
4244c6f5e1 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-06-07 22:04:45 -07:00
Alexander Rose
b00b0827c8 fix circular deps 2023-06-05 21:33:19 -07:00
Alexander Rose
fe4df71d02 wip, generic loader
- zip with json manifest
2023-06-05 00:08:15 -07:00
Alexander Rose
8f03e07632 tweak clip props handling
- don't force instance variant
2023-06-04 18:35:31 -07:00
Alexander Rose
5958b31a6d add Euler math primitive 2023-06-04 18:15:12 -07:00
Alexander Rose
ace991da5d optimize select/highlight
- highlight whole repr when possible
- make cellpack entity structures their own roots
2023-05-29 23:21:33 -07:00
Alexander Rose
5e9bc89c75 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-05-29 15:35:34 -07:00
Alexander Rose
8d0c80f270 various tweaks
- add MesoSelectLoci behavior
- add MesoscaleState transform
- show groupBy only if there is more than one option
- make groupBy and filter state-savable
2023-05-29 15:00:38 -07:00
Alexander Rose
2ae36e181d add keyReleased event 2023-05-29 14:57:26 -07:00
Alexander Rose
52692a405d ignore LociLabelManager without providers 2023-05-29 14:57:08 -07:00
Alexander Rose
0a91a35cf3 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-05-28 12:37:46 -07:00
Alexander Rose
103b0dcebd Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-05-27 13:26:03 -07:00
giagitom
5934f355c2 Fixes and added changelog entry 2023-05-22 17:02:25 +02:00
giagitom
225d051dd6 Merge branch 'master' into 2-colors-cylinders 2023-05-22 16:50:59 +02:00
Alexander Rose
fae4aae99f update immer to v10 (#819) 2023-05-20 17:56:11 -07:00
Alexander Rose
6acc87abc6 change build target to ES6 2023-05-20 17:55:05 -07:00
Alexander Rose
af489e8cc5 fix interactivity granularity 2023-05-14 22:49:57 -07:00
Alexander Rose
a15f2ed456 mesoscale state cleanup 2023-05-14 22:21:50 -07:00
Alexander Rose
9c921fd061 adjust lod-level stride by sizeFactor 2023-05-14 22:21:27 -07:00
Alexander Rose
e719c54e2b Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-05-14 11:59:27 -07:00
Alexander Rose
18c4de4c0f Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-05-07 20:43:13 -07:00
Alexander Rose
a12820bab9 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-04-30 21:11:27 -07:00
giagitom
c59ae908b8 Implement 2 colors interpolation on impostor cylinders 2023-04-26 15:24:15 +02:00
Alexander Rose
de5b7f31f9 add custom sizeFactor for some cellpack entities 2023-04-25 22:21:18 -07:00
Alexander Rose
e264abb57c prevent dragging of snapshot images 2023-04-25 21:03:50 -07:00
Alexander Rose
46a84799b1 no componentManager in state 2023-04-25 20:58:02 -07:00
Alexander Rose
af451db432 group-by support for entities 2023-04-22 21:53:34 -07:00
Alexander Rose
5ac435c6d1 only calculate instance-grid if there are instances 2023-04-16 12:54:09 -07:00
Alexander Rose
aab3759da2 fix file-info use 2023-04-16 12:46:52 -07:00
Alexander Rose
db1174bd32 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-04-16 12:23:07 -07:00
Alexander Rose
7a3fc2047e Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-04-08 13:31:51 -07:00
Alexander Rose
f2e5c8a30f Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-04-02 12:54:04 -07:00
Alexander Rose
34b9a46f2d add camera controls behavior 2023-04-02 12:39:56 -07:00
Alexander Rose
95d84b9dbe Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-04-01 23:36:17 -07:00
Alexander Rose
1f78994ac0 changelog 2023-04-01 23:36:07 -07:00
Alexander Rose
e5e02e966f Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-04-01 11:47:30 -07:00
Alexander Rose
e1a5ad16f1 ssao improvements
- make bias a global param
- add a 4th level by default
2023-03-22 20:07:30 -07:00
Alexander Rose
512ec4f8ea Merge pull request #16 from arose/ssao-levels
[DRAFT] ssao levels
2023-03-21 23:31:58 -07:00
Alexander Rose
bc46b07ee1 refactor ssao levels as multi-scale option 2023-03-21 23:06:23 -07:00
Alexander Rose
f750f1581e Merge branch 'master' of https://github.com/molstar/molstar into ssao-levels 2023-03-21 21:11:13 -07:00
Alexander Rose
e24dc0a680 handle cellpack missing asymid 2023-03-18 10:46:41 -07:00
Alexander Rose
94cd5abf30 add lod-levels-preset param 2023-03-05 21:13:09 -08:00
Alexander Rose
cec33fa6a7 add support for examples 2023-03-04 10:37:02 -08:00
Alexander Rose
7b14a45582 skip ssao level when too close wrt to radius 2023-02-26 21:44:47 -08:00
Alexander Rose
f0efffceaa half/quarter res depth for distant ssao samples 2023-02-26 19:08:57 -08:00
Alexander Rose
8b37a9509f tweak ssao-blur 2023-02-26 19:07:35 -08:00
Alexander Rose
de61956bf7 wip, ssao teaks 2023-02-25 21:40:58 -08:00
Alexander Rose
c1bc8fc262 adjust lod preset 2023-02-25 15:31:22 -08:00
Alexander Rose
797e6a5318 Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into ssao-levels 2023-02-25 15:25:06 -08:00
Alexander Rose
bd41f45370 fix adjustPluginProps 2023-02-25 15:15:49 -08:00
Alexander Rose
7946b7403c Merge pull request #18 from arose/lod-radius-scale
Lod radius scale
2023-02-25 15:12:23 -08:00
Alexander Rose
eccf6e21e0 Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into lod-radius-scale 2023-02-25 15:09:01 -08:00
Alexander Rose
41c40da6c7 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-02-25 15:04:49 -08:00
Alexander Rose
25a4c18dce Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into ssao-levels 2023-02-18 14:04:40 -08:00
Alexander Rose
0fe4eda8ae shape, ensure lodLevels update 2023-02-18 12:47:04 -08:00
Alexander Rose
e244a17b57 add cellSize to LodParams for testing 2023-02-18 11:25:31 -08:00
Alexander Rose
7b61d78a9e ensure lodLevels update 2023-02-18 11:24:39 -08:00
Alexander Rose
b5540c7fe9 Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into lod-radius-scale 2023-02-18 10:24:48 -08:00
Alexander Rose
1c48509a17 consolidate cellpack & petworld ext into meso app
- remove petworld & cellpack ext for now
- automatically detect cif type
2023-02-18 10:20:00 -08:00
Alexander Rose
bf08b24991 increase minNear to reduce banding 2023-02-12 21:27:13 -08:00
Alexander Rose
0151ee1387 tweak lodLevel presets 2023-02-12 20:56:13 -08:00
Alexander Rose
eb6d5586cd fix near clipping avoidance in impostor shaders 2023-02-12 20:37:07 -08:00
Alexander Rose
f66f1f545e Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into lod-radius-scale 2023-02-12 11:05:54 -08:00
Alexander Rose
5a3f245ac0 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-02-12 11:05:11 -08:00
Alexander Rose
fc7af1f60c add lod to entities ui (for testing) 2023-02-11 13:23:01 -08:00
Alexander Rose
258cc1ef66 add scaleBias to lodLevels, consider element count 2023-02-11 13:14:39 -08:00
Alexander Rose
b3727f3774 fix getUnitsByEntity 2023-02-11 11:47:08 -08:00
Alexander Rose
b627d6a612 add mesoscale-explorer to webpack viewer config 2023-02-11 11:46:37 -08:00
Alexander Rose
918b83133e Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-02-11 11:45:35 -08:00
Alexander Rose
6aed941fc9 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-02-05 20:07:59 -08:00
Alexander Rose
b22e5bb7dd tweak stats.calls 2023-02-05 15:41:09 -08:00
Alexander Rose
eca70d7536 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-02-05 15:14:44 -08:00
Alexander Rose
adf8e2932f Merge pull request #17 from arose/optimize-load
Optimize cellpack assembly creation, unit bbox computation & and show commit visual progress
2023-02-05 13:31:25 -08:00
Alexander Rose
e35cde3d6b remove unused code 2023-02-05 13:07:32 -08:00
Alexander Rose
bbbb960b50 cellpack assembly transform 2023-02-05 12:35:56 -08:00
Alexander Rose
47a63e8906 Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into optimize-load 2023-02-05 11:29:34 -08:00
dsehnal
e9cbd06652 cellpack symmetry build optimization 2023-02-05 19:08:01 +01:00
Alexander Rose
97fb6c044c Merge branch 'mesoscale-explorer' of https://github.com/arose/mesoscale-explorer into ssao-levels 2023-01-29 21:49:15 -08:00
Alexander Rose
85c5575b96 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-01-29 16:41:04 -08:00
Alexander Rose
4ada0a0d29 use single channel for downsampled depth when possible 2023-01-29 15:13:08 -08:00
Alexander Rose
54714c06af fix downsampled ssao 2023-01-29 13:41:42 -08:00
Alexander Rose
d3e674b135 use fast boundary for petworld structure 2023-01-28 17:24:34 -08:00
Alexander Rose
253d872599 add fast boundary helper and unit trait 2023-01-28 17:24:00 -08:00
Alexander Rose
b8ca8a9b34 refactor cellpack/petworld hierarchy creation 2023-01-28 17:00:46 -08:00
Alexander Rose
d180c26ca1 cache entity units mapping
- fix quadratic behavior
2023-01-28 14:01:12 -08:00
Alexander Rose
b51e50978e improve calcInstanceGrid perf 2023-01-28 11:28:32 -08:00
Alexander Rose
b897cfd028 perf tweaks
- reuse vec3 in getMatrices
- fast path in createElementSphereImpostor
2023-01-28 11:23:05 -08:00
Alexander Rose
2ce8863709 tweaks 2023-01-28 10:29:08 -08:00
dsehnal
37362968d9 buildAssembly improvements for lipid layer 2023-01-24 10:38:19 +01:00
dsehnal
800393bc2b Observable canvas commit 2023-01-23 23:54:39 +01:00
dsehnal
7a111c49f4 optimize assembly creation & bbbox computation 2023-01-23 13:48:53 +01:00
Alexander Rose
756ea140e2 ssao levels 2023-01-22 20:45:03 -08:00
Alexander Rose
f9400c2547 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-01-22 14:23:30 -08:00
Alexander Rose
1a52c2bab4 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-01-22 11:50:24 -08:00
Alexander Rose
b4b79a6102 meso app, enable snapshot images 2023-01-16 13:24:07 -08:00
Alexander Rose
b9054723d7 state snapshot images 2023-01-15 22:45:32 -08:00
Alexander Rose
ed37890519 cellpack/petworld preset tweak 2023-01-15 22:42:59 -08:00
Alexander Rose
22dc6bb1ea improve instance-grid creation performance 2023-01-15 22:39:57 -08:00
Alexander Rose
0917713613 improve boundary calculation performance 2023-01-15 22:38:55 -08:00
Alexander Rose
0b6be09678 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-01-15 14:14:43 -08:00
Alexander Rose
500fdd31d7 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-01-15 10:13:42 -08:00
Alexander Rose
ca9a566c25 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-01-15 09:07:31 -08:00
Alexander Rose
8386bf2ec6 wip, mesoscale-explorer app 2023-01-14 12:24:45 -08:00
Alexander Rose
a50443589f add .getCenter and .center to Camera 2023-01-14 12:06:13 -08:00
Alexander Rose
fd74bfe9c2 add default to ParamDefinition.Values type 2023-01-14 12:03:33 -08:00
Alexander Rose
f82693103a support dim unmarked groups & marker edge strength 2023-01-14 12:02:32 -08:00
Alexander Rose
cff72bcb67 fix 2023-01-09 20:27:57 -08:00
Alexander Rose
e3326fca29 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-01-09 20:19:43 -08:00
Alexander Rose
7ccf376beb wip, explorer ui 2023-01-08 21:20:00 -08:00
Alexander Rose
8678ac301c improve checks in UnitsRepresentation setVisualState 2023-01-08 21:08:00 -08:00
Alexander Rose
dde6f10b22 defer calculation of more scene properties 2023-01-08 21:06:22 -08:00
Alexander Rose
f9f41dac6a make SymmetryOperator.createMapping monomorphic 2023-01-08 21:05:10 -08:00
Alexander Rose
af6ab0ec9e tweak BoundaryHelper performance 2023-01-08 20:56:20 -08:00
Alexander Rose
485e91796b improve calculateInvariantBoundingSphere 2023-01-08 20:53:59 -08:00
Alexander Rose
2451ad3f0a add repr/theme registry .clear method 2023-01-08 20:49:23 -08:00
Alexander Rose
b0125d139a Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2023-01-08 20:48:29 -08:00
Alexander Rose
d0069b9684 wip, explorer state ui 2023-01-02 16:01:42 -08:00
Alexander Rose
ed6a6f71ce Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2022-12-31 12:08:48 -08:00
Alexander Rose
a042e72f28 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2022-12-30 20:46:24 -08:00
Alexander Rose
6ac12fd457 wip, mesoscale explorer app 2022-12-29 21:49:14 -08:00
Alexander Rose
0abedba665 avoid cloning of structure repr params 2022-12-29 11:42:59 -08:00
Alexander Rose
8bc688a491 improve structure/unit areEqual methods
- always equal when objects are identical
2022-12-29 10:20:36 -08:00
Alexander Rose
46524b19ab only update clip defines if changed 2022-12-29 10:17:32 -08:00
Alexander Rose
a9bcd4c1c6 add isDisabled to ParameterMappingControl 2022-12-29 10:16:52 -08:00
Alexander Rose
a355be9c0f add input-color picker 2022-12-27 12:02:22 -08:00
Alexander Rose
4b663baa88 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2022-12-22 21:39:20 -08:00
Alexander Rose
22402280a7 wip, mesoscale explorer app 2022-12-21 20:13:02 -08:00
Alexander Rose
7e9872871d support tags in StructureBuilder.createStructure 2022-12-21 20:12:09 -08:00
Alexander Rose
6c595377a3 add escapeRegExp util 2022-12-21 20:11:14 -08:00
Alexander Rose
56ed5a784d Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2022-12-19 16:19:59 -08:00
Alexander Rose
fbb9f09536 tweak interior in presets 2022-12-17 21:25:38 -08:00
Alexander Rose
d8e5b9a5c6 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2022-12-17 21:18:44 -08:00
Alexander Rose
b067b0eed5 Merge branch 'master' of https://github.com/molstar/molstar into mesoscale-explorer 2022-12-04 12:20:32 -08:00
Alexander Rose
d6dffe89d6 Merge pull request #1 from arose/lod-cull
Lod cull
2022-12-03 21:12:53 -08:00
Alexander Rose
0107159b84 changelog 2022-12-03 18:21:50 -08:00
Alexander Rose
c106b99d5d fix renderer spec 2022-12-03 18:18:10 -08:00
Alexander Rose
2f695ca68f changelog 2022-12-03 17:40:57 -08:00
Alexander Rose
461592f6e3 add shadow to petworld preset 2022-12-03 17:31:53 -08:00
Alexander Rose
ccf3b5c75d Merge branch 'master' of https://github.com/molstar/molstar into lod-cull 2022-12-03 16:24:48 -08:00
Alexander Rose
a40702cda4 compat docs 2022-12-03 12:19:26 -08:00
Alexander Rose
a5ad456b52 Merge branch 'master' of https://github.com/molstar/molstar into lod-cull 2022-11-28 11:45:06 -08:00
Alexander Rose
357243c717 set marking off by default for mesoscale loaders 2022-11-13 22:39:00 -08:00
Alexander Rose
23f1d57064 Merge branch 'master' of https://github.com/molstar/molstar into lod-cull 2022-11-13 21:21:22 -08:00
Alexander Rose
a56a2500d4 improve cellpack cif support 2022-11-12 20:27:34 -08:00
Alexander Rose
4950bb9e0a various lod tweaks & fixes 2022-11-12 16:25:24 -08:00
Alexander Rose
7fc409a8d1 cellpack & petworld cif loader improvements 2022-11-12 10:16:14 -08:00
Alexander Rose
f52872718c emulate gl_DrawID as needed 2022-11-12 09:44:08 -08:00
Alexander Rose
abca6e3bf7 share vertex-array between program variants 2022-11-12 09:32:20 -08:00
Alexander Rose
4fedef90c7 adjust radius for lod in sphere shader 2022-11-08 21:44:02 -08:00
Alexander Rose
69ca72ff86 rename MultiDrawInstancedData to MultiDrawBaseData 2022-11-08 21:42:30 -08:00
Alexander Rose
542a4725ad add elements-based lod levels for spheres 2022-11-06 15:14:14 -08:00
Alexander Rose
5d8bd6f474 support rendering elements-based lod levels 2022-11-06 15:13:32 -08:00
Alexander Rose
d8b98cc39d add instance-grid culling support 2022-11-06 15:00:05 -08:00
Alexander Rose
1d558c32d5 add per-object lod support 2022-11-06 13:13:32 -08:00
Alexander Rose
72c560e5a2 fix missing material texture disposal 2022-11-06 12:54:25 -08:00
Alexander Rose
268246e1ef cleanup types 2022-11-06 12:51:19 -08:00
Alexander Rose
8957f8a55f ignore built-ins in checkActiveUniforms 2022-11-06 12:50:14 -08:00
Alexander Rose
cdf7a1dfe8 add InstanceGrid to group instances spatially 2022-11-06 12:49:57 -08:00
Alexander Rose
398d82f359 frustum culling of whole render-objects 2022-11-06 12:43:53 -08:00
Alexander Rose
11fb1b655f support changing offset of gl attributes 2022-11-06 12:38:31 -08:00
Alexander Rose
5032a2538a add Plane3D and Frustum3D math objects 2022-11-06 12:31:24 -08:00
Alexander Rose
cabc0e5344 remove multi draw emulation from compat
- can't emulate gl_DrawID there
2022-11-05 11:12:55 -07:00
Alexander Rose
fe1414dd6b improve typing of various toArray functions 2022-11-05 09:00:59 -07:00
Alexander Rose
13cee09a1c fix multi draw webgl extension handling
- no gl_DrawID when emulated
- add missing .bind(gl/ext)
- use typed arrays in signatures
2022-10-30 00:18:16 -07:00
Alexander Rose
eb5c6bd30a add support for more webgl extensions
- WEBGL_draw_instanced_base_vertex_base_instance
- WEBGL_multi_draw_instanced_base_vertex_base_instance
2022-10-27 21:29:58 -07:00
Alexander Rose
6f5e6f2fe2 improve Vec3 and Box3D
- add Vec3.clamp
- add Vec3.transformDirection
- add Box3D.addSphere3D
- add Box3D.intersectsSphere3D
- add Box3D.containsSphere3D
2022-10-26 22:32:23 -07:00
Alexander Rose
fc8c932874 add stride param to element sphere/point visual 2022-10-26 22:10:32 -07:00
Alexander Rose
784523e635 misc tweaks 2022-10-26 22:09:01 -07:00
Alexander Rose
56db418949 improve canvas3d consoleStats 2022-10-26 22:02:30 -07:00
Alexander Rose
31d6e81a59 add basic petworld extension 2022-10-26 22:01:34 -07:00
Alexander Rose
6356628c80 add support for webgl multiDraw extension 2022-10-26 21:58:51 -07:00
412 changed files with 28584 additions and 28225 deletions

View File

@@ -55,7 +55,8 @@
"block-spacing": "error",
"keyword-spacing": "off",
"space-before-blocks": "error",
"semi-spacing": "error"
"semi-spacing": "error",
"no-constant-binary-expression": "error"
},
"overrides": [
{

View File

@@ -7,4 +7,5 @@
- [ ] Added description of changes to the `[Unreleased]` section of `CHANGELOG.md`
- [ ] Updated headers of modified files
- [ ] Added my name to `package.json`'s `contributors`
- [ ] Added my name to `package.json`'s `contributors`
- [ ] (Optional but encouraged) Improved documentation in `docs`

62
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Build & Deploy Docs
on:
push:
branches: master
paths:
- docs/**
- ".github/workflows/docs.yml"
pull_request:
branches: master
paths:
- docs/**
- ".github/workflows/docs.yml"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 👨🏼‍💻 checkout
uses: actions/checkout@v4
- name: 🐍 python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: ⛓️ dependencies
run: |
pip install mkdocs-material
- name: 🔧 build site
run: |
cd docs
mkdocs build
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/master'
steps:
- name: 👨🏼‍💻 checkout
uses: actions/checkout@v4
- name: 🐍 python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: ⛓️ dependencies
run: |
pip install mkdocs-material
- name: 🔧 build site
run: |
cd docs
mkdocs build
- name: 🚢 deploy docs
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
external_repository: molstar/docs
publish_branch: gh-pages
publish_dir: ./docs/site

View File

@@ -2,7 +2,9 @@ name: Build
on:
push:
branches: master
pull_request:
branches: master
jobs:
build:
@@ -11,7 +13,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16
node-version: 18
- run: npm ci
- run: sudo apt-get install xvfb
- name: Lint

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
build/
lib/
docs/site/
node_modules/
debug.log

View File

@@ -3,9 +3,186 @@ All notable changes to this project will be documented in this file, following t
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
## [Unreleased]
## [v4.2.0] - 2023-04-05
- Add emissive material support
- Add bloom post-processing
- MolViewSpec extension: `loadMVS` supports `keepCamera` parameter
- Return StateTransform selectors from measurements API (addDistance, addAngle, etc.)
- Refactor transparency rendering
- More uniform behavior for blended, wboit, dpoit
- Fix issues with text & image geometry
- Fix render-spheres example (#1100)
- Wrong step size in sphere geometry boundingSphere & groupmapping
- Handle empty `instanceGrid` in renderer & renderable
- Fix bond assignment from `IndexPairBonds`
- Can not always be cached in `ElementSetIntraBondCache`
- Wrong operator checks in `findPairBonds`
- Fix SSAO artifacts (@corredD, #1082)
- Fix bumpiness artifacts (#1107, #1084)
## [v4.1.0] - 2023-03-31
- Add `VolumeTransform` to translate/rotate a volume like in a structure superposition
- Fix BinaryCIF encoder edge cases caused by re-encoding an existing BinaryCIF file
- Fix edge-case where width/height in InputObserver are not correct
- Fix transparency rendering fallback (#1058)
- Fix SSAO broken when `OES_texture_float_linear` is unavailable
- Add `normalOffset` to `external-volume` color theme
- This can give results similar to pymol's surface_ramp_above_mode=1
- Add `rotation` parameter to skybox background
## [v4.0.1] - 2023-02-19
- Fix BinaryCIF decoder edge cases. Fixes mmCIF model export from data provided by ModelServer.
- MolViewSpec extension: support for MVSX file format
- Revert "require WEBGL_depth_texture extension" & "remove renderbuffer use"
## [v4.0.0] - 2023-02-04
- Add Mesoscale Explorer app for investigating large systems
- [Breaking] Remove `cellpack` extension (superseded by Mesoscale Explorer app)
- [Breaking] Set minimal node.js version to 18
- [Breaking] Generalize rcsb/assembly-symmetry/ extension
- Move to assembly-symmetry/
- Remove RCSB specific dependencies and prefixes
- [Breaking] Require `WEBGL_depth_texture` webgl extension
- Remove `renderbuffer` use
- [Breaking] Change build target to ES2018
- Custom builds only require ES6 for dependencies like immer.js
- [Breaking] Changed `createPluginUI`
- The function now takes a single `options` argument
- The caller must specify a `render` method that mounts the Mol* react component to DOM
- A default `renderReact18` method is provided, but needs to be imported separately
- To support React 16 and 17, `ReactDOM.render` can be passed
- Improve `SetUtils` performance using ES6 features
- [Breaking] Reduce memory usage of `SymmetryOperator.ArrayMapping`
- Requires calling methods from instance
- [Breaking] Fix `mol-model/structure/model/properties/seconday-structure.ts` file name (#938)
- [Breaking] Add `Canvas3DContext` runtime props
- Props: pixelScale, pickScale, transparency (blended, wboit, dpoit)
- Replaces instantiation-time attribs
- [Breaking] Change default compile target to ES2018
- [Breaking] Add culling & LOD support
- Cull per-object and per-instance
- Cull based on frustum and camera distance
- LOD visibility based on camera distance
- Special LOD mode for spheres with automatic levels
- Occlusion culling (only WebGL2)
- Hi-Z pass
- Cull based on previous frame's Hi-Z buffer
- Add stochastic/dithered transparency to fade overlapping LODs in and out
- Add "Automatic Detail" preset that shows surface/cartoon/ball & stick based on camera distance
## [v3.45.0] - 2023-02-03
- Add color interpolation to impostor cylinders
- MolViewSpec components are applicable only when the model has been loaded from MolViewSpec
- Add `snapshotKey` and `tooltip` params to loci `LabelRepresentation`
- Update `FocusLoci` behavior to support `snapshotKey` param
- Clicking a visual with `snapshotKey` will trigger that snapshot
- Render multiline loci label tooltips as Markdown
- `ParamDefinition.Text` updates:
- Support `multiline` inputs
- Support `placeholder` parameter
- Support `disableInteractiveUpdates` to only trigger updates once the control loses focus
- Move dependencies related to the headless context from optional deps to optional peer deps
## [v3.44.0] - 2023-01-06
- Add new `cartoon` visuals to support atomic nucleotide base with sugar
- Add `thicknessFactor` to `cartoon` representation for scaling nucleotide block/ring/atomic-fill visuals
- Use bonds from `_struct_conn` in mmCIF files that use `label_seq_id`
- Fix measurement label `offsetZ` default: not needed when `scaleByRadius` is enbaled
- Support for label rendering in HeadlessPluginContext
- MolViewSpec extension
- Support all X11 colors
- Support relative URIs
- CLI tools: mvs-validate, mvs-render, mvs-print-schema
- Labels applied in one node
- ModelServer SDF/MOL2 ligand export: fix atom indices when additional atoms are present
- Avoid showing (and calculating) inter-unit bonds for huge structures
- Fixed `DragOverlay` on WebKit/Safari browsers
## [v3.43.1] - 2023-12-04
- Fix `react-markdown` dependency
## [v3.43.0] - 2023-12-02
- Fix `State.tryGetCellData` (return type & data check)
- Don't change camera.target unless flyMode or pointerLock are enabled
- Handle empty CIF files
- Snapshot improvements:
- Add `key` property
- Ability to existing snapshot name, key, and description
- Support markdown in descriptions (ignores all HTML tags)
- Ability to link to snapshots by key from descriptions
- Separate UI control showing description of the current snapshot
- Do not activate drag overlay for non-file content
- Add `structure-element-sphere` visual to `spacefill` representation
- Fix missing `await` in `HeadlessPluginContext.saveStateSnapshot`
- Added support for providing custom sequence viewers to the plugin spec
- MolViewSpec extension (MVS)
- Add URL parameters `mvs-url`, `mvs-data`, `mvs-format`
- Add drag&drop for `.mvsj` files
- Fix `bumpiness` scaling with `ignoreLight` enabled
- Add `transforms` & `label` params to `ShapeFromPly`
- Optimize `LociSelectManager.selectOnly` to avoid superfluous loci set operations
- Dispose of viewer on `unload` event to aid GC
## [v3.42.0] - 2023-11-05
- Fix handling of PDB files with insertion codes (#945)
- Fix de-/saturate of colors with no hue
- Improve `distinctColors` function
- Add `sort` and `sampleCountFactor` parameters
- Fix clustering issues
- Add `clipPrimitive` option to spheres geometry, clipping whole spheres instead of cutting them
- Add `DragAndDropManager`
- Add `options` support for default bond labels
## [v3.41.0] - 2023-10-15
- Add `PluginContext.initialized` promise & support for it in the `Plugin` UI component.
- Fix undesired interaction between settings panel and the panel on the right.
- Add ability to customize server parameters for `RCSBAssemblySymmetry`.
## [v3.40.1] - 2023-09-30
- Do not call `updateFocusRepr` if default `StructureFocusRepresentation` isn't present.
- Treat "tap" as a click in `InputObserver`
- ModelServer ligand queries: fix atom count reported by SDF/MOL/MOL2 export
- CCD extension: Make visuals for aromatic bonds configurable
- Add optional `file?: CifFile` to `MmcifFormat.data`
- Add support for webgl extensions
- `WEBGL_clip_cull_distance`
- `EXT_conservative_depth`
- `WEBGL_stencil_texturing`
- `EXT_clip_control`
- Add `MultiSampleParams.reduceFlicker` (to be able to switch it off)
- Add `alphaThickness` parameter to adjust alpha of spheres for radius
- Ability to hide "right" panel from simplified viewport controls
- Add `blockIndex` parameter to TrajectoryFromMmCif
- Fix bounding sphere calculation for "element-like" visuals
- Fix RCSB PDB validation report URL
- Add sharpening postprocessing option
- Take pixel-ratio into account for outline scale
- Gracefully handle missing HTMLImageElement
- Fix pixel-ratio changes not applied to all render passes
## [v3.39.0] - 2023-09-02
- Add some elements support for `guessElementSymbolString` function
- Faster bounding rectangle calculation for imposter spheres
- Allow toggling of hydrogens as part of `LabelTextVisual`
## [v3.38.3] - 2023-07-29
- Fix imposter spheres not updating, e.g. in trajectories (broke in v3.38.0)
## [v3.38.2] - 2023-07-24
- Don't rely solely on `chem_comp_atom` when detecting CCD files (#877)

View File

@@ -124,10 +124,6 @@ and navigate to `build/viewer`
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-saccharides.js src/mol-model/structure/model/types/saccharides.ts
**GraphQL schemas**
node node_modules/@graphql-codegen/cli/cjs/bin -c src/extensions/rcsb/graphql/codegen.yml
### Other scripts
**Create chem comp bond table**
@@ -167,13 +163,12 @@ If node complains about a missing acorn peer dependency, run the following comma
### Editor
To get syntax highlighting for shader and graphql files add the following to Visual Code's settings files and make sure relevant extensions are installed in the editor.
To get syntax highlighting for shader files add the following to Visual Code's settings files and make sure relevant extensions are installed in the editor.
"files.associations": {
"*.glsl.ts": "glsl",
"*.frag.ts": "glsl",
"*.vert.ts": "glsl",
"*.gql.ts": "graphql"
"*.vert.ts": "glsl"
},
## Publish

11
docs/README.md Normal file
View File

@@ -0,0 +1,11 @@
# Mol* Developer Documentation
Contributions to the documentations are highly welcome! Please make a pull request with your changes.
Requires Python 3.x to build. From this directory:
```
pip install mkdocs-material
mkdocs serve
```

View File

@@ -0,0 +1,41 @@
# Convert CIF to BinaryCIF
BinaryCIF is an efficient, binary flavor of the CIF format. See [specification](https://github.com/molstar/BinaryCIF) and [publication](https://doi.org/10.1371/journal.pcbi.1008247) for further details.
This script reads data in CIF format and converts it lossless to a BinaryCIF file that can be read by Mol* or other
applications.
## Example
```sh
node lib/commonjs/cli/cif2bcif/index.js file.cif file.bcif
```
## Usage
| Argument | Description |
| --- | --- |
| `src` | Source CIF to convert (can be gzipped) |
| `out` | Generated BinaryCIF output path |
| `-c` | Path to optional config file |
| `-f` | Path to optional filter file |
```sh
index.js [-h] [-c CONFIG] [-f FILTER] src out
```
### Config file
Controls how certain columns will be encoded. This is a JSON array of instructions:
```ts
interface EncodingStrategyHint {
categoryName: string,
columnName: string,
encoding: 'pack' | 'rle' | 'delta' | 'delta-rle',
precision?: number
}
```
Identify a particular CIF columns by its name and override the encoding by Integer Packing, Run-Length Encoding, Delta
Encoding, or Delta & Run-Length Encoding. You can optionally control the precision if dealing with float values.
### Filter file
Specifies which categories and columns will be written. This is a plain text file, each line represents one entry.
You can specify explicitly which categories or columns to include by adding `category_name` or
`category_name.field_name`. You can also choose to ignore some categories or columns by adding `!category_name` or
`!category_name.field_name`.

View File

@@ -0,0 +1,25 @@
# Create Table from CCD
The [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd) is as an external reference file describing
all residue and small molecule components found in PDB entries. The
[Protonation Variants Companion Dictionary (PVCD)](https://www.wwpdb.org/data/ccd) enumerates protonation variants of
canonical amino acids.
This script bundles all `chem_comp_bond` information from the CCD and the PVCD into a single file for later use.
Optionally, it can also generate a second output file that contains all `chem_comp_atom` information.
## Example
```sh
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-table.js build/data/ccb.bcif -b
```
## Usage
| Argument | Description |
| --- | --- |
| `out` | Generated file output path |
| `--forceDownload`, `-f` | Force download of CCD and PVCD |
| `--binary`, `-b` | Output as BinaryCIF |
| `--ccaOut`, `-a` | File output path of optionally generated chem_comp_atom |
```sh
create-table.js [-h] [--forceDownload] [--binary] [--ccaOut CCAOUT] out
```

View File

@@ -0,0 +1,20 @@
# Extract Ions from CCD
The [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd) is as an external reference file describing
all residue and small molecule components found in PDB entries.
This script extracts all ions from the CCD and provides their names as TypeScript set.
## Example
```sh
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-ions.js src/mol-model/structure/model/types/ions.ts
```
## Usage
| Argument | Description |
| --- | --- |
| `out` | Generated file output path |
| `--forceDownload`, `-f` | Force download of CCD |
```sh
create-ions.js [-h] [--forceDownload] out
```

View File

@@ -0,0 +1,119 @@
# Model Server
Provides access to molecular 1D, 2D, and 3D (sub-)structure models of molecules. Substructures are described by the
mol-script (MolQL) language. It has the ability to include additional data to mmCIF “on the fly”, e.g. integrate
primary PDB archival data from [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd),
[Protonation Variants Companion Dictionary (PVCD)](https://www.wwpdb.org/data/ccd) and
[Biologically Interesting moleculeReference Dictionary (BIRD)](https://www.wwpdb.org/data/bird).
## Example
```sh
node lib/commonjs/servers/model/server --sourceMap pdb-bcif '/opt/data/bcif/${id}.bcif'
```
## Usage
| Argument | Description |
| --- | --- |
| `--version`, `-v` | Show program's version number and exit. |
| `--cfg` | JSON config file path. If a property is not specified, cmd line param/OS variable/default value are used. |
| `--printCfg` | Print current config for validation and exit. |
| `--cfgTemplate` | Prints default JSON config template to be modified and exit. |
| `--apiPrefix` | Specify the prefix of the API, i.e. &lt;host&gt;/&lt;apiPrefix&gt;/&lt;API queries&gt; |
| `--defaultPort` | Specify the port the server is running on |
| `--cacheMaxSizeInBytes` | Read structures are cached, this specifies the cache size, 0 for off. |
| `--cacheEntryTimeoutMs` | Specify in ms how long to keep entries in cache. |
| `--requestTimeoutMs` | The maximum number of ms the server spends on a request. |
| `--queryTimeoutMs` | The maximum time the server dedicates to executing a query in ms. Does not include the time it takes to read and export the data. |
| `--shutdownTimeoutMinutes` | Server will shut down after this amount of minutes, 0 for off. |
| `--shutdownTimeoutVarianceMinutes` | Modifies the shutdown timer by +/- `timeoutVarianceMinutes` (to avoid multiple instances shutting at the same time) |
| `--maxQueryManyQueries` | Maximum number of queries allowed by the query-many at a time |
| `--defaultSource` | modifies which 'sourceMap' source to use by default |
| `--sourceMap` | Map `id`s for a `source` to a file path. Example: `pdb-bcif '../../data/bcif/${id}.bcif'` - JS expressions can be used inside `${}`, e.g. `${id.substr(1, 2)}/${id}.mdb` Can be specified multiple times. The `SOURCE` variable (e.g. `pdb-bcif`) is arbitrary and depends on how you plan to use the server. Supported formats: cif, bcif, cif.gz, bcif.gz |
| `--sourceMapUrl` | Same as `--sourceMap` but for URL. `--sourceMapUrl src url format` Example: `pdb-cif "https://www.ebi.ac.uk/pdbe/entry-files/download/${id}_updated.cif" cif` Supported formats: cif, bcif, cif.gz, bcif.gz |
```sh
node lib/commonjs/servers/model/server [-h] [-v]
[--cfg CFG]
[--printCfg]
[--cfgTemplate]
[--apiPrefix PREFIX]
[--defaultPort PORT]
[--cacheMaxSizeInBytes CACHE_SIZE]
[--cacheEntryTimeoutMs CACHE_TIMEOUT]
[--requestTimeoutMs REQUEST_TIMEOUT]
[--queryTimeoutMs QUERY_TIMEOUT]
[--shutdownTimeoutMinutes TIME]
[--shutdownTimeoutVarianceMinutes VARIANCE]
[--maxQueryManyQueries QUERY_MANY_LIMIT]
[--defaultSource DEFAULT_SOURCE]
[--sourceMap SOURCE PATH]
[--sourceMapUrl SOURCE PATH SOURCE_MAP_FORMAT]
```
### Production Use
In production, it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
### Memory Issues
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
### Preprocessor Example
The preprocessor application allows addiing custom data to CIF files and/or
[convert CIF to BinaryCIF](./convert-to-bcif.md).
```sh
node lib/commonjs/servers/model/preprocess
```
### Preprocessor Usage
| Argument | Description |
| --- | --- |
| `--input`, `-i` | Input filename |
| `--outCIF`, `-oc` | Output CIF filename |
| `--outBCIF`, `-ob` | Output BinaryCIF filename |
| `--cfg`, `-c` | Config file path |
| `--folderIn`, `-fin` | Convert folder |
| `--folderOutCIF`, `-foc` | Convert folder text output |
| `--folderOutBCIF`, `-fob` | Convert folder binary output |
| `--folderNumProcesses`, `-fp` | Convert folder number processes |
Example cfg.json:
```ts
{
"numProcesses": 1,
"customProperties": {
"sources": [ "wwpdb" ],
"params": {
"wwPDB": {
"chemCompBondTablePath": "./build/data/ccb.bcif"
}
}
}
}
```
### Local Mode
The server can be run in local/file based mode using
```sh
node lib/commonjs/servers/model/query
```
### Custom Properties
This feature is still in development.
It is possible to provide property descriptors that transform data to internal representation and define how it should
be exported into one or mode CIF categories. Examples of this are located in the ``mol-model-props`` module and are
linked to the server in the config and ``servers/model/properties``.
## From NPM
```
npm install --production molstar
cd ./model-server
```
(or ``node node_modules\.bin\model-server`` in Windows).
The NPM package contains all the tools mentioned in the previous sections as "binaries":
- ``model-server``
- ``model-server-query``
- ``model-server-preprocess``

View File

@@ -0,0 +1,22 @@
# Plugin State Server
Provides a simple backend for online storing and sharing of Mol* sessions used by
[``mol-plugin``](https://github.com/molstar/molstar/tree/master/src/mol-plugin) and
[``mol-state``](https://github.com/molstar/molstar/tree/master/src/mol-state) modules.
## Example
```sh
node lib/commonjs/servers/plugin-state --workding-folder ~
```
## Usage
| Argument | Description |
| --- | --- |
| `--working-folder` | Working folder path |
| `--port` | Server port. Alternatively, use ENV variable PORT. |
| `--api-prefix` | Server API prefix |
| `--max-states` | Maximum number of states to save |
```sh
node lib/commonjs/servers/plugin-state [-h] --working-folder WORKING_FOLDER [--port PORT] [--api-prefix API_PREFIX] [--max-states MAX_STATES]
```

View File

@@ -1,9 +1,9 @@
Zika Virus
==========
# VolumeServer Examples
## Zika Virus
![Zika Virus](img/zika_downsampled.png)
1TQN
====
## 1TQN
![1tqn](img/1tqn_downsampled.png)

View File

@@ -1,5 +1,4 @@
How it works
============
## VolumeServer: How it works
This document provides a high level overview of how the DensityServer works.

View File

Before

Width:  |  Height:  |  Size: 292 KiB

After

Width:  |  Height:  |  Size: 292 KiB

View File

Before

Width:  |  Height:  |  Size: 310 KiB

After

Width:  |  Height:  |  Size: 310 KiB

View File

@@ -0,0 +1,126 @@
# VolumeServer
## What is VolumeServer
Provides near-instantaneous access to volumetric data including density maps (for instance, from X-ray crystallography
or cryo-electron microscopy experiments), spatial distribution data, output from electrostatic calculations. It works by
utilizing adaptive downsampling (similar to how Google Earth works).
It uses the text based CIF and BinaryCIF formats to deliver the data to the client.
For quick info about the benefits of using the server, check out the [examples](examples.md).
## Installing and Running
Requires nodejs 8+.
### From GitHub
```
git clone https://github.com/molstar/molstar
npm install
```
Afterwards, build the project source:
```
npm run build-tsc
```
and run the server by
```
node lib/commonjs/servers/volume/server
```
### From NPM
```
npm install --production molstar
./volume-server
```
(or ``node node_modules\.bin\volume-server`` in Windows).
The NPM package contains all the tools mentioned here as "binaries":
- ``volume-server``
- ``volume-server-pack``
- ``volume-server-query``
#### Production use
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
#### Memory issues
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
### Preparing the Data
For the server to work, CCP4/MAP (models 0, 1, 2 are supported) input data need to be converted into a custom block format.
To achieve this, use the ``pack`` application (``node lib/commonjs/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
### Local Mode
The program ``lib/commonjs/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
### Navigating the Source Code
The source code is split into 2 mains parts: ``pack`` and ``server``:
- The ``pack`` part provides the means of converting CCP4 files into the internal block format.
- The ``server`` includes
- ``query``: the main part of the server that handles a query. ``execute.ts`` is the "entry point".
- ``algebra``: linear, "coordinate", and "box" algebra provides the means for calculations necessary to concent a user query into a menaningful response.
- API wrapper that handles the requests.
## Consuming the Data
The data can be consumed in any (modern) browser using the [ciftools library](https://github.com/molstar/ciftools) (or any other piece of code that can read text or binary CIF).
The [Data Format](./response-data-format.md) document gives a detailed description of the server response format.
As a reference/example of the server usage is available in Mol* ``mol-plugin`` module.
## Hosting the server
### Example
```sh
node lib/commonjs/servers/volume/server --idMap x-ray '/opt/data/xray/${id}.mdb'
```
### Usage
| Argument= | Description |
| --- | --- |
| `--cfg` | JSON config file path. If a property is not specified, cmd line param/OS variable/default value are used. |
| `--printCfg` | Print current config for validation and exit. |
| `--cfgTemplate` | Prints default JSON config template to be modified and exit. |
| `--apiPrefix` | Specify the prefix of the API, i.e. &lt;host&gt;/&lt;apiPrefix&gt;/&lt;API queries&gt; |
| `--defaultPort` | Specify the port the server is running on |
| `--shutdownTimeoutMinutes` | Server will shut down after this amount of minutes, 0 for off. |
| `--shutdownTimeoutVarianceMinutes` | Modifies the shutdown timer by +/- `timeoutVarianceMinutes` (to avoid multiple instances shutting at the same time) |
| `--idMap` | Map `id`s for a `type` to a file path. Example: `x-ray '../../data/mdb/xray/${id}-ccp4.mdb'` - JS expressions can be used inside `${}`, e.g. `${id.substr(1, 2)}/${id}.mdb` - Can be specified multiple times. - The `TYPE` variable (e.g. `x-ray`) is arbitrary and depends on how you plan to use the server. By default, Mol* Viewer uses `x-ray` and `em`, but any particular use case may vary. |
| `--maxRequestBlockCount` | Maximum number of blocks that could be read in 1 query. This is somewhat tied to the ``maxOutputSizeInVoxelCountByPrecisionLevel`` in that the `&lt;maximum number of voxel&gt; = maxRequestBlockCount * &lt;block size&gt;^3`. The default block size is 96 which corresponds to 28,311,552 voxels with 32 max blocks. |
| `--maxFractionalBoxVolume` | The maximum fractional volume of the query box (to prevent queries that are too big). |
| `--maxOutputSizeInVoxelCountByPrecisionLevel` | What is the (approximate) maximum desired size in voxel count by precision level - Rule of thumb: `&lt;response gzipped size&gt;` in `[&lt;voxel count&gt; / 8, &lt;voxel count&gt; / 4]`. The maximum number of voxels is tied to maxRequestBlockCount. |
```sh
node lib/commonjs/servers/volume/server [-h] [-v]
[--cfg CFG]
[--printCfg]
[--cfgTemplate]
[--apiPrefix PREFIX]
[--defaultPort PORT]
[--shutdownTimeoutMinutes TIME]
[--shutdownTimeoutVarianceMinutes VARIANCE]
[--idMap TYPE PATH]
[--maxRequestBlockCount COUNT]
[--maxFractionalBoxVolume VOLUME]
[--maxOutputSizeInVoxelCountByPrecisionLevel LEVEL [LEVEL ...]]
```

View File

@@ -1,10 +1,8 @@
Data Format
===========
# VolumeServer: Response Data Format
This document describes the CIF categories and fields generated by the server.
Query info
----------
## Query info
The reponse always contains a data block called ``SERVER`` with this format:
@@ -28,8 +26,7 @@ _density_server_result.query_box_b[1] 35.737
_density_server_result.query_box_b[2] 32.037001
```
Query data
----------
## Query data
If the query completed successfully with a non-empty result the response will contain one or more data blocks that correpond to the
"channels" present in the data (e.g. for x-ray data there will be ``2Fo-Fc`` and ``Fo-Fc``) channels.
@@ -41,6 +38,7 @@ data_2FO-FC
#
_volume_data_3d_info.name 2Fo-Fc
```
### Axis order
Axis order determines the order of axes of ``origin``, ``dimensions`` and ``sample_count`` fields. It also specifies

View File

@@ -0,0 +1,3 @@
# MolViewSpec
Please see the [standalone MolViewSpec documentation](https://molstar.org/mol-view-spec-docs/).

View File

@@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Replace "latest" by the specific version you want to use, e.g. "4.0.0" -->
<script src="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.js"></script>
<!-- Replace "latest" by the specific version you want to use, e.g. "4.0.0" -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.css" />
</head>
<body>
<h1>Integration of Mol* with MolViewSpec Extension</h1>
<p>
This page demonstrates several methods to integrate Mol* Viewer in a web page and use MolViewSpec functionality.
See the source HTML to see the actual code.
</p>
<h2>Method 1: Get MVS view from a server and pass to the viewer</h2>
<p>
The recommended method is to serve the MVS view files by your server (either as static files or generated by the
server on-demand) and call the <code>loadMvsFromUrl</code> method to retrieve and load them.
This example uses a MVS view file from the address specified in the <code>sourceUrl</code> variable.
If the MVS view file contains relative references, they will be resolved as relative to <code>sourceUrl</code>.
</p>
<div id="viewer1" style="position: relative; width: 500px; height: 500px;"></div>
<script>
const sourceUrl = 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1h9t_domain_labels.mvsj';
molstar.Viewer.create('viewer1', { layoutIsExpanded: false, layoutShowControls: false })
.then(viewer => viewer.loadMvsFromUrl(sourceUrl, 'mvsj'));
</script>
<p>
A variation of this method uses <code>molstar.PluginExtensions.mvs.loadMVS</code> instead of
<code>loadMvsFromUrl</code> and allows replacing the MVS view after it has been loaded.
</p>
<div id="viewer1b" style="position: relative; width: 500px; height: 500px;"></div>
<button onclick="loadView1();">View 1</button>
<button onclick="loadView2();">View 2</button>
<script>
let theViewer;
function load(viewer, url, replace) {
fetch(url)
.then(response => response.text())
.then(text => molstar.PluginExtensions.mvs.MVSData.fromMVSJ(text))
.then(mvsData => molstar.PluginExtensions.mvs.loadMVS(viewer.plugin, mvsData, { sourceUrl: url, sanityChecks: true, replaceExisting: replace }));
}
function loadView1() {
load(theViewer, 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj', true);
}
function loadView2() {
load(theViewer, 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs-focus.mvsj', true);
}
molstar.Viewer.create('viewer1b', { layoutIsExpanded: false, layoutShowControls: false })
.then(viewer => {
theViewer = viewer;
loadView1();
});
</script>
<h2>Method 2: Construct MVS view on frontend and pass to the viewer</h2>
<p>
Another option is to utilize the MVS builder provided by the extension to build the view on frontend and then
pass it to the viewer. This example builds the view in plain JavaScript, directly in a &lt;script&gt; tag in
HTML. However, for a better developer experience consider writing the code in TypeScript.
If the built MVS view contains relative references, they will be resolved as relative to the URL of this HTML
page.
</p>
<div id="viewer2" style="position: relative; width: 500px; height: 500px;"></div>
<script>
// Build an ad-hoc MVS view
const builder = molstar.PluginExtensions.mvs.MVSData.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' })
.label({ text: 'Retinoic acid' })
.focus({})
.representation({ type: 'ball_and_stick' })
.color({ color: '#cc3399' });
const mvsData = builder.getState();
// Initialize viewer and load MVSJ
const mvsj = molstar.PluginExtensions.mvs.MVSData.toMVSJ(mvsData);
molstar.Viewer.create('viewer2', { layoutIsExpanded: false, layoutShowControls: false })
.then(viewer => viewer.loadMvsData(mvsj, 'mvsj'));
// // Alternative initialization and loading (avoids encoding and again decoding the data, allows changing the view by using `replaceExisting: true`):
// molstar.Viewer.create('viewer2', { layoutIsExpanded: false, layoutShowControls: false })
// .then(viewer => molstar.PluginExtensions.mvs.loadMVS(viewer.plugin, mvsData, { sourceUrl: undefined, sanityChecks: true, replaceExisting: false }));
</script>
<p>
Again, there is variation with using <code>molstar.PluginExtensions.mvs.loadMVS</code> instead of
<code>loadMvsData</code>.
</p>
</body>
</html>

43
docs/docs/index.md Normal file
View File

@@ -0,0 +1,43 @@
# Installation
## NPM Package
```
yarn add molstar
```
or
```
npm install molstar
```
Mol* code can then be imported from the ``molstar/lib/...`` namespace, e.g.
```ts
import { PluginContext } from 'molstar/lib/mol-plugin/context';
```
## Clone from GitHub
```
git clone https://github.com/molstar/molstar.git
cd molstar
npm install
npm build
```
--------------------
For a watch task to automatically rebuild the source code on changes, run
```
npm run watch
```
or if working just with the Viewer app for better performance
```
npm run watch-viewer
```

View File

@@ -1,3 +1,5 @@
# Interesting PDB Entries
* Cyclic polymers (1sfi, 6dny, 1HVZ)
* B-DNA (1bna)
* Missing carbonyl oxygen (1gfl)
@@ -44,7 +46,6 @@
* TA1 (e.g. 1JFF) - many fused rings (incl. a 8-member rings)
* BPA (e.g. 1JDG) - many fused rings
* CLR (e.g. 3GKI) - four fused rings
Assembly symmetries
* 5M30 (Assembly 1, C3 local and pseudo)
* 1RB8 (Assembly 1, I global)
* Assembly symmetries
* 5M30 (Assembly 1, C3 local and pseudo)
* 1RB8 (Assembly 1, I global)

View File

@@ -23,7 +23,7 @@ interface Snapshot {
When defining the state object, all components are optional, i.e., it is possible to define just the ``data`` component.
Example state is available [here](example-state.json). In the plugin, it is possible to create and load these objects using ``Download JSON``
Example state is available [here](./example-state.json). In the plugin, it is possible to create and load these objects using ``Download JSON``
and ``Open JSON`` buttons in the ``State Snapshots`` section.
# State Tree
@@ -69,7 +69,7 @@ interface Transform.Props {
}
```
"Built-in" data state transforms and description of their parameters are defined in ``mol-plugin/state/transforms``. Behavior transforms are defined in ``mol-plugin/behavior``. Auto-generated documentation for the transforms is also [available](transforms.md).
"Built-in" data state transforms and description of their parameters are defined in ``mol-plugin/state/transforms``. Behavior transforms are defined in ``mol-plugin/behavior``.
# Animation State

View File

@@ -0,0 +1,3 @@
# Plugin Examples
Refer to Mol* [Apps](https://github.com/molstar/molstar/tree/master/src/apps) and [Examples](https://github.com/molstar/molstar/tree/master/src/examples).

View File

@@ -0,0 +1,274 @@
# Creating Plugin Instance
## Intro
What is a plugin? A plugin is a collection of modules that provide functionality to the `Mol*` UI. The plugin is responsible for managing the state of the viewer, internal and user interactions. It has been a previous point of confusion for new users of `Mol*` to associate the __viewer__ part of the library with what is further referred to as the __plugin__. These two are closely connected in the `molstar-plugin-ui` module, which is the user-facing part of the library and ultimately provides the viewer, but they are ultimately distinct.
It is recommended that you inspect the general class structure of [`PluginInitWrapper`](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/plugin.tsx#L41), [`PluginUIContext`](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin/context.ts#L71) and [`PluginUIComponent`](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/base.tsx#L16) to better understand the flow of data and events in the plugin.
A passing analogy is that a [ `PluginContext` ](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin/context.ts#L71) is the engine that powers computation, rendering, events and subscriptions inside the molstar UI. All UI components depend on `PluginContext`.
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.
Example usage without using WebPack:
```HTML
<style>
#app {
position: absolute;
left: 100px;
top: 100px;
width: 800px;
height: 600px;
}
</style>
<!--
molstar.js and .css are obtained from
- 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" />
<script type="text/javascript" src="./molstar.js"></script>
<div id="app"></div>
<script type="text/javascript">
molstar.Viewer.create('app', {
layoutIsExpanded: false,
layoutShowControls: false,
layoutShowRemoteState: false,
layoutShowSequence: true,
layoutShowLog: false,
layoutShowLeftPanel: true,
viewportShowExpand: true,
viewportShowSelectionMode: false,
viewportShowAnimation: false,
pdbProvider: 'rcsb',
emdbProvider: 'rcsb',
}).then(viewer => {
viewer.loadPdb('7bv2');
viewer.loadEmdb('EMD-30210', { detail: 6 });
});
</script>
```
When using WebPack (or possibly other build tool) with the Mol* NPM package installed, the viewer class can be imported using
```ts
import { Viewer } from 'molstar/build/viewer/molstar'
function initViewer(target: string | HTMLElement) {
return new Viewer(target, { /* options */})
}
```
## ``PluginContext`` with built-in React UI
- For more customization options it is possible to use the [``PluginContext``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/context.ts) directly.
- When creating the plugin instance it is possible to customize the [``PluginSpec``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/spec.ts).
- The default [``PluginSpec``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/spec.ts) is available [here](https://github.com/molstar/molstar/blob/master/src/mol-plugin/spec.ts).
- [``PluginConfig``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/config.ts) object provides additional customization options.
- See the [Viewer State Management](viewer-state.md) section for more information on customizing things like background.
- See the [Data State Management](data-state.md) section for more information on build the state.
```ts
import { DefaultPluginUISpec, PluginUISpec } from 'molstar/lib/mol-plugin-ui/spec';
import { createPluginUI } from 'molstar/lib/mol-plugin-ui';
import { renderReact18 } from 'molstar/lib/mol-plugin-ui/react18';
import { PluginConfig } from 'molstar/lib/mol-plugin/config';
const MySpec: PluginUISpec = {
...DefaultPluginUISpec(),
config: [
[PluginConfig.VolumeStreaming.Enabled, false]
]
}
async function createPlugin(parent: HTMLElement) {
const plugin = await createPluginUI({
target: parent,
spec: MySpec,
render: renderReact18
});
const data = await plugin.builders.data.download({ url: '...' }, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
return plugin;
}
createPlugin(document.getElementById('app')!); // app is a <div> element with position: relative
```
To use the plugin (with the React UI) inside another React app:
A single-plugin setup is shown the example below. In order to initialize multiple
plugins, each with its own context and viewport, some extra steps are required (docs section to be added).
```ts
import { useEffect, createRef } from "react";
import { createPluginUI } from "molstar/lib/mol-plugin-ui";
import { renderReact18 } from "molstar/lib/mol-plugin-ui/react18";
import { PluginUIContext } from "molstar/lib/mol-plugin-ui/context";
/* Might require extra configuration,
see https://webpack.js.org/loaders/sass-loader/ for example.
create-react-app should support this natively. */
import "molstar/lib/mol-plugin-ui/skin/light.scss";
declare global {
interface Window {
molstar?: PluginUIContext;
}
}
export function MolStarWrapper() {
const parent = createRef<HTMLDivElement>();
// In debug mode of react's strict mode, this code will
// be called twice in a row, which might result in unexpected behavior.
useEffect(() => {
async function init() {
window.molstar = await createPluginUI({
target: parent.current as HTMLDivElement,
render: renderReact18
});
const data = await window.molstar.builders.data.download(
{ url: "https://files.rcsb.org/download/3PTB.pdb" }, /* replace with your URL */
{ state: { isGhost: true } }
);
const trajectory =
await window.molstar.builders.structure.parseTrajectory(data, "pdb");
await window.molstar.builders.structure.hierarchy.applyPreset(
trajectory,
"default"
);
}
init();
return () => {
window.molstar?.dispose();
window.molstar = undefined;
};
}, []);
return <div ref={parent} style={{ width: 640, height: 480 }}/>;
}
```
Furthermore, if it is desirable in your project to use the `molstar`'s React UI components, but you wish to alter or rearrange the layout, you should take a look at the signatures of [ `PluginUIComponent` ](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/base.tsx#L16) which every "control" subclasses.
[ `SequenceView` ](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/sequence.tsx#L221C4-L221C4), for example, can be used separately from the `PluginUI`. Yet you would need to pass the `PluginUIContext` to it in order for it to observe the changes in the state of the plugin. This can be done via a `PluginContextContainer`:
```typescript
// your_app.plugin: PluginUIContext
...
<div className="your_custom_ui">
<PluginContextContainer plugin={your_app.plugin}>
<SequenceView />
</PluginContextContainer>
</div>
```
## Directly using Mol* React UI
```ts
class MolStarWrapper {
private resolveInit: () => void;
initialized = new Promise<boolean>(res => { this.resolveInit = () => res(true); });
private initCalled = false;
plugin: PluginUIContext;
async init() {
if (this.initCalled) return;
this.initCalled = true;
this.plugin = ...;
await this.plugin.init();
this.resolveInit();
}
}
function MolStar({ model }: { model: MolStarWrapper }) {
const [initialized, setInitialized] = useState(false);
useEffect(() => {
async function init() {
await model.init();
setInitialized(true);
}
init();
}, [model]);
if (!initialized) return <>Loading</>;
return <div style={{ ..., position: 'relative' }}>
<Plugin plugin={model.plugin} />
</div>;
}
```
## ``PluginContext`` without built-in React UI
- The [``PluginContext``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/context.ts) can be instantiated without using the default React UI.
```HTML
<div id='molstar-parent' style='position: absolute; top: 0; left: 0; right: 0; bottom: 0'>
<canvas id='molstar-canvas' style='position: absolute; top: 0; left: 0; right: 0; bottom: 0'></canvas>
</div>
```
```ts
import { DefaultPluginSpec, PluginSpec } from 'molstar/lib/mol-plugin/spec';
import { PluginContext } from 'molstar/lib/mol-plugin/context';
import { PluginConfig } from 'molstar/lib/mol-plugin/config';
const MySpec: PluginSpec = {
...DefaultPluginSpec(),
config: [
[PluginConfig.VolumeStreaming.Enabled, false]
]
}
async function init() {
const plugin = new PluginContext(MySpec);
await plugin.init();
const canvas = <HTMLCanvasElement> document.getElementById('molstar-canvas');
const parent = <HTMLDivElement> document.getElementById('molstar-parent');
if (!plugin.initViewer(canvas, parent)) {
console.error('Failed to init Mol*');
return;
}
// Example url:"https://files.rcsb.org/download/3j7z.pdb"
// Example url:"https://files.rcsb.org/download/5AFI.cif"
const data = await plugin.builders.data.download({ url: '...' }, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(data, format); //format is 'mmcif' or 'pdb' etc.
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
}
```
## ``Canvas3D`` without built-in state management
- The ``PluginContext`` object from the above examples can be completely omitted.
- See [Browser Tests](https://github.com/molstar/molstar/tree/master/src/tests/browser) for example usage.
```ts
const canvas = document.getElementById('canvas'); // parent <canvas> element
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
canvas3d.animate();
// use the canvas3d object here
```

View File

@@ -0,0 +1,110 @@
# Selections
Assuming you have a model already loaded into the plugin (see [Creating Plugin Instance](./instance.md)), these are some of the following method you can select structural data.
### Selecting directly from the `hierarchy` manager
One can select a subcomponent's data directly from the plugin manager.
```typescript
import { Structure } from '../mol-model/structure';
const ligandData = plugin.managers.structure.hierarchy.selection.structures[0]?.components[0]?.cell.obj?.data;
const ligandLoci = Structure.toStructureElementLoci(ligandData as any);
plugin.managers.camera.focusLoci(ligandLoci);
plugin.managers.interactivity.lociSelects.select({ loci: ligandLoci });
```
## Selection callbacks
If you want to subscribe to selection events (e.g. to change external state in your application based on a user selection), you can use: `plugin.behaviors.interaction.click.subscribe`
Here's an example of passing in a React "set" function to update selected residue positions.
```typescript
import {
Structure,
StructureProperties,
} from "molstar/lib/mol-model/structure"
// setSelected is assumed to be a "set" function returned by useState
// (selected: any[]) => void
plugin.behaviors.interaction.click.subscribe(
(event: InteractivityManager.ClickEvent) => {
const selections = Array.from(
plugin.managers.structure.selection.entries.values()
);
// This bit can be customized to record any piece information you want
const localSelected: any[] = [];
for (const { structure } of selections) {
if (!structure) continue;
Structure.eachAtomicHierarchyElement(structure, {
residue: (loc) => {
const position = StructureProperties.residue.label_seq_id(loc);
localSelected.push({ position });
},
});
}
setSelected(localSelected);
}
)
```
### `Molscript` language
Molscript is a language for addressing crystallographic structures and is a part of the Mol* library found at `https://github.com/molstar/molstar/tree/master/src/mol-script`. It can be used against the Molstar plugin as a query language and transpiled against multiple external molecular visualization libraries(see [here](https://github.com/molstar/molstar/tree/master/src/mol-script/transpilers)).
### Querying a structure for a specific chain and residue range (select residues with 12<res_id<200 of chain with auth_asym_id==A) :
```typescript
import { compileIdListSelection } from 'molstar/lib/mol-script/util/id-list'
const query = compileIdListSelection('A 12-200', 'auth');
window.molstar?.managers.structure.selection.fromCompiledQuery('add',query);
```
## Selection Queries
Another way to create a selection is via a `SelectionQuery` object. This is a more programmatic way to create a selection. The following example shows how to select a chain and a residue range using a `SelectionQuery` object.
This relies on the concept of `Expression` which is basically a intermediate representation between a Molscript statement and a selection query.
### Select residues 10-15 of chains A and F in a structure using a `SelectionQuery` object:
```typescript
import { MolScriptBuilder as MS, MolScriptBuilder } from 'molstar/lib/mol-script/language/builder';
import { Expression } from 'molstar/lib/mol-script/language/expression';
import { StructureSelectionQuery } from 'molstar/lib/mol-plugin-state/helpers/structure-selection-query'
export function select_multiple() {
const args = [['A', 10, 15], ['F', 10, 15]]
const groups: Expression[] = [];
for (var chain of args) {
groups.push(MS.struct.generator.atomGroups({
"chain-test": MS.core.rel.eq([MolScriptBuilder.struct.atomProperty.macromolecular.auth_asym_id(), chain[0]]),
"residue-test": MS.core.rel.inRange([MolScriptBuilder.struct.atomProperty.macromolecular.label_seq_id(), chain[1], chain[2]])
}));
}
var sq = StructureSelectionQuery('residue_range_10_15_in_A_and_F', MS.struct.combinator.merge(groups))
mstar.managers.structure.selection.fromSelectionQuery('set', sq)
}
```
Complex queries can be constructed by combining primitive queries at the level of [`chain-test`, `residue-test`, `entity-test`, etc] (https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-script/language/symbol-table/structure-query.ts#L88C4-L94C112) by combining them via logical connectives provided in the `MolscriptBuilder.core.rel` as above.
Inspect these examples to get a better feeling for this syntax: `https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-state/helpers/structure-selection-query.ts#L88-L580`
Furthermore, a query made this way can be converted to a `Loci` object which is important in many parts of the libary:
```typescript
// Select residue 124 of chain A and convert to Loci
const Q = MolScriptBuilder;
var sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
'chain-test' : Q.core.rel.eq([Q.struct.atomProperty.macromolecular.auth_asym_id(), A]),
"residue-test": Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), 124]),
}), objdata)
let loci = StructureSelection.toLociWithSourceUnits(sel);
```

View File

@@ -0,0 +1,72 @@
# Load Trajectory from a Custom Format
This section shows a high level example for loading trajectory from custom data in specialized plugin instances. A more complete solution is available for example in form of the [G3D format extension](https://github.com/molstar/molstar/tree/master/src/extensions/g3d).
## Defining and Using a Custom Transformer
```ts
import { StateTransformer } from 'molstar/lib/mol-state';
const CreateTransformer = StateTransformer.builderFactory('custom-namespace');
export interface CustomTrajectoryData {
// ...
}
export const TrajectoryFromCustomData = CreateTransformer({
name: 'trajectory-from-custom-data',
display: 'Trajectory',
from: PluginStateObject.Root,
to: PluginStateObject.Molecule.Trajectory,
params: {
data: PD.Value<CustomTrajectoryData>(void 0 as any, { isHidden: true }),
},
})({
apply({ params }) {
return Task.create('Trajectory', async (ctx) => {
const models = await customParse(params.data, ctx);
return new PluginStateObject.Molecule.Trajectory(models, {
label: 'Trajectory',
});
});
},
});
```
The ``customParse`` function can usually be implemented
by modifying/extending an [existing parser already available in Mol*](https://github.com/molstar/molstar/tree/master/src/mol-model-formats/structure).
To use the transformer:
```ts
const data: CustomTrajectoryData = await (await fetch(url)).json();
const trajectory = await plugin.build().toRoot().apply(TrajectoryFromCustomData, { data }).commit();
// Create the representation
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
```
## Using Mol* to Download the Data
```ts
export const TrajectoryFromCustomData = CreateTransformer({
name: 'trajectory-from-custom-data',
display: 'Trajectory',
from: PluginStateObject.Data.String, // or PluginStateObject.Data.Binary
to: PluginStateObject.Molecule.Trajectory,
})({
apply({ a }) {
return Task.create('Trajectory', async (ctx) => {
const models = await customParse(a.data, ctx);
return new PluginStateObject.Molecule.Trajectory(models, {
label: 'Trajectory',
});
});
},
});
//////////////
const data = await plugin.builders.data.download({ url, isBinary });
const trajectory = await plugin.build().to(data).apply(TrajectoryFromCustomData, { data }).commit();
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
```

View File

@@ -0,0 +1,132 @@
# Viewer State Management
## ``Canvas3D`` Properties
Properties of the [``Canvas3D``](https://github.com/molstar/molstar/blob/master/src/mol-canvas3d/canvas3d.ts) can be
changed using [``PluginCommands``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/commands.ts).
### Change background, highlight, or select color
```ts
import { ColorNames } from 'molstar/lib/mol-util/color/names';
import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
const renderer = plugin.canvas3d!.props.renderer;
PluginCommands.Canvas3D.SetSettings(plugin, { settings: { renderer: { ...renderer, backgroundColor: ColorNames.red /* or: 0xff0000 as Color */ } } });
```
Similarly, `highlightColor` and `selectColor` can be updated.
## Interactivity
Interactivity in Mol* is based on the concept of ``Loci``. A ``Loci`` usually references a collection of objects and can be created by a [``Selection``](selections.md). For example, the
``Loci`` captures all atoms in the chain with label_asym_id B of a protein:
```ts
import { Script } from 'molstar/lib/mol-script/script';
import { StructureSelection } from 'molstar/lib/mol-model/structure/query';
const data = plugin.managers.structure.hierarchy.current.structures[0]?.cell.obj?.data;
if (!data) return;
const selection = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
'chain-test': Q.core.rel.eq(['B', Q.ammp('label_asym_id')])
}), data);
const loci = StructureSelection.toLociWithSourceUnits(selection);
```
A ``Loci`` can be used to trigger custom [``Behaviors``](#behaviors).
### Log message to Mol* console
The built-in console in the bottom center of the plugin shows log entries.
```ts
plugin.log.message('This message will appear in the Mol* console');
```
Other log levels are: `info`, `warn`, and `error`.
### Show toast message
Toast messages will appear in the bottom right of the plugin and will linger for a limited time before disappearing.
```ts
import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
PluginCommands.Toast.Show(plugin, {
title: 'Custom Message',
message: 'A custom toast message that will disappear after 2 seconds.',
key: 'toast-custom',
timeoutMs: 2000
});
```
## Behaviors
The state of the Mol* plugin is usually governed by dynamic behaviors which can be set up in initial plugin specification or updated during the plugin runtime. This allows for high modularity and customizability of individual plugin instances.
### Highlight ``Loci``
Highlighting adds a transient overpaint to a representation that will linger until the mouse enters hovers over another
object. Highlights can be applied to a previously defined ``Loci`` by:
```ts
plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }); // loci: Loci
```
Reset all highlights by:
```ts
plugin.managers.interactivity.clearHighlights();
```
### Select ``Loci``
Selected elements will appear with distinct visuals and, if applicable, the corresponding sequence positions will be
shown in the Sequence Viewer panel. Selections persist until removed, for example by clicking the background. A ``Loci``
is selected by:
```ts
plugin.managers.interactivity.lociSelects.select({ loci }); // loci: Loci
```
Deselect a specific ``Loci`` by:
```ts
plugin.managers.interactivity.lociSelects.deselect({ loci }); // loci: Loci
```
To deselect everything:
```ts
plugin.managers.interactivity.lociSelects.deselectAll();
```
### Focus ``Loci``
The focus representation shows a ``Loci`` in ball-and-stick representation and, additionally, visualizes non-covalent
interactions between atoms of the ``Loci`` as well as interactions with surrounding residues (default: 5 Å).
```ts
plugin.managers.structure.focus.setFromLoci(loci);
```
Extend an existing focus representation by:
```ts
plugin.managers.structure.focus.addFromLoci(loci); // loci: Loci
```
Reset by:
```ts
plugin.managers.structure.focus.clear();
```
### Zoom ``Loci``
A ``Loci`` can also be used to manipulate the camera. Zoom in by:
```ts
plugin.managers.camera.focusLoci(loci); // loci: Loci
```
Restore the default camera position by:
```ts
plugin.managers.camera.reset();
```
### Turn off view resetting on new representations
A new representation via something like
```ts
.apply(StateTransforms.Representation.VolumeRepresentation3D, ...)
```
can reset the view to make the whole representation visible.
When one wants to keep the view the same instead of having the rep reset the view,
keep the view constant by:
```ts
plugin.canvas3d?.setProps({ camera: { manualReset: true } });
```

58
docs/mkdocs.yml Normal file
View File

@@ -0,0 +1,58 @@
site_name: Mol* Developer Documentation
theme:
name: material
# 404 page
static_templates:
- 404.html
# Necessary for search to work properly
include_search_page: false
search_index_only: true
# Default values, taken from mkdocs_theme.yml
language: en
font:
text: Roboto
code: Roboto Mono
favicon: assets/favicon.png
icon:
logo: logo
markdown_extensions:
- pymdownx.highlight
- pymdownx.superfences
- pymdownx.arithmatex:
generic: true
# Scripts for rendering Latex equations (in addition to pymdownx.arithmatex):
extra_javascript:
- https://polyfill.io/v3/polyfill.min.js?features=es6
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
nav:
- 'index.md'
- Plugin:
- Creating Instance: 'plugin/instance.md'
- Examples: plugin/examples.md
- Selections: 'plugin/selections.md'
- Viewer State: 'plugin/viewer-state.md'
- Data State: 'plugin/data-state.md'
- File Formats: 'plugin/file-formats.md'
- CIF Schemas: 'plugin/cif-schemas.md'
- State Transforms:
- Custom Trajectory: 'plugin/transforms/custom-trajectory.md'
- Data Access Tools:
- 'data-access-tools/model-server.md'
- Volume Server:
- Overview: 'data-access-tools/volume-server/index.md'
- Examples: 'data-access-tools/volume-server/examples.md'
- How it Works: 'data-access-tools/volume-server/how-it-works.md'
- Data Format: 'data-access-tools/volume-server/response-data-format.md'
- 'data-access-tools/plugin-state-server.md'
- 'data-access-tools/convert-to-bcif.md'
- 'data-access-tools/create-ccd-table.md'
- 'data-access-tools/extract-ccd-ions.md'
- Extensions:
- MolViewSpec: 'extensions/mvs/index.md'
- wwPDB StructConn: 'extensions/struct-conn.md'
- Misc:
- Interesting PDB entries: misc/interesting-pdb-entries.md
repo_url: https://github.com/molstar/docs

View File

@@ -1,69 +0,0 @@
Model Server
============
Model Server is a tool for preprocessing and querying macromolecular structure data.
Installing and Running
=====================
Requires nodejs 8+.
## From GitHub
```
git clone https://github.com/molstar/molstar
npm install
```
Afterwards, build the project source:
```
npm run build-tsc
```
and run the server by
```
node lib/commonjs/servers/model/server/server
```
## From NPM
```
npm install --production molstar
./model-server
```
(or ``node node_modules\.bin\model-server`` in Windows).
The NPM package contains all the tools mentioned here as "binaries":
- ``model-server``
- ``model-server-query``
- ``model-server-preprocess``
### Production use
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
### Memory issues
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
## Preprocessor
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/commonjs/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
## Local Mode
The server can be run in local/file based mode using ``node lib/commonjs/servers/model/query`` (``model-server-query`` binary from the NPM package).
Custom Properties
=================
This feature is still in development.
It is possible to provide property descriptors that transform data to internal representation and define how it should be exported into one or mode CIF categories. Examples of this are located in the ``mol-model-props`` module and are linked to the server in the config and ``servers/model/properties``.

View File

@@ -1,738 +0,0 @@
# Mol* Plugin State Transformer Reference
* [build-in.root](#build-in-root)
* [ms-plugin.download](#ms-plugin-download)
* [ms-plugin.read-file](#ms-plugin-read-file)
* [ms-plugin.parse-cif](#ms-plugin-parse-cif)
* [ms-plugin.parse-ccp4](#ms-plugin-parse-ccp4)
* [ms-plugin.parse-dsn6](#ms-plugin-parse-dsn6)
* [ms-plugin.trajectory-from-mmcif](#ms-plugin-trajectory-from-mmcif)
* [ms-plugin.trajectory-from-pdb](#ms-plugin-trajectory-from-pdb)
* [ms-plugin.model-from-trajectory](#ms-plugin-model-from-trajectory)
* [ms-plugin.structure-from-model](#ms-plugin-structure-from-model)
* [ms-plugin.structure-assembly-from-model](#ms-plugin-structure-assembly-from-model)
* [ms-plugin.structure-symmetry-from-model](#ms-plugin-structure-symmetry-from-model)
* [ms-plugin.structure-selection](#ms-plugin-structure-selection)
* [ms-plugin.structure-complex-element](#ms-plugin-structure-complex-element)
* [ms-plugin.custom-model-properties](#ms-plugin-custom-model-properties)
* [ms-plugin.volume-from-ccp4](#ms-plugin-volume-from-ccp4)
* [ms-plugin.volume-from-dsn6](#ms-plugin-volume-from-dsn6)
* [ms-plugin.representation-highlight-loci](#ms-plugin-representation-highlight-loci)
* [ms-plugin.representation-select-loci](#ms-plugin-representation-select-loci)
* [ms-plugin.default-loci-label-provider](#ms-plugin-default-loci-label-provider)
* [ms-plugin.structure-representation-3d](#ms-plugin-structure-representation-3d)
* [ms-plugin.explode-structure-representation-3d](#ms-plugin-explode-structure-representation-3d)
* [ms-plugin.volume-representation-3d](#ms-plugin-volume-representation-3d)
* [ms-plugin.focus-loci-on-select](#ms-plugin-focus-loci-on-select)
* [ms-plugin.pdbe-structure-quality-report-prop](#ms-plugin-pdbe-structure-quality-report-prop)
* [ms-plugin.rcsb-assembly-symmetry-prop](#ms-plugin-rcsb-assembly-symmetry-prop)
* [ms-plugin.structure-animation](#ms-plugin-structure-animation)
* [ms-plugin.scene-labels](#ms-plugin-scene-labels)
----------------------------
## <a name="build-in-root"></a>build-in.root :: () -> ()
*For internal use.*
----------------------------
## <a name="ms-plugin-download"></a>ms-plugin.download :: Root -> String | Binary
*Download string or binary data from the specified URL*
### Parameters
- **url**: String *(Resource URL. Must be the same domain or support CORS.)*
- **label**?: String
- **isBinary**?: true/false *(If true, download data as binary (string otherwise))*
### Default Parameters
```js
{
"url": "https://www.ebi.ac.uk/pdbe/static/entry/1cbs_updated.cif"
}
```
----------------------------
## <a name="ms-plugin-read-file"></a>ms-plugin.read-file :: Root -> String | Binary
*Read string or binary data from the specified file*
### Parameters
- **file**: JavaScript File Handle
- **label**?: String
- **isBinary**?: true/false *(If true, open file as as binary (string otherwise))*
### Default Parameters
```js
{}
```
----------------------------
## <a name="ms-plugin-parse-cif"></a>ms-plugin.parse-cif :: String | Binary -> Cif
*Parse CIF from String or Binary data*
----------------------------
## <a name="ms-plugin-parse-ccp4"></a>ms-plugin.parse-ccp4 :: Binary -> Ccp4
*Parse CCP4/MRC/MAP from Binary data*
----------------------------
## <a name="ms-plugin-parse-dsn6"></a>ms-plugin.parse-dsn6 :: Binary -> Dsn6
*Parse CCP4/BRIX from Binary data*
----------------------------
## <a name="ms-plugin-trajectory-from-mmcif"></a>ms-plugin.trajectory-from-mmcif :: Cif -> Trajectory
*Identify and create all separate models in the specified CIF data block*
### Parameters
- **blockHeader**?: String *(Header of the block to parse. If none is specifed, the 1st data block in the file is used.)*
### Default Parameters
```js
{}
```
----------------------------
## <a name="ms-plugin-trajectory-from-pdb"></a>ms-plugin.trajectory-from-pdb :: String -> Trajectory
----------------------------
## <a name="ms-plugin-model-from-trajectory"></a>ms-plugin.model-from-trajectory :: Trajectory -> Model
*Create a molecular structure from the specified model.*
### Parameters
- **modelIndex**: Numeric value *(Zero-based index of the model)*
### Default Parameters
```js
{
"modelIndex": 0
}
```
----------------------------
## <a name="ms-plugin-structure-from-model"></a>ms-plugin.structure-from-model :: Model -> Structure
*Create a molecular structure from the specified model.*
----------------------------
## <a name="ms-plugin-structure-assembly-from-model"></a>ms-plugin.structure-assembly-from-model :: Model -> Structure
*Create a molecular structure assembly.*
### Parameters
- **id**?: String *(Assembly Id. Value 'deposited' can be used to specify deposited asymmetric unit.)*
### Default Parameters
```js
{}
```
----------------------------
## <a name="ms-plugin-structure-symmetry-from-model"></a>ms-plugin.structure-symmetry-from-model :: Model -> Structure
*Create a molecular structure symmetry.*
### Parameters
- **ijkMin**: 3D vector [x, y, z]
- **ijkMax**: 3D vector [x, y, z]
### Default Parameters
```js
{
"ijkMin": [
-1,
-1,
-1
],
"ijkMax": [
1,
1,
1
]
}
```
----------------------------
## <a name="ms-plugin-structure-selection"></a>ms-plugin.structure-selection :: Structure -> Structure
*Create a molecular structure from the specified query expression.*
### Parameters
- **query**: Value
- **label**?: String
### Default Parameters
```js
{}
```
----------------------------
## <a name="ms-plugin-structure-complex-element"></a>ms-plugin.structure-complex-element :: Structure -> Structure
*Create a molecular structure from the specified model.*
### Parameters
- **type**: One of 'atomic-sequence', 'water', 'atomic-het', 'spheres'
### Default Parameters
```js
{
"type": "atomic-sequence"
}
```
----------------------------
## <a name="ms-plugin-custom-model-properties"></a>ms-plugin.custom-model-properties :: Model -> Model
### Parameters
- **properties**: Array of *(A list of property descriptor ids.)*
### Default Parameters
```js
{
"properties": []
}
```
----------------------------
## <a name="ms-plugin-volume-from-ccp4"></a>ms-plugin.volume-from-ccp4 :: Ccp4 -> Data
*Create Volume from CCP4/MRC/MAP data*
### Parameters
- **voxelSize**: 3D vector [x, y, z]
### Default Parameters
```js
{
"voxelSize": [
1,
1,
1
]
}
```
----------------------------
## <a name="ms-plugin-volume-from-dsn6"></a>ms-plugin.volume-from-dsn6 :: Dsn6 -> Data
*Create Volume from DSN6/BRIX data*
### Parameters
- **voxelSize**: 3D vector [x, y, z]
### Default Parameters
```js
{
"voxelSize": [
1,
1,
1
]
}
```
----------------------------
## <a name="ms-plugin-representation-highlight-loci"></a>ms-plugin.representation-highlight-loci :: Root -> Behavior
----------------------------
## <a name="ms-plugin-representation-select-loci"></a>ms-plugin.representation-select-loci :: Root -> Behavior
----------------------------
## <a name="ms-plugin-default-loci-label-provider"></a>ms-plugin.default-loci-label-provider :: Root -> Behavior
----------------------------
## <a name="ms-plugin-structure-representation-3d"></a>ms-plugin.structure-representation-3d :: Structure -> Representation3D
### Parameters
- **type**: Object { name: string, params: object } where name+params are:
- **cartoon**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **sizeFactor**: Numeric value
- **linearSegments**: Numeric value
- **radialSegments**: Numeric value
- **aspectRatio**: Numeric value
- **arrowFactor**: Numeric value
- **visuals**: Array of 'polymer-trace', 'polymer-gap', 'nucleotide-block', 'direction-wedge'
- **ball-and-stick**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **sizeFactor**: Numeric value
- **detail**: Numeric value
- **linkScale**: Numeric value
- **linkSpacing**: Numeric value
- **radialSegments**: Numeric value
- **sizeAspectRatio**: Numeric value
- **visuals**: Array of 'element-sphere', 'intra-link', 'inter-link'
- **carbohydrate**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **detail**: Numeric value
- **sizeFactor**: Numeric value
- **linkScale**: Numeric value
- **linkSpacing**: Numeric value
- **radialSegments**: Numeric value
- **linkSizeFactor**: Numeric value
- **visuals**: Array of 'carbohydrate-symbol', 'carbohydrate-link', 'carbohydrate-terminal-link'
- **distance-restraint**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **linkScale**: Numeric value
- **linkSpacing**: Numeric value
- **radialSegments**: Numeric value
- **sizeFactor**: Numeric value
- **molecular-surface**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **resolution**: Numeric value
- **radiusOffset**: Numeric value
- **smoothness**: Numeric value
- **useGpu**: true/false
- **ignoreCache**: true/false
- **sizeFactor**: Numeric value
- **lineSizeAttenuation**: true/false
- **visuals**: Array of 'gaussian-surface', 'gaussian-wireframe'
- **molecular-volume**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **isoValueNorm**: Numeric value *(Normalized Isolevel Value)*
- **renderMode**: One of 'isosurface', 'volume'
- **controlPoints**: A list of 2d vectors [xi, yi][]
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **resolution**: Numeric value
- **radiusOffset**: Numeric value
- **smoothness**: Numeric value
- **point**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **sizeFactor**: Numeric value
- **pointSizeAttenuation**: true/false
- **pointFilledCircle**: true/false
- **pointEdgeBleach**: Numeric value
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **spacefill**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **sizeFactor**: Numeric value
- **detail**: Numeric value
- **colorTheme**: Object { name: string, params: object } where name+params are:
- **carbohydrate-symbol**:
Object with:
- **chain-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **cross-link**:
Object with:
- **domain**: Interval [min, max]
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **element-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **element-symbol**:
Object with:
- **molecule-type**:
Object with:
- **polymer-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **polymer-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **residue-name**:
Object with:
- **secondary-structure**:
Object with:
- **sequence-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **shape-group**:
Object with:
- **unit-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **uniform**:
Object with:
- **value**: Color as 0xrrggbb
- **sizeTheme**: Object { name: string, params: object } where name+params are:
- **physical**:
Object with:
- **shape-group**:
Object with:
- **uniform**:
Object with:
- **value**: Numeric value
### Default Parameters
```js
{
"type": {
"name": "cartoon",
"params": {
"alpha": 1,
"useFog": true,
"highlightColor": 16737945,
"selectColor": 3407641,
"quality": "auto",
"doubleSided": false,
"flipSided": false,
"flatShaded": false,
"unitKinds": [
"atomic",
"spheres"
],
"sizeFactor": 0.2,
"linearSegments": 8,
"radialSegments": 16,
"aspectRatio": 5,
"arrowFactor": 1.5,
"visuals": [
"polymer-trace"
]
}
},
"colorTheme": {
"name": "polymer-id",
"params": {
"list": "RedYellowBlue"
}
},
"sizeTheme": {
"name": "uniform",
"params": {
"value": 1
}
}
}
```
----------------------------
## <a name="ms-plugin-explode-structure-representation-3d"></a>ms-plugin.explode-structure-representation-3d :: Representation3D -> Obj
### Parameters
- **t**: Numeric value
### Default Parameters
```js
{
"t": 0
}
```
----------------------------
## <a name="ms-plugin-volume-representation-3d"></a>ms-plugin.volume-representation-3d :: Data -> Representation3D
### Parameters
- **type**: Object { name: string, params: object } where name+params are:
- **isosurface**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **isoValue**: - **absolute**: Numeric value
- **relative**: Numeric value
- **sizeFactor**: Numeric value
- **lineSizeAttenuation**: true/false
- **visuals**: Array of 'solid', 'wireframe'
- **direct-volume**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **isoValueNorm**: Numeric value *(Normalized Isolevel Value)*
- **renderMode**: One of 'isosurface', 'volume'
- **controlPoints**: A list of 2d vectors [xi, yi][]
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **colorTheme**: Object { name: string, params: object } where name+params are:
- **carbohydrate-symbol**:
Object with:
- **chain-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **cross-link**:
Object with:
- **domain**: Interval [min, max]
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **element-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **element-symbol**:
Object with:
- **molecule-type**:
Object with:
- **polymer-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **polymer-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **residue-name**:
Object with:
- **secondary-structure**:
Object with:
- **sequence-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **shape-group**:
Object with:
- **unit-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **uniform**:
Object with:
- **value**: Color as 0xrrggbb
- **sizeTheme**: Object { name: string, params: object } where name+params are:
- **physical**:
Object with:
- **shape-group**:
Object with:
- **uniform**:
Object with:
- **value**: Numeric value
### Default Parameters
```js
{
"type": {
"name": "isosurface",
"params": {
"alpha": 1,
"useFog": true,
"highlightColor": 16737945,
"selectColor": 3407641,
"quality": "auto",
"doubleSided": false,
"flipSided": false,
"flatShaded": false,
"isoValue": {
"kind": "relative",
"stats": {
"min": 0,
"max": 0,
"mean": 0,
"sigma": 0
},
"relativeValue": 2
},
"sizeFactor": 1,
"lineSizeAttenuation": false,
"visuals": [
"solid"
]
}
},
"colorTheme": {
"name": "uniform",
"params": {
"value": 13421772
}
},
"sizeTheme": {
"name": "uniform",
"params": {
"value": 1
}
}
}
```
----------------------------
## <a name="ms-plugin-focus-loci-on-select"></a>ms-plugin.focus-loci-on-select :: Root -> Behavior
### Parameters
- **minRadius**: Numeric value
- **extraRadius**: Numeric value *(Value added to the boundning sphere radius of the Loci.)*
### Default Parameters
```js
{
"minRadius": 10,
"extraRadius": 4
}
```
----------------------------
## <a name="ms-plugin-pdbe-structure-quality-report-prop"></a>ms-plugin.pdbe-structure-quality-report-prop :: Root -> Behavior
### Parameters
- **autoAttach**: true/false
### Default Parameters
```js
{
"autoAttach": false
}
```
----------------------------
## <a name="ms-plugin-rcsb-assembly-symmetry-prop"></a>ms-plugin.rcsb-assembly-symmetry-prop :: Root -> Behavior
### Parameters
- **autoAttach**: true/false
### Default Parameters
```js
{
"autoAttach": false
}
```
----------------------------
## <a name="ms-plugin-structure-animation"></a>ms-plugin.structure-animation :: Root -> Behavior
### Parameters
- **rotate**: true/false
- **rotateValue**: Numeric value
- **explode**: true/false
- **explodeValue**: Numeric value
### Default Parameters
```js
{
"rotate": false,
"rotateValue": 0,
"explode": false,
"explodeValue": 0
}
```
----------------------------
## <a name="ms-plugin-scene-labels"></a>ms-plugin.scene-labels :: Root -> Behavior
### Parameters
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **fontFamily**: One of 'sans-serif', 'monospace', 'serif', 'cursive'
- **fontQuality**: One of '0', '1', '2', '3', '4'
- **fontStyle**: One of 'normal', 'italic', 'oblique'
- **fontVariant**: One of 'normal', 'small-caps'
- **fontWeight**: One of 'normal', 'bold'
- **sizeFactor**: Numeric value
- **borderWidth**: Numeric value
- **borderColor**: Color as 0xrrggbb
- **offsetX**: Numeric value
- **offsetY**: Numeric value
- **offsetZ**: Numeric value
- **background**: true/false
- **backgroundMargin**: Numeric value
- **backgroundColor**: Color as 0xrrggbb
- **backgroundOpacity**: Numeric value
- **attachment**: One of 'bottom-left', 'bottom-center', 'bottom-right', 'middle-left', 'middle-center', 'middle-right', 'top-left', 'top-center', 'top-right'
- **levels**: Array of 'structure', 'polymer', 'ligand'
### Default Parameters
```js
{
"alpha": 1,
"useFog": true,
"highlightColor": 16737945,
"selectColor": 3407641,
"quality": "auto",
"fontFamily": "sans-serif",
"fontQuality": 3,
"fontStyle": "normal",
"fontVariant": "normal",
"fontWeight": "normal",
"sizeFactor": 1,
"borderWidth": 0,
"borderColor": 8421504,
"offsetX": 0,
"offsetY": 0,
"offsetZ": 0,
"background": true,
"backgroundMargin": 0.2,
"backgroundColor": 16775930,
"backgroundOpacity": 0.9,
"attachment": "middle-center",
"levels": []
}
```
----------------------------

View File

@@ -1,86 +0,0 @@
What is VolumeServer
=====================
VolumeServer is a service for accessing subsets of volumetric density data. It automatically downsamples the data depending on the volume of the requested region to reduce the bandwidth requirements and provide near-instant access to even the largest data sets.
It uses the text based CIF and BinaryCIF formats to deliver the data to the client.
For quick info about the benefits of using the server, check out the [examples](examples.md).
Installing and Running
=====================
Requires nodejs 8+.
## From GitHub
```
git clone https://github.com/molstar/molstar
npm install
```
Afterwards, build the project source:
```
npm run build-tsc
```
and run the server by
```
node lib/commonjs/servers/volume/server
```
## From NPM
```
npm install --production molstar
./volume-server
```
(or ``node node_modules\.bin\volume-server`` in Windows).
The NPM package contains all the tools mentioned here as "binaries":
- ``volume-server``
- ``volume-server-pack``
- ``volume-server-query``
### Production use
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
### Memory issues
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
## Preparing the Data
For the server to work, CCP4/MAP (models 0, 1, 2 are supported) input data need to be converted into a custom block format.
To achieve this, use the ``pack`` application (``node lib/commonjs/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
## Local Mode
The program ``lib/commonjs/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
## Navigating the Source Code
The source code is split into 2 mains parts: ``pack`` and ``server``:
- The ``pack`` part provides the means of converting CCP4 files into the internal block format.
- The ``server`` includes
- ``query``: the main part of the server that handles a query. ``execute.ts`` is the "entry point".
- ``algebra``: linear, "coordinate", and "box" algebra provides the means for calculations necessary to concent a user query into a menaningful response.
- API wrapper that handles the requests.
Consuming the Data
==================
The data can be consumed in any (modern) browser using the [ciftools library](https://github.com/molstar/ciftools) (or any other piece of code that can read text or binary CIF).
The [Data Format](DataFormat.md) document gives a detailed description of the server response format.
As a reference/example of the server usage is available in Mol* ``mol-plugin`` module.

View File

@@ -0,0 +1,115 @@
{
"metadata": {
"title": "Example MolViewSpec - 1cbs with labelled and zoomed ligand",
"version": "1",
"timestamp": "2023-11-24T10:45:49.873Z"
},
"root": {
"kind": "root",
"children": [
{
"kind": "download",
"params": {
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif"
},
"children": [
{
"kind": "parse",
"params": {
"format": "bcif"
},
"children": [
{
"kind": "structure",
"params": {
"type": "model"
},
"children": [
{
"kind": "component",
"params": {
"selector": "polymer"
},
"children": [
{
"kind": "representation",
"params": {
"type": "cartoon"
},
"children": [
{
"kind": "color",
"params": {
"color": "green"
}
},
{
"kind": "color",
"params": {
"selector": {
"label_asym_id": "A",
"end_label_seq_id": 50
},
"color": "#6688ff"
}
}
]
},
{
"kind": "label",
"params": {
"text": "Protein"
}
}
]
},
{
"kind": "component",
"params": {
"selector": "ligand"
},
"children": [
{
"kind": "focus",
"params": {
"direction": [0.5, 0, -1],
"up": [0.365, 0.913, 0.183]
}
},
{
"kind": "representation",
"params": {
"type": "ball_and_stick"
},
"children": [
{
"kind": "color",
"params": {
"color": "#cc3399"
}
}
]
},
{
"kind": "label",
"params": {
"text": "Retinoic Acid"
}
}
]
}
]
}
]
}
]
},
{
"kind": "canvas",
"params": {
"background_color": "#ffffee"
}
}
]
}
}

117
examples/mvs/1cbs.mvsj Normal file
View File

@@ -0,0 +1,117 @@
{
"metadata": {
"title": "Example MolViewSpec - 1cbs with labelled protein and ligand",
"version": "1",
"timestamp": "2023-11-24T10:38:17.483Z"
},
"root": {
"kind": "root",
"children": [
{
"kind": "download",
"params": {
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif"
},
"children": [
{
"kind": "parse",
"params": {
"format": "bcif"
},
"children": [
{
"kind": "structure",
"params": {
"type": "model"
},
"children": [
{
"kind": "component",
"params": {
"selector": "polymer"
},
"children": [
{
"kind": "representation",
"params": {
"type": "cartoon"
},
"children": [
{
"kind": "color",
"params": {
"color": "green"
}
},
{
"kind": "color",
"params": {
"selector": {
"label_asym_id": "A",
"beg_label_seq_id": 1,
"end_label_seq_id": 50
},
"color": "#6688ff"
}
}
]
},
{
"kind": "label",
"params": {
"text": "Protein"
}
}
]
},
{
"kind": "component",
"params": {
"selector": "ligand"
},
"children": [
{
"kind": "representation",
"params": {
"type": "ball_and_stick"
},
"children": [
{
"kind": "color",
"params": {
"color": "#cc3399"
}
}
]
},
{
"kind": "label",
"params": {
"text": "Retinoic Acid"
}
}
]
}
]
}
]
}
]
},
{
"kind": "canvas",
"params": {
"background_color": "#ffffee"
}
},
{
"kind": "camera",
"params": {
"target": [17, 21, 27],
"position": [41, 34, 69],
"up": [-0.129,0.966,-0.224]
}
}
]
}
}

BIN
examples/mvs/1h9t.mvsx Normal file

Binary file not shown.

View File

@@ -0,0 +1,67 @@
{
"metadata": {
"title": "Example MolViewSpec - 1h9t colored by external annotation",
"version": "1",
"timestamp": "2023-11-24T10:47:33.182Z"
},
"root": {
"kind": "root",
"children": [
{
"kind": "download",
"params": {
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1h9t.bcif"
},
"children": [
{
"kind": "parse",
"params": {
"format": "bcif"
},
"children": [
{
"kind": "structure",
"params": {
"type": "model"
},
"children": [
{
"kind": "component",
"params": {
"selector": "polymer"
},
"children": [
{
"kind": "representation",
"params": {
"type": "cartoon"
},
"children": [
{
"kind": "color",
"params": {
"selector": "all",
"color": "white"
}
},
{
"kind": "color_from_uri",
"params": {
"uri": "./1h9t_domains.json",
"format": "json",
"schema": "all_atomic"
}
}
]
}
]
}
]
}
]
}
]
}
]
}
}

View File

@@ -0,0 +1,583 @@
{
"metadata": {
"title": "Example MolViewSpec - 1h9t colored and labelled by external annotation",
"version": "1",
"timestamp": "2023-11-24T10:48:28.677Z"
},
"root": {
"kind": "root",
"children": [
{
"kind": "download",
"params": {
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1h9t.bcif"
},
"children": [
{
"kind": "parse",
"params": {
"format": "bcif"
},
"children": [
{
"kind": "structure",
"params": {
"type": "model"
},
"children": [
{
"kind": "component",
"params": {
"selector": "protein"
},
"children": [
{
"kind": "representation",
"params": {
"type": "cartoon"
},
"children": [
{
"kind": "color",
"params": {
"selector": "all",
"color": "white"
}
},
{
"kind": "color_from_uri",
"params": {
"uri": "./1h9t_domains.json",
"format": "json",
"schema": "all_atomic"
}
}
]
}
]
},
{
"kind": "component",
"params": {
"selector": "nucleic"
},
"children": [
{
"kind": "representation",
"params": {
"type": "ball_and_stick"
},
"children": [
{
"kind": "color",
"params": {
"selector": "all",
"color": "white"
}
},
{
"kind": "color_from_uri",
"params": {
"uri": "./1h9t_domains.json",
"format": "json",
"schema": "all_atomic"
}
}
]
}
]
},
{
"kind": "component",
"params": {
"selector": "ion"
},
"children": [
{
"kind": "representation",
"params": {
"type": "surface"
},
"children": [
{
"kind": "color_from_uri",
"params": {
"uri": "./1h9t_domains.json",
"format": "json",
"schema": "all_atomic"
}
}
]
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "A",
"beg_label_seq_id": 9,
"end_label_seq_id": 83
}
},
"children": [
{
"kind": "label",
"params": {
"text": "DNA-binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "B",
"beg_label_seq_id": 9,
"end_label_seq_id": 83
}
},
"children": [
{
"kind": "label",
"params": {
"text": "DNA-binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "A",
"beg_label_seq_id": 84,
"end_label_seq_id": 231
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Acyl-CoA\nbinding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "B",
"beg_label_seq_id": 84,
"end_label_seq_id": 231
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Acyl-CoA binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "C"
}
},
"children": [
{
"kind": "label",
"params": {
"text": "DNA X"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "D"
}
},
"children": [
{
"kind": "label",
"params": {
"text": "DNA Y"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "D",
"atom_id": 4016
}
},
"children": [
{
"kind": "label",
"params": {
"text": "DNA Y O5'"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "D",
"atom_id": 4391
}
},
"children": [
{
"kind": "label",
"params": {
"text": "DNA Y O3'"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "E"
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Gold"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "H"
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Gold"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "F"
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Chloride"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "G"
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Chloride"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "I"
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Chloride"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "A",
"label_seq_id": 57
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "A",
"label_seq_id": 67
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "A",
"label_seq_id": 121
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "A",
"label_seq_id": 125
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "A",
"label_seq_id": 129
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "A",
"label_seq_id": 178
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "A",
"beg_label_seq_id": 203,
"end_label_seq_id": 205
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "B",
"label_seq_id": 67
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "B",
"label_seq_id": 121
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "B",
"label_seq_id": 125
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "B",
"label_seq_id": 129
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "B",
"label_seq_id": 178
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": {
"label_asym_id": "B",
"beg_label_seq_id": 203,
"end_label_seq_id": 205
}
},
"children": [
{
"kind": "label",
"params": {
"text": "Ligand binding"
}
}
]
},
{
"kind": "component",
"params": {
"selector": "all"
},
"children": [
{
"kind": "focus",
"params": {
"direction": [-0.3, -0.1, -1]
}
}
]
}
]
}
]
}
]
},
{
"kind": "canvas",
"params": {
"background_color": "#eeffee"
}
}
]
}
}

View File

@@ -0,0 +1,155 @@
[
{
"label_asym_id": "A",
"beg_label_seq_id": 9,
"end_label_seq_id": 83,
"color": "#dd6600",
"tooltip": "DNA-binding"
},
{
"label_asym_id": "A",
"beg_label_seq_id": 84,
"end_label_seq_id": 231,
"color": "#008800",
"tooltip": "Acyl-CoA binding"
},
{
"label_asym_id": "B",
"beg_label_seq_id": 9,
"end_label_seq_id": 83,
"color": "#cc8800",
"tooltip": "DNA-binding"
},
{
"label_asym_id": "B",
"beg_label_seq_id": 84,
"end_label_seq_id": 231,
"color": "#008888",
"tooltip": "Acyl-CoA binding"
},
{
"label_asym_id": "C",
"color": "#1100aa",
"tooltip": "DNA X"
},
{
"label_asym_id": "D",
"color": "#dddddd",
"tooltip": "DNA Y"
},
{
"label_asym_id": "D",
"atom_id": 4016,
"color": "#ff0044",
"tooltip": "DNA Y - O5'"
},
{
"label_asym_id": "D",
"atom_id": 4391,
"color": "#4400ff",
"tooltip": "DNA Y - O3'"
},
{
"label_asym_id": "E",
"color": "#ffff00",
"tooltip": "Gold"
},
{
"label_asym_id": "H",
"color": "#ffff00",
"tooltip": "Gold"
},
{
"label_asym_id": "F",
"color": "#00dd00",
"tooltip": "Chloride"
},
{
"label_asym_id": "G",
"color": "#00dd00",
"tooltip": "Chloride"
},
{
"label_asym_id": "I",
"color": "#00dd00",
"tooltip": "Chloride"
},
{
"label_asym_id": "A",
"label_seq_id": 57,
"color": "#ff0000",
"tooltip": "Ligand binding site"
},
{
"label_asym_id": "A",
"label_seq_id": 67,
"color": "#ff0000",
"tooltip": "Ligand binding site"
},
{
"label_asym_id": "A",
"label_seq_id": 121,
"color": "#ff0000",
"tooltip": "Ligand binding site"
},
{
"label_asym_id": "A",
"label_seq_id": 125,
"color": "#ff0000",
"tooltip": "Ligand binding site"
},
{
"label_asym_id": "A",
"label_seq_id": 129,
"color": "#ff0000",
"tooltip": "Ligand binding site"
},
{
"label_asym_id": "A",
"label_seq_id": 178,
"color": "#ff0000",
"tooltip": "Ligand binding site"
},
{
"label_asym_id": "A",
"beg_label_seq_id": 203,
"end_label_seq_id": 205,
"color": "#ff0000",
"tooltip": "Ligand binding site"
},
{
"label_asym_id": "B",
"label_seq_id": 67,
"color": "#ff0000",
"tooltip": "Ligand binding site"
},
{
"label_asym_id": "B",
"label_seq_id": 121,
"color": "#ff0000",
"tooltip": "Ligand binding site"
},
{
"label_asym_id": "B",
"label_seq_id": 125,
"color": "#ff0000",
"tooltip": "Ligand binding site"
},
{
"label_asym_id": "B",
"label_seq_id": 129,
"color": "#ff0000",
"tooltip": "Ligand binding site"
},
{
"label_asym_id": "B",
"beg_label_seq_id": 203,
"end_label_seq_id": 205,
"color": "#ff0000",
"tooltip": "Ligand binding site"
}
]

17048
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.38.2",
"version": "4.2.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -48,6 +48,9 @@
"bin": {
"cif2bcif": "lib/commonjs/cli/cif2bcif/index.js",
"cifschema": "lib/commonjs/cli/cifschema/index.js",
"mvs-validate": "lib/commonjs/cli/mvs/mvs-validate.js",
"mvs-render": "lib/commonjs/cli/mvs/mvs-render.js",
"mvs-print-schema": "lib/commonjs/cli/mvs/mvs-print-schema.js",
"model-server": "lib/commonjs/servers/model/server.js",
"model-server-query": "lib/commonjs/servers/model/query.js",
"model-server-preprocess": "lib/commonjs/servers/model/preprocess.js",
@@ -99,83 +102,94 @@
"Zhenyu Zhang <jump2cn@gmail.com>",
"Russell Parker <russell@benchling.com>",
"Dominik Tichy <tichydominik451@gmail.com>",
"Yana Rose <yana.v.rose@gmail.com>"
"Yana Rose <yana.v.rose@gmail.com>",
"Yakov Pechersky <ffxen158@gmail.com>",
"Christian Dominguez <christian.99dominguez@gmail.com>",
"Cai Huiyu <szmun.caihy@gmail.com>",
"Ryan DiRisio <rjdiris@gmail.com>"
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^5.0.0",
"@graphql-codegen/cli": "^4.0.1",
"@graphql-codegen/time": "^5.0.0",
"@graphql-codegen/typescript": "^4.0.1",
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
"@graphql-codegen/typescript-graphql-request": "^5.0.0",
"@graphql-codegen/typescript-operations": "^4.0.1",
"@types/cors": "^2.8.13",
"@types/gl": "^6.0.2",
"@types/jpeg-js": "^0.3.7",
"@types/pngjs": "^6.0.1",
"@types/jest": "^29.5.2",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@typescript-eslint/eslint-plugin": "^5.61.0",
"@typescript-eslint/parser": "^5.61.0",
"@types/cors": "^2.8.17",
"@types/gl": "^6.0.5",
"@types/pngjs": "^6.0.5",
"@types/jest": "^29.5.12",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"benchmark": "^2.1.4",
"concurrently": "^8.2.0",
"cpx2": "^5.0.0",
"concurrently": "^8.2.2",
"cpx2": "^7.0.1",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.8.1",
"eslint": "^8.44.0",
"css-loader": "^7.1.1",
"eslint": "^8.57.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^11.1.1",
"graphql": "^16.7.1",
"fs-extra": "^11.2.0",
"http-server": "^14.1.1",
"jest": "^29.6.1",
"mini-css-extract-plugin": "^2.7.6",
"jest": "^29.7.0",
"jpeg-js": "^0.4.4",
"mini-css-extract-plugin": "^2.9.0",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.63.6",
"sass-loader": "^13.3.2",
"simple-git": "^3.19.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sass": "^1.76.0",
"sass-loader": "^14.2.1",
"simple-git": "^3.24.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.3",
"ts-jest": "^29.1.1",
"typescript": "^5.1.6",
"webpack": "^5.88.1",
"style-loader": "^4.0.0",
"ts-jest": "^29.1.2",
"typescript": "^5.4.5",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@types/argparse": "^2.0.10",
"@types/benchmark": "^2.1.2",
"@types/compression": "1.7.2",
"@types/express": "^4.17.17",
"@types/node": "^16.18.38",
"@types/node-fetch": "^2.6.4",
"@types/swagger-ui-dist": "3.30.1",
"@types/argparse": "^2.0.16",
"@types/benchmark": "^2.1.5",
"@types/compression": "1.7.5",
"@types/express": "^4.17.21",
"@types/node": "^18.19.31",
"@types/node-fetch": "^2.6.11",
"@types/swagger-ui-dist": "3.30.4",
"argparse": "^2.0.1",
"body-parser": "^1.20.2",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.18.2",
"express": "^4.19.2",
"h264-mp4-encoder": "^1.0.12",
"immer": "^9.0.21",
"immutable": "^4.3.0",
"node-fetch": "^2.6.12",
"immer": "^10.1.1",
"immutable": "^4.3.5",
"io-ts": "^2.2.21",
"node-fetch": "^2.7.0",
"react-markdown": "^9.0.1",
"rxjs": "^7.8.1",
"swagger-ui-dist": "^5.1.0",
"tslib": "^2.6.0",
"swagger-ui-dist": "^5.17.2",
"tslib": "^2.6.2",
"util.promisify": "^1.1.2",
"xhr2": "^0.2.1"
},
"peerDependencies": {
"canvas": "^2.11.2",
"gl": "^6.0.2",
"jpeg-js": "^0.4.4",
"pngjs": "^6.0.0",
"react": "^18.1.0 || ^17.0.2 || ^16.14.0",
"react-dom": "^18.1.0 || ^17.0.2 || ^16.14.0"
},
"optionalDependencies": {
"gl": "^6.0.2",
"jpeg-js": "^0.4.4",
"pngjs": "^6.0.0"
"peerDependenciesMeta": {
"canvas": {
"optional": true
},
"gl": {
"optional": true
},
"jpeg-js": {
"optional": true
},
"pngjs": {
"optional": true
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -39,6 +39,14 @@ function copyViewer() {
addAnalytics(path.resolve(viewerDeployPath, 'index.html'));
}
function copyMe() {
console.log('\n###', 'copy me files');
const meBuildPath = path.resolve(buildDir, '../build/mesoscale-explorer/');
const meDeployPath = path.resolve(localPath, 'me/');
fse.copySync(meBuildPath, meDeployPath, { overwrite: true });
addAnalytics(path.resolve(meDeployPath, 'index.html'));
}
function copyDemos() {
console.log('\n###', 'copy demos files');
const lightingBuildPath = path.resolve(buildDir, '../build/examples/lighting/');
@@ -54,6 +62,7 @@ function copyDemos() {
function copyFiles() {
copyViewer();
copyMe();
copyDemos();
}

View File

@@ -8,7 +8,8 @@
import { Structure } from '../../mol-model/structure';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
import { createPluginUI } from '../../mol-plugin-ui/react18';
import { createPluginUI } from '../../mol-plugin-ui';
import { renderReact18 } from '../../mol-plugin-ui/react18';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
@@ -128,7 +129,7 @@ class Viewer {
? document.getElementById(elementOrId)
: elementOrId;
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
const plugin = await createPluginUI(element, spec);
const plugin = await createPluginUI({ target: element, spec, render: renderReact18 });
(plugin.customState as any) = {
colorPalette: {

View File

@@ -0,0 +1,280 @@
/**
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mp4Export } from '../../extensions/mp4-export';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { createPluginUI } from '../../mol-plugin-ui';
import { renderReact18 } from '../../mol-plugin-ui/react18';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { PluginSpec } from '../../mol-plugin/spec';
import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers';
import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
import { Backgrounds } from '../../extensions/backgrounds';
import { LeftPanel, RightPanel } from './ui/panels';
import { Color } from '../../mol-util/color';
import { SpacefillRepresentationProvider } from '../../mol-repr/structure/representation/spacefill';
import { PluginBehaviors } from '../../mol-plugin/behavior';
import { MesoFocusLoci } from './behavior/camera';
import { GraphicsMode, MesoscaleState } from './data/state';
import { MesoSelectLoci } from './behavior/select';
import { Transparency } from '../../mol-gl/webgl/render-item';
import { LoadModel, loadExampleEntry, loadPdb, loadPdbDev, loadUrl, openState } from './ui/states';
import { Asset } from '../../mol-util/assets';
import { AnimateCameraSpin } from '../../mol-plugin-state/animation/built-in/camera-spin';
import { AnimateCameraRock } from '../../mol-plugin-state/animation/built-in/camera-rock';
import { AnimateStateSnapshots } from '../../mol-plugin-state/animation/built-in/state-snapshots';
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug';
export type ExampleEntry = {
id: string,
label: string,
url: string,
type: 'molx' | 'molj' | 'cif' | 'bcif',
description?: string,
link?: string,
}
export type MesoscaleExplorerState = {
examples?: ExampleEntry[],
graphicsMode: GraphicsMode,
stateRef?: string,
stateCache: { [k: string]: any },
}
//
const Extensions = {
'backgrounds': PluginSpec.Behavior(Backgrounds),
'mp4-export': PluginSpec.Behavior(Mp4Export),
};
const DefaultMesoscaleExplorerOptions = {
customFormats: [] as [string, DataFormatProvider][],
extensions: ObjectKeys(Extensions),
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: 'blended' as Transparency,
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
viewportShowSelectionMode: false,
viewportShowAnimation: false,
viewportShowTrajectoryControls: false,
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,
graphicsMode: 'quality' as GraphicsMode
};
type MesoscaleExplorerOptions = typeof DefaultMesoscaleExplorerOptions;
export class MesoscaleExplorer {
constructor(public plugin: PluginUIContext) {
}
async loadExample(id: string) {
const entries = (this.plugin.customState as MesoscaleExplorerState).examples || [];
const entry = entries.find(e => e.id === id);
if (entry !== undefined) {
await loadExampleEntry(this.plugin, entry);
}
}
async loadUrl(url: string, type: 'molx' | 'molj' | 'cif' | 'bcif') {
await loadUrl(this.plugin, url, type);
}
async loadPdb(id: string) {
await loadPdb(this.plugin, id);
}
async loadPdbDev(id: string) {
await loadPdbDev(this.plugin, id);
}
static async create(elementOrId: string | HTMLElement, options: Partial<MesoscaleExplorerOptions> = {}) {
const definedOptions = {} as any;
// filter for defined properies only so the default values
// are property applied
for (const p of Object.keys(options) as (keyof MesoscaleExplorerOptions)[]) {
if (options[p] !== void 0) definedOptions[p] = options[p];
}
const o: MesoscaleExplorerOptions = { ...DefaultMesoscaleExplorerOptions, ...definedOptions };
const defaultSpec = DefaultPluginUISpec();
const spec: PluginUISpec = {
actions: defaultSpec.actions,
behaviors: [
PluginSpec.Behavior(PluginBehaviors.Camera.CameraAxisHelper),
PluginSpec.Behavior(PluginBehaviors.Camera.CameraControls),
PluginSpec.Behavior(MesoFocusLoci),
PluginSpec.Behavior(MesoSelectLoci),
...o.extensions.map(e => Extensions[e]),
],
animations: [
AnimateCameraSpin,
AnimateCameraRock,
AnimateStateSnapshots,
],
customParamEditors: defaultSpec.customParamEditors,
customFormats: o?.customFormats,
layout: {
initial: {
isExpanded: o.layoutIsExpanded,
showControls: o.layoutShowControls,
controlsDisplay: o.layoutControlsDisplay,
regionState: {
bottom: 'full',
left: o.collapseLeftPanel ? 'collapsed' : 'full',
right: o.collapseRightPanel ? 'hidden' : 'full',
top: 'full',
}
},
},
components: {
...defaultSpec.components,
controls: {
...defaultSpec.components?.controls,
top: 'none',
bottom: 'none',
left: LeftPanel,
right: RightPanel,
},
remoteState: 'none',
},
config: [
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
[PluginConfig.General.PixelScale, o.pixelScale],
[PluginConfig.General.PickScale, o.pickScale],
[PluginConfig.General.Transparency, o.transparency],
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
[PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat],
[PluginConfig.General.PowerPreference, o.powerPreference],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
[PluginConfig.Viewport.ShowTrajectoryControls, o.viewportShowTrajectoryControls],
[PluginConfig.State.DefaultServer, o.pluginStateServer],
[PluginConfig.State.CurrentServer, o.pluginStateServer],
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
[PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled],
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
[PluginConfig.Structure.SaccharideCompIdMapType, o.saccharideCompIdMapType],
]
};
const element = typeof elementOrId === 'string'
? document.getElementById(elementOrId)
: elementOrId;
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
const plugin = await createPluginUI({
target: element,
spec,
render: renderReact18,
onBeforeUIRender: async plugin => {
let examples: MesoscaleExplorerState['examples'] = undefined;
try {
examples = await plugin.fetch({ url: './examples/list.json', type: 'json' }).run();
} catch (e) {
console.log(e);
}
(plugin.customState as MesoscaleExplorerState) = {
examples,
graphicsMode: o.graphicsMode,
stateCache: {},
};
await MesoscaleState.init(plugin);
}
});
plugin.canvas3d?.setProps({
renderer: {
backgroundColor: Color(0x101010),
},
cameraFog: { name: 'off', params: {} },
hiZ: { enabled: true },
});
plugin.representation.structure.registry.clear();
plugin.representation.structure.registry.add(SpacefillRepresentationProvider);
plugin.state.setSnapshotParams({
image: true,
componentManager: false,
structureSelection: true,
behavior: true,
});
plugin.managers.lociLabels.clearProviders();
plugin.managers.dragAndDrop.addHandler('mesoscale-explorer', (files) => {
const sessions = files.filter(f => {
const fn = f.name.toLowerCase();
return fn.endsWith('.molx') || fn.endsWith('.molj');
});
if (sessions.length > 0) {
openState(plugin, sessions[0]);
} else {
plugin.runTask(plugin.state.data.applyAction(LoadModel, {
files: files.map(f => Asset.File(f)),
}));
}
return true;
});
plugin.state.events.object.created.subscribe(e => {
(plugin.customState as MesoscaleExplorerState).stateCache = {};
});
plugin.state.events.object.removed.subscribe(e => {
(plugin.customState as MesoscaleExplorerState).stateCache = {};
});
return new MesoscaleExplorer(plugin);
}
handleResize() {
this.plugin.layout.events.updated.next(void 0);
}
dispose() {
this.plugin.dispose();
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Loci } from '../../../mol-model/loci';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { PluginBehavior } from '../../../mol-plugin/behavior';
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
import { Binding } from '../../../mol-util/binding';
import { PluginCommands } from '../../../mol-plugin/commands';
import { Sphere3D } from '../../../mol-math/geometry';
const B = ButtonsType;
const M = ModifiersKeys;
const Trigger = Binding.Trigger;
const DefaultMesoFocusLociBindings = {
clickCenter: Binding([
Trigger(B.Flag.Primary, M.create()),
], 'Camera center', 'Click element using ${triggers}'),
clickCenterFocus: Binding([
Trigger(B.Flag.Secondary, M.create()),
], 'Camera center and focus', 'Click element using ${triggers}'),
};
const MesoFocusLociParams = {
minRadius: PD.Numeric(8, { min: 1, max: 50, step: 1 }),
extraRadius: PD.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the bounding-sphere radius of the Loci' }),
durationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'Camera transition duration' }),
centerOnly: PD.Boolean(true, { description: 'Keep current camera distance' }),
bindings: PD.Value(DefaultMesoFocusLociBindings, { isHidden: true }),
};
type MesoFocusLociProps = PD.Values<typeof MesoFocusLociParams>
export const MesoFocusLoci = PluginBehavior.create<MesoFocusLociProps>({
name: 'camera-meso-focus-loci',
category: 'interaction',
ctor: class extends PluginBehavior.Handler<MesoFocusLociProps> {
register(): void {
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => {
const { canvas3d } = this.ctx;
if (!canvas3d) return;
const loci = Loci.normalize(current.loci, this.ctx.managers.interactivity.props.granularity);
const sphere = Loci.getBoundingSphere(loci) || Sphere3D();
const { clickCenter, clickCenterFocus } = this.params.bindings;
const { durationMs, extraRadius, minRadius } = this.params;
const radius = Math.max(sphere.radius + extraRadius, minRadius);
if (Binding.match(clickCenter, button, modifiers)) {
if (Loci.isEmpty(current.loci)) {
PluginCommands.Camera.Reset(this.ctx, { });
return;
}
const snapshot = canvas3d.camera.getCenter(sphere.center);
canvas3d.requestCameraReset({ durationMs, snapshot });
} else if (Binding.match(clickCenterFocus, button, modifiers)) {
if (Loci.isEmpty(current.loci)) {
PluginCommands.Camera.Reset(this.ctx, { });
return;
}
const snapshot = canvas3d.camera.getCenter(sphere.center, radius);
canvas3d.requestCameraReset({ durationMs, snapshot });
}
});
}
},
params: () => MesoFocusLociParams,
display: { name: 'Camera Meso Focus Loci on Canvas' }
});

View File

@@ -0,0 +1,164 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { EveryLoci, Loci } from '../../../mol-model/loci';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { PluginBehavior } from '../../../mol-plugin/behavior';
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
import { Binding } from '../../../mol-util/binding';
import { PluginStateObject as SO } from '../../../mol-plugin-state/objects';
import { Structure, StructureElement } from '../../../mol-model/structure';
import { StateSelection } from '../../../mol-state';
import { StateTreeSpine } from '../../../mol-state/tree/spine';
import { Representation } from '../../../mol-repr/representation';
import { MarkerAction } from '../../../mol-util/marker-action';
import { PluginContext } from '../../../mol-plugin/context';
const B = ButtonsType;
const M = ModifiersKeys;
const Trigger = Binding.Trigger;
const DefaultMesoSelectLociBindings = {
clickToggleSelect: Binding([
Trigger(B.Flag.Primary, M.create({ shift: true })),
Trigger(B.Flag.Primary, M.create({ control: true })),
], 'Toggle select', 'Click element using ${triggers}'),
hoverHighlightOnly: Binding([
Trigger(B.Flag.None, M.create({ shift: true })),
Trigger(B.Flag.None, M.create({ control: true })),
], 'Highlight', 'Hover element using ${triggers}'),
};
const MesoSelectLociParams = {
bindings: PD.Value(DefaultMesoSelectLociBindings, { isHidden: true }),
};
type MesoSelectLociProps = PD.Values<typeof MesoSelectLociParams>
export const MesoSelectLoci = PluginBehavior.create<MesoSelectLociProps>({
name: 'camera-meso-select-loci',
category: 'interaction',
ctor: class extends PluginBehavior.Handler<MesoSelectLociProps> {
private spine: StateTreeSpine.Impl;
private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction) => {
if (!this.ctx.canvas3d) return;
this.ctx.canvas3d.mark(interactionLoci, action);
};
private applySelectMark(ref: string, clear?: boolean) {
const cell = this.ctx.state.data.cells.get(ref);
if (cell && SO.isRepresentation3D(cell.obj)) {
this.spine.current = cell;
const so = this.spine.getRootOfType(SO.Molecule.Structure);
if (so) {
if (clear) {
this.lociMarkProvider({ loci: Structure.Loci(so.data) }, MarkerAction.Deselect);
}
const loci = this.ctx.managers.structure.selection.getLoci(so.data);
this.lociMarkProvider({ loci }, MarkerAction.Select);
}
}
}
register(): void {
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => {
if (!this.ctx.canvas3d || this.ctx.isBusy) return;
const { clickToggleSelect } = this.params.bindings;
if (Binding.match(clickToggleSelect, button, modifiers)) {
if (Loci.isEmpty(current.loci)) {
this.ctx.managers.interactivity.lociSelects.deselectAll();
return;
}
const loci = Loci.normalize(current.loci, modifiers.control ? 'entity' : 'chain');
this.ctx.managers.interactivity.lociSelects.toggle({ loci }, false);
}
});
this.ctx.managers.interactivity.lociSelects.addProvider(this.lociMarkProvider);
this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, button, modifiers }) => {
if (!this.ctx.canvas3d || this.ctx.isBusy) return;
const pointerLock = !!this.ctx.canvas3dContext?.input.pointerLock;
const { hoverHighlightOnly } = this.params.bindings;
if (!pointerLock && Binding.match(hoverHighlightOnly, button, modifiers)) {
if (Loci.isEmpty(current.loci)) {
this.ctx.managers.interactivity.lociHighlights.clearHighlights();
return;
}
if (modifiers.control) {
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EveryLoci }, false);
} else {
const loci = Loci.normalize(current.loci, 'chain');
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci }, false);
}
}
if (Loci.isEmpty(current.loci)) {
this.ctx.behaviors.labels.highlight.next({ labels: [] });
} else {
const labels: string[] = [];
if (StructureElement.Loci.is(current.loci)) {
const cell = this.ctx.helpers.substructureParent.get(current.loci.structure);
labels.push(cell?.obj?.label || 'Unknown');
}
this.ctx.behaviors.labels.highlight.next({ labels });
}
});
this.ctx.managers.interactivity.lociHighlights.addProvider(this.lociMarkProvider);
let dimDisabled = false;
this.subscribeObservable(this.ctx.behaviors.interaction.keyReleased, ({ code, modifiers }) => {
if (!this.ctx.canvas3d) return;
if ((code.startsWith('Shift') && !modifiers.control) || (code.startsWith('Control') && !modifiers.shift)) {
if (dimDisabled) {
dimDisabled = false;
this.ctx.canvas3d?.setProps({ renderer: { dimStrength: 1 } }, true);
}
this.ctx.managers.interactivity.lociHighlights.clearHighlights();
}
});
this.subscribeObservable(this.ctx.behaviors.interaction.key, ({ modifiers }) => {
if (!this.ctx.canvas3d) return;
if (!dimDisabled && modifiers.control && modifiers.shift) {
dimDisabled = true;
this.ctx.canvas3d?.setProps({ renderer: { dimStrength: 0 } });
}
});
this.subscribeObservable(this.ctx.state.events.object.created, ({ ref }) => this.applySelectMark(ref));
// re-apply select-mark to all representation of an updated structure
this.subscribeObservable(this.ctx.state.events.object.updated, ({ ref, obj, oldObj, oldData, action }) => {
const cell = this.ctx.state.data.cells.get(ref);
if (cell && SO.Molecule.Structure.is(cell.obj)) {
const structure: Structure = obj.data;
const oldStructure: Structure | undefined = action === 'recreate' ? oldObj?.data :
action === 'in-place' ? oldData : undefined;
if (oldStructure &&
Structure.areEquivalent(structure, oldStructure) &&
Structure.areHierarchiesEqual(structure, oldStructure)) return;
const reprs = this.ctx.state.data.select(StateSelection.children(ref).ofType(SO.Molecule.Structure.Representation3D));
for (const repr of reprs) this.applySelectMark(repr.transform.ref, true);
}
});
}
unregister() {
this.ctx.managers.interactivity.lociSelects.removeProvider(this.lociMarkProvider);
this.ctx.managers.interactivity.lociHighlights.removeProvider(this.lociMarkProvider);
}
constructor(ctx: PluginContext, params: MesoSelectLociProps) {
super(ctx, params);
this.spine = new StateTreeSpine.Impl(ctx.state.data.cells);
}
},
params: () => MesoSelectLociParams,
display: { name: 'Camera Meso Select Loci on Canvas' }
});

View File

@@ -0,0 +1,178 @@
/**
* Copyright (c) 2019-2023 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>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { SortedArray } from '../../../../mol-data/int';
import { SymmetryOperator } from '../../../../mol-math/geometry';
import { Mat4 } from '../../../../mol-math/linear-algebra';
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
import { CustomStructureProperty } from '../../../../mol-model-props/common/custom-structure-property';
import { ElementIndex, EntityIndex, Model, Structure, Unit } from '../../../../mol-model/structure';
import { Assembly, Symmetry } from '../../../../mol-model/structure/model/properties/symmetry';
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
import { PluginContext } from '../../../../mol-plugin/context';
import { Task } from '../../../../mol-task';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
function createModelChainMap(model: Model) {
const builder = new Structure.StructureBuilder();
const units = new Map<string, Unit>();
const { label_asym_id, _rowCount } = model.atomicHierarchy.chains;
const { offsets } = model.atomicHierarchy.chainAtomSegments;
for (let i = 0; i < _rowCount; i++) {
const elements = SortedArray.ofBounds(offsets[i] as ElementIndex, offsets[i + 1] as ElementIndex);
const unit = builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, Unit.Trait.FastBoundary);
units.set(label_asym_id.value(i), unit);
}
return units;
}
function buildCellpackAssembly(model: Model, assembly: Assembly) {
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } });
const assembler = Structure.Builder({
coordinateSystem,
label: model.label,
});
const units = createModelChainMap(model);
for (const g of assembly.operatorGroups) {
for (const oper of g.operators) {
for (const id of g.asymIds!) {
const u = units.get(id);
if (u) {
assembler.addWithOperator(u, oper);
} else {
console.log(`missing asymId '${id}'`);
}
}
}
}
return assembler.getStructure();
}
export { CellpackAssembly };
type CellpackAssembly = typeof CellpackAssembly
const CellpackAssembly = PluginStateTransform.BuiltIn({
name: 'cellpack-assembly',
display: { name: 'Cellpack Assembly' },
from: PSO.Molecule.Model,
to: PSO.Molecule.Structure,
params: {
id: PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' }),
}
})({
canAutoUpdate({ newParams }) {
return true;
},
apply({ a, params }, plugin: PluginContext) {
return Task.create('Build Structure', async ctx => {
const model = a.data;
let id = params.id;
let asm: Assembly | undefined = void 0;
const symmetry = ModelSymmetry.Provider.get(model);
// if no id is specified, use the 1st assembly.
if (!id && symmetry && symmetry.assemblies.length !== 0) {
id = symmetry.assemblies[0].id;
}
if (!symmetry || symmetry.assemblies.length === 0) {
plugin.log.warn(`Model '${model.entryId}' has no assembly, returning model structure.`);
} else {
asm = Symmetry.findAssembly(model, id || '');
if (!asm) {
plugin.log.warn(`Model '${model.entryId}' has no assembly called '${id}', returning model structure.`);
}
}
const base = Structure.ofModel(model);
if (!asm) {
const label = { label: 'Model', description: Structure.elementDescription(base) };
return new PSO.Molecule.Structure(base, label);
}
const s = buildCellpackAssembly(model, asm);
const objProps = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
return new PSO.Molecule.Structure(s, objProps);
});
},
dispose({ b }) {
b?.data.customPropertyDescriptors.dispose();
}
});
type UnitsByEntity = Map<EntityIndex, Unit[]>;
const UnitsByEntity = CustomStructureProperty.createSimple<UnitsByEntity>('units_by_entity', 'root');
function getUnitsByEntity(structure: Structure): UnitsByEntity {
if (UnitsByEntity.get(structure).value) {
return UnitsByEntity.get(structure).value!;
}
const atomicIndex = structure.model.atomicHierarchy.index;
const map: UnitsByEntity = new Map();
for (const ug of structure.unitSymmetryGroups) {
const u = ug.units[0] as Unit.Atomic;
const e = atomicIndex.getEntityFromChain(u.chainIndex[u.elements[0]]);
if (!map.has(e)) map.set(e, []);
const entityUnits = map.get(e)!;
for (let i = 0, il = ug.units.length; i < il; ++i) {
entityUnits.push(ug.units[i]);
}
}
UnitsByEntity.set(structure, { value: map }, map);
return map;
}
export { CellpackStructure };
type CellpackStructure = typeof CellpackStructure
const CellpackStructure = PluginStateTransform.BuiltIn({
name: 'cellpack-structure',
display: { name: 'Cellpack Structure' },
from: PSO.Root,
to: PSO.Molecule.Structure,
params: {
structureRef: PD.Text(''),
entityId: PD.Text('')
}
})({
canAutoUpdate({ newParams }) {
return true;
},
apply({ a, params, dependencies }) {
return Task.create('Build Structure', async ctx => {
const parent = dependencies![params.structureRef].data as Structure;
const { entities } = parent.model;
const idx = entities.getEntityIndex(params.entityId);
const unitsByEntity = getUnitsByEntity(parent);
const units = unitsByEntity.get(idx) || [];
// if (!unitsByEntity.get(idx)) {
// console.log(entities.data.pdbx_description.value(idx));
// }
const structure = Structure.create(units);
const description = entities.data.pdbx_description.value(idx)[0] || 'model';
const label = description.split('.').at(-1) || a.label;
return new PSO.Molecule.Structure(structure, { label, description: `${a.description}` });
});
},
dispose({ b }) {
b?.data.customPropertyDescriptors.dispose();
}
});

View File

@@ -0,0 +1,227 @@
/**
* Copyright (c) 2019-2024 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>
*/
import { PluginStateObject } from '../../../../mol-plugin-state/objects';
import { StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
import { PluginContext } from '../../../../mol-plugin/context';
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
import { StateObjectRef, StateObjectSelector, StateBuilder } from '../../../../mol-state';
import { Color } from '../../../../mol-util/color';
import { ColorNames } from '../../../../mol-util/color/names';
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getDistinctGroupColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
import { CellpackAssembly, CellpackStructure } from './model';
function getSpacefillParams(color: Color, sizeFactor: number, graphics: GraphicsMode, merge?: boolean) {
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
return {
type: {
name: 'spacefill',
params: {
...SpacefillRepresentationProvider.defaultValues,
ignoreHydrogens: false,
instanceGranularity: true,
ignoreLight: true,
lodLevels: gmp.lodLevels,
quality: 'lowest', // avoid 'auto', triggers boundary calc
sizeFactor,
clip: {
variant: merge ? 'pixel' : 'instance',
objects: [],
},
clipPrimitive: true,
approximate: gmp.approximate,
alphaThickness: gmp.alphaThickness,
visuals: [merge ? 'structure-element-sphere' : 'element-sphere'],
},
},
colorTheme: {
name: 'uniform',
params: {
value: color,
saturation: 0,
lightness: 0,
}
},
sizeTheme: {
name: 'physical',
params: {
value: 1,
}
},
};
}
function getSizeFactor(name: string): number {
switch (name) {
case 'dLDL':
return 2.5;
case 'iLDL':
return 5;
case 'NP_CA':
case 'POL_CA':
case 'FactorH1':
case 'iIgM_Antibody_5mer':
// case 'MG_271_272_273_274_192MER': // has a coarse and an atomic part
return 2;
default: return 1;
}
}
export async function createCellpackHierarchy(plugin: PluginContext, trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory>) {
const builder = plugin.builders.structure;
const state = plugin.state.data;
const model = await builder.createModel(trajectory, { modelIndex: 0 });
const entities = model.data!.entities.data;
const compGroups = new Map<string, StateObjectSelector>();
const compIds = new Map<string, { idx: number, members: Map<string, number> }>();
const compColors = new Map<string, Color[]>();
const funcGroups = new Map<string, StateObjectSelector>();
const funcIds = new Map<string, { idx: number, size: number }>();
const funcColors = new Map<string, Color[]>();
const graphicsMode = MesoscaleState.get(plugin).graphics;
const groupParams = getMesoscaleGroupParams(graphicsMode);
const base = await state.build()
.to(model)
.apply(CellpackAssembly, { id: '' })
.commit();
const compRoot = await state.build()
.toRoot()
.applyOrUpdateTagged('group:comp:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `comp:`, label: 'compartment', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:comp:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.commit();
const funcRoot = await state.build()
.toRoot()
.applyOrUpdateTagged('group:func:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `func:`, label: 'function', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:func:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.commit();
if (entities._rowCount > 1) {
for (let i = 0; i < entities._rowCount; i++) {
const description = entities.pdbx_description.value(i)[0] || 'unknown compartment';
const d = description.split('.');
const n = d.slice(0, -1).join('.');
const l = d.at(-1)!;
if (!compIds.has(n)) {
compIds.set(n, { idx: compIds.size, members: new Map() });
}
const cm = compIds.get(n)!;
cm.members.set(l, cm.members.size);
const f = entities.details.value(i) || 'unknown function';
if (!funcIds.has(f)) {
funcIds.set(f, { idx: funcIds.size, size: 0 });
}
funcIds.get(f)!.size += 1;
}
//
const baseCompColors = getDistinctBaseColors(compIds.size, 0);
const compIdEntries = Array.from(compIds.entries());
for (let i = 0; i < compIdEntries.length; ++i) {
const [n, m] = compIdEntries[i];
const groupColors = getDistinctGroupColors(m.members.size, baseCompColors[i], 20, 0);
compColors.set(n, groupColors);
}
//
const baseFuncColors = getDistinctBaseColors(funcIds.size, 0);
const funcIdEntries = Array.from(funcIds.entries());
for (let i = 0; i < funcIdEntries.length; ++i) {
const [n, m] = funcIdEntries[i];
const groupColors = getDistinctGroupColors(m.size, baseFuncColors[i], 20, 0);
funcColors.set(n, groupColors);
}
//
for (let i = 0; i < entities._rowCount; i++) {
const description = entities.pdbx_description.value(i)[0] || 'unknown compartment';
const nodes = description.split('.');
for (let j = 0, jl = nodes.length - 1; j < jl; ++j) {
const n = nodes.slice(0, j + 1).join('.');
const p = nodes.slice(0, j).join('.');
if (!compGroups.has(n)) {
const colorIdx = compIds.get(n)?.idx;
const color = colorIdx !== undefined ? baseCompColors[colorIdx] : ColorNames.white;
const label = nodes[j];
const parent = compGroups.get(p) ?? compRoot;
parent.cell!.state.isCollapsed = false;
const group = await state.build()
.to(parent)
.applyOrUpdateTagged(`group:comp:${n}`, MesoscaleGroup, { ...groupParams, root: parent === compRoot, index: colorIdx, tag: `comp:${n}`, label, color: { type: 'generate', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `comp:${p}`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit({ revertOnError: true });
compGroups.set(n, group);
}
}
const f = entities.details.value(i) || 'unknown function';
if (!funcGroups.has(f)) {
const colorIdx = funcIds.get(f)?.idx;
const color = colorIdx !== undefined ? baseFuncColors[colorIdx] : ColorNames.white;
const group = await state.build()
.to(funcRoot)
.applyOrUpdateTagged(`group:func:${f}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `func:${f}`, label: f, color: { type: 'custom', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'func:', state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit({ revertOnError: true });
funcGroups.set(f, group);
}
}
//
await state.transaction(async () => {
try {
const dependsOn = [base.ref];
plugin.animationLoop.stop({ noDraw: true });
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
for (let i = 0; i < entities._rowCount; i++) {
const description = entities.pdbx_description.value(i)[0] || 'model';
const d = description.split('.');
const n = d.slice(0, -1).join('.');
const l = d.at(-1)!;
const f = entities.details.value(i) || 'unknown function';
const color = compColors.get(n)![compIds.get(n)!.members.get(l)!];
const sizeFactor = getSizeFactor(l);
build = build
.toRoot()
.apply(CellpackStructure, { structureRef: base.ref, entityId: entities.id.value(i) }, { dependsOn })
.apply(StructureRepresentation3D, getSpacefillParams(color, sizeFactor, graphicsMode), { tags: [`comp:${n}`, `func:${f}`] });
}
await build.commit();
} catch (e) {
console.error(e);
plugin.log.error(e);
} finally {
plugin.animationLoop.start();
}
}).run();
} else {
const dependsOn = [base.ref];
const merge = (
base.data &&
base.data.model.entities.data._rowCount === 1 &&
base.data.unitSymmetryGroups.length > 100 &&
base.data.unitSymmetryGroups.some(usg => usg.units.length > 1)
);
await state.build()
.toRoot()
.apply(CellpackStructure, { structureRef: base.ref, entityId: entities.id.value(0) }, { dependsOn })
.apply(StructureRepresentation3D, getSpacefillParams(ColorNames.lightgray, 1, graphicsMode, merge), { tags: [`comp:`, `func:`] })
.commit();
}
}

View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4 } from '../../../../mol-math/linear-algebra/3d/mat4';
import { ElementIndex, Model, Structure, Unit } from '../../../../mol-model/structure';
import { PluginStateObject as SO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
import { Task } from '../../../../mol-task';
import { StateObject, StateTransformer } from '../../../../mol-state';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { SymmetryOperator } from '../../../../mol-math/geometry';
import { mergeUnits, partitionUnits } from '../util';
import { Assembly, Symmetry } from '../../../../mol-model/structure/model/properties/symmetry';
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
import { SortedArray } from '../../../../mol-data/int';
import { GenericInstances, getTransforms } from './preset';
import { Asset } from '../../../../mol-util/assets';
import { PluginContext } from '../../../../mol-plugin/context';
import { deepEqual } from '../../../../mol-util';
function createModelChainMap(model: Model) {
const builder = new Structure.StructureBuilder();
const units = new Map<string, Unit>();
const { label_asym_id, _rowCount } = model.atomicHierarchy.chains;
const { offsets } = model.atomicHierarchy.chainAtomSegments;
for (let i = 0; i < _rowCount; i++) {
const elements = SortedArray.ofBounds(offsets[i] as ElementIndex, offsets[i + 1] as ElementIndex);
const unit = builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, Unit.Trait.FastBoundary);
units.set(label_asym_id.value(i), unit);
}
return units;
}
function buildAssembly(model: Model, assembly: Assembly) {
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } });
const assembler = Structure.Builder({
coordinateSystem,
label: model.label,
});
const units = createModelChainMap(model);
for (const g of assembly.operatorGroups) {
for (const oper of g.operators) {
for (const id of g.asymIds!) {
const u = units.get(id);
if (u) {
assembler.addWithOperator(u, oper);
} else {
console.log(`missing asymId '${id}'`);
}
}
}
}
return assembler.getStructure();
}
const EmptyInstances: GenericInstances<Asset> = {
positions: { data: [] },
rotations: { variant: 'euler', data: [] }
};
export { StructureFromGeneric };
type StructureFromGeneric = typeof StructureFromGeneric
const StructureFromGeneric = PluginStateTransform.BuiltIn({
name: 'structure-from-generic',
display: { name: 'Structure from Generic', description: 'Create a molecular structure from Generic models.' },
from: SO.Molecule.Model,
to: SO.Molecule.Structure,
params: {
instances: PD.Value<GenericInstances<Asset>>(EmptyInstances),
label: PD.Optional(PD.Text('')),
cellSize: PD.Numeric(500, { min: 0, max: 10000, step: 100 }),
}
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Build Structure', async ctx => {
const transforms = await getTransforms(plugin, params.instances);
if (transforms.length === 0) return StateObject.Null;
const model = a.data;
const label = params.label || model.label;
const base = Structure.ofModel(a.data);
let structure: Structure;
if (transforms.length === 1 && Mat4.isIdentity(transforms[0])) {
const symmetry = ModelSymmetry.Provider.get(model);
const id = symmetry?.assemblies[0]?.id;
const asm = Symmetry.findAssembly(model, id || '');
if (asm) {
structure = buildAssembly(model, asm);
} else {
const mergedUnits = partitionUnits(base.units, params.cellSize);
structure = Structure.create(mergedUnits, { label });
}
} else {
const assembler = Structure.Builder({ label });
const unit = mergeUnits(base.units, 0);
for (let i = 0, il = transforms.length; i < il; ++i) {
const t = transforms[i];
const op = SymmetryOperator.create(`op-${i}`, t);
assembler.addWithOperator(unit, op);
}
structure = assembler.getStructure();
}
const props = { label, description: Structure.elementDescription(structure) };
return new SO.Molecule.Structure(structure, props);
});
},
update({ newParams, oldParams }, plugin: PluginContext) {
if (deepEqual(newParams, oldParams)) {
return StateTransformer.UpdateResult.Unchanged;
}
if (oldParams.instances) releaseInstances(plugin, oldParams.instances);
return StateTransformer.UpdateResult.Recreate;
},
dispose({ b, params }, plugin: PluginContext) {
b?.data.customPropertyDescriptors.dispose();
if (params?.instances) releaseInstances(plugin, params.instances);
}
});
function releaseInstances(plugin: PluginContext, instances: GenericInstances<Asset>) {
if (!Array.isArray(instances.positions.data)) {
plugin.managers.asset.release(instances.positions.data.file);
}
if (!Array.isArray(instances.rotations.data)) {
plugin.managers.asset.release(instances.rotations.data.file);
}
}

View File

@@ -0,0 +1,581 @@
/**
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4 } from '../../../../mol-math/linear-algebra/3d/mat4';
import { StateBuilder, StateObjectSelector } from '../../../../mol-state';
import { PluginContext } from '../../../../mol-plugin/context';
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
import { Color } from '../../../../mol-util/color';
import { utf8Read } from '../../../../mol-io/common/utf8';
import { Mat3, Quat, Vec3 } from '../../../../mol-math/linear-algebra';
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getGraphicsModeProps, getMesoscaleGroupParams, updateColors } from '../state';
import { ColorNames } from '../../../../mol-util/color/names';
import { ShapeRepresentation3D, StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
import { ParseCif, ParsePly, ReadFile } from '../../../../mol-plugin-state/transforms/data';
import { ModelFromTrajectory, ShapeFromPly, TrajectoryFromGRO, TrajectoryFromMOL, TrajectoryFromMOL2, TrajectoryFromMmCif, TrajectoryFromPDB, TrajectoryFromSDF, TrajectoryFromXYZ } from '../../../../mol-plugin-state/transforms/model';
import { Euler } from '../../../../mol-math/linear-algebra/3d/euler';
import { Asset } from '../../../../mol-util/assets';
import { Clip } from '../../../../mol-util/clip';
import { StructureFromGeneric } from './model';
import { getFileNameInfo } from '../../../../mol-util/file-info';
import { NumberArray } from '../../../../mol-util/type-helpers';
import { BaseGeometry } from '../../../../mol-geo/geometry/base';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
function getSpacefillParams(color: Color, sizeFactor: number, graphics: GraphicsMode, clipVariant: Clip.Variant) {
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
return {
type: {
name: 'spacefill',
params: {
...SpacefillRepresentationProvider.defaultValues,
ignoreHydrogens: true,
instanceGranularity: true,
ignoreLight: true,
lodLevels: gmp.lodLevels.map(l => {
return {
...l,
stride: Math.max(1, Math.round(l.stride / Math.pow(sizeFactor, l.scaleBias)))
};
}),
quality: 'lowest', // avoid 'auto', triggers boundary calc
sizeFactor,
clip: {
variant: clipVariant,
objects: [],
},
clipPrimitive: true,
approximate: gmp.approximate,
alphaThickness: gmp.alphaThickness,
},
},
colorTheme: {
name: 'uniform',
params: {
value: color,
saturation: 0,
lightness: 0,
}
},
sizeTheme: {
name: 'physical',
params: {
scale: 1,
}
},
};
}
function getPlyShapeParams(color: Color, clipVariant: Clip.Variant) {
return {
...PD.getDefaultValues(BaseGeometry.Params),
instanceGranularity: true,
ignoreLight: true,
clip: {
variant: clipVariant,
objects: [],
},
quality: 'custom',
doubleSided: true,
coloring: {
name: 'uniform',
params: { color }
},
grouping: {
name: 'none',
params: {}
},
material: {
metalness: 0.0,
roughness: 1.0,
bumpiness: 1.0,
},
bumpAmplitude: 0.1,
bumpFrequency: 0.1 / 10,
};
}
export async function createGenericHierarchy(plugin: PluginContext, file: Asset.File) {
const asset = await plugin.runTask(plugin.managers.asset.resolve(file, 'zip'));
let manifest: GenericManifest;
// TODO: remove special handling for martini prototype
if (asset.data['instanced_structure.json']) {
const d = asset.data['instanced_structure.json'];
const t = utf8Read(d, 0, d.length);
const martini = JSON.parse(t) as { model: string, positions: Vec3[], rotations: Vec3[], function: string }[];
console.log(martini);
manifest = martiniToGeneric(martini);
} else if (asset.data['manifest.json']) {
const d = asset.data['manifest.json'];
const t = utf8Read(d, 0, d.length);
manifest = JSON.parse(t) as GenericManifest;
} else {
throw new Error('no manifest found');
}
console.log(manifest);
const state = plugin.state.data;
const graphicsMode = MesoscaleState.get(plugin).graphics;
const groupParams = getMesoscaleGroupParams(graphicsMode);
async function addGroup(g: GenericGroup, cell: StateObjectSelector, parent: string) {
const group = await state.build()
.to(cell)
.apply(MesoscaleGroup, { ...groupParams, index: undefined, tag: `${g.root}:${g.id}`, label: g.label || g.id, color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: [`group:${g.root}:${g.id}`, g.root === parent ? `${g.root}:` : `${g.root}:${parent}`], state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit();
if (g.children) {
for (const c of g.children) {
await addGroup(c, group, g.id);
}
}
}
for (const r of manifest.roots) {
const root = await state.build()
.toRoot()
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `${r.id}:`, label: r.label || r.id, color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `group:${r.id}:`, state: { isCollapsed: false, isHidden: groupParams.hidden } })
.commit();
if (r.children) {
for (const c of r.children!) {
await addGroup(c, root, r.id);
}
}
}
const transformAssets = new Map<string, Asset>();
const getTransformAsset = (file: string) => {
if (!transformAssets.has(file)) {
const d = asset.data[file];
transformAssets.set(file, Asset.File(new File([d], file)));
}
return transformAssets.get(file)!;
};
const getAssetInstances = (instances: GenericInstances<string>): GenericInstances<Asset> => {
return {
positions: {
data: Array.isArray(instances.positions.data)
? instances.positions.data
: {
file: getTransformAsset(instances.positions.data.file),
view: instances.positions.data.view,
},
type: instances.positions.type,
},
rotations: {
data: Array.isArray(instances.rotations.data)
? instances.rotations.data
: {
file: getTransformAsset(instances.rotations.data.file),
view: instances.rotations.data.view,
},
variant: instances.rotations.variant,
type: instances.rotations.type,
}
};
};
await state.transaction(async () => {
try {
plugin.animationLoop.stop({ noDraw: true });
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
for (const ent of manifest.entities) {
const d = asset.data[ent.file];
const info = getFileNameInfo(ent.file);
const isBinary = ['bcif'].includes(info.ext);
const t = isBinary ? d : utf8Read(d, 0, d.length);
const file = Asset.File(new File([t], ent.file));
const color = ColorNames.skyblue;
const label = ent.label || ent.file.split('.')[0];
const sizeFactor = ent.sizeFactor || 1;
const tags = ent.groups.map(({ id, root }) => `${root}:${id}`);
const instances = ent.instances && getAssetInstances(ent.instances);
build = build
.toRoot()
.apply(ReadFile, { file, label, isBinary });
if (['gro', 'cif', 'mmcif', 'mcif', 'bcif', 'pdb', 'ent', 'xyz', 'mol', 'sdf', 'sd', 'mol2'].includes(info.ext)) {
if (['gro'].includes(info.ext)) {
build = build.apply(TrajectoryFromGRO);
} else if (['cif', 'mmcif', 'mcif', 'bcif'].includes(info.ext)) {
build = build.apply(ParseCif).apply(TrajectoryFromMmCif);
} else if (['pdb', 'ent'].includes(info.ext)) {
build = build.apply(TrajectoryFromPDB);
} else if (['xyz'].includes(info.ext)) {
build = build.apply(TrajectoryFromXYZ);
} else if (['mol'].includes(info.ext)) {
build = build.apply(TrajectoryFromMOL);
} else if (['sdf', 'sd'].includes(info.ext)) {
build = build.apply(TrajectoryFromSDF);
} else if (['mol2'].includes(info.ext)) {
build = build.apply(TrajectoryFromMOL2);
}
let clipVariant: Clip.Variant = 'pixel';
if (ent.instances) {
if (Array.isArray(ent.instances.positions.data)) {
clipVariant = ent.instances.positions.data.length <= 3 ? 'pixel' : 'instance';
} else {
const byteLength = ent.instances.positions.data.view
? ent.instances.positions.data.view.byteLength
: asset.data[ent.instances.positions.data.file].length;
clipVariant = byteLength <= 3 * 4 ? 'pixel' : 'instance';
}
}
build = build
.apply(ModelFromTrajectory, { modelIndex: 0 })
.apply(StructureFromGeneric, { instances, label })
.apply(StructureRepresentation3D, getSpacefillParams(color, sizeFactor, graphicsMode, clipVariant), { tags });
} else if (['ply'].includes(info.ext)) {
if (['ply'].includes(info.ext)) {
const transforms = await getTransforms(plugin, instances);
const clipVariant = transforms.length === 1 ? 'pixel' : 'instance';
build = build
.apply(ParsePly)
.apply(ShapeFromPly, { label, transforms })
.apply(ShapeRepresentation3D, getPlyShapeParams(color, clipVariant), { tags });
}
} else {
console.warn(`unknown file format '${info.ext}'`);
}
}
await build.commit();
const rootId = `${manifest.roots[0].id}:`;
const values = { type: 'group-generate', value: ColorNames.white, lightness: 0, alpha: 1 };
await updateColors(plugin, values, rootId, '');
} catch (e) {
console.error(e);
plugin.log.error(e);
} finally {
plugin.animationLoop.start();
}
}).run();
asset.dispose();
}
//
type GenericRoot = {
id: string
label?: string
description?: string
children: GenericGroup[]
}
type GenericGroup = {
id: string
/** reference to `${GenericRoot.id}` */
root: string
label?: string
description?: string
children?: GenericGroup[]
}
type GenericEntity = {
/**
* the entity file name
*
* the following extensions/formats are supported
*
* structures
* - gro
* - cif, mmcif, mcif, bcif
* - pdb, ent
* - xyz
* - mol
* - sdf, sd
* - mol2
*
* meshes
* - ply
*/
file: string
label?: string
description?: string
groups: {
/** reference to `${GenericGroup.id}` */
id: string,
/** reference to `${GenericGroup.root}` */
root: string
}[]
/**
* defaults to a single, untransformed instance
*/
instances?: GenericInstances<string>
/**
* defaults to 1 (assuming fully atomic structures)
* for C-alpha only structures set to 2
* for Martini coarse-grained set to 1.5
*/
sizeFactor?: number
}
type BinaryData<T extends string | Asset> = {
file: T,
view?: {
byteOffset: number,
byteLength: number
}
}
export type GenericInstances<T extends string | Asset> = {
/**
* translation vectors in Angstrom
* [x0, y0, z0, ..., xn, yn, zn] with n = count - 1
*/
positions: {
/**
* either the data itself or a pointer to binary data
*/
data: number[] | BinaryData<T>
/**
* how to interpret the data
* defaults to `{ kind: 'Array', type: 'Float32' }`
*/
type?: { kind: 'Array', type: 'Float32' }
// TODO: maybe worthwhile in the future, mirroring encoders from BinaryCIF
// | { kind: 'IntegerPackedFixedPoint', byteCount: number, srcSize: number, factor: number, srcType: 'Float32' }
}
/**
* euler angles in XYZ order
* [x0, y0, z0, ..., xn, yn, zn] with n = count - 1
*
* quaternion rotations in XYZW order
* [x0, y0, z0, w0, ..., xn, yn, zn, wn] with n = count - 1
*
* rotation matrices in row-major order
* [m00_0, m01_0, m02_0, ..., m20_n, m21_n, m22_n] with n = count - 1
*/
rotations: {
variant: 'euler' | 'quaternion' | 'matrix',
/**
* either the data itself or a pointer to binary data
*/
data: number[] | BinaryData<T>
/**
* how to interpret the data
* defaults to `{ kind: 'Array', type: 'Float32' }`
*/
type?: { kind: 'Array', type: 'Float32' }
}
}
type GenericFrame = {
time: number
entities: {
file: string
instances: GenericInstances<string>
}[]
}
type GenericTrajectory = {
label?: string
description?: string
frames: GenericFrame[]
}
type GenericManifest = {
label?: string
description?: string
roots: GenericRoot[]
entities: GenericEntity[]
trajectories?: GenericTrajectory[]
}
//
const p = Vec3();
const q = Quat();
const m = Mat3();
const e = Euler();
async function getPositions(plugin: PluginContext, p: GenericInstances<Asset>['positions']): Promise<NumberArray> {
if (Array.isArray(p.data)) {
return p.data;
} else {
const a = await plugin.runTask(plugin.managers.asset.resolve(p.data.file, 'binary'));
const o = p.data.view?.byteOffset || 0;
const l = p.data.view?.byteLength || a.data.byteLength;
return new Float32Array(a.data.buffer, o + a.data.byteOffset, l / 4);
}
};
async function getRotations(plugin: PluginContext, r: GenericInstances<Asset>['rotations']): Promise<NumberArray> {
if (Array.isArray(r.data)) {
return r.data;
} else {
const a = await plugin.runTask(plugin.managers.asset.resolve(r.data.file, 'binary'));
const o = r.data.view?.byteOffset || 0;
const l = r.data.view?.byteLength || a.data.byteLength;
return new Float32Array(a.data.buffer, o + a.data.byteOffset, l / 4);
}
};
export async function getTransforms(plugin: PluginContext, instances?: GenericInstances<Asset>) {
const transforms: Mat4[] = [];
if (instances) {
const positions = await getPositions(plugin, instances.positions);
const rotations = await getRotations(plugin, instances.rotations);
for (let i = 0, il = positions.length / 3; i < il; ++i) {
Vec3.fromArray(p, positions, i * 3);
if (instances.rotations.variant === 'matrix') {
Mat3.fromArray(m, rotations, i * 9);
const t = Mat4.fromMat3(Mat4(), m);
Mat4.setTranslation(t, p);
transforms.push(t);
} else if (instances.rotations.variant === 'quaternion') {
Quat.fromArray(q, rotations, i * 4);
const t = Mat4.fromQuat(Mat4(), q);
Mat4.setTranslation(t, p);
transforms.push(t);
} else if (instances.rotations.variant === 'euler') {
Euler.fromArray(e, rotations, i * 3);
Quat.fromEuler(q, e, 'XYZ');
const t = Mat4.fromQuat(Mat4(), q);
Mat4.setTranslation(t, p);
transforms.push(t);
}
}
} else {
transforms.push(Mat4.identity());
}
return transforms;
}
//
type MartiniManifest = {
model: string,
positions: Vec3[],
rotations: Vec3[],
function: string
}[]
function martiniToGeneric(martini: MartiniManifest): GenericManifest {
const functionRoot: GenericRoot = {
id: 'function',
label: 'Function',
description: 'Functional classification',
children: [],
};
const entities: GenericEntity[] = [];
const seenGroups = new Set<string>();
const membraneGroup = {
id: 'membane',
root: 'function',
label: 'Membrane',
children: [] as GenericGroup[],
};
functionRoot.children!.push(membraneGroup);
seenGroups.add(membraneGroup.id);
const lipidsGroup = {
id: 'lipid',
root: 'function',
label: 'Lipid',
children: [] as GenericGroup[],
};
membraneGroup.children!.push(lipidsGroup);
seenGroups.add(lipidsGroup.id);
const upperGroup = {
id: 'upper',
root: 'function',
label: 'Upper Leaflet',
};
lipidsGroup.children!.push(upperGroup);
seenGroups.add(upperGroup.id);
const lowerGroup = {
id: 'lower',
root: 'function',
label: 'Lower Leaflet',
};
lipidsGroup.children!.push(lowerGroup);
seenGroups.add(lowerGroup.id);
const memprotGroup = {
id: 'memprot',
root: 'function',
label: 'Transmembrane Protein',
};
membraneGroup.children!.push(memprotGroup);
seenGroups.add(memprotGroup.id);
for (const e of martini) {
const label = e.model.split('.')[0];
const group = e.function || 'Metabolite';
const positions = {
data: e.positions.flat().map(x => Math.round((x * 10) * 100) / 100)
};
const rotations = {
data: e.rotations.flat().map(x => Math.round(x * 100) / 100),
variant: 'euler' as const,
};
if (group.includes('lower leaflet')) {
entities.push({
file: e.model,
label: label.substring(15),
groups: [{ root: 'function', id: 'lower' }],
instances: { positions, rotations },
sizeFactor: 1.5,
});
} else if (group.includes('upper leaflet')) {
entities.push({
file: e.model,
label: label.substring(15),
groups: [{ root: 'function', id: 'upper' }],
instances: { positions, rotations },
sizeFactor: 1.5,
});
} else if (group.length === 4) {
entities.push({
file: e.model,
label: label.substring(17),
groups: [{ root: 'function', id: 'memprot' }],
instances: { positions, rotations },
sizeFactor: 1.5,
});
} else {
if (!seenGroups.has(group)) {
functionRoot.children!.push({
id: group,
root: 'function',
label: group,
});
seenGroups.add(group);
}
entities.push({
file: e.model,
label,
groups: [{ root: 'function', id: group }],
instances: { positions, rotations },
sizeFactor: 1.5,
});
}
}
return {
label: 'Martini',
description: 'Martini coarse-grained model',
roots: [functionRoot],
entities,
};
}

View File

@@ -0,0 +1,202 @@
/**
* Copyright (c) 2023 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 { SortedArray } from '../../../../mol-data/int';
import { SymmetryOperator } from '../../../../mol-math/geometry';
import { Mat4 } from '../../../../mol-math/linear-algebra';
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
import { CustomStructureProperty } from '../../../../mol-model-props/common/custom-structure-property';
import { ElementIndex, EntityIndex, Model, Structure, Unit } from '../../../../mol-model/structure';
import { Assembly, Symmetry } from '../../../../mol-model/structure/model/properties/symmetry';
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
import { PluginContext } from '../../../../mol-plugin/context';
import { StateTransformer } from '../../../../mol-state/transformer';
import { Task } from '../../../../mol-task';
import { deepEqual } from '../../../../mol-util';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { partitionUnits } from '../util';
function createModelChainMap(model: Model) {
const builder = new Structure.StructureBuilder();
const units = new Map<string, Unit>();
const { label_asym_id, _rowCount } = model.atomicHierarchy.chains;
const { offsets } = model.atomicHierarchy.chainAtomSegments;
for (let i = 0; i < _rowCount; i++) {
const elements = SortedArray.ofBounds(offsets[i] as ElementIndex, offsets[i + 1] as ElementIndex);
const unit = builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, Unit.Trait.FastBoundary);
units.set(label_asym_id.value(i), unit);
}
return units;
}
function buildAssembly(model: Model, assembly: Assembly) {
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } });
const assembler = Structure.Builder({
coordinateSystem,
label: model.label,
});
const units = createModelChainMap(model);
for (const g of assembly.operatorGroups) {
for (const oper of g.operators) {
for (const id of g.asymIds!) {
const u = units.get(id);
if (u) {
assembler.addWithOperator(u, oper);
} else {
console.log(`missing asymId '${id}'`);
}
}
}
}
return assembler.getStructure();
}
export { MmcifAssembly };
type MmcifAssembly = typeof MmcifAssembly
const MmcifAssembly = PluginStateTransform.BuiltIn({
name: 'mmcif-assembly',
display: { name: 'Mmcif Assembly' },
from: PSO.Molecule.Model,
to: PSO.Molecule.Structure,
params: {
id: PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' }),
}
})({
canAutoUpdate({ newParams }) {
return true;
},
apply({ a, params }, plugin: PluginContext) {
return Task.create('Build Structure', async ctx => {
const model = a.data;
let id = params.id;
let asm: Assembly | undefined = void 0;
const symmetry = ModelSymmetry.Provider.get(model);
// if no id is specified, use the 1st assembly.
if (!id && symmetry && symmetry.assemblies.length !== 0) {
id = symmetry.assemblies[0].id;
}
if (!symmetry || symmetry.assemblies.length === 0) {
plugin.log.warn(`Model '${model.entryId}' has no assembly, returning model structure.`);
} else {
asm = Symmetry.findAssembly(model, id || '');
if (!asm) {
plugin.log.warn(`Model '${model.entryId}' has no assembly called '${id}', returning model structure.`);
}
}
const base = Structure.ofModel(model);
if (!asm) {
const label = { label: 'Model', description: Structure.elementDescription(base) };
return new PSO.Molecule.Structure(base, label);
}
const s = buildAssembly(model, asm);
const objProps = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
return new PSO.Molecule.Structure(s, objProps);
});
},
update({ newParams, oldParams }) {
return deepEqual(newParams, oldParams)
? StateTransformer.UpdateResult.Unchanged
: StateTransformer.UpdateResult.Recreate;
},
dispose({ b }) {
b?.data.customPropertyDescriptors.dispose();
}
});
type UnitsByEntity = Map<EntityIndex, Unit[]>;
const UnitsByEntity = CustomStructureProperty.createSimple<UnitsByEntity>('units_by_entity', 'root');
function getUnitsByEntity(structure: Structure): UnitsByEntity {
if (UnitsByEntity.get(structure).value) {
return UnitsByEntity.get(structure).value!;
}
const atomicIndex = structure.model.atomicHierarchy.index;
const spheresIndex = structure.model.coarseHierarchy.spheres;
const map: UnitsByEntity = new Map();
for (const ug of structure.unitSymmetryGroups) {
const u = ug.units[0];
let e: EntityIndex;
if (Unit.isAtomic(u)) {
e = atomicIndex.getEntityFromChain(u.chainIndex[u.elements[0]]);
} else if (Unit.isSpheres(u)) {
e = spheresIndex.getEntityFromChain(u.coarseElements.chainElementSegments.index[u.elements[0]]);
} else {
continue;
}
if (!map.has(e)) map.set(e, []);
const entityUnits = map.get(e)!;
for (let i = 0, il = ug.units.length; i < il; ++i) {
entityUnits.push(ug.units[i]);
}
}
UnitsByEntity.set(structure, { value: map }, map);
return map;
}
export { MmcifStructure };
type MmcifStructure = typeof MmcifStructure
const MmcifStructure = PluginStateTransform.BuiltIn({
name: 'mmcif-structure',
display: { name: 'Mmcif Structure' },
from: PSO.Root,
to: PSO.Molecule.Structure,
params: {
structureRef: PD.Text(''),
entityId: PD.Text(''),
cellSize: PD.Numeric(500, { min: 0, max: 10000, step: 100 }),
}
})({
canAutoUpdate({ newParams }) {
return true;
},
apply({ a, params, dependencies }) {
return Task.create('Build Structure', async ctx => {
const parent = dependencies![params.structureRef].data as Structure;
const { entities } = parent.model;
const idx = entities.getEntityIndex(params.entityId);
const unitsByEntity = getUnitsByEntity(parent);
const units = unitsByEntity.get(idx) || [];
const unitCount = units.length;
let structure: Structure;
if (unitCount > 1 && units.every(u => u.conformation.operator.isIdentity)) {
const mergedUnits = partitionUnits(units, params.cellSize);
structure = Structure.create(mergedUnits);
} else {
structure = Structure.create(units);
}
const label = entities.data.pdbx_description.value(idx).join(', ') || 'model';
return new PSO.Molecule.Structure(structure, { label, description: `${a.description}` });
});
},
update({ newParams, oldParams }) {
return deepEqual(newParams, oldParams)
? StateTransformer.UpdateResult.Unchanged
: StateTransformer.UpdateResult.Recreate;
},
dispose({ b }) {
b?.data.customPropertyDescriptors.dispose();
}
});

View File

@@ -0,0 +1,182 @@
/**
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
import { PluginStateObject } from '../../../../mol-plugin-state/objects';
import { StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
import { PluginContext } from '../../../../mol-plugin/context';
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
import { StateObjectRef, StateObjectSelector, StateBuilder } from '../../../../mol-state';
import { Clip } from '../../../../mol-util/clip';
import { Color } from '../../../../mol-util/color';
import { ColorNames } from '../../../../mol-util/color/names';
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getDistinctGroupColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
import { MmcifAssembly, MmcifStructure } from './model';
function getSpacefillParams(color: Color, scaleFactor: number, graphics: GraphicsMode, clipVariant: Clip.Variant) {
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
return {
type: {
name: 'spacefill',
params: {
...SpacefillRepresentationProvider.defaultValues,
ignoreHydrogens: false,
instanceGranularity: false,
ignoreLight: true,
lodLevels: gmp.lodLevels.map(l => {
return {
...l,
stride: Math.max(1, Math.round(l.stride / Math.pow(scaleFactor, l.scaleBias)))
};
}),
quality: 'lowest', // avoid 'auto', triggers boundary calc
clip: {
variant: clipVariant,
objects: [],
},
clipPrimitive: true,
approximate: gmp.approximate,
alphaThickness: gmp.alphaThickness,
},
},
colorTheme: {
name: 'uniform',
params: {
value: color,
saturation: 0,
lightness: 0,
}
},
sizeTheme: {
name: 'physical',
params: {
value: 1,
}
},
};
}
export async function createMmcifHierarchy(plugin: PluginContext, trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory>) {
const builder = plugin.builders.structure;
const state = plugin.state.data;
const model = await builder.createModel(trajectory, { modelIndex: 0 });
const { data: entities, subtype } = model.data!.entities;
const sd = model.data?.sourceData;
if (MmcifFormat.is(sd)) {
const pdbId = sd.data.db.struct.entry_id.value(0);
MesoscaleState.set(plugin, {
description: sd.data.db.struct.title.value(0),
link: pdbId ? `https://www.rcsb.org/structure/${pdbId}` : ''
});
}
const spheresAvgRadius = new Map<string, number>();
if (model.data!.coarseHierarchy.isDefined) {
const spheresCount = new Map<string, number>();
const spheresEntity_id = model.data!.coarseHierarchy.spheres.entity_id;
const spheresRadius = model.data!.coarseConformation.spheres.radius;
for (let i = 0, il = spheresEntity_id.rowCount; i < il; ++i) {
const entitiId = spheresEntity_id.value(i);
const radius = spheresRadius[i];
if (!spheresCount.has(entitiId)) {
spheresCount.set(entitiId, 1);
spheresAvgRadius.set(entitiId, radius);
} else {
spheresCount.set(entitiId, spheresCount.get(entitiId)! + 1);
spheresAvgRadius.set(entitiId, spheresAvgRadius.get(entitiId)! + radius);
}
}
spheresAvgRadius.forEach((v, k) => {
spheresAvgRadius.set(k, v / spheresCount.get(k)!);
});
}
const entGroups = new Map<string, StateObjectSelector>();
const entIds = new Map<string, { idx: number, members: Map<number, number> }>();
const entColors = new Map<string, Color[]>();
const graphicsMode = MesoscaleState.get(plugin).graphics;
const groupParams = getMesoscaleGroupParams(graphicsMode);
const base = await state.build()
.to(model)
.apply(MmcifAssembly, { id: '' })
.commit();
const units = base.data!.units;
const willBeMerged = units.length > 1 && units.every(u => u.conformation.operator.isIdentity);
const clipVariant = willBeMerged ? 'pixel' : 'instance';
const entRoot = await state.build()
.toRoot()
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.commit();
const getEntityType = (i: number) => {
if (entities.type.value(i) === 'water') return 'water' as const;
return subtype.value(i) || 'unknown type';
};
for (let i = 0; i < entities._rowCount; i++) {
const t = getEntityType(i);
if (!entIds.has(t)) {
entIds.set(t, { idx: entIds.size, members: new Map() });
}
const cm = entIds.get(t)!;
cm.members.set(i, cm.members.size);
}
//
const baseEntColors = getDistinctBaseColors(entIds.size, 0);
const entIdEntries = Array.from(entIds.entries());
for (let i = 0; i < entIdEntries.length; ++i) {
const [t, m] = entIdEntries[i];
const groupColors = getDistinctGroupColors(m.members.size, baseEntColors[i], 20, 0);
entColors.set(t, groupColors);
}
for (let i = 0; i < entities._rowCount; i++) {
const t = getEntityType(i);
if (!entGroups.has(t)) {
const colorIdx = entIds.get(t)?.idx;
const color = colorIdx !== undefined ? baseEntColors[colorIdx] : ColorNames.white;
const group = await state.build()
.to(entRoot)
.applyOrUpdateTagged(`group:ent:${t}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `ent:${t}`, label: t, color: { type: 'generate', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit({ revertOnError: true });
entGroups.set(t, group);
}
}
//
await state.transaction(async () => {
try {
const dependsOn = [base.ref];
plugin.animationLoop.stop({ noDraw: true });
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
for (let i = 0; i < entities._rowCount; i++) {
const t = getEntityType(i);
const color = entColors.get(t)![entIds.get(t)!.members.get(i)!];
const scaleFactor = spheresAvgRadius.get(entities.id.value(i)) || 1;
build = build
.toRoot()
.apply(MmcifStructure, { structureRef: base.ref, entityId: entities.id.value(i) }, { dependsOn })
.apply(StructureRepresentation3D, getSpacefillParams(color, scaleFactor, graphicsMode, clipVariant), { tags: [`ent:${t}`] });
}
await build.commit();
} catch (e) {
console.error(e);
plugin.log.error(e);
} finally {
plugin.animationLoop.start();
}
}).run();
}

View File

@@ -0,0 +1,138 @@
/**
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4 } from '../../../../mol-math/linear-algebra/3d/mat4';
import { getMatrices, operatorGroupsProvider } from '../../../../mol-model-formats/structure/property/assembly';
import { Structure, StructureElement, StructureProperties, Trajectory, Unit } from '../../../../mol-model/structure';
import { Assembly } from '../../../../mol-model/structure/model/properties/symmetry';
import { PluginStateObject as SO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
import { Task } from '../../../../mol-task';
import { Table } from '../../../../mol-data/db';
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
import { arrayFind } from '../../../../mol-data/util';
import { StateObject, StateTransformer } from '../../../../mol-state';
import { CifField } from '../../../../mol-io/reader/cif';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { mergeUnits } from '../util';
import { deepEqual } from '../../../../mol-util';
export { StructureFromPetworld };
type StructureFromPetworld = typeof StructureFromPetworld
const StructureFromPetworld = PluginStateTransform.BuiltIn({
name: 'structure-from-petworld',
display: { name: 'Structure from PetWorld', description: 'Create a molecular structure from PetWorld models.' },
from: SO.Molecule.Trajectory,
to: SO.Molecule.Structure,
params: {
modelIndex: PD.Numeric(0),
entityIds: PD.Value<string[]>([]),
}
})({
apply({ a, params }) {
return Task.create('Build Structure', async ctx => {
const s = await buildModelsAssembly(a.data, '1', params.modelIndex, params.entityIds).runInContext(ctx);
if (!s || !MmcifFormat.is(s.model.sourceData)) return StateObject.Null;
const { frame } = s.model.sourceData.data;
const pdbx_model = frame.categories.pdbx_model.getField('name')!;
const label = pdbx_model.str(params.modelIndex);
const props = { label, description: Structure.elementDescription(s) };
return new SO.Molecule.Structure(s, props);
});
},
update({ newParams, oldParams }) {
return deepEqual(newParams, oldParams)
? StateTransformer.UpdateResult.Unchanged
: StateTransformer.UpdateResult.Recreate;
},
dispose({ b }) {
b?.data.customPropertyDescriptors.dispose();
}
});
function buildModelsAssembly(trajectory: Trajectory, asmName: string, modelIndex: number, entitiyIds: string[]) {
return Task.create('Build Models Assembly', async ctx => {
const model = await Task.resolveInContext(trajectory.getFrameAtIndex(modelIndex), ctx);
if (!MmcifFormat.is(model.sourceData)) return;
const { db, frame } = model.sourceData.data;
const PDB_model_num = frame.categories.pdbx_struct_assembly_gen.getField('PDB_model_num')!;
// hack to cache models assemblies
if (!(trajectory as any).__modelsAssemblies) {
(trajectory as any).__modelsAssemblies = createModelsAssemblies(db.pdbx_struct_assembly, db.pdbx_struct_assembly_gen as StructAssemblyGen, db.pdbx_struct_oper_list, PDB_model_num);
}
const modelsAssemblies = (trajectory as any).__modelsAssemblies as ModelsAssembly[];
const modelsAssembly = arrayFind(modelsAssemblies, ma => ma.assembly.id.toLowerCase() === asmName);
if (!modelsAssembly) throw new Error(`Models Assembly '${asmName}' is not defined.`);
const { assembly } = modelsAssembly;
const assembler = Structure.Builder();
const g = assembly.operatorGroups[modelIndex];
const structure = Structure.ofModel(model);
const l = StructureElement.Location.create(structure);
const units = structure.units.filter(u => {
l.unit = u;
l.element = u.elements[0];
return entitiyIds.includes(StructureProperties.entity.id(l));
});
const unit = mergeUnits(units, 0);
for (const oper of g.operators) {
assembler.addUnit(unit.kind, unit.model, oper, unit.elements, unit.traits | Unit.Trait.FastBoundary, unit.invariantId);
}
return assembler.getStructure();
});
}
//
type StructAssembly = Table<mmCIF_Schema['pdbx_struct_assembly']>
type StructAssemblyGen = Table<mmCIF_Schema['pdbx_struct_assembly_gen']>
type StructOperList = Table<mmCIF_Schema['pdbx_struct_oper_list']>
type ModelsAssembly = { assembly: Assembly, modelNums: number[] };
function createModelsAssemblies(pdbx_struct_assembly: StructAssembly, pdbx_struct_assembly_gen: StructAssemblyGen, pdbx_struct_oper_list: StructOperList, PDB_model_num: CifField): ReadonlyArray<ModelsAssembly> {
if (!pdbx_struct_assembly._rowCount) return [];
const matrices = getMatrices(pdbx_struct_oper_list);
const assemblies: ModelsAssembly[] = [];
for (let i = 0; i < pdbx_struct_assembly._rowCount; i++) {
assemblies[assemblies.length] = createModelsAssembly(pdbx_struct_assembly, pdbx_struct_assembly_gen, i, matrices, PDB_model_num);
}
return assemblies;
}
type Matrices = Map<string, Mat4>
type Generator = { assemblyId: string, expression: string, asymIds: string[] }
function createModelsAssembly(pdbx_struct_assembly: StructAssembly, pdbx_struct_assembly_gen: StructAssemblyGen, index: number, matrices: Matrices, PDB_model_num: CifField): ModelsAssembly {
const id = pdbx_struct_assembly.id.value(index);
const details = pdbx_struct_assembly.details.value(index);
const generators: Generator[] = [];
const modelNums: number[] = [];
const { assembly_id, oper_expression, asym_id_list } = pdbx_struct_assembly_gen;
for (let i = 0, _i = pdbx_struct_assembly_gen._rowCount; i < _i; i++) {
if (assembly_id.value(i) !== id) continue;
generators[generators.length] = {
assemblyId: id,
expression: oper_expression.value(i),
asymIds: asym_id_list.value(i)
};
modelNums[modelNums.length] = PDB_model_num.int(i);
}
const assembly = Assembly.create(id, details, operatorGroupsProvider(generators, matrices));
return { assembly, modelNums };
}

View File

@@ -0,0 +1,134 @@
/**
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { StateBuilder, StateObjectRef } from '../../../../mol-state';
import { StructureFromPetworld } from './model';
import { Color } from '../../../../mol-util/color';
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
import { StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
import { PluginContext } from '../../../../mol-plugin/context';
import { PluginStateObject } from '../../../../mol-plugin-state/objects';
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
import { ColorNames } from '../../../../mol-util/color/names';
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
import { Task } from '../../../../mol-task';
function getSpacefillParams(color: Color, graphics: GraphicsMode) {
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
return {
type: {
name: 'spacefill',
params: {
...SpacefillRepresentationProvider.defaultValues,
ignoreHydrogens: true,
instanceGranularity: true,
ignoreLight: true,
lodLevels: gmp.lodLevels,
quality: 'lowest', // avoid 'auto', triggers boundary calc
clip: {
variant: 'instance',
objects: [],
},
clipPrimitive: true,
approximate: gmp.approximate,
alphaThickness: gmp.alphaThickness,
},
},
colorTheme: {
name: 'uniform',
params: {
value: color,
saturation: 0,
lightness: 0,
}
},
sizeTheme: {
name: 'physical',
params: {
scale: 1,
}
},
};
}
export async function createPetworldHierarchy(plugin: PluginContext, trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory>) {
const cell = StateObjectRef.resolveAndCheck(plugin.state.data, trajectory);
const tr = cell?.obj?.data;
if (!cell || !tr) return;
if (!MmcifFormat.is(tr.representative.sourceData)) return;
const membrane: { modelIndex: number, entityIds: string[] }[] = [];
const other: { modelIndex: number, entityIds: string[] }[] = [];
for (let i = 0; i < tr.frameCount; ++i) {
const m = await Task.resolveInContext(tr.getFrameAtIndex(i));
// cannot use m.properties.structAsymMap because petworld models
// may assign the same asymId to multiple entities
const { label_asym_id, label_entity_id, _rowCount } = m.atomicHierarchy.chains;
const membraneIds: string[] = [];
const otherIds: string[] = [];
const seen = new Set<string>();
for (let i = 0; i < _rowCount; i ++) {
const entityId = label_entity_id.value(i);
if (seen.has(entityId)) continue;
const asymId = label_asym_id.value(i);
if (asymId.startsWith('MEM')) {
membraneIds.push(entityId);
} else {
otherIds.push(entityId);
}
seen.add(entityId);
}
if (membraneIds.length) {
membrane.push({ modelIndex: i, entityIds: membraneIds });
}
if (otherIds.length) {
other.push({ modelIndex: i, entityIds: otherIds });
}
}
const state = plugin.state.data;
const graphicsMode = MesoscaleState.get(plugin).graphics;
const groupParams = getMesoscaleGroupParams(graphicsMode);
const group = await state.build()
.toRoot()
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'generate', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.commit({ revertOnError: true });
await state.build()
.to(group)
.applyOrUpdateTagged(`group:ent:mem`, MesoscaleGroup, { ...groupParams, index: undefined, tag: `ent:mem`, label: 'Membrane', color: { type: 'uniform', value: ColorNames.lightgrey, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit();
const colors = getDistinctBaseColors(other.length, 0);
await state.transaction(async () => {
try {
plugin.animationLoop.stop({ noDraw: true });
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
for (let i = 0, il = membrane.length; i < il; ++i) {
build = build
.to(cell)
.apply(StructureFromPetworld, membrane[i])
.apply(StructureRepresentation3D, getSpacefillParams(ColorNames.lightgrey, graphicsMode), { tags: [`ent:mem`] });
}
for (let i = 0, il = other.length; i < il; ++i) {
build = build
.to(cell)
.apply(StructureFromPetworld, other[i])
.apply(StructureRepresentation3D, getSpacefillParams(colors[i], graphicsMode), { tags: [`ent:`] });
}
await build.commit();
} catch (e) {
console.error(e);
plugin.log.error(e);
} finally {
plugin.animationLoop.start();
}
}).run();
}

View File

@@ -0,0 +1,605 @@
/**
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PluginStateObject as PSO, PluginStateTransform } from '../../../mol-plugin-state/objects';
import { PluginContext } from '../../../mol-plugin/context';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Task } from '../../../mol-task';
import { Color } from '../../../mol-util/color';
import { Spheres } from '../../../mol-geo/geometry/spheres/spheres';
import { Clip } from '../../../mol-util/clip';
import { escapeRegExp, stringToWords } from '../../../mol-util/string';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { ParamMapping } from '../../../mol-util/param-mapping';
import { EntityNode } from '../ui/entities';
import { DistinctColorsProps, distinctColors } from '../../../mol-util/color/distinct';
import { Sphere3D } from '../../../mol-math/geometry';
import { Hcl } from '../../../mol-util/color/spaces/hcl';
import { StateObjectCell, StateObjectRef, StateSelection } from '../../../mol-state';
import { ShapeRepresentation3D, StructureRepresentation3D } from '../../../mol-plugin-state/transforms/representation';
import { SpacefillRepresentationProvider } from '../../../mol-repr/structure/representation/spacefill';
import { assertUnreachable } from '../../../mol-util/type-helpers';
import { MesoscaleExplorerState } from '../app';
import { saturate } from '../../../mol-math/interpolate';
function getHueRange(hue: number, variability: number) {
let min = hue - variability;
const minOverflow = (min < 0 ? -min : 0);
let max = hue + variability;
if (max > 360) min -= max - 360;
max += minOverflow;
return [Math.max(0, min), Math.min(360, max)] as [number, number];
}
function getGrayscaleColors(count: number, luminance: number, variability: number) {
const out: Color[] = [];
for (let i = 0; i < count; ++ i) {
const l = saturate(luminance / 100);
const v = saturate(variability / 180) * Math.random();
const s = Math.random() > 0.5 ? 1 : -1;
const d = Math.abs(l + s * v) % 1;
out[i] = Color.fromNormalizedRgb(d, d, d);
}
return out;
}
export function getDistinctGroupColors(count: number, color: Color, variability: number, shift: number, props?: Partial<DistinctColorsProps>) {
const hcl = Hcl.fromColor(Hcl(), color);
if (isNaN(hcl[0])) {
return getGrayscaleColors(count, hcl[2], variability);
}
if (count === 1) {
hcl[1] = 65;
hcl[2] = 55;
return [Hcl.toColor(hcl)];
}
const colors = distinctColors(count, {
hue: getHueRange(hcl[0], variability),
chroma: [30, 100],
luminance: [50, 100],
clusteringStepCount: 0,
minSampleCount: 1000,
sampleCountFactor: 100,
sort: 'none',
...props,
});
if (shift !== 0) {
const offset = Math.floor(shift / 100 * count);
return [...colors.slice(offset), ...colors.slice(0, offset)];
} else {
return colors;
}
}
const Colors = [0x377eb8, 0xe41a1c, 0x4daf4a, 0x984ea3, 0xff7f00, 0xffff33, 0xa65628, 0xf781bf] as Color[];
export function getDistinctBaseColors(count: number, shift: number, props?: Partial<DistinctColorsProps>): Color[] {
let colors: Color[];
if (count <= Colors.length) {
colors = Colors.slice(0, count).map(e => Array.isArray(e) ? e[0] : e);
} else {
colors = distinctColors(count, {
hue: [1, 360],
chroma: [25, 100],
luminance: [30, 100],
clusteringStepCount: 0,
minSampleCount: 1000,
sampleCountFactor: 100,
sort: 'none',
...props,
});
}
if (shift !== 0) {
const offset = Math.floor(shift / 100 * count);
return [...colors.slice(offset), ...colors.slice(0, offset)];
} else {
return colors;
}
}
export const ColorParams = {
type: PD.Select('generate', PD.arrayToOptions(['generate', 'uniform', 'custom'])),
value: PD.Color(Color(0xFFFFFF), { hideIf: p => p.type === 'custom' }),
variability: PD.Numeric(20, { min: 1, max: 180, step: 1 }, { hideIf: p => p.type !== 'generate' }),
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }, { hideIf: p => p.type === 'custom' }),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
};
export type ColorProps = PD.Values<typeof ColorParams>
export const ColorValueParam = PD.Color(Color(0xFFFFFF));
export const RootParams = {
type: PD.Select('custom', PD.arrayToOptions(['group-generate', 'group-uniform', 'generate', 'uniform', 'custom'])),
value: PD.Color(Color(0xFFFFFF), { hideIf: p => p.type !== 'uniform' }),
variability: PD.Numeric(20, { min: 1, max: 180, step: 1 }, { hideIf: p => p.type !== 'group-generate' }),
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }, { hideIf: p => p.type === 'custom' }),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
};
export const LightnessParams = {
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
};
export const DimLightness = 6;
export const OpacityParams = {
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
};
export const EmissiveParams = {
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
};
export const PatternParams = {
frequency: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
amplitude: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
};
export const LodParams = {
lodLevels: Spheres.Params.lodLevels,
cellSize: Spheres.Params.cellSize,
batchSize: Spheres.Params.batchSize,
approximate: Spheres.Params.approximate,
};
export const SimpleClipParams = {
type: PD.Select('none', PD.objectToOptions(Clip.Type, t => stringToWords(t))),
invert: PD.Boolean(false),
position: PD.Group({
x: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
y: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
z: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
}, { hideIf: g => g.type === 'none', isExpanded: true }),
rotation: PD.Group({
axis: PD.Vec3(Vec3.create(1, 0, 0)),
angle: PD.Numeric(0, { min: -180, max: 180, step: 1 }, { immediateUpdate: true }),
}, { hideIf: g => g.type === 'none', isExpanded: true }),
scale: PD.Group({
x: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
y: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
z: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
}, { hideIf: g => ['none', 'plane'].includes(g.type), isExpanded: true }),
};
export type SimpleClipParams = typeof SimpleClipParams
export type SimpleClipProps = PD.Values<SimpleClipParams>
export function getClipObjects(values: SimpleClipProps, boundingSphere: Sphere3D): Clip.Props['objects'] {
const { center, radius } = boundingSphere;
const position = Vec3.clone(center);
Vec3.add(position, position, Vec3.create(
radius * values.position.x / 100,
radius * values.position.y / 100,
radius * values.position.z / 100
));
const scale = Vec3.create(values.scale.x, values.scale.y, values.scale.z);
Vec3.scale(scale, scale, 2 * radius / 100);
return [{
type: values.type,
invert: values.invert,
position,
scale,
rotation: values.rotation
}];
}
export function createClipMapping(node: EntityNode) {
return ParamMapping({
params: SimpleClipParams,
target: (ctx: PluginContext) => {
return node.clipValue;
}
})({
values(props, ctx) {
if (!props || props.objects.length === 0) {
return {
type: 'none',
invert: false,
position: { x: 0, y: 0, z: 0 },
rotation: { axis: Vec3.create(1, 0, 0), angle: 0 },
scale: { x: 100, y: 100, z: 100 },
};
}
const { center, radius } = node.plugin.canvas3d!.boundingSphere;
const { invert, position, scale, rotation, type } = props.objects[0];
const p = Vec3.clone(position);
Vec3.sub(p, p, center);
Vec3.scale(p, p, 100 / radius);
Vec3.round(p, p);
const s = Vec3.clone(scale);
Vec3.scale(s, s, 100 / radius / 2);
Vec3.round(s, s);
return {
type,
invert,
position: { x: p[0], y: p[1], z: p[2] },
rotation,
scale: { x: s[0], y: s[1], z: s[2] },
};
},
update: (s, props) => {
if (!props) return;
const clipObjects = getClipObjects(s, node.plugin.canvas3d!.boundingSphere);
props.objects = clipObjects;
},
apply: async (props, ctx) => {
if (props) node.updateClip(props);
}
});
}
export const MesoscaleGroupParams = {
root: PD.Value<boolean>(false, { isHidden: true }),
index: PD.Value<number>(-1, { isHidden: true }),
tag: PD.Value<string>('', { isHidden: true }),
label: PD.Value<string>('', { isHidden: true }),
hidden: PD.Boolean(false),
color: PD.Group(RootParams),
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
lod: PD.Group(LodParams),
clip: PD.Group(SimpleClipParams),
};
export type MesoscaleGroupProps = PD.Values<typeof MesoscaleGroupParams>;
export class MesoscaleGroupObject extends PSO.Create({ name: 'Mesoscale Group', typeClass: 'Object' }) { }
export const MesoscaleGroup = PluginStateTransform.BuiltIn({
name: 'mesoscale-group',
display: { name: 'Mesoscale Group' },
from: [PSO.Root, MesoscaleGroupObject],
to: MesoscaleGroupObject,
params: MesoscaleGroupParams,
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Apply Mesoscale Group', async () => {
return new MesoscaleGroupObject({}, { label: params.label });
});
},
});
export function getMesoscaleGroupParams(graphicsMode: GraphicsMode): MesoscaleGroupProps {
const groupParams = PD.getDefaultValues(MesoscaleGroupParams);
if (graphicsMode === 'custom') return groupParams;
return {
...groupParams,
lod: {
...groupParams.lod,
...getGraphicsModeProps(graphicsMode),
}
};
}
//
export type LodLevels = typeof SpacefillRepresentationProvider.defaultValues['lodLevels']
export function getLodLevels(graphicsMode: Exclude<GraphicsMode, 'custom'>): LodLevels {
switch (graphicsMode) {
case 'performance':
return [
{ minDistance: 1, maxDistance: 300, overlap: 0, stride: 1, scaleBias: 1 },
{ minDistance: 300, maxDistance: 2000, overlap: 0, stride: 40, scaleBias: 3 },
{ minDistance: 2000, maxDistance: 6000, overlap: 0, stride: 150, scaleBias: 3 },
{ minDistance: 6000, maxDistance: 10000000, overlap: 0, stride: 300, scaleBias: 2.5 },
];
case 'balanced':
return [
{ minDistance: 1, maxDistance: 500, overlap: 0, stride: 1, scaleBias: 1 },
{ minDistance: 500, maxDistance: 2000, overlap: 0, stride: 15, scaleBias: 3 },
{ minDistance: 2000, maxDistance: 6000, overlap: 0, stride: 70, scaleBias: 2.7 },
{ minDistance: 6000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2.5 },
];
case 'quality':
return [
{ minDistance: 1, maxDistance: 1000, overlap: 0, stride: 1, scaleBias: 1 },
{ minDistance: 1000, maxDistance: 4000, overlap: 0, stride: 10, scaleBias: 3 },
{ minDistance: 4000, maxDistance: 10000, overlap: 0, stride: 50, scaleBias: 2.7 },
{ minDistance: 10000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2.3 },
];
case 'ultra':
return [
{ minDistance: 1, maxDistance: 2000, overlap: 0, stride: 1, scaleBias: 1 },
{ minDistance: 2000, maxDistance: 8000, overlap: 0, stride: 10, scaleBias: 3 },
{ minDistance: 8000, maxDistance: 20000, overlap: 0, stride: 50, scaleBias: 2.5 },
{ minDistance: 20000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2 },
];
default:
assertUnreachable(graphicsMode);
}
}
export type GraphicsMode = 'ultra' | 'quality' | 'balanced' | 'performance' | 'custom';
export function getGraphicsModeProps(graphicsMode: Exclude<GraphicsMode, 'custom'>) {
return {
lodLevels: getLodLevels(graphicsMode),
approximate: graphicsMode !== 'quality' && graphicsMode !== 'ultra',
alphaThickness: graphicsMode === 'performance' ? 15 : 12,
};
}
export function setGraphicsCanvas3DProps(ctx: PluginContext, graphics: GraphicsMode) {
const pixelScale = graphics === 'balanced' ? 0.75
: graphics === 'performance' ? 0.5 : 1;
ctx.canvas3dContext?.setProps({ pixelScale });
ctx.canvas3d?.setProps({
postprocessing: {
sharpening: pixelScale < 1 ? {
name: 'on',
params: { sharpness: 0.5, denoise: true }
} : { name: 'off', params: {} }
}
});
}
//
export const MesoscaleStateParams = {
filter: PD.Value<string>('', { isHidden: true }),
graphics: PD.Select('quality', PD.arrayToOptions(['ultra', 'quality', 'balanced', 'performance', 'custom'] as GraphicsMode[])),
description: PD.Value<string>('', { isHidden: true }),
link: PD.Value<string>('', { isHidden: true }),
};
export class MesoscaleStateObject extends PSO.Create<MesoscaleState>({ name: 'Mesoscale State', typeClass: 'Object' }) { }
const MesoscaleStateTransform = PluginStateTransform.BuiltIn({
name: 'mesoscale-state',
display: { name: 'Mesoscale State' },
from: PSO.Root,
to: MesoscaleStateObject,
params: MesoscaleStateParams,
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Apply Mesoscale State', async () => {
return new MesoscaleStateObject(params);
});
},
});
export { MesoscaleState };
type MesoscaleState = PD.Values<typeof MesoscaleStateParams>;
const MesoscaleState = {
async init(ctx: PluginContext) {
const cell = ctx.state.data.selectQ(q => q.ofType(MesoscaleStateObject))[0];
if (cell) throw new Error('MesoscaleState already initialized');
const customState = ctx.customState as MesoscaleExplorerState;
const state = await ctx.state.data.build().toRoot().apply(MesoscaleStateTransform, {
filter: '',
graphics: customState.graphicsMode,
}).commit();
customState.stateRef = state.ref;
},
get(ctx: PluginContext): MesoscaleState {
const ref = this.ref(ctx);
return ctx.state.data.tryGetCellData<MesoscaleStateObject>(ref);
},
async set(ctx: PluginContext, props: Partial<MesoscaleState>) {
const ref = this.ref(ctx);
await ctx.state.data.build().to(ref).update(MesoscaleStateTransform, old => Object.assign(old, props)).commit();
},
ref(ctx: PluginContext): string {
const ref = (ctx.customState as MesoscaleExplorerState).stateRef;
if (!ref) throw new Error('MesoscaleState not initialized');
return ref;
},
has(ctx: PluginContext): boolean {
const ref = (ctx.customState as MesoscaleExplorerState).stateRef || '';
return ctx.state.data.cells.has(ref) ? true : false;
},
};
//
export function getRoots(plugin: PluginContext): StateSelection.CellSeq<StateObjectCell<MesoscaleGroupObject>> {
const s = plugin.customState as MesoscaleExplorerState;
if (!s.stateCache.roots) {
s.stateCache.roots = plugin.state.data.select(StateSelection.Generators.rootsOfType(MesoscaleGroupObject));
}
return s.stateCache.roots;
}
export function getGroups(plugin: PluginContext, tag?: string): StateSelection.CellSeq<StateObjectCell<MesoscaleGroupObject>> {
const s = plugin.customState as MesoscaleExplorerState;
const k = `groups-${tag || ''}`;
if (!s.stateCache[k]) {
const selector = tag !== undefined
? StateSelection.Generators.ofTransformer(MesoscaleGroup).withTag(tag)
: StateSelection.Generators.ofTransformer(MesoscaleGroup);
s.stateCache[k] = plugin.state.data.select(selector);
}
return s.stateCache[k];
}
function _getAllGroups(plugin: PluginContext, tag: string | undefined, list: StateObjectCell[]) {
const groups = getGroups(plugin, tag);
list.push(...groups);
for (const g of groups) {
_getAllGroups(plugin, g.params?.values.tag, list);
}
return list;
}
export function getAllGroups(plugin: PluginContext, tag?: string) {
return _getAllGroups(plugin, tag, []);
}
export function getAllLeafGroups(plugin: PluginContext, tag: string) {
const allGroups = getAllGroups(plugin, tag);
allGroups.sort((a, b) => a.params?.values.index - b.params?.values.index);
return allGroups.filter(g => {
return getEntities(plugin, g.params?.values.tag).length > 0;
});
}
type EntityCells = StateSelection.CellSeq<StateObjectCell<PSO.Molecule.Structure.Representation3D | PSO.Shape.Representation3D>>
export function getEntities(plugin: PluginContext, tag?: string): EntityCells {
const s = plugin.customState as MesoscaleExplorerState;
const k = `entities-${tag || ''}`;
if (!s.stateCache[k]) {
const structureSelector = tag !== undefined
? StateSelection.Generators.ofTransformer(StructureRepresentation3D).withTag(tag)
: StateSelection.Generators.ofTransformer(StructureRepresentation3D);
const shapeSelector = tag !== undefined
? StateSelection.Generators.ofTransformer(ShapeRepresentation3D).withTag(tag)
: StateSelection.Generators.ofTransformer(ShapeRepresentation3D);
s.stateCache[k] = [
...plugin.state.data.select(structureSelector).filter(c => c.obj!.data.sourceData.elementCount > 0),
...plugin.state.data.select(shapeSelector),
];
}
return s.stateCache[k];
}
function getFilterMatcher(filter: string) {
return filter.startsWith('"') && filter.endsWith('"')
? new RegExp(`^${escapeRegExp(filter.substring(1, filter.length - 1))}$`, 'g')
: new RegExp(escapeRegExp(filter), 'gi');
}
export function getFilteredEntities(plugin: PluginContext, tag: string, filter: string) {
const matcher = getFilterMatcher(filter);
return getEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
}
function _getAllEntities(plugin: PluginContext, tag: string | undefined, list: EntityCells) {
list.push(...getEntities(plugin, tag));
for (const g of getGroups(plugin, tag)) {
_getAllEntities(plugin, g.params?.values.tag, list);
}
return list;
}
export function getAllEntities(plugin: PluginContext, tag?: string) {
return _getAllEntities(plugin, tag, []);
}
export function getAllFilteredEntities(plugin: PluginContext, tag: string, filter: string) {
const matcher = getFilterMatcher(filter);
return getAllEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
}
export function getEntityLabel(plugin: PluginContext, cell: StateObjectCell) {
return StateObjectRef.resolve(plugin.state.data, cell.transform.parent)?.obj?.label || 'Entity';
}
//
export async function updateColors(plugin: PluginContext, values: PD.Values, tag: string, filter: string) {
const update = plugin.state.data.build();
const { type, value, shift, lightness, alpha, emissive } = values;
if (type === 'group-generate' || type === 'group-uniform') {
const groups = getAllLeafGroups(plugin, tag);
const baseColors = getDistinctBaseColors(groups.length, shift);
for (let i = 0; i < groups.length; ++i) {
const g = groups[i];
const entities = getFilteredEntities(plugin, g.params?.values.tag, filter);
let groupColors: Color[] = [];
if (type === 'group-generate') {
const c = g.params?.values.color;
groupColors = getDistinctGroupColors(entities.length, baseColors[i], c.variability, c.shift);
}
for (let j = 0; j < entities.length; ++j) {
const c = type === 'group-generate' ? groupColors[j] : baseColors[i];
update.to(entities[j]).update(old => {
if (old.type) {
old.colorTheme.params.value = c;
old.colorTheme.params.lightness = lightness;
old.type.params.alpha = alpha;
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
old.type.params.emissive = emissive;
} else if (old.coloring) {
old.coloring.params.color = c;
old.coloring.params.lightness = lightness;
old.alpha = alpha;
old.xrayShaded = alpha < 1 ? true : false;
old.emissive = emissive;
}
});
}
update.to(g.transform.ref).update(old => {
old.color.type = type === 'group-generate' ? 'generate' : 'uniform';
old.color.value = baseColors[i];
old.color.lightness = lightness;
old.color.alpha = alpha;
old.color.emissive = emissive;
});
}
} else if (type === 'generate' || type === 'uniform') {
const entities = getAllFilteredEntities(plugin, tag, filter);
let groupColors: Color[] = [];
if (type === 'generate') {
groupColors = getDistinctBaseColors(entities.length, shift);
}
for (let j = 0; j < entities.length; ++j) {
const c = type === 'generate' ? groupColors[j] : value;
update.to(entities[j]).update(old => {
if (old.type) {
old.colorTheme.params.value = c;
old.colorTheme.params.lightness = lightness;
old.type.params.alpha = alpha;
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
old.type.params.emissive = emissive;
} else if (old.coloring) {
old.coloring.params.color = c;
old.coloring.params.lightness = lightness;
old.alpha = alpha;
old.xrayShaded = alpha < 1 ? true : false;
old.emissive = emissive;
}
});
}
const others = getAllLeafGroups(plugin, tag);
for (const o of others) {
update.to(o).update(old => {
old.color.type = type === 'generate' ? 'custom' : 'uniform';
old.color.value = value;
old.color.lightness = lightness;
old.color.alpha = alpha;
old.color.emissive = emissive;
});
}
}
await update.commit();
};
export function expandAllGroups(plugin: PluginContext) {
for (const g of getAllGroups(plugin)) {
if (g.state.isCollapsed) {
plugin.state.data.updateCellState(g.transform.ref, { isCollapsed: false });
}
}
};

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { OrderedSet, SortedArray } from '../../../mol-data/int';
import { Box3D, GridLookup3D, PositionData, Sphere3D } from '../../../mol-math/geometry';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { ElementIndex, Unit } from '../../../mol-model/structure';
export function mergeUnits(units: readonly Unit[], id: number): Unit {
const u = units[0];
let start = -1 as ElementIndex, end = -1 as ElementIndex;
let elements = SortedArray.Empty as SortedArray<ElementIndex>;
for (let i = 0, il = units.length; i < il; ++i) {
const e = units[i].elements;
if (SortedArray.isRange(e)) {
if (end !== -1 && e[0] === end + 1) {
// extend range
end = e[e.length - 1];
} else {
if (end !== -1) {
// pending range
elements = SortedArray.union(elements, SortedArray.ofRange(start, end));
}
// new range
start = e[0];
end = e[e.length - 1];
}
} else {
if (end !== -1) {
// pending range
elements = SortedArray.union(elements, SortedArray.ofRange(start, end));
start = -1 as ElementIndex, end = -1 as ElementIndex;
}
elements = SortedArray.union(elements, e);
}
}
if (end !== -1) {
// pending range
elements = SortedArray.union(elements, SortedArray.ofRange(start, end));
}
return Unit.create(id, id, 0, u.traits | Unit.Trait.MultiChain, u.kind, u.model, u.conformation.operator, elements);
}
export function partitionUnits(units: readonly Unit[], cellSize: number) {
const unitCount = units.length;
const mergedUnits: Unit[] = [];
const box = Box3D.setEmpty(Box3D());
const x = new Float32Array(unitCount);
const y = new Float32Array(unitCount);
const z = new Float32Array(unitCount);
const indices = OrderedSet.ofBounds(0, unitCount);
for (let i = 0, il = unitCount; i < il; ++i) {
const v = units[i].boundary.sphere.center;
x[i] = v[0];
y[i] = v[1];
z[i] = v[2];
Box3D.add(box, v);
}
Box3D.expand(box, box, Vec3.create(1, 1, 1));
const positionData: PositionData = { x, y, z, indices };
const boundary = { box, sphere: Sphere3D.fromBox3D(Sphere3D(), box) };
const lookup = GridLookup3D(positionData, boundary, Vec3.create(cellSize, cellSize, cellSize));
const { array, offset, count } = lookup.buckets;
for (let i = 0, il = offset.length; i < il; ++i) {
const start = offset[i];
const size = count[i];
const cellUnits: Unit[] = [];
for (let j = start, jl = start + size; j < jl; ++j) {
cellUnits.push(units[array[j]]);
}
mergedUnits.push(mergeUnits(cellUnits, i));
}
return mergedUnits;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,101 @@
<!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* Mesoscale Explorer</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
hr {
margin: 10px;
}
h1, h2, h3, h4, h5 {
margin-top: 5px;
margin-bottom: 3px;
}
button {
padding: 2px;
}
#app {
position: absolute;
left: 100px;
top: 100px;
width: 800px;
height: 600px;
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="./molstar.js"></script>
<script type="text/javascript">
function getParam(name, regex) {
var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i');
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
}
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
if (debugMode) molstar.setDebugMode(debugMode);
var timingMode = getParam('timing-mode', '[^&]+').trim() === '1';
if (timingMode) molstar.setTimingMode(timingMode);
var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
var powerPreference = getParam('power-preference', '[^&]+').trim().toLowerCase();
var graphicsMode = getParam('graphics-mode', '[^&]+').trim().toLowerCase();
molstar.MesoscaleExplorer.create('app', {
layoutShowControls: !hideControls,
viewportShowExpand: false,
preferWebgl1: preferWebgl1,
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
powerPreference: powerPreference || 'high-performance',
graphicsMode: graphicsMode || 'quality',
}).then(me => {
var example = getParam('example', '[^&]+').trim();
if (example) {
me.loadExample(example);
return;
}
var url = getParam('url', '[^&]+').trim();
var type = getParam('type', '[^&]+').trim();
if (url && type) {
me.loadUrl(url, type);
return;
}
var pdb = getParam('pdb', '[^&]+').trim();
if (pdb) {
me.loadPdb(pdb);
return;
}
var pdbdev = getParam('pdbdev', '[^&]+').trim();
if (pdbdev) {
me.loadPdbDev(pdbdev);
return;
}
window.addEventListener('unload', () => {
// to aid GC
me.dispose();
});
});
</script>
<!-- __MOLSTAR_ANALYTICS__ -->
</body>
</html>

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import './favicon.ico';
import './index.html';
require('./style.scss');
export * from './app';

View File

@@ -0,0 +1,33 @@
$default-background: #2D3E50;
$font-color: #EDF1F2;
$hover-font-color: #3B9AD9;
$entity-current-font-color: #FFFFFF;
$msp-btn-remove-background: #BF3A31;
$msp-btn-remove-hover-font-color:#ffffff;
$msp-btn-commit-on-font-color: #ffffff;
$entity-badge-font-color: #ccd4e0;
// used in LOG
$log-message: #0CCA5D;
$log-info: #5E3673;
$log-warning: #FCC937;
$log-error: #FD354B;
$logo-background: rgba(0,0,0,0.75);
@function color-lower-contrast($color, $amount) {
@return darken($color, $amount);
}
@function color-increase-contrast($color, $amount) {
@return lighten($color, $amount);
}
@import 'mol-plugin-ui/skin/base/base';
a {
color: $font-color;
&:hover {
color: $hover-font-color;
}
}

View File

@@ -0,0 +1,973 @@
/**
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
import { Button, ControlGroup, IconButton } from '../../../mol-plugin-ui/controls/common';
import { ArrowDropDownSvg, ArrowRightSvg, CloseSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, ContentCutSvg, BrushSvg, SearchSvg } from '../../../mol-plugin-ui/controls/icons';
import { PluginCommands } from '../../../mol-plugin/commands';
import { State, StateObjectCell, StateSelection, StateTransformer } from '../../../mol-state';
import { ParameterControls, ParameterMappingControl, ParamOnChange, SelectControl } from '../../../mol-plugin-ui/controls/parameters';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Clip } from '../../../mol-util/clip';
import { StructureRepresentation3D } from '../../../mol-plugin-state/transforms/representation';
import { Color } from '../../../mol-util/color';
import { CombinedColorControl } from '../../../mol-plugin-ui/controls/color';
import { MarkerAction } from '../../../mol-util/marker-action';
import { EveryLoci, Loci } from '../../../mol-model/loci';
import { deepEqual } from '../../../mol-util';
import { ColorValueParam, ColorParams, ColorProps, DimLightness, LightnessParams, LodParams, MesoscaleGroup, MesoscaleGroupProps, OpacityParams, SimpleClipParams, SimpleClipProps, createClipMapping, getClipObjects, getDistinctGroupColors, RootParams, MesoscaleState, getRoots, getAllGroups, getAllLeafGroups, getFilteredEntities, getAllFilteredEntities, getGroups, getEntities, getAllEntities, getEntityLabel, updateColors, getGraphicsModeProps, GraphicsMode, MesoscaleStateParams, setGraphicsCanvas3DProps, PatternParams, expandAllGroups, EmissiveParams } from '../data/state';
import React from 'react';
import { MesoscaleExplorerState } from '../app';
import { StructureElement } from '../../../mol-model/structure/structure/element';
import { PluginStateObject as PSO } from '../../../mol-plugin-state/objects';
import { Structure } from '../../../mol-model/structure';
import { PluginContext } from '../../../mol-plugin/context';
import { Sphere3D } from '../../../mol-math/geometry';
function centerLoci(plugin: PluginContext, loci: Loci, durationMs = 250) {
const { canvas3d } = plugin;
if (!canvas3d) return;
const sphere = Loci.getBoundingSphere(loci) || Sphere3D();
const snapshot = canvas3d.camera.getCenter(sphere.center);
canvas3d.requestCameraReset({ durationMs, snapshot });
}
export class ModelInfo extends PluginUIComponent<{}, { isDisabled: boolean }> {
state = {
isDisabled: false,
};
componentDidMount() {
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
this.setState({ isDisabled: v });
});
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
if (!this.state.isDisabled && MesoscaleState.has(this.plugin) && MesoscaleState.ref(this.plugin) === e.ref) {
this.forceUpdate();
}
});
}
get info() {
if (!MesoscaleState.has(this.plugin)) return;
const state = MesoscaleState.get(this.plugin);
if (!state.description && !state.link) return;
return {
description: state.description,
link: state.link,
};
}
render() {
const info = this.info;
return info && <>
<div className='msp-help-text'>
<div>{info.description}</div>
<div><a href={info.link} target='_blank'>Source</a></div>
</div>
</>;
}
}
const SelectionStyleParam = PD.Select('color+outline', PD.objectToOptions({
'color+outline': 'Color & Outline',
'color': 'Color',
'outline': 'Outline'
} as const));
type SelectionStyle = typeof SelectionStyleParam['defaultValue']
export class SelectionInfo extends PluginUIComponent<{}, { isDisabled: boolean }> {
state = {
isDisabled: false,
};
componentDidMount() {
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
this.setState({ isDisabled: v });
});
this.subscribe(this.plugin.managers.structure.selection.events.changed, e => {
if (!this.state.isDisabled) {
this.forceUpdate();
}
});
}
get info() {
const info: { label: string, key: string }[] = [];
this.plugin.managers.structure.selection.entries.forEach((e, k) => {
if (StructureElement.Loci.is(e.selection) && !StructureElement.Loci.isEmpty(e.selection)) {
const cell = this.plugin.helpers.substructureParent.get(e.selection.structure);
info.push({
label: cell?.obj?.label || 'Unknown',
key: k,
});
}
});
return info;
}
find(label: string) {
MesoscaleState.set(this.plugin, { filter: `"${label}"` });
if (label) expandAllGroups(this.plugin);
};
remove(key: string) {
const e = this.plugin.managers.structure.selection.entries.get(key);
if (!e) return;
const loci = Structure.toStructureElementLoci(e.selection.structure);
this.plugin.managers.interactivity.lociSelects.deselect({ loci }, false);
}
center(key: string) {
const e = this.plugin.managers.structure.selection.entries.get(key);
if (!e) return;
const loci = Structure.toStructureElementLoci(e.selection.structure);
centerLoci(this.plugin, loci);
}
get selection() {
const info = this.info;
if (!info.length) return <>
<div className='msp-help-text'>
<div>Use <i>ctrl+left click</i> to select entities, either on the 3D canvas or in the tree below</div>
</div>
</>;
return <>
{info.map((entry, index) => {
const label = <Button className={`msp-btn-tree-label`} noOverflow disabled={this.state.isDisabled}
onClick={() => this.center(entry.key)}
>
<span title={entry.label}>{entry.label}</span>
</Button>;
const find = <IconButton svg={SearchSvg} toggleState={false} disabled={this.state.isDisabled} small onClick={() => this.find(entry.label)} />;
const remove = <IconButton svg={CloseSvg} toggleState={false} disabled={this.state.isDisabled} onClick={() => this.remove(entry.key)} />;
return <div key={index} className={`msp-flex-row`} style={{ margin: `1px 5px 1px ${1 * 10 + 5}px` }}>
{label}
{find}
{remove}
</div>;
})}
</>;
}
get style() {
const p = this.plugin.canvas3d?.props;
if (!p) return;
if (p.renderer.dimStrength === 1 && p.marking.enabled) return 'color+outline';
if (p.renderer.dimStrength === 1) return 'color';
if (p.marking.enabled) return 'outline';
}
setStyle(value: SelectionStyle) {
if (value.includes('color') && value.includes('outline')) {
this.plugin.canvas3d?.setProps({
renderer: {
dimStrength: 1,
},
marking: {
enabled: true
}
});
} else if (value.includes('color')) {
this.plugin.canvas3d?.setProps({
renderer: {
dimStrength: 1,
},
marking: {
enabled: false
}
});
} else if (value.includes('outline')) {
this.plugin.canvas3d?.setProps({
renderer: {
dimStrength: 0,
selectStrength: 0.3,
},
marking: {
enabled: true
}
});
} else {
this.plugin.canvas3d?.setProps({
renderer: {
dimStrength: 0,
selectStrength: 0,
},
marking: {
enabled: false
}
});
}
this.forceUpdate();
}
renderStyle() {
const style = this.style || '';
return <div style={{ margin: '5px', marginBottom: '10px' }}>
<SelectControl name={'Style'} param={SelectionStyleParam} value={style} onChange={(e) => { this.setStyle(e.value); }} />
</div>;
}
render() {
return <>
{this.renderStyle()}
{this.selection}
</>;
}
}
export class EntityControls extends PluginUIComponent<{}, { isDisabled: boolean }> {
filterRef = React.createRef<HTMLInputElement>();
prevFilter = '';
filterFocus = false;
state = {
isDisabled: false,
};
componentDidMount() {
this.subscribe(this.plugin.state.events.object.created, e => {
this.forceUpdate();
});
this.subscribe(this.plugin.state.events.object.removed, e => {
this.forceUpdate();
});
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
this.setState({ isDisabled: v });
});
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
if (!this.state.isDisabled && this.roots.some(r => e.cell === r) || (MesoscaleState.has(this.plugin) && MesoscaleState.ref(this.plugin) === e.ref)) {
this.forceUpdate();
}
});
}
componentDidUpdate(): void {
const filter = this.filter;
if (this.filterFocus) {
this.filterRef.current?.focus();
this.prevFilter = filter;
}
}
get roots() {
return getRoots(this.plugin);
}
setGroupBy = (value: number) => {
this.roots.forEach((c, i) => {
if (c.state.isHidden && value === i || !c.state.isHidden && value !== i) {
PluginCommands.State.ToggleVisibility(this.plugin, { state: c.parent!, ref: c.transform.ref });
}
});
};
get groupBy() {
const roots = this.roots;
for (let i = 0, il = roots.length; i < il; ++i) {
if (!roots[i].state.isHidden) return i;
}
return 0;
}
setFilter = (value: string) => {
this.filterFocus = true;
const filter = value.trim().replace(/\s+/gi, ' ');
MesoscaleState.set(this.plugin, { filter });
if (filter) expandAllGroups(this.plugin);
};
get filter() {
return MesoscaleState.has(this.plugin) ? MesoscaleState.get(this.plugin).filter : '';
}
setGraphics = (graphics: GraphicsMode) => {
MesoscaleState.set(this.plugin, { graphics });
(this.plugin.customState as MesoscaleExplorerState).graphicsMode = graphics;
if (graphics === 'custom') return;
const update = this.plugin.state.data.build();
const { lodLevels, approximate, alphaThickness } = getGraphicsModeProps(graphics);
for (const r of getAllEntities(this.plugin)) {
update.to(r).update(old => {
if (old.type) {
old.type.params.lodLevels = lodLevels;
old.type.params.approximate = approximate;
old.type.params.alphaThickness = alphaThickness;
}
});
}
for (const g of getAllGroups(this.plugin)) {
update.to(g).update(old => {
old.lod.lodLevels = lodLevels;
old.lod.approximate = approximate;
});
}
update.commit();
setGraphicsCanvas3DProps(this.plugin, graphics);
};
get graphics() {
const customState = this.plugin.customState as MesoscaleExplorerState;
return MesoscaleState.has(this.plugin) ? MesoscaleState.get(this.plugin).graphics : customState.graphicsMode;
}
renderGraphics() {
const graphics = this.graphics;
return <div style={{ margin: '5px', marginBottom: '10px' }}>
<SelectControl name={'Graphics'} param={MesoscaleStateParams.graphics} value={`${graphics}`} onChange={(e) => { this.setGraphics(e.value); }} />
</div>;
}
render() {
const roots = this.roots;
if (roots.length === 0 || !MesoscaleState.has(this.plugin)) {
return <>
{this.renderGraphics()}
</>;
}
const disabled = this.state.isDisabled;
const groupBy = this.groupBy;
const options: [string, string][] = [];
roots.forEach((c, i) => {
options.push([`${i}`, c.obj!.label]);
});
const groupParam = PD.Select(options[0][0], options);
const root = roots.length === 1 ? roots[0] : roots[groupBy];
const filter = this.filter;
return <>
{this.renderGraphics()}
<div className={`msp-flex-row msp-control-row`} style={{ margin: '5px', marginBottom: '10px' }}>
<input type='text' ref={this.filterRef}
value={filter}
placeholder='Search'
onChange={e => this.setFilter(e.target.value)}
disabled={disabled}
onBlur={() => this.filterFocus = false}
/>
<IconButton svg={CloseSvg} toggleState={false} disabled={disabled} onClick={() => this.setFilter('')} />
</div>
{options.length > 1 && <div style={{ margin: '5px', marginBottom: '10px' }}>
<SelectControl name={'Group By'} param={groupParam} value={`${groupBy}`} onChange={(e) => { this.setGroupBy(parseInt(e.value)); }} />
</div>}
<GroupNode filter={filter} cell={root} depth={0} />
</>;
}
}
class Node<P extends {}, S extends { isDisabled: boolean }> extends PluginUIComponent<P & { cell: StateObjectCell, depth: number }, S> {
is(e: State.ObjectEvent) {
return e.ref === this.ref && e.state === this.props.cell.parent;
}
get ref() {
return this.props.cell.transform.ref;
}
get cell() {
return this.props.cell;
}
get roots() {
return getRoots(this.plugin);
}
componentDidMount() {
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
this.setState({ isDisabled: v });
});
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
if (!this.state.isDisabled && this.is(e)) {
this.forceUpdate();
}
});
}
}
export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean, action?: 'color' | 'clip' | 'root', isDisabled: boolean }> {
state = {
isCollapsed: !!this.props.cell.state.isCollapsed,
action: undefined,
isDisabled: false,
};
toggleExpanded = (e: React.MouseEvent<HTMLElement>) => {
PluginCommands.State.ToggleExpanded(this.plugin, { state: this.cell.parent!, ref: this.ref });
};
toggleColor = (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
this.setState({ action: this.state.action === 'color' ? undefined : 'color' });
};
toggleClip = () => {
this.setState({ action: this.state.action === 'clip' ? undefined : 'clip' });
};
toggleRoot = () => {
this.setState({ action: this.state.action === 'root' ? undefined : 'root' });
};
highlight = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
this.plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
for (const r of this.allFilteredEntities) {
const repr = r.obj?.data.repr;
if (repr) {
this.plugin.canvas3d?.mark({ repr, loci: EveryLoci }, MarkerAction.Highlight);
}
}
};
clearHighlight = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
this.plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
e.currentTarget.blur();
};
get groups() {
return getGroups(this.plugin, this.cell.params?.values.tag);
}
get allGroups() {
const allGroups = getAllGroups(this.plugin, this.cell.params?.values.tag);
allGroups.push(this.cell);
return allGroups;
}
get entities() {
return getEntities(this.plugin, this.cell.params?.values.tag);
}
get filteredEntities() {
return getFilteredEntities(this.plugin, this.cell.params?.values.tag, this.props.filter);
}
get allEntities() {
return getAllEntities(this.plugin, this.cell.params?.values.tag);
}
get allFilteredEntities() {
return getAllFilteredEntities(this.plugin, this.cell.params?.values.tag, this.props.filter);
}
toggleVisible = (e: React.MouseEvent<HTMLElement>) => {
PluginCommands.State.ToggleVisibility(this.plugin, { state: this.cell.parent!, ref: this.ref });
const isHidden = this.cell.state.isHidden;
for (const r of this.allFilteredEntities) {
this.plugin.state.data.updateCellState(r.transform.ref, { isHidden });
}
this.plugin.build().to(this.ref).update(old => {
old.hidden = isHidden;
}).commit();
};
updateColor = (values: ColorProps) => {
const update = this.plugin.state.data.build();
const { value, type, lightness, alpha, emissive } = values;
const entities = this.filteredEntities;
let groupColors: Color[] = [];
if (type === 'generate') {
groupColors = getDistinctGroupColors(entities.length, value, values.variability, values.shift);
}
for (let i = 0; i < entities.length; ++i) {
const c = type === 'generate' ? groupColors[i] : value;
update.to(entities[i]).update(old => {
if (old.type) {
old.colorTheme.params.value = c;
old.colorTheme.params.lightness = lightness;
old.type.params.alpha = alpha;
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
old.type.params.emissive = emissive;
} else {
old.coloring.params.color = c;
old.coloring.params.lightness = lightness;
old.alpha = alpha;
old.xrayShaded = alpha < 1 ? true : false;
old.emissive = emissive;
}
});
}
update.to(this.ref).update(old => {
old.color = values;
});
for (const r of this.roots) {
update.to(r).update(old => {
old.color.type = 'custom';
});
}
update.commit();
};
updateRoot = async (values: PD.Values) => {
await updateColors(this.plugin, values, this.cell.params?.values.tag, this.props.filter);
const update = this.plugin.state.data.build();
for (const r of this.roots) {
if (r !== this.cell) {
update.to(r).update(old => {
old.color.type = 'custom';
});
const others = getAllLeafGroups(this.plugin, r.params?.values.tag);
for (const o of others) {
update.to(o).update(old => {
old.color.type = 'custom';
});
}
}
}
update.to(this.ref).update(old => {
old.color = values;
});
update.commit();
};
updateClip = (values: PD.Values) => {
const update = this.plugin.state.data.build();
const clipObjects = getClipObjects(values as SimpleClipProps, this.plugin.canvas3d!.boundingSphere);
for (const r of this.allFilteredEntities) {
update.to(r).update(old => {
if (old.type) {
old.type.params.clip.objects = clipObjects;
} else {
old.clip.objects = clipObjects;
}
});
}
for (const g of this.allGroups) {
update.to(g).update(old => {
old.clip = values;
});
}
update.commit();
};
updateLod = (values: PD.Values) => {
MesoscaleState.set(this.plugin, { graphics: 'custom' });
(this.plugin.customState as MesoscaleExplorerState).graphicsMode = 'custom';
const update = this.plugin.state.data.build();
for (const r of this.allFilteredEntities) {
update.to(r).update(old => {
if (old.type) {
old.type.params.lodLevels = values.lodLevels;
old.type.params.cellSize = values.cellSize;
old.type.params.batchSize = values.batchSize;
old.type.params.approximate = values.approximate;
}
});
}
for (const g of this.allGroups) {
update.to(g).update(old => {
old.lod = values;
});
}
update.commit();
};
update = (props: MesoscaleGroupProps) => {
this.plugin.state.data.build().to(this.ref).update(props);
};
renderColor() {
const color = this.cell.params?.values.color;
if (this.cell.params?.values.color.type === 'uniform') {
const style = {
backgroundColor: Color.toStyle(color.value),
minWidth: 32,
width: 32,
borderRight: `6px solid ${Color.toStyle(Color.lighten(color.value, color.lightness))}`
};
return <Button style={style} onClick={this.toggleColor} />;
} else if (this.cell.params?.values.color.type === 'generate') {
const style = {
minWidth: 32,
width: 32,
borderRight: `6px solid ${Color.toStyle(Color.lighten(color.value, color.lightness))}`
};
return <IconButton style={style} svg={BrushSvg} toggleState={false} small onClick={this.toggleColor} />;
} else {
return <IconButton svg={BrushSvg} toggleState={false} small onClick={this.toggleColor} />;
}
}
render() {
if (this.allFilteredEntities.length === 0) return;
const state = this.cell.state;
const disabled = false;
const groupLabel = this.cell.obj!.label;
const depth = this.props.depth;
const colorValue = this.cell.params?.values.color;
const rootValue = this.cell.params?.values.color;
const clipValue = this.cell.params?.values.clip;
const lodValue = this.cell.params?.values.lod;
const isRoot = this.cell.params?.values.root;
const groups = this.groups;
const entities = this.entities;
const label = <Button className={`msp-btn-tree-label`} noOverflow disabled={disabled}
onMouseEnter={this.highlight}
onMouseLeave={this.clearHighlight}
>
<span title={groupLabel}>{groupLabel}</span>
</Button>;
const expand = <IconButton svg={state.isCollapsed ? ArrowRightSvg : ArrowDropDownSvg} flex='20px' disabled={disabled} onClick={this.toggleExpanded} transparent className='msp-no-hover-outline' style={{ visibility: groups.length > 0 || entities.length > 0 ? 'visible' : 'hidden' }} />;
const color = (entities.length > 0 && !isRoot) && this.renderColor();
const root = (isRoot && this.allGroups.length > 1) && <IconButton svg={BrushSvg} toggleState={false} disabled={disabled} small onClick={this.toggleRoot} />;
const clip = <IconButton svg={ContentCutSvg} toggleState={false} disabled={disabled} small onClick={this.toggleClip} />;
const visibility = <IconButton svg={state.isHidden ? VisibilityOffOutlinedSvg : VisibilityOutlinedSvg} toggleState={false} disabled={disabled} small onClick={this.toggleVisible} />;
return <>
<div className={`msp-flex-row`} style={{ margin: `1px 5px 1px ${depth * 10 + 5}px` }}>
{expand}
{label}
{root || color}
{clip}
{visibility}
</div>
{this.state.action === 'color' && <div style={{ marginRight: 5 }} className='msp-accent-offset'>
<ControlGroup header='Color' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor}
topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
<ParameterControls params={ColorParams} values={colorValue} onChangeValues={this.updateColor} />
</ControlGroup>
</div>}
{this.state.action === 'clip' && <div style={{ marginRight: 5 }} className='msp-accent-offset'>
<ControlGroup header='Clip' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleClip}
topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
<ParameterControls params={SimpleClipParams} values={clipValue} onChangeValues={this.updateClip} />
<ParameterControls params={LodParams} values={lodValue} onChangeValues={this.updateLod} />
</ControlGroup>
</div>}
{this.state.action === 'root' && <div style={{ marginRight: 5 }} className='msp-accent-offset'>
<ControlGroup header='Color' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleRoot}
topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
<ParameterControls params={RootParams} values={rootValue} onChangeValues={this.updateRoot} />
</ControlGroup>
</div>}
{(!state.isCollapsed) && <>
{groups.map(c => {
return <GroupNode filter={this.props.filter} cell={c} depth={depth + 1} key={c.transform.ref} />;
})}
{this.filteredEntities.map(c => {
return <EntityNode cell={c} depth={depth + 1} key={c.transform.ref} />;
})}
</>}
</>;
}
}
export class EntityNode extends Node<{}, { action?: 'color' | 'clip', isDisabled: boolean }> {
state = {
action: undefined,
isDisabled: false,
};
clipMapping = createClipMapping(this);
get groups() {
return this.plugin.state.data.select(StateSelection.Generators.ofTransformer(MesoscaleGroup)
.filter(c => !!this.cell.transform.tags?.includes(c.params?.values.tag)));
}
toggleVisible = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
PluginCommands.State.ToggleVisibility(this.plugin, { state: this.props.cell.parent!, ref: this.ref });
e.currentTarget.blur();
};
toggleColor = (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if (e?.ctrlKey) {
this.updateLightness({ lightness: this.lightnessValue?.lightness ? 0 : DimLightness });
e.preventDefault();
} else {
this.setState({ action: this.state.action === 'color' ? undefined : 'color' });
}
};
toggleClip = () => {
this.setState({ action: this.state.action === 'clip' ? undefined : 'clip' });
};
highlight = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
this.plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
const repr = this.cell?.obj?.data.repr;
if (repr) {
this.plugin.canvas3d?.mark({ repr, loci: EveryLoci }, MarkerAction.Highlight);
}
e.currentTarget.blur();
};
clearHighlight = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
this.plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
e.currentTarget.blur();
};
toggleSelect = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
const cell = this.cell as StateObjectCell<PSO.Molecule.Structure.Representation3D | PSO.Shape.Representation3D> | undefined;
if (!(cell?.obj?.data.sourceData instanceof Structure)) return;
const loci = Structure.toStructureElementLoci(cell.obj.data.sourceData);
this.plugin.managers.interactivity.lociSelects.toggle({ loci }, false);
};
center = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
const cell = this.cell as StateObjectCell<PSO.Molecule.Structure.Representation3D | PSO.Shape.Representation3D> | undefined;
if (!(cell?.obj?.data.sourceData instanceof Structure)) return;
const loci = Structure.toStructureElementLoci(cell.obj.data.sourceData);
centerLoci(this.plugin, loci);
};
handleClick = (e: React.MouseEvent<HTMLElement>) => {
if (e.ctrlKey) {
this.toggleSelect(e);
} else {
this.center(e);
}
};
get colorValue(): Color | undefined {
return this.cell.transform.params?.colorTheme?.params.value ?? this.cell.transform.params?.coloring?.params.color;
}
get lightnessValue(): { lightness: number } | undefined {
return {
lightness: this.cell.transform.params?.colorTheme?.params.lightness ?? this.cell.transform.params?.coloring?.params.lightness ?? 0
};
}
get opacityValue(): { alpha: number } | undefined {
return {
alpha: this.cell.transform.params?.type?.params.alpha ?? this.cell.transform.params?.alpha ?? 1
};
}
get emissiveValue(): { emissive: number } | undefined {
return {
emissive: this.cell.transform.params?.type?.params.emissive ?? this.cell.transform.params?.emissive ?? 0
};
}
get clipValue(): Clip.Props | undefined {
return this.cell.transform.params.type?.params.clip ?? this.cell.transform.params.clip;
}
get lodValue(): PD.Values<typeof LodParams> | undefined {
const p = this.cell.transform.params?.type?.params;
if (!p) return;
return {
lodLevels: p.lodLevels,
cellSize: p.cellSize,
batchSize: p.batchSize,
approximate: p.approximate,
};
}
get patternValue(): { amplitude: number, frequency: number } | undefined {
const p = this.cell.transform.params;
if (p.type) return;
return {
amplitude: p.bumpAmplitude,
frequency: p.bumpFrequency * 10,
};
}
updateColor: ParamOnChange = ({ value }) => {
const update = this.plugin.state.data.build();
for (const g of this.groups) {
update.to(g.transform.ref).update(old => {
old.color.type = 'custom';
});
}
for (const r of this.roots) {
update.to(r).update(old => {
old.color.type = 'custom';
});
}
update.to(this.ref).update(old => {
if (old.colorTheme) {
old.colorTheme.params.value = value;
} else if (old.coloring) {
old.coloring.params.color = value;
}
});
update.commit();
};
updateLightness = (values: PD.Values) => {
return this.plugin.build().to(this.ref).update(old => {
if (old.colorTheme) {
old.colorTheme.params.lightness = values.lightness;
} else if (old.coloring) {
old.coloring.params.lightness = values.lightness;
}
}).commit();
};
updateOpacity = (values: PD.Values) => {
return this.plugin.build().to(this.ref).update(old => {
if (old.type) {
old.type.params.alpha = values.alpha;
old.type.params.xrayShaded = values.alpha < 1 ? 'inverted' : false;
} else {
old.alpha = values.alpha;
old.xrayShaded = values.alpha < 1 ? true : false;
}
}).commit();
};
updateEmissive = (values: PD.Values) => {
return this.plugin.build().to(this.ref).update(old => {
if (old.type) {
old.type.params.emissive = values.emissive;
} else {
old.emissive = values.emissive;
}
}).commit();
};
updateClip = (props: Clip.Props) => {
const params = this.cell.transform.params;
const clip = params.type ? params.type.params.clip : params.clip;
if (!PD.areEqual(Clip.Params, clip, props)) {
this.plugin.build().to(this.ref).update(old => {
if (old.type) {
old.type.params.clip = props;
} else {
old.clip = props;
}
}).commit();
}
};
updateLod = (values: PD.Values) => {
const params = this.cell.transform.params as StateTransformer.Params<StructureRepresentation3D>;
if (!params.type) return;
MesoscaleState.set(this.plugin, { graphics: 'custom' });
(this.plugin.customState as MesoscaleExplorerState).graphicsMode = 'custom';
if (!deepEqual(params.type.params.lodLevels, values.lodLevels) || params.type.params.cellSize !== values.cellSize || params.type.params.batchSize !== values.batchSize || params.type.params.approximate !== values.approximate) {
this.plugin.build().to(this.ref).update(old => {
old.type.params.lodLevels = values.lodLevels;
old.type.params.cellSize = values.cellSize;
old.type.params.batchSize = values.batchSize;
old.type.params.approximate = values.approximate;
}).commit();
}
};
updatePattern = (values: PD.Values) => {
return this.plugin.build().to(this.ref).update(old => {
if (!old.type) {
old.bumpAmplitude = values.amplitude;
old.bumpFrequency = values.frequency / 10;
}
}).commit();
};
render() {
const cellState = this.cell.state;
const disabled = this.cell.status !== 'error' && this.cell.status !== 'ok';
const depth = this.props.depth;
const colorValue = this.colorValue;
const lightnessValue = this.lightnessValue;
const opacityValue = this.opacityValue;
const emissiveValue = this.emissiveValue;
const lodValue = this.lodValue;
const patternValue = this.patternValue;
const l = getEntityLabel(this.plugin, this.cell);
const label = <Button className={`msp-btn-tree-label msp-type-class-${this.cell.obj!.type.typeClass}`} noOverflow disabled={disabled}
onClick={this.handleClick}
onMouseEnter={this.highlight}
onMouseLeave={this.clearHighlight}
>
<span title={l}>{l}</span>
</Button>;
const color = colorValue !== undefined && <Button style={{ backgroundColor: Color.toStyle(colorValue), minWidth: 32, width: 32, borderRight: `6px solid ${Color.toStyle(Color.lighten(colorValue, lightnessValue?.lightness || 0))}` }} onClick={this.toggleColor} />;
const clip = <IconButton svg={ContentCutSvg} toggleState={false} disabled={disabled} small onClick={this.toggleClip} />;
const visibility = <IconButton svg={cellState.isHidden ? VisibilityOffOutlinedSvg : VisibilityOutlinedSvg} toggleState={false} disabled={disabled} small onClick={this.toggleVisible} />;
return <>
<div className={`msp-flex-row`} style={{ margin: `1px 5px 1px ${depth * 10 + 5}px` }}>
{label}
{color}
{clip}
{visibility}
</div>
{this.state.action === 'color' && colorValue !== void 0 && <div style={{ marginRight: 5 }} className='msp-accent-offset'>
<ControlGroup header='Color' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor}
topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
<CombinedColorControl param={ColorValueParam} value={colorValue ?? Color(0xFFFFFF)} onChange={this.updateColor} name='color' hideNameRow />
<ParameterControls params={LightnessParams} values={lightnessValue} onChangeValues={this.updateLightness} />
<ParameterControls params={OpacityParams} values={opacityValue} onChangeValues={this.updateOpacity} />
<ParameterControls params={EmissiveParams} values={emissiveValue} onChangeValues={this.updateEmissive} />
{patternValue && <ParameterControls params={PatternParams} values={patternValue} onChangeValues={this.updatePattern} />}
</ControlGroup>
</div>}
{this.state.action === 'clip' && <div style={{ marginRight: 5 }} className='msp-accent-offset'>
<ControlGroup header='Clip' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleClip}
topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
<ParameterMappingControl mapping={this.clipMapping} />
{lodValue && <ParameterControls params={LodParams} values={lodValue} onChangeValues={this.updateLod} />}
</ControlGroup>
</div>}
</>;
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mp4EncoderUI } from '../../../extensions/mp4-export/ui';
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
import { SectionHeader } from '../../../mol-plugin-ui/controls/common';
import { MesoscaleExplorerState } from '../app';
import { MesoscaleState } from '../data/state';
import { EntityControls, ModelInfo, SelectionInfo } from './entities';
import { LoaderControls, ExampleControls, SessionControls, SnapshotControls, DatabaseControls } from './states';
const Spacer = () => <div style={{ height: '2em' }} />;
export class LeftPanel extends PluginUIComponent {
render() {
const customState = this.plugin.customState as MesoscaleExplorerState;
return <div className='msp-scrollable-container'>
<SectionHeader title='Database' />
<DatabaseControls />
<Spacer />
<SectionHeader title='Open' />
<LoaderControls />
<Spacer />
{customState.examples?.length && <>
<SectionHeader title='Example' />
<ExampleControls />
<Spacer />
</>}
<SectionHeader title='Session' />
<SessionControls />
<Spacer />
<SectionHeader title='Snapshots' />
<SnapshotControls />
<Spacer />
<Mp4EncoderUI />
</div>;
}
}
export class RightPanel extends PluginUIComponent<{}, { isDisabled: boolean }> {
state = {
isDisabled: false,
};
get hasModelInfo() {
return (
MesoscaleState.has(this.plugin) &&
!!(MesoscaleState.get(this.plugin).description ||
MesoscaleState.get(this.plugin).link)
);
}
componentDidMount() {
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
this.setState({ isDisabled: v });
});
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
if (!this.state.isDisabled && MesoscaleState.has(this.plugin) && MesoscaleState.ref(this.plugin) === e.ref) {
this.forceUpdate();
}
});
this.subscribe(this.plugin.managers.structure.selection.events.changed, e => {
if (!this.state.isDisabled) {
this.forceUpdate();
}
});
}
render() {
return <div className='msp-scrollable-container'>
{this.hasModelInfo && <>
<SectionHeader title='Model' />
<ModelInfo />
<Spacer />
</>}
<>
<SectionHeader title='Selection' />
<SelectionInfo />
<Spacer />
</>
<SectionHeader title='Entities' />
<EntityControls />
</div>;
}
}

View File

@@ -0,0 +1,363 @@
/**
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
import { MmcifProvider } from '../../../mol-plugin-state/formats/trajectory';
import { PluginStateObject } from '../../../mol-plugin-state/objects';
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
import { Button, ExpandGroup } from '../../../mol-plugin-ui/controls/common';
import { GetAppSvg, Icon, OpenInBrowserSvg } from '../../../mol-plugin-ui/controls/icons';
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
import { LocalStateSnapshotList, LocalStateSnapshotParams, LocalStateSnapshots } from '../../../mol-plugin-ui/state/snapshots';
import { PluginCommands } from '../../../mol-plugin/commands';
import { PluginContext } from '../../../mol-plugin/context';
import { StateAction, StateObjectRef, StateTransform } from '../../../mol-state';
import { Task } from '../../../mol-task';
import { Color } from '../../../mol-util/color/color';
import { getFileNameInfo } from '../../../mol-util/file-info';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { ExampleEntry, MesoscaleExplorerState } from '../app';
import { createCellpackHierarchy } from '../data/cellpack/preset';
import { createGenericHierarchy } from '../data/generic/preset';
import { createMmcifHierarchy } from '../data/mmcif/preset';
import { createPetworldHierarchy } from '../data/petworld/preset';
import { MesoscaleState, MesoscaleStateObject, setGraphicsCanvas3DProps } from '../data/state';
function adjustPluginProps(ctx: PluginContext) {
ctx.managers.interactivity.setProps({ granularity: 'chain' });
ctx.canvas3d?.setProps({
multiSample: { mode: 'off' },
cameraClipping: { far: false, minNear: 50 },
sceneRadiusFactor: 2,
renderer: {
colorMarker: true,
highlightColor: Color(0xffffff),
highlightStrength: 0,
selectColor: Color(0xffffff),
selectStrength: 0,
dimColor: Color(0xffffff),
dimStrength: 1,
markerPriority: 2,
interiorColorFlag: false,
interiorDarkening: 0.15,
exposure: 1.1,
xrayEdgeFalloff: 3,
},
marking: {
enabled: true,
highlightEdgeColor: Color(0x999999),
selectEdgeColor: Color(0xffff00),
highlightEdgeStrength: 1,
selectEdgeStrength: 1,
ghostEdgeStrength: 1,
innerEdgeFactor: 2.5,
edgeScale: 2,
},
postprocessing: {
occlusion: {
name: 'on',
params: {
samples: 32,
multiScale: {
name: 'on',
params: {
levels: [
{ radius: 2, bias: 1.0 },
{ radius: 5, bias: 1.0 },
{ radius: 8, bias: 1.0 },
{ radius: 11, bias: 1.0 },
],
nearThreshold: 10,
farThreshold: 1500,
}
},
radius: 5,
bias: 1,
blurKernelSize: 11,
resolutionScale: 1,
color: Color(0x000000),
}
},
shadow: {
name: 'on',
params: {
bias: 0.6,
maxDistance: 80,
steps: 3,
tolerance: 1.0,
}
},
outline: {
name: 'on',
params: {
scale: 1,
threshold: 0.15,
color: Color(0x000000),
includeTransparent: false,
}
}
}
});
const { graphics } = MesoscaleState.get(ctx);
setGraphicsCanvas3DProps(ctx, graphics);
}
async function createHierarchy(ctx: PluginContext, ref: string) {
const parsed = await MmcifProvider.parse(ctx, ref);
const tr = StateObjectRef.resolveAndCheck(ctx.state.data, parsed.trajectory)?.obj?.data;
if (!tr) throw new Error('no trajectory');
if (!MmcifFormat.is(tr.representative.sourceData)) {
throw new Error('not mmcif');
}
const { frame, db } = tr.representative.sourceData.data;
let hasCellpackAssemblyMethodDetails = false;
const { method_details } = db.pdbx_struct_assembly;
for (let i = 0, il = method_details.rowCount; i < il; ++i) {
if (method_details.value(i).toUpperCase() === 'CELLPACK') {
hasCellpackAssemblyMethodDetails = true;
break;
}
}
if (frame.categories.pdbx_model) {
await createPetworldHierarchy(ctx, parsed.trajectory);
} else if (
frame.header.toUpperCase().includes('CELLPACK') ||
hasCellpackAssemblyMethodDetails
) {
await createCellpackHierarchy(ctx, parsed.trajectory);
} else {
await createMmcifHierarchy(ctx, parsed.trajectory);
}
}
async function reset(ctx: PluginContext) {
const customState = ctx.customState as MesoscaleExplorerState;
delete customState.stateRef;
customState.stateCache = {};
ctx.managers.asset.clear();
await PluginCommands.State.Snapshots.Clear(ctx);
await PluginCommands.State.RemoveObject(ctx, { state: ctx.state.data, ref: StateTransform.RootRef });
await MesoscaleState.init(ctx);
adjustPluginProps(ctx);
}
export async function loadExampleEntry(ctx: PluginContext, entry: ExampleEntry) {
const { url, type } = entry;
await loadUrl(ctx, url, type);
MesoscaleState.set(ctx, {
description: entry.description || entry.label,
link: entry.link,
});
}
export async function loadUrl(ctx: PluginContext, url: string, type: 'molx' | 'molj' | 'cif' | 'bcif') {
if (type === 'molx' || type === 'molj') {
await PluginCommands.State.Snapshots.OpenUrl(ctx, { url, type });
} else {
await reset(ctx);
const isBinary = type === 'bcif';
const data = await ctx.builders.data.download({ url, isBinary });
await createHierarchy(ctx, data.ref);
}
}
export async function loadPdb(ctx: PluginContext, id: string) {
await reset(ctx);
const url = `https://models.rcsb.org/${id.toUpperCase()}.bcif`;
const data = await ctx.builders.data.download({ url, isBinary: true });
await createHierarchy(ctx, data.ref);
}
export async function loadPdbDev(ctx: PluginContext, id: string) {
await reset(ctx);
const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
const url = `https://pdb-dev.wwpdb.org/bcif/${nId.toUpperCase()}.bcif`;
const data = await ctx.builders.data.download({ url, isBinary: true });
await createHierarchy(ctx, data.ref);
}
//
export const LoadDatabase = StateAction.build({
display: { name: 'Database', description: 'Load from Database' },
params: (a, ctx: PluginContext) => {
return {
source: PD.Select('pdb', PD.objectToOptions({ pdb: 'PDB', pdbDev: 'PDB-Dev' })),
entry: PD.Text(''),
};
},
from: PluginStateObject.Root
})(({ params }, ctx: PluginContext) => Task.create('Loading from database...', async taskCtx => {
if (params.source === 'pdb') {
await loadPdb(ctx, params.entry);
} else if (params.source === 'pdbDev') {
await loadPdbDev(ctx, params.entry);
}
}));
export const LoadExample = StateAction.build({
display: { name: 'Load', description: 'Load an example' },
params: (a, ctx: PluginContext) => {
const entries = (ctx.customState as MesoscaleExplorerState).examples || [];
return {
entry: PD.Select(0, entries.map((s, i) => [i, s.label])),
};
},
from: PluginStateObject.Root
})(({ params }, ctx: PluginContext) => Task.create('Loading example...', async taskCtx => {
const entries = (ctx.customState as MesoscaleExplorerState).examples || [];
await loadExampleEntry(ctx, entries[params.entry]);
}));
export const LoadModel = StateAction.build({
display: { name: 'Load', description: 'Load a model' },
params: {
files: PD.FileList({ accept: '.cif,.bcif,.cif.gz,.bcif.gz,.zip', multiple: true, description: 'mmCIF or Cellpack- or Petworld-style cif file.', label: 'File(s)' }),
},
from: PluginStateObject.Root
})(({ params }, ctx: PluginContext) => Task.create('Loading model...', async taskCtx => {
if (params.files === null || params.files.length === 0) {
ctx.log.error('No file(s) selected');
return;
}
await reset(ctx);
const firstFile = params.files[0];
const firstInfo = getFileNameInfo(firstFile.file!.name);
if (firstInfo.name.endsWith('zip')) {
try {
await createGenericHierarchy(ctx, firstFile);
} catch (e) {
console.error(e);
ctx.log.error(`Error opening file '${firstFile.name}'`);
}
} else {
for (const file of params.files) {
try {
const info = getFileNameInfo(file.file!.name);
if (!['cif', 'bcif'].includes(info.ext)) continue;
const isBinary = ctx.dataFormats.binaryExtensions.has(info.ext);
const { data } = await ctx.builders.data.readFile({ file, isBinary });
await createHierarchy(ctx, data.ref);
} catch (e) {
console.error(e);
ctx.log.error(`Error opening file '${file.name}'`);
}
}
}
}));
//
export class DatabaseControls extends PluginUIComponent {
componentDidMount() {
}
render() {
return <div style={{ margin: '5px' }}>
<ApplyActionControl state={this.plugin.state.data} action={LoadDatabase} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
</div>;
}
}
export class LoaderControls extends PluginUIComponent {
componentDidMount() {
}
render() {
return <div style={{ margin: '5px' }}>
<ApplyActionControl state={this.plugin.state.data} action={LoadModel} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
</div>;
}
}
export class ExampleControls extends PluginUIComponent {
componentDidMount() {
}
render() {
return <div style={{ margin: '5px' }}>
<ApplyActionControl state={this.plugin.state.data} action={LoadExample} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
</div>;
}
}
export async function openState(ctx: PluginContext, file: File) {
const customState = ctx.customState as MesoscaleExplorerState;
delete customState.stateRef;
customState.stateCache = {};
ctx.managers.asset.clear();
await PluginCommands.State.Snapshots.Clear(ctx);
await PluginCommands.State.Snapshots.OpenFile(ctx, { file });
const cell = ctx.state.data.selectQ(q => q.ofType(MesoscaleStateObject))[0];
if (!cell) throw new Error('Missing MesoscaleState');
customState.stateRef = cell.transform.ref;
customState.graphicsMode = cell.obj?.data.graphics || customState.graphicsMode;
}
export class SessionControls extends PluginUIComponent {
downloadToFileZip = () => {
PluginCommands.State.Snapshots.DownloadToFile(this.plugin, { type: 'zip' });
};
open = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files || !e.target.files[0]) {
this.plugin.log.error('No state file selected');
return;
}
openState(this.plugin, e.target.files[0]);
};
render() {
return <div style={{ margin: '5px' }}>
<div className='msp-flex-row'>
<Button icon={GetAppSvg} onClick={this.downloadToFileZip} title='Download the state.'>
Download
</Button>
<div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file'>
<Icon svg={OpenInBrowserSvg} inline /> Open <input onChange={this.open} type='file' multiple={false} accept='.molx,.molj' />
</div>
</div>
</div>;
}
}
export class SnapshotControls extends PluginUIComponent<{}> {
render() {
return <div style={{ margin: '5px' }}>
<div style={{ marginBottom: '10px' }}>
<LocalStateSnapshotList />
</div>
<div style={{ marginBottom: '10px' }}>
<LocalStateSnapshots />
</div>
<div style={{ marginBottom: '10px' }}>
<ExpandGroup header='Snapshot Options' initiallyExpanded={false}>
<LocalStateSnapshotParams />
</ExpandGroup>
</div>
</div>;
}
}

View File

@@ -1,40 +1,49 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { Backgrounds } from '../../extensions/backgrounds';
import { DnatcoNtCs } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { Volseg, VolsegVolumeServerConfig } from '../../extensions/volumes-and-segmentations';
import { GeometryExport } from '../../extensions/geo-export';
import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
import { MAQualityAssessment, 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 { loadMVSX } from '../../extensions/mvs/components/formats';
import { loadMVS } from '../../extensions/mvs/load';
import { MVSData } from '../../extensions/mvs/mvs-data';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { RCSBValidationReport } from '../../extensions/rcsb';
import { AssemblySymmetry, AssemblySymmetryConfig } from '../../extensions/assembly-symmetry';
import { SbNcbrPartialCharges, SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider } from '../../extensions/sb-ncbr';
import { Volseg, VolsegVolumeServerConfig } from '../../extensions/volumes-and-segmentations';
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 { Volume } from '../../mol-model/volume';
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 { BuiltInCoordinatesFormat } from '../../mol-plugin-state/formats/coordinates';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { BuiltInTopologyFormat } from '../../mol-plugin-state/formats/topology';
import { BuiltInCoordinatesFormat } from '../../mol-plugin-state/formats/coordinates';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
import { createPluginUI } from '../../mol-plugin-ui/react18';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { createPluginUI } from '../../mol-plugin-ui';
import { renderReact18 } from '../../mol-plugin-ui/react18';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
@@ -42,18 +51,14 @@ import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { PluginSpec } from '../../mol-plugin/spec';
import { PluginState } from '../../mol-plugin/state';
import { StateObjectRef, 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 { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
import { Backgrounds } from '../../extensions/backgrounds';
import { SbNcbrPartialCharges, SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider } from '../../extensions/sb-ncbr';
import { wwPDBStructConnExtensionFunctions } from '../../extensions/wwpdb/struct-conn';
import { wwPDBChemicalComponentDictionary } from '../../extensions/wwpdb/ccd/behavior';
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug';
export { consoleStats, setDebugMode, setProductionMode, setTimingMode } from '../../mol-util/debug';
const CustomFormats = [
['g3d', G3dProvider] as const
@@ -62,10 +67,9 @@ const CustomFormats = [
export const ExtensionMap = {
'volseg': PluginSpec.Behavior(Volseg),
'backgrounds': PluginSpec.Behavior(Backgrounds),
'cellpack': PluginSpec.Behavior(CellPack),
'dnatco-ntcs': PluginSpec.Behavior(DnatcoNtCs),
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
'assembly-symmetry': PluginSpec.Behavior(AssemblySymmetry),
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
@@ -76,6 +80,7 @@ export const ExtensionMap = {
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
'sb-ncbr-partial-charges': PluginSpec.Behavior(SbNcbrPartialCharges),
'wwpdb-chemical-component-dictionary': PluginSpec.Behavior(wwPDBChemicalComponentDictionary),
'mvs': PluginSpec.Behavior(MolViewSpec),
};
const DefaultViewerOptions = {
@@ -94,9 +99,7 @@ const DefaultViewerOptions = {
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
pixelScale: PluginConfig.General.PixelScale.defaultValue,
pickScale: PluginConfig.General.PickScale.defaultValue,
pickPadding: PluginConfig.General.PickPadding.defaultValue,
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
enableDpoit: PluginConfig.General.EnableDpoit.defaultValue,
transparency: PluginConfig.General.Transparency.defaultValue,
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
@@ -114,6 +117,9 @@ const DefaultViewerOptions = {
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
volumesAndSegmentationsDefaultServer: VolsegVolumeServerConfig.DefaultServer.defaultValue,
rcsbAssemblySymmetryDefaultServerType: AssemblySymmetryConfig.DefaultServerType.defaultValue,
rcsbAssemblySymmetryDefaultServerUrl: AssemblySymmetryConfig.DefaultServerUrl.defaultValue,
rcsbAssemblySymmetryApplyColors: AssemblySymmetryConfig.ApplyColors.defaultValue,
};
type ViewerOptions = typeof DefaultViewerOptions;
@@ -170,9 +176,7 @@ export class Viewer {
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
[PluginConfig.General.PixelScale, o.pixelScale],
[PluginConfig.General.PickScale, o.pickScale],
[PluginConfig.General.PickPadding, o.pickPadding],
[PluginConfig.General.EnableWboit, o.enableWboit],
[PluginConfig.General.EnableDpoit, o.enableDpoit],
[PluginConfig.General.Transparency, o.transparency],
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
[PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat],
[PluginConfig.General.PowerPreference, o.powerPreference],
@@ -191,6 +195,9 @@ export class Viewer {
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
[PluginConfig.Structure.SaccharideCompIdMapType, o.saccharideCompIdMapType],
[VolsegVolumeServerConfig.DefaultServer, o.volumesAndSegmentationsDefaultServer],
[AssemblySymmetryConfig.DefaultServerType, o.rcsbAssemblySymmetryDefaultServerType],
[AssemblySymmetryConfig.DefaultServerUrl, o.rcsbAssemblySymmetryDefaultServerUrl],
[AssemblySymmetryConfig.ApplyColors, o.rcsbAssemblySymmetryApplyColors],
]
};
@@ -198,7 +205,10 @@ export class Viewer {
? document.getElementById(elementOrId)
: elementOrId;
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
const plugin = await createPluginUI(element, spec, {
const plugin = await createPluginUI({
target: element,
spec,
render: renderReact18,
onBeforeUIRender: plugin => {
// the preset needs to be added before the UI renders otherwise
// "Download Structure" wont be able to pick it up
@@ -460,9 +470,55 @@ export class Viewer {
return { model, coords, preset };
}
async loadMvsFromUrl(url: string, format: 'mvsj' | 'mvsx', options?: { replaceExisting?: boolean, keepCamera?: boolean }) {
if (format === 'mvsj') {
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'string' }));
const mvsData = MVSData.fromMVSJ(data);
await loadMVS(this.plugin, mvsData, { sanityChecks: true, sourceUrl: url, ...options });
} else if (format === 'mvsx') {
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
await this.plugin.runTask(Task.create('Load MVSX file', async ctx => {
const parsed = await loadMVSX(this.plugin, ctx, data);
await loadMVS(this.plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl, ...options });
}));
} else {
throw new Error(`Unknown MolViewSpec format: ${format}`);
}
}
/** Load MolViewSpec from `data`.
* If `format` is 'mvsj', `data` must be a string or a Uint8Array containing a UTF8-encoded string.
* If `format` is 'mvsx', `data` must be a Uint8Array or a string containing base64-encoded binary data prefixed with 'base64,'. */
async loadMvsData(data: string | Uint8Array, format: 'mvsj' | 'mvsx', options?: { replaceExisting?: boolean, keepCamera?: boolean }) {
if (typeof data === 'string' && data.startsWith('base64')) {
data = Uint8Array.from(atob(data.substring(7)), c => c.charCodeAt(0)); // Decode base64 string to Uint8Array
}
if (format === 'mvsj') {
if (typeof data !== 'string') {
data = new TextDecoder().decode(data); // Decode Uint8Array to string using UTF8
}
const mvsData = MVSData.fromMVSJ(data);
await loadMVS(this.plugin, mvsData, { sanityChecks: true, sourceUrl: undefined, ...options });
} else if (format === 'mvsx') {
if (typeof data === 'string') {
throw new Error("loadMvsData: if `format` is 'mvsx', then `data` must be a Uint8Array or a base64-encoded string prefixed with 'base64,'.");
}
await this.plugin.runTask(Task.create('Load MVSX file', async ctx => {
const parsed = await loadMVSX(this.plugin, ctx, data as Uint8Array);
await loadMVS(this.plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl, ...options });
}));
} else {
throw new Error(`Unknown MolViewSpec format: ${format}`);
}
}
handleResize() {
this.plugin.layout.events.updated.next(void 0);
}
dispose() {
this.plugin.dispose();
}
}
export interface LoadStructureOptions {
@@ -521,4 +577,5 @@ export const ViewerAutoPreset = StructureRepresentationPresetProvider({
export const PluginExtensions = {
wwPDBStructConn: wwPDBStructConnExtensionFunctions,
mvs: { MVSData, loadMVS },
};

View File

@@ -59,8 +59,7 @@
var pixelScale = getParam('pixel-scale', '[^&]+').trim();
var pickScale = getParam('pick-scale', '[^&]+').trim();
var pickPadding = getParam('pick-padding', '[^&]+').trim();
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
var enableDpoit = getParam('enable-dpoit', '[^&]+').trim() === '1';
var transparency = getParam('transparency', '[^&]+').trim().toLowerCase();
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
var powerPreference = getParam('power-preference', '[^&]+').trim().toLowerCase();
@@ -80,8 +79,7 @@
pixelScale: parseFloat(pixelScale) || 1,
pickScale: parseFloat(pickScale) || 0.25,
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
enableWboit: (disableWboit || enableDpoit) ? false : void 0, // use default value if disable-wboit is not set
enableDpoit: enableDpoit ? true : void 0,
transparency: transparency || undefined,
preferWebgl1: preferWebgl1,
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
powerPreference: powerPreference || 'high-performance',
@@ -98,6 +96,14 @@
var structureUrlIsBinary = getParam('structure-url-is-binary', '[^&]+').trim() === '1';
if (structureUrl) viewer.loadStructureFromUrl(structureUrl, structureUrlFormat, structureUrlIsBinary);
var mvsUrl = getParam('mvs-url', '[^&]+').trim();
var mvsData = getParam('mvs-data', '[^&]+').trim();
var mvsFormat = getParam('mvs-format', '[^&]+').trim() || 'mvsj';
if (mvsUrl && mvsData) console.error('Cannot specify mvs-url and mvs-data URL parameters at the same time. Ignoring both.');
else if (mvsUrl) viewer.loadMvsFromUrl(mvsUrl, mvsFormat);
else if (mvsData) viewer.loadMvsData(mvsData, mvsFormat);
var pdb = getParam('pdb', '[^&]+').trim();
if (pdb) viewer.loadPdb(pdb);
@@ -112,6 +118,11 @@
var modelArchive = getParam('model-archive', '[^&]+').trim();
if (modelArchive) viewer.loadModelArchive(modelArchive);
window.addEventListener('unload', () => {
// to aid GC
viewer.dispose();
});
});
</script>
<!-- __MOLSTAR_ANALYTICS__ -->

View File

@@ -158,8 +158,8 @@ async function ensureDicAvailable(dicPath: string, dicUrl: string) {
const DIC_DIR = path.resolve(__dirname, '../../../../build/dics/');
const MMCIF_DIC_PATH = `${DIC_DIR}/mmcif_pdbx_v50.dic`;
const MMCIF_DIC_URL = 'http://mmcif.wwpdb.org/dictionaries/ascii/mmcif_pdbx_v50.dic';
const IHM_DIC_PATH = `${DIC_DIR}/ihm-extension.dic`;
const IHM_DIC_URL = 'https://raw.githubusercontent.com/ihmwg/IHM-dictionary/master/ihm-extension.dic';
const IHM_DIC_PATH = `${DIC_DIR}/mmcif_ihm_ext.dic`;
const IHM_DIC_URL = 'https://raw.githubusercontent.com/ihmwg/IHMCIF/master/dist/mmcif_ihm_ext.dic';
const MA_DIC_PATH = `${DIC_DIR}/ma-extension.dic`;
const MA_DIC_URL = 'https://raw.githubusercontent.com/ihmwg/ModelCIF/master/dist/mmcif_ma.dic';

View File

@@ -22,6 +22,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'uline':
case 'uchar3':
case 'uchar1':
case 'uchar5':
// only force lower-case for enums
return values && values.length ? EnumCol(values.map(x => x.toLowerCase()), 'lstr', description) : StrCol(description);
case 'aliasname':
@@ -40,6 +41,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'pdbx_related_db_id':
case 'sequence_dep':
case 'pdb_id':
case 'pdb_id_u': // should be case insensitve, but can't express that
case 'emd_id':
// todo, consider adding specialised fields
case 'yyyy-mm-dd':
@@ -61,6 +63,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'symop':
case 'exp_data_doi':
case 'asym_id':
case 'uniprot_ptm_id':
return StrCol(description);
case 'int':
case 'non_negative_int':
@@ -89,6 +92,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'Tag':
case 'Implied':
case 'Word':
case 'Uri':
return wrapContainer('str', ',', description, container);
case 'Real':
return wrapContainer('float', ',', description, container);

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env node
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*
* Command-line application for printing MolViewSpec tree schema
* Build: npm run build
* Run: node lib/commonjs/cli/mvs/mvs-print-schema
* node lib/commonjs/cli/mvs/mvs-print-schema --markdown
*/
import { ArgumentParser } from 'argparse';
import { treeSchemaToMarkdown, treeSchemaToString } from '../../extensions/mvs/tree/generic/tree-schema';
import { MVSDefaults } from '../../extensions/mvs/tree/mvs/mvs-defaults';
import { MVSTreeSchema } from '../../extensions/mvs/tree/mvs/mvs-tree';
/** Command line argument values for `main` */
interface Args {
markdown: boolean,
}
/** Return parsed command line arguments for `main` */
function parseArguments(): Args {
const parser = new ArgumentParser({ description: 'Command-line application for printing MolViewSpec tree schema.' });
parser.add_argument('-m', '--markdown', { action: 'store_true', help: 'Print the schema as markdown instead of plain text.' });
const args = parser.parse_args();
return { ...args };
}
/** Main workflow for printing MolViewSpec tree schema. */
function main(args: Args) {
if (args.markdown) {
console.log(treeSchemaToMarkdown(MVSTreeSchema, MVSDefaults));
} else {
console.log(treeSchemaToString(MVSTreeSchema, MVSDefaults));
}
}
main(parseArguments());

158
src/cli/mvs/mvs-render.ts Normal file
View File

@@ -0,0 +1,158 @@
#!/usr/bin/env node
/**
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*
* Command-line application for rendering images from MolViewSpec files
* Build: npm install --no-save canvas gl jpeg-js pngjs // these packages are not listed in Mol* dependencies for performance reasons
* npm run build
* Run: node lib/commonjs/cli/mvs/mvs-render -i examples/mvs/1cbs.mvsj -o ../outputs/1cbs.png --size 800x600 --molj
*/
import { ArgumentParser } from 'argparse';
import fs from 'fs';
import gl from 'gl';
import jpegjs from 'jpeg-js';
import path from 'path';
import pngjs from 'pngjs';
import { Canvas3DParams } from '../../mol-canvas3d/canvas3d';
import { setCanvasModule } from '../../mol-geo/geometry/text/font-atlas';
import { PluginContext } from '../../mol-plugin/context';
import { HeadlessPluginContext } from '../../mol-plugin/headless-plugin-context';
import { DefaultPluginSpec, PluginSpec } from '../../mol-plugin/spec';
import { ExternalModules, defaultCanvas3DParams } from '../../mol-plugin/util/headless-screenshot';
import { Task } from '../../mol-task';
import { setFSModule } from '../../mol-util/data-source';
import { onelinerJsonString } from '../../mol-util/json';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
// MolViewSpec must be imported after HeadlessPluginContext
import { MolViewSpec } from '../../extensions/mvs/behavior';
import { loadMVSX } from '../../extensions/mvs/components/formats';
import { loadMVS } from '../../extensions/mvs/load';
import { MVSData } from '../../extensions/mvs/mvs-data';
setFSModule(fs);
setCanvasModule(require('canvas'));
const DEFAULT_SIZE = '800x800';
/** Command line argument values for `main` */
interface Args {
input: string[],
output: string[],
size: { width: number, height: number },
molj: boolean,
}
/** Return parsed command line arguments for `main` */
function parseArguments(): Args {
const parser = new ArgumentParser({ description: 'Command-line application for rendering images from MolViewSpec files' });
parser.add_argument('-i', '--input', { required: true, nargs: '+', help: 'Input file(s) in .mvsj or .mvsx format. File format is inferred from the file extension.' });
parser.add_argument('-o', '--output', { required: true, nargs: '+', help: 'File path(s) for output files (one output path for each input file). Output format is inferred from the file extension (.png or .jpg)' });
parser.add_argument('-s', '--size', { help: `Output image resolution, {width}x{height}. Default: ${DEFAULT_SIZE}.`, default: DEFAULT_SIZE });
parser.add_argument('-m', '--molj', { action: 'store_true', help: `Save Mol* state (.molj) in addition to rendered images (use the same output file paths but with .molj extension)` });
const args = parser.parse_args();
try {
const parts = args.size.split('x');
if (parts.length !== 2) throw new Error('Must contain two x-separated parts');
args.size = { width: parseIntStrict(parts[0]), height: parseIntStrict(parts[1]) };
} catch {
parser.error(`argument: --size: invalid image size string: '${args.size}' (must be two x-separated integers (width and height), e.g. '400x300')`);
}
if (args.input.length !== args.output.length) {
parser.error(`argument: --output: must specify the same number of input and output file paths (specified ${args.input.length} input path${args.input.length !== 1 ? 's' : ''} but ${args.output.length} output path${args.output.length !== 1 ? 's' : ''})`);
}
return { ...args };
}
/** Main workflow for rendering images from MolViewSpec files */
async function main(args: Args): Promise<void> {
const plugin = await createHeadlessPlugin(args);
for (let i = 0; i < args.input.length; i++) {
const input = args.input[i];
const output = args.output[i];
console.log(`Processing ${input} -> ${output}`);
let mvsData: MVSData;
let sourceUrl: string | undefined;
if (input.toLowerCase().endsWith('.mvsj')) {
const data = fs.readFileSync(input, { encoding: 'utf8' });
mvsData = MVSData.fromMVSJ(data);
sourceUrl = `file://${path.resolve(input)}`;
} else if (input.toLowerCase().endsWith('.mvsx')) {
const data = fs.readFileSync(input);
const mvsx = await plugin.runTask(Task.create('Load MVSX', async ctx => loadMVSX(plugin, ctx, data)));
mvsData = mvsx.mvsData;
sourceUrl = mvsx.sourceUrl;
} else {
throw new Error(`Input file name must end with .mvsj or .mvsx: ${input}`);
}
await loadMVS(plugin, mvsData, { sanityChecks: true, replaceExisting: true, sourceUrl: sourceUrl });
fs.mkdirSync(path.dirname(output), { recursive: true });
if (args.molj) {
await plugin.saveStateSnapshot(withExtension(output, '.molj'));
}
await plugin.saveImage(output);
checkState(plugin);
}
await plugin.clear();
plugin.dispose();
}
/** Return a new and initiatized HeadlessPlugin */
async function createHeadlessPlugin(args: Pick<Args, 'size'>): Promise<HeadlessPluginContext> {
const externalModules: ExternalModules = { gl, pngjs, 'jpeg-js': jpegjs };
const spec = DefaultPluginSpec();
spec.behaviors.push(PluginSpec.Behavior(MolViewSpec));
const headlessCanvasOptions = defaultCanvas3DParams();
const canvasOptions = {
...PD.getDefaultValues(Canvas3DParams),
cameraResetDurationMs: headlessCanvasOptions.cameraResetDurationMs,
postprocessing: headlessCanvasOptions.postprocessing,
};
const plugin = new HeadlessPluginContext(externalModules, spec, args.size, { canvas: canvasOptions });
try {
await plugin.init();
} catch (error) {
plugin.dispose();
throw error;
}
return plugin;
}
/** Parse integer, fail early. */
function parseIntStrict(str: string): number {
if (str === '') throw new Error('Is empty string');
const result = Number(str);
if (isNaN(result)) throw new Error('Is NaN');
if (Math.floor(result) !== result) throw new Error('Is not integer');
return result;
}
/** Replace the file extension in `filename` by `extension`. If `filename` has no extension, add it. */
function withExtension(filename: string, extension: string): string {
const oldExtension = path.extname(filename);
return filename.slice(0, -oldExtension.length) + extension;
}
/** Check Mol* state, print and throw error if any cell is not OK. */
function checkState(plugin: PluginContext): void {
const cells = Array.from(plugin.state.data.cells.values());
const badCell = cells.find(cell => cell.status !== 'ok');
if (badCell) {
console.error(`Building Mol* state failed`);
console.error(` Transformer: ${badCell.transform.transformer.id}`);
console.error(` Params: ${onelinerJsonString(badCell.transform.params)}`);
console.error(` Error: ${badCell.errorText}`);
console.error(``);
throw new Error(`Building Mol* state failed: ${badCell.errorText}`);
}
}
main(parseArguments());

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env node
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*
* Command-line application for validating MolViewSpec files
* Build: npm run build
* Run: node lib/commonjs/cli/mvs/mvs-validate examples/mvs/1cbs.mvsj
*/
import { ArgumentParser } from 'argparse';
import fs from 'fs';
import { setFSModule } from '../../mol-util/data-source';
import { MVSData } from '../../extensions/mvs/mvs-data';
setFSModule(fs);
/** Command line argument values for `main` */
interface Args {
input: string[],
no_extra: boolean,
}
/** Return parsed command line arguments for `main` */
function parseArguments(): Args {
const parser = new ArgumentParser({ description: 'Command-line application for validating MolViewSpec files. Prints validation status (OK/FAILED) to stdout, detailed validation issues to stderr. Exits with a zero exit code if all input files are OK.' });
parser.add_argument('input', { nargs: '+', help: 'Input file(s) in .mvsj format' });
parser.add_argument('--no-extra', { action: 'store_true', help: 'Treat presence of extra node params as an issue.' });
const args = parser.parse_args();
return { ...args };
}
/** Main workflow for validating MolViewSpec files. Returns the number of failed input files. */
function main(args: Args): number {
let nFailed = 0;
for (const input of args.input) {
const data = fs.readFileSync(input, { encoding: 'utf8' });
const mvsData = MVSData.fromMVSJ(data);
const issues = MVSData.validationIssues(mvsData, { noExtra: args.no_extra });
const status = issues ? 'FAILED' : 'OK';
console.log(`${status.padEnd(6)} ${input}`);
if (issues) {
nFailed++;
for (const issue of issues) {
console.error(issue);
}
}
}
return nFailed;
}
const nFailed = main(parseArguments());
if (nFailed > 0) {
process.exitCode = 1;
}

View File

@@ -11,7 +11,8 @@ import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/spherical-f
import { BasisAndOrbitals, CreateOrbitalDensityVolume, CreateOrbitalRepresentation3D, CreateOrbitalVolume, StaticBasisAndOrbitals } from '../../extensions/alpha-orbitals/transforms';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { createPluginUI } from '../../mol-plugin-ui/react18';
import { createPluginUI } from '../../mol-plugin-ui';
import { renderReact18 } from '../../mol-plugin-ui/react18';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
@@ -56,28 +57,32 @@ export class AlphaOrbitalsExample {
async init(target: string | HTMLElement) {
const defaultSpec = DefaultPluginUISpec();
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
...defaultSpec,
layout: {
initial: {
isExpanded: false,
showControls: false
this.plugin = await createPluginUI({
target: typeof target === 'string' ? document.getElementById(target)! : target,
render: renderReact18,
spec: {
...defaultSpec,
layout: {
initial: {
isExpanded: false,
showControls: false
},
},
},
components: {
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
},
canvas3d: {
camera: {
helper: { axes: { name: 'off', params: {} } }
}
},
config: [
[PluginConfig.Viewport.ShowExpand, false],
[PluginConfig.Viewport.ShowControls, false],
[PluginConfig.Viewport.ShowSelectionMode, false],
[PluginConfig.Viewport.ShowAnimation, false],
]
components: {
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
},
canvas3d: {
camera: {
helper: { axes: { name: 'off', params: {} } }
}
},
config: [
[PluginConfig.Viewport.ShowExpand, false],
[PluginConfig.Viewport.ShowControls, false],
[PluginConfig.Viewport.ShowSelectionMode, false],
[PluginConfig.Viewport.ShowAnimation, false],
]
}
});
this.plugin.managers.interactivity.setProps({ granularity: 'element' });

View File

@@ -9,8 +9,9 @@ import { EmptyLoci } from '../../mol-model/loci';
import { StructureSelection } from '../../mol-model/structure';
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { createPluginUI } from '../../mol-plugin-ui/react18';
import { createPluginUI } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { renderReact18 } from '../../mol-plugin-ui/react18';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { Script } from '../../mol-script/script';
@@ -29,16 +30,20 @@ class BasicWrapper {
plugin: PluginUIContext;
async init(target: string | HTMLElement) {
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginUISpec(),
layout: {
initial: {
isExpanded: false,
showControls: false
this.plugin = await createPluginUI({
target: typeof target === 'string' ? document.getElementById(target)! : target,
render: renderReact18,
spec: {
...DefaultPluginUISpec(),
layout: {
initial: {
isExpanded: false,
showControls: false
}
},
components: {
remoteState: 'none'
}
},
components: {
remoteState: 'none'
}
});
@@ -46,6 +51,14 @@ class BasicWrapper {
this.plugin.representation.structure.themes.colorThemeRegistry.add(CustomColorThemeProvider);
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
this.plugin.managers.dragAndDrop.addHandler('custom-wrapper', (files) => {
if (files.some(f => f.name.toLowerCase().endsWith('.testext'))) {
console.log('.testext File dropped');
return true;
}
return false;
});
}
async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) {

View File

@@ -6,8 +6,9 @@
import { Canvas3DProps } from '../../mol-canvas3d/canvas3d';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { createPluginUI } from '../../mol-plugin-ui/react18';
import { createPluginUI } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { renderReact18 } from '../../mol-plugin-ui/react18';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { Asset } from '../../mol-util/assets';
@@ -113,16 +114,20 @@ class LightingDemo {
private preset: Canvas3DPreset = 'illustrative';
async init(target: string | HTMLElement) {
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginUISpec(),
layout: {
initial: {
isExpanded: false,
showControls: false
this.plugin = await createPluginUI({
target: typeof target === 'string' ? document.getElementById(target)! : target,
render: renderReact18,
spec: {
...DefaultPluginUISpec(),
layout: {
initial: {
isExpanded: false,
showControls: false
},
},
},
components: {
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
components: {
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
}
}
});

View File

@@ -10,7 +10,8 @@ import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/mod
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
import { PluginStateObject, PluginStateObject as PSO } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { createPluginUI } from '../../mol-plugin-ui/react18';
import { createPluginUI } from '../../mol-plugin-ui';
import { renderReact18 } from '../../mol-plugin-ui/react18';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { CreateVolumeStreamingInfo, InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
@@ -46,19 +47,23 @@ class MolStarProteopediaWrapper {
async init(target: string | HTMLElement, options?: {
customColorList?: number[]
}) {
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginUISpec(),
animations: [
AnimateModelIndex
],
layout: {
initial: {
isExpanded: false,
showControls: false
this.plugin = await createPluginUI({
target: typeof target === 'string' ? document.getElementById(target)! : target,
render: renderReact18,
spec: {
...DefaultPluginUISpec(),
animations: [
AnimateModelIndex
],
layout: {
initial: {
isExpanded: false,
showControls: false
}
},
components: {
remoteState: 'none'
}
},
components: {
remoteState: 'none'
}
});

View File

@@ -50,7 +50,7 @@ const _testBasis: Basis = {
0.025886090588624934,
0.019164790004065606,
-0.013539970104105408
] as Vec3,
],
'shells': [
{
'angularMomentum': [0],
@@ -101,7 +101,7 @@ const _testBasis: Basis = {
0.5082729578468134,
1.6880351220025265,
0.4963443067810461
] as Vec3,
],
'shells': [
{
'angularMomentum': [0],
@@ -158,7 +158,7 @@ const _testBasis: Basis = {
1.1367367844436005,
-0.47018519422670163,
-1.356802622574504
] as Vec3,
],
'shells': [
{
'angularMomentum': [0],

View File

@@ -53,7 +53,7 @@ export async function sphericalCollocation(
L,
shell.coefficients[amIndex++],
shell.exponents,
atom.center,
atom.center as unknown as Vec3,
cutoffThreshold,
alpha
);

View File

@@ -22,7 +22,7 @@ export interface SphericalElectronShell {
export interface Basis {
atoms: {
// in Bohr units!
center: Vec3;
center: [number, number, number];
shells: SphericalElectronShell[];
}[];
}
@@ -78,7 +78,7 @@ export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
const count = geometry.length;
const box = Box3D.expand(
Box3D(),
Box3D.fromVec3Array(Box3D(), geometry),
Box3D.fromVec3Array(Box3D(), geometry as unknown as Vec3[]),
Vec3.create(expand, expand, expand)
);
const size = Box3D.size(Vec3(), box);

View File

@@ -4,27 +4,28 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { AssemblySymmetryProvider, AssemblySymmetry, AssemblySymmetryDataProvider } from './prop';
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { AssemblySymmetryProvider, AssemblySymmetryData, AssemblySymmetryDataProvider, AssemblySymmetryDataParams } from './prop';
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
import { AssemblySymmetryParams, AssemblySymmetryRepresentation } from './representation';
import { AssemblySymmetryClusterColorThemeProvider } from './color';
import { PluginStateTransform, PluginStateObject } from '../../../mol-plugin-state/objects';
import { Task } from '../../../mol-task';
import { PluginContext } from '../../../mol-plugin/context';
import { StateTransformer, StateAction, StateObject, StateTransform, StateObjectRef } from '../../../mol-state';
import { GenericRepresentationRef } from '../../../mol-plugin-state/manager/structure/hierarchy-state';
import { PluginStateTransform, PluginStateObject } from '../../mol-plugin-state/objects';
import { Task } from '../../mol-task';
import { PluginConfigItem } from '../../mol-plugin/config';
import { PluginContext } from '../../mol-plugin/context';
import { StateTransformer, StateAction, StateObject, StateTransform, StateObjectRef } from '../../mol-state';
import { GenericRepresentationRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { AssemblySymmetryControls } from './ui';
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset';
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../mol-plugin-state/builder/structure/representation-preset';
const Tag = AssemblySymmetry.Tag;
const Tag = AssemblySymmetryData.Tag;
export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean }>({
name: 'rcsb-assembly-symmetry-prop',
export const AssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean }>({
name: 'assembly-symmetry-prop',
category: 'custom-props',
display: {
name: 'Assembly Symmetry',
description: 'Assembly Symmetry data calculated with BioJava, obtained via RCSB PDB.'
description: 'Assembly Symmetry data provided by RCSB PDB (calculated with BioJava) or by PDBe.'
},
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> {
private provider = AssemblySymmetryProvider;
@@ -65,7 +66,7 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
},
params: () => ({
autoAttach: PD.Boolean(false),
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl)
serverUrl: PD.Text(AssemblySymmetryData.DefaultServerUrl)
})
});
@@ -74,23 +75,24 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
export const InitAssemblySymmetry3D = StateAction.build({
display: {
name: 'Assembly Symmetry',
description: 'Initialize Assembly Symmetry axes and cage. Data calculated with BioJava, obtained via RCSB PDB.'
description: 'Initialize Assembly Symmetry axes and cage. Data provided by RCSB PDB (calculated with BioJava) or by PDBe.'
},
from: PluginStateObject.Molecule.Structure,
isApplicable: (a) => AssemblySymmetry.isApplicable(a.data)
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
isApplicable: (a) => AssemblySymmetryData.isApplicable(a.data),
params: (a, plugin: PluginContext) => getConfiguredDefaultParams(plugin)
})(({ a, ref, state, params }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
try {
const propCtx = { runtime: ctx, assetManager: plugin.managers.asset };
await AssemblySymmetryDataProvider.attach(propCtx, a.data);
await AssemblySymmetryDataProvider.attach(propCtx, a.data, params);
const assemblySymmetryData = AssemblySymmetryDataProvider.get(a.data).value;
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
await AssemblySymmetryProvider.attach(propCtx, a.data, { symmetryIndex });
const symmetryIndex = assemblySymmetryData ? AssemblySymmetryData.firstNonC1(assemblySymmetryData) : -1;
await AssemblySymmetryProvider.attach(propCtx, a.data, { ...params, symmetryIndex });
} catch (e) {
plugin.log.error(`Assembly Symmetry: ${e}`);
return;
}
const tree = state.build().to(ref)
.applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D);
.applyOrUpdateTagged(AssemblySymmetryData.Tag.Representation, AssemblySymmetry3D);
await state.updateTree(tree).runInContext(ctx);
}));
@@ -101,7 +103,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
name: Tag.Representation,
display: {
name: 'Assembly Symmetry',
description: 'Assembly Symmetry axes and cage. Data calculated with BioJava, obtained via RCSB PDB.'
description: 'Assembly Symmetry axes and cage. Data provided by RCSB PDB (calculated with BioJava) or by PDBe.'
},
from: PluginStateObject.Molecule.Structure,
to: PluginStateObject.Shape.Representation3D,
@@ -146,26 +148,27 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
});
},
isApplicable(a) {
return AssemblySymmetry.isApplicable(a.data);
return AssemblySymmetryData.isApplicable(a.data);
}
});
//
export const AssemblySymmetryPresetParams = {
...StructureRepresentationPresetProvider.CommonParams,
};
export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-rcsb-assembly-symmetry',
id: 'preset-structure-representation-assembly-symmetry',
display: {
name: 'Assembly Symmetry', group: 'Annotation',
description: 'Shows Assembly Symmetry axes and cage; colors structure according to assembly symmetry cluster membership. Data calculated with BioJava, obtained via RCSB PDB.'
description: 'Shows Assembly Symmetry axes and cage; colors structure according to assembly symmetry cluster membership. Data provided by RCSB PDB (calculated with BioJava) or by PDBe.'
},
isApplicable(a) {
return AssemblySymmetry.isApplicable(a.data);
return AssemblySymmetryData.isApplicable(a.data);
},
params: (a, plugin) => {
return {
...StructureRepresentationPresetProvider.CommonParams,
...getConfiguredDefaultParams(plugin)
};
},
params: () => AssemblySymmetryPresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
@@ -174,15 +177,16 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
if (!AssemblySymmetryDataProvider.get(structure).value) {
await plugin.runTask(Task.create('Assembly Symmetry', async runtime => {
const propCtx = { runtime, assetManager: plugin.managers.asset };
await AssemblySymmetryDataProvider.attach(propCtx, structure);
const propProps = { serverType: params.serverType, serverUrl: params.serverUrl };
await AssemblySymmetryDataProvider.attach(propCtx, structure, propProps);
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value;
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
await AssemblySymmetryProvider.attach(propCtx, structure, { symmetryIndex });
const symmetryIndex = assemblySymmetryData ? AssemblySymmetryData.firstNonC1(assemblySymmetryData) : -1;
await AssemblySymmetryProvider.attach(propCtx, structure, { ...propProps, symmetryIndex });
}));
}
const assemblySymmetry = await tryCreateAssemblySymmetry(plugin, structureCell);
const colorTheme = assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
const colorTheme = getAssemblySymmetryConfig(plugin).ApplyColors && assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
return { components: preset.components, representations: { ...preset.representations, assemblySymmetry } };
@@ -192,6 +196,29 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
export function tryCreateAssemblySymmetry(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, params?: StateTransformer.Params<AssemblySymmetry3D>, initialState?: Partial<StateTransform.State>) {
const state = plugin.state.data;
const assemblySymmetry = state.build().to(structure)
.applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D, params, { state: initialState });
.applyOrUpdateTagged(AssemblySymmetryData.Tag.Representation, AssemblySymmetry3D, params, { state: initialState });
return assemblySymmetry.commit({ revertOnError: true });
}
}
//
export const AssemblySymmetryConfig = {
DefaultServerType: new PluginConfigItem('assembly-symmetry.server-type', AssemblySymmetryDataParams.serverType.defaultValue),
DefaultServerUrl: new PluginConfigItem('assembly-symmetry.server-url', AssemblySymmetryDataParams.serverUrl.defaultValue),
ApplyColors: new PluginConfigItem('assembly-symmetry.apply-colors', true),
};
export function getAssemblySymmetryConfig(plugin: PluginContext): { [key in keyof typeof AssemblySymmetryConfig]: NonNullable<typeof AssemblySymmetryConfig[key]['defaultValue']> } {
return {
ApplyColors: plugin.config.get(AssemblySymmetryConfig.ApplyColors) ?? AssemblySymmetryConfig.ApplyColors.defaultValue ?? true,
DefaultServerType: plugin.config.get(AssemblySymmetryConfig.DefaultServerType) ?? AssemblySymmetryConfig.DefaultServerType.defaultValue ?? AssemblySymmetryDataParams.serverType.defaultValue,
DefaultServerUrl: plugin.config.get(AssemblySymmetryConfig.DefaultServerUrl) ?? AssemblySymmetryConfig.DefaultServerUrl.defaultValue ?? AssemblySymmetryDataParams.serverUrl.defaultValue,
};
}
function getConfiguredDefaultParams(plugin: PluginContext) {
const config = getAssemblySymmetryConfig(plugin);
const params = PD.clone(AssemblySymmetryDataParams);
PD.setDefaultValues(params, { serverType: config.DefaultServerType, serverUrl: config.DefaultServerUrl });
return params;
}

View File

@@ -4,16 +4,16 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ThemeDataContext } from '../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { AssemblySymmetryProvider, AssemblySymmetry } from './prop';
import { Color } from '../../../mol-util/color';
import { Unit, StructureElement, StructureProperties, Bond } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { ThemeDataContext } from '../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../mol-theme/color';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { AssemblySymmetryProvider, AssemblySymmetryData } from './prop';
import { Color } from '../../mol-util/color';
import { Unit, StructureElement, StructureProperties, Bond } from '../../mol-model/structure';
import { Location } from '../../mol-model/location';
import { ScaleLegend, TableLegend } from '../../mol-util/legend';
import { getPalette, getPaletteParams } from '../../mol-util/color/palette';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
const DefaultColor = Color(0xCCCCCC);
@@ -94,19 +94,19 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
color,
props,
contextHash,
description: 'Assigns chain colors according to assembly symmetry cluster membership calculated with BioJava and obtained via RCSB PDB.',
description: 'Assigns chain colors according to assembly symmetry cluster membership data provided by RCSB PDB (calculated with BioJava) or by PDBe.',
legend
};
}
export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<AssemblySymmetryClusterColorThemeParams, AssemblySymmetry.Tag.Cluster> = {
name: AssemblySymmetry.Tag.Cluster,
export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<AssemblySymmetryClusterColorThemeParams, AssemblySymmetryData.Tag.Cluster> = {
name: AssemblySymmetryData.Tag.Cluster,
label: 'Assembly Symmetry Cluster',
category: ColorTheme.Category.Symmetry,
factory: AssemblySymmetryClusterColorTheme,
getParams: getAssemblySymmetryClusterColorThemeParams,
defaultValues: PD.getDefaultValues(AssemblySymmetryClusterColorThemeParams),
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetryData.isApplicable(ctx.structure),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
detach: (data) => data.structure && AssemblySymmetryProvider.ref(data.structure, false)

View File

@@ -0,0 +1,7 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
export { AssemblySymmetry, AssemblySymmetryConfig } from './behavior';

View File

@@ -1,25 +1,48 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from '../graphql/types';
import { symmetry_gql } from '../graphql/symmetry.gql';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Structure, Model, StructureSelection, QueryContext } from '../../mol-model/structure';
import { Database as _Database, Column } from '../../mol-data/db';
import { GraphQLClient } from '../../mol-util/graphql-client';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
import { CustomStructureProperty } from '../../mol-model-props/common/custom-structure-property';
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
import { ReadonlyVec3 } from '../../mol-math/linear-algebra/3d/vec3';
import { SetUtils } from '../../mol-util/set';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { compile } from '../../mol-script/runtime/query/compiler';
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
import { Asset } from '../../mol-util/assets';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Structure, Model, StructureSelection, QueryContext } from '../../../mol-model/structure';
import { Database as _Database, Column } from '../../../mol-data/db';
import { GraphQLClient } from '../../../mol-util/graphql-client';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { NonNullableArray } from '../../../mol-util/type-helpers';
import { CustomStructureProperty } from '../../../mol-model-props/common/custom-structure-property';
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
import { ReadonlyVec3 } from '../../../mol-math/linear-algebra/3d/vec3';
import { SetUtils } from '../../../mol-util/set';
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
import { compile } from '../../../mol-script/runtime/query/compiler';
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
const rcsb_symmetry_gql = /* GraphQL */ `
query AssemblySymmetry($assembly_id: String!, $entry_id: String!) {
assembly(assembly_id: $assembly_id, entry_id: $entry_id) {
rcsb_struct_symmetry {
clusters {
avg_rmsd
members {
asym_id
pdbx_struct_oper_list_ids
}
}
kind
oligomeric_state
rotation_axes {
order
start
end
}
stoichiometry
symbol
type
}
}
}
`;
const BiologicalAssemblyNames = new Set([
'author_and_software_defined_assembly',
@@ -42,13 +65,13 @@ export function isBiologicalAssembly(structure: Structure): boolean {
return BiologicalAssemblyNames.has(details);
}
export namespace AssemblySymmetry {
export namespace AssemblySymmetryData {
export enum Tag {
Cluster = 'rcsb-assembly-symmetry-cluster',
Representation = 'rcsb-assembly-symmetry-3d'
Cluster = 'assembly-symmetry-cluster',
Representation = 'assembly-symmetry-3d'
}
export const DefaultServerUrl = 'https://data.rcsb.org/graphql';
export const DefaultServerUrl = 'https://data.rcsb.org/graphql'; // Alternative: 'https://www.ebi.ac.uk/pdbe/aggregated-api/pdb/symmetry' (if serverType is 'pdbe')
export function isApplicable(structure?: Structure): boolean {
return (
@@ -61,12 +84,17 @@ export namespace AssemblySymmetry {
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
if (!isApplicable(structure)) return { value: [] };
if (props.serverType === 'pdbe') return fetchPDBe(ctx, structure, props);
else return fetchRCSB(ctx, structure, props);
}
export async function fetchRCSB(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
const client = new GraphQLClient(props.serverUrl, ctx.assetManager);
const variables: AssemblySymmetryQueryVariables = {
const variables = {
assembly_id: structure.units[0].conformation.operator.assembly?.id || '',
entry_id: structure.units[0].model.entryId
};
const result = await client.request(ctx.runtime, symmetry_gql, variables);
const result = await client.request(ctx.runtime, rcsb_symmetry_gql, variables);
let value: AssemblySymmetryDataValue = [];
if (!result.data.assembly?.rcsb_struct_symmetry) {
@@ -77,6 +105,37 @@ export namespace AssemblySymmetry {
return { value, assets: [result] };
}
async function fetchPDBe(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
const assembly_id = structure.units[0].conformation.operator.assembly?.id || '-1'; // should use '' instead of '-1' but the API does not support non-number assembly_id
const entry_id = structure.units[0].model.entryId.toLowerCase();
const url = `${props.serverUrl}/${entry_id}?assembly_id=${assembly_id}`;
const asset = Asset.getUrlAsset(ctx.assetManager, url);
let dataWrapper: Asset.Wrapper<'json'>;
try {
dataWrapper = await ctx.assetManager.resolve(asset, 'json').runInContext(ctx.runtime);
} catch (err) {
// PDBe API returns 404 when there are no symmetries -> treat as success with empty json in body
if (`${err}`.includes('404')) { // dirrrty
dataWrapper = Asset.Wrapper({}, asset, ctx.assetManager);
} else {
throw err;
}
}
const data = dataWrapper.data;
const value: AssemblySymmetryDataValue = (data[entry_id] ?? []).map((v: any) => ({
kind: 'Global Symmetry',
oligomeric_state: v.oligomeric_state,
stoichiometry: [v.stoichiometry],
symbol: v.symbol,
type: v.type,
clusters: [],
rotation_axes: v.rotation_axes,
}));
return { value, assets: [dataWrapper] };
}
/** Returns the index of the first non C1 symmetry or -1 */
export function firstNonC1(assemblySymmetryData: AssemblySymmetryDataValue) {
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
@@ -114,7 +173,7 @@ export namespace AssemblySymmetry {
/** Returns structure limited to all cluster member chains */
export function getStructure(structure: Structure, assemblySymmetry: AssemblySymmetryValue) {
const asymIds = AssemblySymmetry.getAsymIds(assemblySymmetry);
const asymIds = AssemblySymmetryData.getAsymIds(assemblySymmetry);
return asymIds.length > 0 ? getAsymIdsStructure(structure, asymIds) : structure;
}
}
@@ -147,26 +206,45 @@ export function getSymmetrySelectParam(structure?: Structure) {
//
export const AssemblySymmetryDataParams = {
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL' })
serverType: PD.Select('rcsb', [['rcsb', 'RCSB'], ['pdbe', 'PDBe']] as const),
serverUrl: PD.Text(AssemblySymmetryData.DefaultServerUrl, { description: 'GraphQL endpoint URL (if server type is RCSB) or PDBe API endpoint URL (if server type is PDBe)' })
};
export type AssemblySymmetryDataParams = typeof AssemblySymmetryDataParams
export type AssemblySymmetryDataProps = PD.Values<AssemblySymmetryDataParams>
export type AssemblySymmetryDataValue = NonNullableArray<NonNullable<NonNullable<AssemblySymmetryQuery['assembly']>['rcsb_struct_symmetry']>>
export type AssemblySymmetryDataValue = ReadonlyArray<{
readonly kind: string,
readonly oligomeric_state: string,
readonly stoichiometry: ReadonlyArray<string>,
readonly symbol: string,
readonly type: string,
readonly clusters: ReadonlyArray<{
readonly avg_rmsd?: number,
readonly members: ReadonlyArray<{
readonly asym_id: string,
readonly pdbx_struct_oper_list_ids?: ReadonlyArray<string>
}>
}>,
readonly rotation_axes?: ReadonlyArray<{
readonly order?: number,
readonly start: ReadonlyArray<number>,
readonly end: ReadonlyArray<number>
}>
}>
export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<AssemblySymmetryDataParams, AssemblySymmetryDataValue> = CustomStructureProperty.createProvider({
label: 'Assembly Symmetry Data',
descriptor: CustomPropertyDescriptor({
name: 'rcsb_struct_symmetry_data',
name: 'molstar_struct_symmetry_data',
// TODO `cifExport` and `symbol`
}),
type: 'root',
defaultParams: AssemblySymmetryDataParams,
getParams: (data: Structure) => AssemblySymmetryDataParams,
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
isApplicable: (data: Structure) => AssemblySymmetryData.isApplicable(data),
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryDataProps>) => {
const p = { ...PD.getDefaultValues(AssemblySymmetryDataParams), ...props };
return await AssemblySymmetry.fetch(ctx, data, p);
return await AssemblySymmetryData.fetch(ctx, data, p);
}
});
@@ -174,7 +252,7 @@ export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<Asse
function getAssemblySymmetryParams(data?: Structure) {
return {
... AssemblySymmetryDataParams,
...AssemblySymmetryDataParams,
symmetryIndex: getSymmetrySelectParam(data)
};
}
@@ -188,13 +266,13 @@ export type AssemblySymmetryValue = AssemblySymmetryDataValue[0]
export const AssemblySymmetryProvider: CustomStructureProperty.Provider<AssemblySymmetryParams, AssemblySymmetryValue> = CustomStructureProperty.createProvider({
label: 'Assembly Symmetry',
descriptor: CustomPropertyDescriptor({
name: 'rcsb_struct_symmetry',
name: 'molstar_struct_symmetry',
// TODO `cifExport` and `symbol`
}),
type: 'root',
defaultParams: AssemblySymmetryParams,
getParams: getAssemblySymmetryParams,
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
isApplicable: (data: Structure) => AssemblySymmetryData.isApplicable(data),
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryProps>) => {
const p = { ...PD.getDefaultValues(getAssemblySymmetryParams(data)), ...props };
await AssemblySymmetryDataProvider.attach(ctx, data, p);

View File

@@ -4,35 +4,35 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { AssemblySymmetryValue, AssemblySymmetryProvider, AssemblySymmetry } from './prop';
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
import { Vec3, Mat4, Mat3 } from '../../../mol-math/linear-algebra';
import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { RuntimeContext } from '../../../mol-task';
import { Shape } from '../../../mol-model/shape';
import { ColorNames } from '../../../mol-util/color/names';
import { ShapeRepresentation } from '../../../mol-repr/shape/representation';
import { MarkerActions } from '../../../mol-util/marker-action';
import { Prism, PrismCage } from '../../../mol-geo/primitive/prism';
import { Wedge, WedgeCage } from '../../../mol-geo/primitive/wedge';
import { Primitive, transformPrimitive } from '../../../mol-geo/primitive/primitive';
import { memoize1 } from '../../../mol-util/memoize';
import { polygon } from '../../../mol-geo/primitive/polygon';
import { ColorMap, Color } from '../../../mol-util/color';
import { TableLegend } from '../../../mol-util/legend';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
import { Cage, transformCage, cloneCage } from '../../../mol-geo/primitive/cage';
import { OctahedronCage } from '../../../mol-geo/primitive/octahedron';
import { TetrahedronCage } from '../../../mol-geo/primitive/tetrahedron';
import { IcosahedronCage } from '../../../mol-geo/primitive/icosahedron';
import { degToRad, radToDeg } from '../../../mol-math/misc';
import { Mutable } from '../../../mol-util/type-helpers';
import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
import { Structure } from '../../../mol-model/structure';
import { isInteger } from '../../../mol-util/number';
import { Sphere3D } from '../../../mol-math/geometry';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { AssemblySymmetryValue, AssemblySymmetryProvider, AssemblySymmetryData } from './prop';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { Vec3, Mat4, Mat3 } from '../../mol-math/linear-algebra';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { RuntimeContext } from '../../mol-task';
import { Shape } from '../../mol-model/shape';
import { ColorNames } from '../../mol-util/color/names';
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
import { MarkerActions } from '../../mol-util/marker-action';
import { Prism, PrismCage } from '../../mol-geo/primitive/prism';
import { Wedge, WedgeCage } from '../../mol-geo/primitive/wedge';
import { Primitive, transformPrimitive } from '../../mol-geo/primitive/primitive';
import { memoize1 } from '../../mol-util/memoize';
import { polygon } from '../../mol-geo/primitive/polygon';
import { ColorMap, Color } from '../../mol-util/color';
import { TableLegend } from '../../mol-util/legend';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation';
import { Cage, transformCage, cloneCage } from '../../mol-geo/primitive/cage';
import { OctahedronCage } from '../../mol-geo/primitive/octahedron';
import { TetrahedronCage } from '../../mol-geo/primitive/tetrahedron';
import { IcosahedronCage } from '../../mol-geo/primitive/icosahedron';
import { degToRad, radToDeg } from '../../mol-math/misc';
import { Mutable } from '../../mol-util/type-helpers';
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
import { Structure } from '../../mol-model/structure';
import { isInteger } from '../../mol-util/number';
import { Sphere3D } from '../../mol-math/geometry';
const OrderColors = ColorMap({
'2': ColorNames.deepskyblue,
@@ -117,7 +117,7 @@ function getAxesMesh(data: AssemblySymmetryValue, props: PD.Values<AxesParams>,
const { scale } = props;
const { rotation_axes } = data;
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
if (!AssemblySymmetryData.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
const { start, end } = rotation_axes[0];
const radius = (Vec3.distance(start, end) / 500) * scale;
@@ -232,12 +232,12 @@ function getSymbolScale(symbol: string) {
return 1;
}
function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.RotationAxes, size: number, structure: Structure) {
function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetryData.RotationAxes, size: number, structure: Structure) {
const eye = Vec3();
const target = Vec3();
const dir = Vec3();
const up = Vec3();
let pair: Mutable<AssemblySymmetry.RotationAxes> | undefined = undefined;
let pair: Mutable<AssemblySymmetryData.RotationAxes> | undefined = undefined;
if (symbol.startsWith('C')) {
pair = [axes[0]];
@@ -337,9 +337,9 @@ function getCageMesh(data: Structure, props: PD.Values<CageParams>, mesh?: Mesh)
const { scale } = props;
const { rotation_axes, symbol } = assemblySymmetry;
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
if (!AssemblySymmetryData.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
const structure = AssemblySymmetry.getStructure(data, assemblySymmetry);
const structure = AssemblySymmetryData.getStructure(data, assemblySymmetry);
const cage = getSymbolCage(symbol);
if (!cage) return Mesh.createEmpty(mesh);

View File

@@ -4,18 +4,18 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';
import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider, AssemblySymmetry } from './prop';
import { ParameterControls } from '../../../mol-plugin-ui/controls/parameters';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { StructureHierarchyManager } from '../../../mol-plugin-state/manager/structure/hierarchy';
import { StateAction, StateSelection } from '../../../mol-state';
import { PluginStateObject } from '../../../mol-plugin-state/objects';
import { PluginContext } from '../../../mol-plugin/context';
import { Task } from '../../../mol-task';
import { ExtensionSvg, CheckSvg } from '../../../mol-plugin-ui/controls/icons';
import { CollapsableState, CollapsableControls } from '../../mol-plugin-ui/base';
import { ApplyActionControl } from '../../mol-plugin-ui/state/apply-action';
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry, getAssemblySymmetryConfig } from './behavior';
import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider, AssemblySymmetryData } from './prop';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { StructureHierarchyManager } from '../../mol-plugin-state/manager/structure/hierarchy';
import { StateAction, StateSelection } from '../../mol-state';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { PluginContext } from '../../mol-plugin/context';
import { Task } from '../../mol-task';
import { ExtensionSvg, CheckSvg } from '../../mol-plugin-ui/controls/icons';
interface AssemblySymmetryControlState extends CollapsableState {
isBusy: boolean
@@ -72,6 +72,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
get params() {
const structure = this.pivot.cell.obj?.data;
const params = PD.clone(structure ? AssemblySymmetryProvider.getParams(structure) : AssemblySymmetryProvider.defaultParams);
params.serverType.isHidden = true;
params.serverUrl.isHidden = true;
return params;
}
@@ -106,12 +107,14 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
for (const components of this.plugin.managers.structure.hierarchy.currentComponentGroups) {
if (values.symmetryIndex === -1) {
const name = components[0]?.representations[0]?.cell.transform.params?.colorTheme.name;
if (name === AssemblySymmetry.Tag.Cluster) {
if (name === AssemblySymmetryData.Tag.Cluster) {
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: 'default' });
}
} else {
tryCreateAssemblySymmetry(this.plugin, s.cell);
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetry.Tag.Cluster as any });
if (getAssemblySymmetryConfig(this.plugin).ApplyColors) {
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetryData.Tag.Cluster as any });
}
}
}
}
@@ -121,7 +124,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
};
get hasAssemblySymmetry3D() {
return !this.pivot.cell.parent || !!StateSelection.findTagInSubtree(this.pivot.cell.parent.tree, this.pivot.cell.transform.ref, AssemblySymmetry.Tag.Representation);
return !this.pivot.cell.parent || !!StateSelection.findTagInSubtree(this.pivot.cell.parent.tree, this.pivot.cell.transform.ref, AssemblySymmetryData.Tag.Representation);
}
get enable() {
@@ -151,5 +154,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
const EnableAssemblySymmetry3D = StateAction.build({
from: PluginStateObject.Molecule.Structure,
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Enable Assembly Symmetry', async ctx => {
await AssemblySymmetryPreset.apply(ref, Object.create(null), plugin);
const presetParams = AssemblySymmetryPreset.params?.(a, plugin) as PD.Params | undefined;
const presetProps = presetParams ? PD.getDefaultValues(presetParams) : Object.create(null);
await AssemblySymmetryPreset.apply(ref, presetProps, plugin);
}));

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -28,6 +28,12 @@ export const Backgrounds = PluginBehavior.create<{ }>({
ctor: class extends PluginBehavior.Handler<{ }> {
register(): void {
this.ctx.config.set(PluginConfig.Background.Styles, [
[{
variant: {
name: 'off',
params: {}
}
}, 'Off'],
[{
variant: {
name: 'radialGradient',
@@ -74,6 +80,7 @@ export const Backgrounds = PluginBehavior.create<{ }>({
saturation: 0,
opacity: 1,
blur: 0.3,
rotation: { x: 0, y: 0, z: 0 },
}
}
}, 'Purple Nebula Skybox'],

View File

@@ -1,95 +0,0 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ThemeDataContext } from '../../../mol-theme/theme';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Color } from '../../../mol-util/color';
import { getPalette } from '../../../mol-util/color/palette';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
import { StructureElement, Bond, Model } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
import { CellPackInfoProvider } from '../property';
import { distinctColors } from '../../../mol-util/color/distinct';
import { Hcl } from '../../../mol-util/color/spaces/hcl';
const DefaultColor = Color(0xCCCCCC);
const Description = 'Gives every model in a CellPack packing a unique generated color similar to other models in the packing.';
export const CellPackGenerateColorThemeParams = {};
export type CellPackGenerateColorThemeParams = typeof CellPackGenerateColorThemeParams
export function getCellPackGenerateColorThemeParams(ctx: ThemeDataContext) {
return CellPackGenerateColorThemeParams; // TODO return copy
}
export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Values<CellPackGenerateColorThemeParams>): ColorTheme<CellPackGenerateColorThemeParams> {
let color: LocationColor;
let legend: ScaleLegend | TableLegend | undefined;
const info = ctx.structure && CellPackInfoProvider.get(ctx.structure).value;
if (ctx.structure && info) {
const colors = distinctColors(info.packingsCount);
const hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex]);
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number];
const { models } = ctx.structure.root;
let size = 0;
for (const m of models) size = Math.max(size, Model.TrajectoryInfo.get(m).size);
const palette = getPalette(size, { palette: {
name: 'generate',
params: {
hue, chroma: [30, 80], luminance: [15, 85],
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75
}
} }, { minLabel: 'Min', maxLabel: 'Max' });
legend = palette.legend;
const modelColor = new Map<number, Color>();
for (let i = 0, il = models.length; i < il; ++i) {
const idx = Model.TrajectoryInfo.get(models[i]).index;
modelColor.set(Model.TrajectoryInfo.get(models[i]).index, palette.color(idx));
}
color = (location: Location): Color => {
if (StructureElement.Location.is(location)) {
return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!;
} else if (Bond.isLocation(location)) {
return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!;
}
return DefaultColor;
};
} else {
color = () => DefaultColor;
}
return {
factory: CellPackGenerateColorTheme,
granularity: 'instance',
color,
props,
description: Description,
legend
};
}
export const CellPackGenerateColorThemeProvider: ColorTheme.Provider<CellPackGenerateColorThemeParams, 'cellpack-generate'> = {
name: 'cellpack-generate',
label: 'CellPack Generate',
category: ColorTheme.Category.Chain,
factory: CellPackGenerateColorTheme,
getParams: getCellPackGenerateColorThemeParams,
defaultValues: PD.getDefaultValues(CellPackGenerateColorThemeParams),
isApplicable: (ctx: ThemeDataContext) => {
return (
!!ctx.structure && ctx.structure.elementCount > 0 &&
!!CellPackInfoProvider.get(ctx.structure).value
);
}
};

View File

@@ -1,75 +0,0 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ThemeDataContext } from '../../../mol-theme/theme';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Color } from '../../../mol-util/color';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
import { StructureElement, Model, Bond } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
import { CellPackInfoProvider } from '../property';
const DefaultColor = Color(0xCCCCCC);
const Description = 'Gives every model in a CellPack the color provied in the packing data.';
export const CellPackProvidedColorThemeParams = {};
export type CellPackProvidedColorThemeParams = typeof CellPackProvidedColorThemeParams
export function getCellPackProvidedColorThemeParams(ctx: ThemeDataContext) {
return CellPackProvidedColorThemeParams; // TODO return copy
}
export function CellPackProvidedColorTheme(ctx: ThemeDataContext, props: PD.Values<CellPackProvidedColorThemeParams>): ColorTheme<CellPackProvidedColorThemeParams> {
let color: LocationColor;
let legend: ScaleLegend | TableLegend | undefined;
const info = ctx.structure && CellPackInfoProvider.get(ctx.structure).value;
if (ctx.structure && info?.colors) {
const { models } = ctx.structure.root;
const modelColor = new Map<number, Color>();
for (let i = 0, il = models.length; i < il; ++i) {
const idx = Model.TrajectoryInfo.get(models[i]).index;
modelColor.set(Model.TrajectoryInfo.get(models[i]).index, info.colors[idx]);
}
color = (location: Location): Color => {
if (StructureElement.Location.is(location)) {
return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!;
} else if (Bond.isLocation(location)) {
return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!;
}
return DefaultColor;
};
} else {
color = () => DefaultColor;
}
return {
factory: CellPackProvidedColorTheme,
granularity: 'instance',
color,
props,
description: Description,
legend
};
}
export const CellPackProvidedColorThemeProvider: ColorTheme.Provider<CellPackProvidedColorThemeParams, 'cellpack-provided'> = {
name: 'cellpack-provided',
label: 'CellPack Provided',
category: ColorTheme.Category.Chain,
factory: CellPackProvidedColorTheme,
getParams: getCellPackProvidedColorThemeParams,
defaultValues: PD.getDefaultValues(CellPackProvidedColorThemeParams),
isApplicable: (ctx: ThemeDataContext) => {
return (
!!ctx.structure && ctx.structure.elementCount > 0 &&
Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1 &&
!!CellPackInfoProvider.get(ctx.structure).value?.colors
);
}
};

View File

@@ -1,229 +0,0 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Ludovic Autin <ludovic.autin@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3, Quat, Mat4 } from '../../mol-math/linear-algebra';
import { NumberArray } from '../../mol-util/type-helpers';
interface Frame {
t: Vec3,
r: Vec3,
s: Vec3,
}
const a0Tmp = Vec3();
const a1Tmp = Vec3();
const a2Tmp = Vec3();
const a3Tmp = Vec3();
function CubicInterpolate(out: Vec3, y0: Vec3, y1: Vec3, y2: Vec3, y3: Vec3, mu: number): Vec3 {
const mu2 = mu * mu;
Vec3.sub(a0Tmp, y3, y2);
Vec3.sub(a0Tmp, a0Tmp, y0);
Vec3.add(a0Tmp, a0Tmp, y1);
Vec3.sub(a1Tmp, y0, y1);
Vec3.sub(a1Tmp, a1Tmp, a0Tmp);
Vec3.sub(a2Tmp, y2, y0);
Vec3.copy(a3Tmp, y1);
out[0] = a0Tmp[0] * mu * mu2 + a1Tmp[0] * mu2 + a2Tmp[0] * mu + a3Tmp[0];
out[1] = a0Tmp[1] * mu * mu2 + a1Tmp[1] * mu2 + a2Tmp[1] * mu + a3Tmp[1];
out[2] = a0Tmp[2] * mu * mu2 + a1Tmp[2] * mu2 + a2Tmp[2] * mu + a3Tmp[2];
return out;
}
const cp0 = Vec3();
const cp1 = Vec3();
const cp2 = Vec3();
const cp3 = Vec3();
const currentPosition = Vec3();
function ResampleControlPoints(points: NumberArray, segmentLength: number) {
const nP = points.length / 3;
// insert a point at the end and at the begining
// controlPoints.Insert(0, controlPoints[0] + (controlPoints[0] - controlPoints[1]) / 2.0f);
// controlPoints.Add(controlPoints[nP - 1] + (controlPoints[nP - 1] - controlPoints[nP - 2]) / 2.0f);
const resampledControlPoints: Vec3[] = [];
// resampledControlPoints.Add(controlPoints[0]);
// resampledControlPoints.Add(controlPoints[1]);
let idx = 1;
// const currentPosition = Vec3.create(points[idx * 3], points[idx * 3 + 1], points[idx * 3 + 2])
Vec3.fromArray(currentPosition, points, idx * 3);
let lerpValue = 0.0;
// Normalize the distance between control points
while (true) {
if (idx + 2 >= nP) break;
Vec3.fromArray(cp0, points, (idx - 1) * 3);
Vec3.fromArray(cp1, points, idx * 3);
Vec3.fromArray(cp2, points, (idx + 1) * 3);
Vec3.fromArray(cp3, points, (idx + 2) * 3);
// const cp0 = Vec3.create(points[(idx-1)*3], points[(idx-1)*3+1], points[(idx-1)*3+2]) // controlPoints[currentPointId - 1];
// const cp1 = Vec3.create(points[idx*3], points[idx*3+1], points[idx*3+2]) // controlPoints[currentPointId];
// const cp2 = Vec3.create(points[(idx+1)*3], points[(idx+1)*3+1], points[(idx+1)*3+2]) // controlPoints[currentPointId + 1];
// const cp3 = Vec3.create(points[(idx+2)*3], points[(idx+2)*3+1], points[(idx+2)*3+2]); // controlPoints[currentPointId + 2];
let found = false;
for (; lerpValue <= 1; lerpValue += 0.01) {
// lerp?slerp
// let candidate:Vec3 = Vec3.lerp(Vec3.zero(), cp0, cp1, lerpValue);
// const candidate:Vec3 = Vec3.bezier(Vec3.zero(), cp0, cp1, cp2, cp3, lerpValue);
const candidate = CubicInterpolate(Vec3(), cp0, cp1, cp2, cp3, lerpValue);
const d = Vec3.distance(currentPosition, candidate);
if (d > segmentLength) {
resampledControlPoints.push(candidate);
Vec3.copy(currentPosition, candidate);
found = true;
break;
}
}
if (!found) {
lerpValue = 0;
idx += 1;
}
}
return resampledControlPoints;
}
const prevV = Vec3();
const tmpV1 = Vec3();
const tmpV2 = Vec3();
const tmpV3 = Vec3();
// easier to align to theses normals
function GetSmoothNormals(points: Vec3[]) {
const nP: number = points.length;
const smoothNormals: Vec3[] = [];
if (points.length < 3) {
for (let i = 0; i < points.length; ++i)
smoothNormals.push(Vec3.normalize(Vec3(), points[i]));
return smoothNormals;
}
let p0 = points[0];
let p1 = points[1];
let p2 = points[2];
const p21 = Vec3.sub(tmpV1, p2, p1);
const p01 = Vec3.sub(tmpV2, p0, p1);
const p0121 = Vec3.cross(tmpV3, p01, p21);
Vec3.normalize(prevV, p0121);
smoothNormals.push(Vec3.clone(prevV));
for (let i = 1; i < points.length - 1; ++i) {
p0 = points[i - 1];
p1 = points[i];
p2 = points[i + 1];
const t = Vec3.normalize(tmpV1, Vec3.sub(tmpV1, p2, p0));
const b = Vec3.normalize(tmpV2, Vec3.cross(tmpV2, t, prevV));
const n = Vec3.normalize(Vec3(), Vec3.cross(tmpV3, t, b));
Vec3.negate(n, n);
Vec3.copy(prevV, n);
smoothNormals.push(n);
}
const last = Vec3();
Vec3.normalize(last, Vec3.cross(last,
Vec3.sub(tmpV1, points[nP - 3], points[nP - 2]),
Vec3.sub(tmpV2, points[nP - 2], points[nP - 1]))
);
smoothNormals.push(last);
return smoothNormals;
}
const frameTmpV1 = Vec3();
const frameTmpV2 = Vec3();
const frameTmpV3 = Vec3();
function getFrame(reference: Vec3, tangent: Vec3) {
const t = Vec3.normalize(Vec3(), tangent);
// make reference vector orthogonal to tangent
const proj_r_to_t = Vec3.scale(
frameTmpV1, tangent, Vec3.dot(reference, tangent) / Vec3.dot(tangent, tangent)
);
const r = Vec3.normalize(Vec3(), Vec3.sub(frameTmpV2, reference, proj_r_to_t));
// make bitangent vector orthogonal to the others
const s = Vec3.normalize(Vec3(), Vec3.cross(frameTmpV3, t, r));
return { t, r, s };
}
const mfTmpV1 = Vec3();
const mfTmpV2 = Vec3();
const mfTmpV3 = Vec3();
const mfTmpV4 = Vec3();
const mfTmpV5 = Vec3();
const mfTmpV6 = Vec3();
const mfTmpV7 = Vec3();
const mfTmpV8 = Vec3();
const mfTmpV9 = Vec3();
// easier to align to theses normals
// https://github.com/bzamecnik/gpg/blob/master/rotation-minimizing-frame/rmf.py
function GetMiniFrame(points: Vec3[], normals: Vec3[]) {
const frames: Frame[] = [];
const t0 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[1], points[0]));
frames.push(getFrame(normals[0], t0));
for (let i = 0; i < points.length - 2; ++i) {
const t2 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[i + 2], points[i + 1]));
const v1 = Vec3.sub(mfTmpV2, points[i + 1], points[i]); // this is tangeant
const c1 = Vec3.dot(v1, v1);
// compute r_i^L = R_1 * r_i
const v1r = Vec3.scale(mfTmpV3, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].r));
const ref_L_i = Vec3.sub(mfTmpV4, frames[i].r, v1r);
// compute t_i^L = R_1 * t_i
const v1t = Vec3.scale(mfTmpV5, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].t));
const tan_L_i = Vec3.sub(mfTmpV6, frames[i].t, v1t);
// # compute reflection vector of R_2
const v2 = Vec3.sub(mfTmpV7, t2, tan_L_i);
const c2 = Vec3.dot(v2, v2);
// compute r_(i+1) = R_2 * r_i^L
const v2l = Vec3.scale(mfTmpV8, v1, (2.0 / c2) * Vec3.dot(v2, ref_L_i));
const ref_next = Vec3.sub(mfTmpV9, ref_L_i, v2l); // ref_L_i - (2 / c2) * v2.dot(ref_L_i) * v2
frames.push(getFrame(ref_next, t2)); // frames.append(Frame(ref_next, tangents[i+1]))
}
return frames;
}
const rpTmpVec1 = Vec3();
export function getMatFromResamplePoints(points: NumberArray, segmentLength: number, resample: boolean) {
let new_points: Vec3[] = [];
if (resample) new_points = ResampleControlPoints(points, segmentLength);
else {
for (let idx = 0; idx < points.length / 3; ++idx) {
new_points.push(Vec3.fromArray(Vec3.zero(), points, idx * 3));
}
}
const npoints = new_points.length;
const new_normal = GetSmoothNormals(new_points);
const frames = GetMiniFrame(new_points, new_normal);
const limit = npoints;
const transforms: Mat4[] = [];
const pti = Vec3.copy(rpTmpVec1, new_points[0]);
for (let i = 0; i < npoints - 2; ++i) {
const pti1: Vec3 = new_points[i + 1]; // Vec3.create(points[(i+1)*3],points[(i+1)*3+1],points[(i+1)*3+2]);
const d = Vec3.distance(pti, pti1);
if (d >= segmentLength) {
// use twist or random?
const quat = Quat.rotationTo(Quat.zero(), Vec3.create(0, 0, 1), frames[i].t); // Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),new_normal[i]);//Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),direction);new_normal
const rq = Quat.setAxisAngle(Quat.zero(), frames[i].t, Math.random() * 3.60); // Quat.setAxisAngle(Quat.zero(),direction, Math.random()*3.60 );//Quat.identity();//
const m = Mat4.fromQuat(Mat4.zero(), Quat.multiply(Quat.zero(), rq, quat)); // Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),quat1,quat2));//Mat4.fromQuat(Mat4.zero(),quat);//Mat4.identity();//Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),rq,quat));
// let pos:Vec3 = Vec3.add(Vec3.zero(),pti1,pti)
// pos = Vec3.scale(pos,pos,1.0/2.0);
// Vec3.makeRotation(Mat4.zero(),Vec3.create(0,0,1),frames[i].t);//
Mat4.setTranslation(m, pti1);
// let m2:Mat4 = GetTubePropertiesMatrix(pti,pti1);
// let q:Quat = Quat.rotationTo(Quat.zero(), Vec3.create(0,1,0),Vec3.create(0,0,1))
// m2=Mat4.mul(Mat4.identity(),Mat4.fromQuat(Mat4.zero(),q),m2);
transforms.push(m);
Vec3.copy(pti, pti1);
}
if (transforms.length >= limit) break;
}
return transforms;
}

View File

@@ -1,118 +0,0 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3, Quat } from '../../mol-math/linear-algebra';
export interface CellPack {
cell: Cell
packings: CellPacking[]
}
export interface CellPacking {
name: string,
location: 'surface' | 'interior' | 'cytoplasme'
ingredients: Packing['ingredients']
compartment?: CellCompartment
}
export interface CellCompartment {
filename?: string
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
compartment_primitives?: CompartmentPrimitives
}
export interface Cell {
recipe: Recipe
options?: RecipeOptions
cytoplasme?: Packing
compartments?: { [key: string]: Compartment }
mapping_ids?: { [key: number]: [number, string] }
}
export interface RecipeOptions {
resultfile?: string
}
export interface Recipe {
setupfile: string
paths: [string, string][] // [name: string, path: string][]
version: string
name: string
}
export interface Compartment {
surface?: Packing
interior?: Packing
geom?: unknown
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
mb?: CompartmentPrimitives
}
// Primitives discribing a compartment
export enum CompartmentPrimitiveType {
MetaBall = 0,
Sphere = 1,
Cube = 2,
Cylinder = 3,
Cone = 4,
Plane = 5,
None = 6
}
export interface CompartmentPrimitives{
positions?: number[];
radii?: number[];
types?: CompartmentPrimitiveType[];
}
export interface Packing {
ingredients: { [key: string]: Ingredient }
}
export interface Positions {
coords?: Vec3[];
}
export interface Radii {
radii?: number[];
}
export interface Ingredient {
source: IngredientSource;
results: [Vec3, Quat][];
name: string;
/** Vec3[]];CoarseGraind Beads coordinates LOD */
positions?: [Positions];
/** number[]];CoarseGraind Beads radii LOD */
radii?: [Radii];
/** Number of `curveX` properties in the object where `X` is a 0-indexed number */
nbCurve?: number;
uLength?: number;
/** Curve properties are Vec3[] but that is not expressable in TypeScript */
[curveX: string]: unknown;
/** the orientation in the membrane */
principalAxis?: Vec3;
principalVector?: Vec3;
/** offset along membrane */
offset?: Vec3;
ingtype?: string;
color?: Vec3;
confidence?: number;
Type?: string;
}
export interface IngredientSource {
pdb: string;
bu?: string; /** biological unit e.g AU,BU1,etc.. */
selection?: string; /** NGL selection or :A or :B etc.. */
model?: string; /** model number e.g 0,1,2... */
transform: {
center: boolean;
translate?: Vec3;
};
biomt?: boolean;
}

View File

@@ -1,32 +0,0 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PluginBehavior } from '../../mol-plugin/behavior';
import { LoadCellPackModel } from './model';
import { CellPackGenerateColorThemeProvider } from './color/generate';
import { CellPackProvidedColorThemeProvider } from './color/provided';
export const CellPack = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
name: 'cellpack',
category: 'custom-props',
display: {
name: 'CellPack',
description: 'CellPack Model Loading and Viewing.'
},
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
register(): void {
this.ctx.state.data.actions.add(LoadCellPackModel);
this.ctx.representation.structure.themes.colorThemeRegistry.add(CellPackGenerateColorThemeProvider);
this.ctx.representation.structure.themes.colorThemeRegistry.add(CellPackProvidedColorThemeProvider);
}
unregister() {
this.ctx.state.data.actions.remove(LoadCellPackModel);
this.ctx.representation.structure.themes.colorThemeRegistry.remove(CellPackGenerateColorThemeProvider);
this.ctx.representation.structure.themes.colorThemeRegistry.remove(CellPackProvidedColorThemeProvider);
}
}
});

View File

@@ -1,633 +0,0 @@
/**
* Copyright (c) 2019-2023 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>
*/
import { StateAction, StateBuilder, StateTransformer, State } from '../../mol-state';
import { PluginContext } from '../../mol-plugin/context';
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Ingredient, CellPacking, CompartmentPrimitives } from './data';
import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit, Trajectory } from '../../mol-model/structure';
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
import { trajectoryFromPDB } from '../../mol-model-formats/structure/pdb';
import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra';
import { SymmetryOperator } from '../../mol-math/geometry';
import { Task, RuntimeContext } from '../../mol-task';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies, CreateCompartmentSphere } from './state';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { getMatFromResamplePoints } from './curve';
import { compile } from '../../mol-script/runtime/query/compiler';
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import { objectForEach } from '../../mol-util/object';
import { readFromFile } from '../../mol-util/data-source';
import { ColorNames } from '../../mol-util/color/names';
function getCellPackModelUrl(fileName: string, baseUrl: string) {
return `${baseUrl}/results/${fileName}`;
}
class TrajectoryCache {
private map = new Map<string, Trajectory>();
set(id: string, trajectory: Trajectory) { this.map.set(id, trajectory); }
get(id: string) { return this.map.get(id); }
}
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient,
baseUrl: string, trajCache: TrajectoryCache, location: string,
file?: Asset.File
) {
const assetManager = plugin.managers.asset;
const modelIndex = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
let surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
if (location === 'surface') surface = true;
let trajectory = trajCache.get(id);
const assets: Asset.Wrapper[] = [];
if (!trajectory) {
if (file) {
if (file.name.endsWith('.cif')) {
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
assets.push(text);
const cif = (await parseCif(plugin, text.data)).blocks[0];
trajectory = await plugin.runTask(trajectoryFromMmCIF(cif));
} else if (file.name.endsWith('.bcif')) {
const binary = await plugin.runTask(assetManager.resolve(file, 'binary'));
assets.push(binary);
const cif = (await parseCif(plugin, binary.data)).blocks[0];
trajectory = await plugin.runTask(trajectoryFromMmCIF(cif));
} else if (file.name.endsWith('.pdb')) {
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
assets.push(text);
const pdb = await parsePDBfile(plugin, text.data, id);
trajectory = await plugin.runTask(trajectoryFromPDB(pdb));
} else {
throw new Error(`unsupported file type '${file.name}'`);
}
} else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
if (surface) {
try {
const data = await getFromOPM(plugin, id, assetManager);
assets.push(data.asset);
data.pdb.id! = id.toUpperCase();
trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
} catch (e) {
// fallback to getFromPdb
// console.error(e);
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
assets.push(asset);
trajectory = await plugin.runTask(trajectoryFromMmCIF(mmcif));
}
} else {
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
assets.push(asset);
trajectory = await plugin.runTask(trajectoryFromMmCIF(mmcif));
}
} else {
const data = await getFromCellPackDB(plugin, id, baseUrl, assetManager);
assets.push(data.asset);
if ('pdb' in data) {
trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
} else {
trajectory = await plugin.runTask(trajectoryFromMmCIF(data.mmcif));
}
}
trajCache.set(id, trajectory!);
}
const model = await plugin.resolveTask(trajectory?.getFrameAtIndex(modelIndex)!);
return { model, assets };
}
async function getStructure(plugin: PluginContext, model: Model, source: Ingredient, props: { assembly?: string } = {}) {
let structure = Structure.ofModel(model);
const { assembly } = props;
if (assembly) {
structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly));
}
let query;
if (source.source.selection) {
const sel = source.source.selection;
// selection can have the model ID as well. remove it
const asymIds: string[] = sel.replace(/ /g, '').replace(/:/g, '').split('or').slice(1);
query = MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('auth_asym_id')])
})
]);
} else {
query = MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer'])
})
]);
}
const compiled = compile<StructureSelection>(query);
const result = compiled(new QueryContext(structure));
structure = StructureSelection.unionStructure(result);
// change here if possible the label ?
// structure.label = source.name;
return structure;
}
function getTransformLegacy(trans: Vec3, rot: Quat) {
const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2]);
const m: Mat4 = Mat4.fromQuat(Mat4(), q);
Mat4.transpose(m, m);
Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0));
Mat4.setTranslation(m, trans);
return m;
}
function getTransform(trans: Vec3, rot: Quat) {
const q: Quat = Quat.create(-rot[0], rot[1], rot[2], -rot[3]);
const m: Mat4 = Mat4.fromQuat(Mat4(), q);
const p: Vec3 = Vec3.create(-trans[0], trans[1], trans[2]);
Mat4.setTranslation(m, p);
return m;
}
function getResultTransforms(results: Ingredient['results'], legacy: boolean) {
if (legacy) return results.map((r: Ingredient['results'][0]) => getTransformLegacy(r[0], r[1]));
else return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]));
}
function getCurveTransforms(ingredient: Ingredient) {
const n = ingredient.nbCurve || 0;
const instances: Mat4[] = [];
let segmentLength = 3.4;
if (ingredient.uLength) {
segmentLength = ingredient.uLength;
} else if (ingredient.radii) {
segmentLength = ingredient.radii[0].radii
? ingredient.radii[0].radii[0] * 2.0
: 3.4;
}
let resampling: boolean = false;
for (let i = 0; i < n; ++i) {
const cname = `curve${i}`;
if (!(cname in ingredient)) {
console.warn(`Expected '${cname}' in ingredient`);
continue;
}
const _points = ingredient[cname] as Vec3[];
if (_points.length <= 2) {
// TODO handle curve with 2 or less points
continue;
}
// test for resampling
const distance: number = Vec3.distance(_points[0], _points[1]);
if (distance >= segmentLength + 2.0) {
// console.info(distance);
resampling = true;
}
const points = new Float32Array(_points.length * 3);
for (let i = 0, il = _points.length; i < il; ++i) Vec3.toArray(_points[i], points, i * 3);
const newInstances = getMatFromResamplePoints(points, segmentLength, resampling);
instances.push(...newInstances);
}
return instances;
}
function getAssembly(name: string, transforms: Mat4[], structure: Structure) {
const builder = Structure.Builder({ label: name });
const { units } = structure;
for (let i = 0, il = transforms.length; i < il; ++i) {
const id = `${i + 1}`;
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [id] } });
for (const unit of units) {
builder.addWithOperator(unit, op);
}
}
return builder.getStructure();
}
async function getCurve(name: string, transforms: Mat4[], model: Model) {
const structure = Structure.ofModel(model);
const assembly = getAssembly(name, transforms, structure);
return assembly;
}
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache, location: 'surface' | 'interior' | 'cytoplasme') {
const { name, source, results, nbCurve } = ingredient;
if (source.pdb === 'None') return;
const file = ingredientFiles[source.pdb];
if (!file) {
// TODO can these be added to the library?
if (name === 'HIV1_CAhex_0_1_0') return; // 1VU4CtoH_hex.pdb
if (name === 'HIV1_CAhexCyclophilA_0_1_0') return; // 1AK4fitTo1VU4hex.pdb
if (name === 'iLDL') return; // EMD-5239
if (name === 'peptides') return; // peptide.pdb
if (name === 'lypoglycane') return;
}
// model id in case structure is NMR
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, location, file);
if (!model) return;
let structure: Structure;
if (nbCurve) {
structure = await getCurve(name, getCurveTransforms(ingredient), model);
} else {
if ((!results || results.length === 0)) return;
let bu: string | undefined = source.bu ? source.bu : undefined;
if (bu) {
if (bu === 'AU') {
bu = undefined;
} else {
bu = bu.slice(2);
}
}
structure = await getStructure(plugin, model, ingredient, { assembly: bu });
// transform with offset and pcp
let legacy: boolean = true;
const pcp = ingredient.principalVector ? ingredient.principalVector : ingredient.principalAxis;
if (pcp) {
legacy = false;
const structureMean = getStructureMean(structure);
Vec3.negate(structureMean, structureMean);
const m1: Mat4 = Mat4.identity();
Mat4.setTranslation(m1, structureMean);
structure = Structure.transform(structure, m1);
if (ingredient.offset) {
const o: Vec3 = Vec3.create(ingredient.offset[0], ingredient.offset[1], ingredient.offset[2]);
if (!Vec3.exactEquals(o, Vec3())) { // -1, 1, 4e-16 ??
if (location !== 'surface') {
Vec3.negate(o, o);
}
const m: Mat4 = Mat4.identity();
Mat4.setTranslation(m, o);
structure = Structure.transform(structure, m);
}
}
if (pcp) {
const p: Vec3 = Vec3.create(pcp[0], pcp[1], pcp[2]);
if (!Vec3.exactEquals(p, Vec3.unitZ)) {
const q: Quat = Quat.identity();
Quat.rotationTo(q, p, Vec3.unitZ);
const m: Mat4 = Mat4.fromQuat(Mat4(), q);
structure = Structure.transform(structure, m);
}
}
}
structure = getAssembly(name, getResultTransforms(results, legacy), structure);
}
return { structure, assets };
}
export function createStructureFromCellPack(plugin: PluginContext, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
return Task.create('Create Packing Structure', async ctx => {
const { ingredients, location, name } = packing;
const assets: Asset.Wrapper[] = [];
const trajCache = new TrajectoryCache();
const structures: Structure[] = [];
const colors: Color[] = [];
for (const iName in ingredients) {
if (ctx.shouldUpdate) await ctx.update(iName);
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache, location);
if (ingredientStructure) {
structures.push(ingredientStructure.structure);
assets.push(...ingredientStructure.assets);
const c = ingredients[iName].color;
if (c) {
colors.push(Color.fromNormalizedRgb(c[0], c[1], c[2]));
} else {
colors.push(Color.fromNormalizedRgb(1, 0, 0));
}
}
}
if (ctx.shouldUpdate) await ctx.update(`${name} - units`);
const units: Unit[] = [];
let offsetInvariantId = 0;
let offsetChainGroupId = 0;
for (const s of structures) {
if (ctx.shouldUpdate) await ctx.update(`${s.label}`);
let maxInvariantId = 0;
const maxChainGroupId = 0;
for (const u of s.units) {
const invariantId = u.invariantId + offsetInvariantId;
const chainGroupId = u.chainGroupId + offsetChainGroupId;
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
units.push(Unit.create(units.length, invariantId, chainGroupId, u.traits, u.kind, u.model, u.conformation.operator, u.elements, u.props));
}
offsetInvariantId += maxInvariantId + 1;
offsetChainGroupId += maxChainGroupId + 1;
}
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
const structure = Structure.create(units, { label: name + '.' + location });
for (let i = 0, il = structure.models.length; i < il; ++i) {
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
}
return { structure, assets, colors: colors };
});
}
async function handleHivRna(plugin: PluginContext, packings: CellPacking[], baseUrl: string) {
for (let i = 0, il = packings.length; i < il; ++i) {
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0' || packings[i].name === 'HIV_capsid') {
const url = Asset.getUrlAsset(plugin.managers.asset, `${baseUrl}/extras/rna_allpoints.json`);
const json = await plugin.runTask(plugin.managers.asset.resolve(url, 'json', false));
const points = json.data.points as number[];
const curve0: Vec3[] = [];
for (let j = 0, jl = points.length; j < jl; j += 3) {
curve0.push(Vec3.fromArray(Vec3(), points, j));
}
packings[i].ingredients['RNA'] = {
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
results: [],
name: 'RNA',
nbCurve: 1,
curve0
};
}
}
}
async function loadMembrane(plugin: PluginContext, name: string, state: State, params: LoadCellPackModelParams) {
let file: Asset.File | undefined = undefined;
if (params.ingredients !== null) {
const fileName = `${name}.bcif`;
for (const f of params.ingredients) {
if (fileName === f.name) {
file = f;
break;
}
}
if (!file) {
// check for cif directly
const cifileName = `${name}.cif`;
for (const f of params.ingredients) {
if (cifileName === f.name) {
file = f;
break;
}
}
}
}
let legacy_membrane: boolean = false; // temporary variable until all membrane are converted to the new correct cif format
let geometry_membrane: boolean = false; // membrane can be a mesh geometry
let b = state.build().toRoot();
if (file) {
if (file.name.endsWith('.cif')) {
b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } });
} else if (file.name.endsWith('.bcif')) {
b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } });
}
} else {
if (name.toLowerCase().endsWith('.bcif')) {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
} else if (name.toLowerCase().endsWith('.cif')) {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
} else if (name.toLowerCase().endsWith('.ply')) {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/geometries/${name}`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
geometry_membrane = true;
} else {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
legacy_membrane = true;
}
}
const props = {
type: {
name: 'assembly' as const,
params: { id: '1' }
}
};
if (legacy_membrane) {
// old membrane
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
.apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
.commit({ revertOnError: true });
const membraneParams = {
ignoreLight: params.preset.adjustStyle,
representation: params.preset.representation,
};
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
} else if (geometry_membrane) {
await b.apply(StateTransforms.Data.ParsePly, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ShapeFromPly)
.apply(StateTransforms.Representation.ShapeRepresentation3D, { xrayShaded: true,
doubleSided: true, coloring: { name: 'uniform', params: { color: ColorNames.orange } } })
.commit({ revertOnError: true });
} else {
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.StructureFromModel, props, { state: { isGhost: true } })
.commit({ revertOnError: true });
const membraneParams = {
ignoreLight: params.preset.adjustStyle,
representation: params.preset.representation,
};
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
}
}
async function handleMembraneSpheres(state: State, primitives: CompartmentPrimitives) {
const nSpheres = primitives.positions!.length / 3;
// console.log('ok mb ', nSpheres);
// TODO : take in account the type of the primitives.
for (let j = 0; j < nSpheres; j++) {
await state.build()
.toRoot()
.apply(CreateCompartmentSphere, {
center: Vec3.create(
primitives.positions![j * 3 + 0],
primitives.positions![j * 3 + 1],
primitives.positions![j * 3 + 2]
),
radius: primitives!.radii![j]
})
.commit();
}
}
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
const ingredientFiles = params.ingredients || [];
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
let resultsFile: Asset.File | null = params.results;
if (params.source.name === 'id') {
const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
cellPackJson = state.build().toRoot()
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } });
} else {
const file = params.source.params;
if (!file?.file) {
plugin.log.error('No file selected');
return;
}
let modelFile: Asset.File;
if (file.name.toLowerCase().endsWith('.zip')) {
const data = await readFromFile(file.file, 'zip').runInContext(runtime);
if (data['model.json']) {
modelFile = Asset.File(new File([data['model.json']], 'model.json'));
} else {
throw new Error('model.json missing from zip file');
}
if (data['results.bin']) {
resultsFile = Asset.File(new File([data['results.bin']], 'results.bin'));
}
objectForEach(data, (v, k) => {
if (k === 'model.json') return;
if (k === 'results.bin') return;
ingredientFiles.push(Asset.File(new File([v], k)));
});
} else {
modelFile = file;
}
cellPackJson = state.build().toRoot()
.apply(StateTransforms.Data.ReadFile, { file: modelFile, isBinary: false, label: modelFile.name }, { state: { isGhost: true } });
}
const cellPackBuilder = cellPackJson
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
.apply(ParseCellPack, { resultsFile, baseUrl: params.baseUrl });
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime);
const { packings } = cellPackObject.obj!.data;
await handleHivRna(plugin, packings, params.baseUrl);
for (let i = 0, il = packings.length; i < il; ++i) {
const p = { packing: i, baseUrl: params.baseUrl, ingredientFiles };
const packing = await state.build()
.to(cellPackBuilder.ref)
.apply(StructureFromCellpack, p)
.commit({ revertOnError: true });
const packingParams = {
traceOnly: params.preset.traceOnly,
ignoreLight: params.preset.adjustStyle,
representation: params.preset.representation,
};
await CellpackPackingPreset.apply(packing, packingParams, plugin);
if (packings[i].compartment) {
if (params.membrane === 'lipids') {
if (packings[i].compartment!.geom_type) {
if (packings[i].compartment!.geom_type === 'file') {
// TODO: load mesh files or vertex,faces data
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
} else if (packings[i].compartment!.compartment_primitives) {
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
}
} else {
// try loading membrane from repo as a bcif file or from the given list of files.
if (params.membrane === 'lipids') {
await loadMembrane(plugin, packings[i].name, state, params);
}
}
} else if (params.membrane === 'geometry') {
if (packings[i].compartment!.compartment_primitives) {
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
} else if (packings[i].compartment!.geom_type === 'file') {
if (packings[i].compartment!.filename!.toLowerCase().endsWith('.ply')) {
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
}
}
}
}
}
}
const LoadCellPackModelParams = {
source: PD.MappedStatic('id', {
'id': PD.Select('InfluenzaModel2.json', [
['blood_hiv_immature_inside.json', 'Blood HIV immature'],
['HIV_immature_model.json', 'HIV immature'],
['Blood_HIV.json', 'Blood HIV'],
['HIV-1_0.1.6-8_mixed_radii_pdb.json', 'HIV'],
['influenza_model1.json', 'Influenza envelope'],
['InfluenzaModel2.json', 'Influenza complete'],
['ExosomeModel.json', 'Exosome Model'],
['MycoplasmaGenitalium.json', 'Mycoplasma Genitalium curated model'],
] as const, { description: 'Download the model definition with `id` from the server at `baseUrl.`' }),
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.', label: 'Recipe file' }),
}, { options: [['id', 'Id'], ['file', 'File']] }),
baseUrl: PD.Text(DefaultCellPackBaseUrl),
results: PD.File({ accept: '.bin', description: 'open results file in binary format from cellpackgpu for the specified recipe', label: 'Results file' }),
membrane: PD.Select('lipids', PD.arrayToOptions(['lipids', 'geometry', 'none'])),
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredient files' }),
preset: PD.Group({
traceOnly: PD.Boolean(false),
adjustStyle: PD.Boolean(true),
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation'] as const))
}, { isExpanded: true })
};
type LoadCellPackModelParams = PD.Values<typeof LoadCellPackModelParams>
export const LoadCellPackModel = StateAction.build({
display: { name: 'Load CellPack', description: 'Open or download a model' },
params: LoadCellPackModelParams,
from: PSO.Root
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
if (params.preset.adjustStyle) {
ctx.managers.interactivity.setProps({ granularity: 'chain' });
ctx.managers.structure.component.setOptions({
... ctx.managers.structure.component.state.options,
visualQuality: 'custom',
ignoreLight: true,
hydrogens: 'hide-all',
});
ctx.canvas3d?.setProps({
multiSample: { mode: 'off' },
cameraClipping: { far: false },
renderer: { colorMarker: false },
marking: {
enabled: true,
ghostEdgeStrength: 1,
},
postprocessing: {
occlusion: {
name: 'on',
params: {
samples: 32,
multiScale: { name: 'off', params: {} },
radius: 8,
bias: 1,
blurKernelSize: 15,
resolutionScale: 1,
color: Color(0x000000),
}
},
shadow: {
name: 'on',
params: {
bias: 0.6,
maxDistance: 80,
steps: 3,
tolerance: 1.0,
}
},
outline: {
name: 'on',
params: {
scale: 1,
threshold: 0.33,
color: ColorNames.black,
includeTransparent: true,
}
}
}
});
}
await loadPackings(ctx, taskCtx, state, params);
}));

View File

@@ -1,100 +0,0 @@
/**
* Copyright (c) 2019-2022 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>
*/
import { StateObjectRef } from '../../mol-state';
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../mol-plugin-state/builder/structure/representation-preset';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ColorNames } from '../../mol-util/color/names';
import { CellPackGenerateColorThemeProvider } from './color/generate';
export const CellpackPackingPresetParams = {
traceOnly: PD.Boolean(true),
ignoreLight: PD.Boolean(false),
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'] as const)),
};
export type CellpackPackingPresetParams = PD.ValuesFor<typeof CellpackPackingPresetParams>
export const CellpackPackingPreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-cellpack-packing',
display: { name: 'CellPack Packing' },
params: () => CellpackPackingPresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const reprProps = {
ignoreHydrogens: true,
traceOnly: params.traceOnly,
instanceGranularity: true,
ignoreLight: params.ignoreLight,
};
const components = {
polymer: await presetStaticComponent(plugin, structureCell, 'polymer')
};
if (params.representation === 'gaussian-surface') {
Object.assign(reprProps, {
quality: 'custom', resolution: 10, radiusOffset: 2, doubleSided: false
});
} else if (params.representation === 'spacefill') {
Object.assign(reprProps, { sizeFactor: params.traceOnly ? 2 : 1 });
}
// default is generated
const color = CellPackGenerateColorThemeProvider.name;
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
const representations = {
polymer: builder.buildRepresentation<any>(update, components.polymer, { type: params.representation, typeParams: { ...typeParams, ...reprProps }, color }, { tag: 'polymer' })
};
await update.commit({ revertOnError: true });
return { components, representations };
}
});
//
export const CellpackMembranePresetParams = {
ignoreLight: PD.Boolean(false),
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'] as const)),
};
export type CellpackMembranePresetParams = PD.ValuesFor<typeof CellpackMembranePresetParams>
export const CellpackMembranePreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-cellpack-membrane',
display: { name: 'CellPack Membrane' },
params: () => CellpackMembranePresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const reprProps = {
ignoreHydrogens: true,
instanceGranularity: true,
ignoreLight: params.ignoreLight,
};
const components = {
membrane: await presetStaticComponent(plugin, structureCell, 'all', { label: 'Membrane' })
};
if (params.representation === 'gaussian-surface') {
Object.assign(reprProps, {
quality: 'custom', resolution: 10, radiusOffset: 2, doubleSided: false
});
}
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
const representations = {
membrane: builder.buildRepresentation(update, components.membrane, { type: params.representation, typeParams: { ...typeParams, ...reprProps }, color: 'uniform', colorParams: { value: ColorNames.lightgrey } }, { tag: 'all' })
};
await update.commit({ revertOnError: true });
return { components, representations };
}
});

View File

@@ -1,37 +0,0 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { CustomStructureProperty } from '../../mol-model-props/common/custom-structure-property';
import { Structure } from '../../mol-model/structure';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Color } from '../../mol-util/color';
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
export type CellPackInfoValue = {
packingsCount: number
packingIndex: number
colors?: Color[]
}
const CellPackInfoParams = {
info: PD.Value<CellPackInfoValue>({ packingsCount: 1, packingIndex: 0, colors: undefined }, { isHidden: true })
};
type CellPackInfoParams = PD.Values<typeof CellPackInfoParams>
export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellPackInfoParams, CellPackInfoValue> = CustomStructureProperty.createProvider({
label: 'CellPack Info',
descriptor: CustomPropertyDescriptor({ name: 'cellpack-info' }),
type: 'root',
defaultParams: CellPackInfoParams,
getParams: (data: Structure) => CellPackInfoParams,
isApplicable: (data: Structure) => true,
obtain: async (ctx: CustomProperty.Context, data: Structure, props: CellPackInfoParams) => {
return {
value: { ...CellPackInfoParams.info.defaultValue, ...props.info }
};
}
});

View File

@@ -1,70 +0,0 @@
/**
* Copyright (c) 2019-2021 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>
*/
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
import { Shape } from '../../mol-model/shape';
import { ColorNames } from '../../mol-util/color/names';
import { RuntimeContext } from '../../mol-task';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
// import { Polyhedron, DefaultPolyhedronProps } from '../../mol-geo/primitive/polyhedron';
// import { Icosahedron } from '../../mol-geo/primitive/icosahedron';
import { Sphere } from '../../mol-geo/primitive/sphere';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { RepresentationParamsGetter, Representation, RepresentationContext } from '../../mol-repr/representation';
interface MembraneSphereData {
radius: number
center: Vec3
}
const MembraneSphereParams = {
...Mesh.Params,
cellColor: PD.Color(ColorNames.orange),
cellScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
radius: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
center: PD.Vec3(Vec3.create(0, 0, 0)),
quality: { ...Mesh.Params.quality, isEssential: false },
};
type MeshParams = typeof MembraneSphereParams
const MembraneSphereVisuals = {
'mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MeshParams>) => ShapeRepresentation(getMBShape, Mesh.Utils),
};
export const MBParams = {
...MembraneSphereParams
};
export type MBParams = typeof MBParams
export type UnitcellProps = PD.Values<MBParams>
function getMBMesh(data: MembraneSphereData, props: UnitcellProps, mesh?: Mesh) {
const state = MeshBuilder.createState(256, 128, mesh);
const radius = props.radius;
const asphere = Sphere(3);
const trans: Mat4 = Mat4.identity();
Mat4.fromScaling(trans, Vec3.create(radius, radius, radius));
state.currentGroup = 1;
MeshBuilder.addPrimitive(state, trans, asphere);
const m = MeshBuilder.getMesh(state);
return m;
}
function getMBShape(ctx: RuntimeContext, data: MembraneSphereData, props: UnitcellProps, shape?: Shape<Mesh>) {
const geo = getMBMesh(data, props, shape && shape.geometry);
const label = 'mb';
return Shape.create(label, data, geo, () => props.cellColor, () => 1, () => label);
}
export type MBRepresentation = Representation<MembraneSphereData, MBParams>
export function MBRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MBParams>): MBRepresentation {
return Representation.createMulti('MB', ctx, getParams, Representation.StateBuilder, MembraneSphereVisuals as unknown as Representation.Def<MembraneSphereData, MBParams>);
}

View File

@@ -1,326 +0,0 @@
/**
* Copyright (c) 2019-2021 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>
*/
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Task } from '../../mol-task';
import { CellPack as _CellPack, Cell, CellPacking } from './data';
import { createStructureFromCellPack } from './model';
import { IngredientFiles } from './util';
import { Asset } from '../../mol-util/assets';
import { PluginContext } from '../../mol-plugin/context';
import { CellPackInfoProvider } from './property';
import { Structure, StructureSymmetry, Unit, Model } from '../../mol-model/structure';
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
import { Vec3, Quat } from '../../mol-math/linear-algebra';
import { StateTransformer } from '../../mol-state';
import { MBRepresentation, MBParams } from './representation';
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
import { getFloatValue } from './util';
export const DefaultCellPackBaseUrl = 'https://raw.githubusercontent.com/mesoscope/cellPACK_data/master/cellPACK_database_1.1.0';
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
export { ParseCellPack };
type ParseCellPack = typeof ParseCellPack
const ParseCellPack = PluginStateTransform.BuiltIn({
name: 'parse-cellpack',
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
from: PSO.Format.Json,
to: CellPack,
params: a => {
return {
resultsFile: PD.File({ accept: '.bin' }),
baseUrl: PD.Text(DefaultCellPackBaseUrl)
};
}
})({
apply({ a, params, cache }, plugin: PluginContext) {
return Task.create('Parse CellPack', async ctx => {
const cell = a.data as Cell;
let counter_id = 0;
let fiber_counter_id = 0;
let comp_counter = 0;
const packings: CellPacking[] = [];
const { compartments, cytoplasme } = cell;
if (!cell.mapping_ids) cell.mapping_ids = {};
if (cytoplasme) {
packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
for (const iName in cytoplasme.ingredients) {
if (cytoplasme.ingredients[iName].ingtype === 'fiber') {
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
if (!cytoplasme.ingredients[iName].nbCurve) cytoplasme.ingredients[iName].nbCurve = 0;
fiber_counter_id++;
} else {
cell.mapping_ids[counter_id] = [comp_counter, iName];
if (!cytoplasme.ingredients[iName].results) { cytoplasme.ingredients[iName].results = []; }
counter_id++;
}
}
comp_counter++;
}
if (compartments) {
for (const name in compartments) {
const { surface, interior } = compartments[name];
let filename = '';
if (compartments[name].geom_type === 'file') {
filename = (compartments[name].geom) ? compartments[name].geom as string : '';
}
const compartment = { filename: filename, geom_type: compartments[name].geom_type, compartment_primitives: compartments[name].mb };
if (surface) {
packings.push({ name, location: 'surface', ingredients: surface.ingredients, compartment: compartment });
for (const iName in surface.ingredients) {
if (surface.ingredients[iName].ingtype === 'fiber') {
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
if (!surface.ingredients[iName].nbCurve) surface.ingredients[iName].nbCurve = 0;
fiber_counter_id++;
} else {
cell.mapping_ids[counter_id] = [comp_counter, iName];
if (!surface.ingredients[iName].results) { surface.ingredients[iName].results = []; }
counter_id++;
}
}
comp_counter++;
}
if (interior) {
if (!surface) packings.push({ name, location: 'interior', ingredients: interior.ingredients, compartment: compartment });
else packings.push({ name, location: 'interior', ingredients: interior.ingredients });
for (const iName in interior.ingredients) {
if (interior.ingredients[iName].ingtype === 'fiber') {
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
if (!interior.ingredients[iName].nbCurve) interior.ingredients[iName].nbCurve = 0;
fiber_counter_id++;
} else {
cell.mapping_ids[counter_id] = [comp_counter, iName];
if (!interior.ingredients[iName].results) { interior.ingredients[iName].results = []; }
counter_id++;
}
}
comp_counter++;
}
}
}
const { options } = cell;
let resultsAsset: Asset.Wrapper<'binary'> | undefined;
if (params.resultsFile) {
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(params.resultsFile, 'binary', true));
} else if (options?.resultfile) {
const url = `${params.baseUrl}/results/${options.resultfile}`;
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(Asset.getUrlAsset(plugin.managers.asset, url), 'binary', true));
}
if (resultsAsset) {
(cache as any).asset = resultsAsset;
const results = resultsAsset.data;
// flip the byte order if needed
const buffer = IsNativeEndianLittle ? results.buffer : flipByteOrder(results, 4);
const numbers = new DataView(buffer);
const ninst = getFloatValue(numbers, 0);
const npoints = getFloatValue(numbers, 4);
const ncurve = getFloatValue(numbers, 8);
let offset = 12;
if (ninst !== 0) {
const pos = new Float32Array(buffer, offset, ninst * 4);
offset += ninst * 4 * 4;
const quat = new Float32Array(buffer, offset, ninst * 4);
offset += ninst * 4 * 4;
for (let i = 0; i < ninst; i++) {
const x: number = pos[i * 4 + 0];
const y: number = pos[i * 4 + 1];
const z: number = pos[i * 4 + 2];
const ingr_id = pos[i * 4 + 3] as number;
const pid = cell.mapping_ids![ingr_id];
if (!packings[pid[0]].ingredients[pid[1]].results) {
packings[pid[0]].ingredients[pid[1]].results = [];
}
packings[pid[0]].ingredients[pid[1]].results.push([Vec3.create(x, y, z),
Quat.create(quat[i * 4 + 0], quat[i * 4 + 1], quat[i * 4 + 2], quat[i * 4 + 3])]);
}
}
if (npoints !== 0) {
const ctr_pos = new Float32Array(buffer, offset, npoints * 4);
offset += npoints * 4 * 4;
offset += npoints * 4 * 4;
const ctr_info = new Float32Array(buffer, offset, npoints * 4);
offset += npoints * 4 * 4;
const curve_ids = new Float32Array(buffer, offset, ncurve * 4);
offset += ncurve * 4 * 4;
let counter = 0;
let ctr_points: Vec3[] = [];
let prev_ctype = 0;
let prev_cid = 0;
for (let i = 0; i < npoints; i++) {
const x: number = -ctr_pos[i * 4 + 0];
const y: number = ctr_pos[i * 4 + 1];
const z: number = ctr_pos[i * 4 + 2];
const cid: number = ctr_info[i * 4 + 0]; // curve id
const ctype: number = curve_ids[cid * 4 + 0]; // curve type
// cid 148 165 -1 0
// console.log("cid ",cid,ctype,prev_cid,prev_ctype);//165,148
if (prev_ctype !== ctype) {
const pid = cell.mapping_ids![-prev_ctype - 1];
const cname = `curve${counter}`;
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
ctr_points = [];
counter = 0;
} else if (prev_cid !== cid) {
ctr_points = [];
const pid = cell.mapping_ids![-prev_ctype - 1];
const cname = `curve${counter}`;
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
counter += 1;
}
ctr_points.push(Vec3.create(x, y, z));
prev_ctype = ctype;
prev_cid = cid;
}
// do the last one
const pid = cell.mapping_ids![-prev_ctype - 1];
const cname = `curve${counter}`;
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
}
}
return new CellPack({ cell, packings });
});
},
dispose({ cache }) {
((cache as any)?.asset as Asset.Wrapper | undefined)?.dispose();
},
});
export { StructureFromCellpack };
type StructureFromCellpack = typeof ParseCellPack
const StructureFromCellpack = PluginStateTransform.BuiltIn({
name: 'structure-from-cellpack',
display: { name: 'Structure from CellPack', description: 'Create Structure from CellPack Packing' },
from: CellPack,
to: PSO.Molecule.Structure,
params: a => {
const options = a ? a.data.packings.map((d, i) => [i, d.name] as const) : [];
return {
packing: PD.Select(0, options),
baseUrl: PD.Text(DefaultCellPackBaseUrl),
ingredientFiles: PD.FileList({ accept: '.cif,.bcif,.pdb' })
};
}
})({
apply({ a, params, cache }, plugin: PluginContext) {
return Task.create('Structure from CellPack', async ctx => {
const packing = a.data.packings[params.packing];
const ingredientFiles: IngredientFiles = {};
if (params.ingredientFiles !== null) {
for (const file of params.ingredientFiles) {
ingredientFiles[file.name] = file;
}
}
const { structure, assets, colors } = await createStructureFromCellPack(plugin, packing, params.baseUrl, ingredientFiles).runInContext(ctx);
await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, {
info: { packingsCount: a.data.packings.length, packingIndex: params.packing, colors }
});
(cache as any).assets = assets;
return new PSO.Molecule.Structure(structure, { label: packing.name + '.' + packing.location });
});
},
dispose({ b, cache }) {
const assets = (cache as any).assets as Asset.Wrapper[];
if (assets) {
for (const a of assets) a.dispose();
}
if (b) {
b.data.customPropertyDescriptors.dispose();
for (const m of b.data.models) {
m.customProperties.dispose();
}
}
}
});
export { StructureFromAssemblies };
type StructureFromAssemblies = typeof StructureFromAssemblies
const StructureFromAssemblies = PluginStateTransform.BuiltIn({
name: 'Structure from all assemblies',
display: { name: 'Structure from all assemblies' },
from: PSO.Molecule.Model,
to: PSO.Molecule.Structure,
params: {
}
})({
canAutoUpdate({ newParams }) {
return true;
},
apply({ a, params }) {
return Task.create('Build Structure', async ctx => {
// TODO: optimze
// TODO: think of ways how to fast-track changes to this for animations
const model = a.data;
const initial_structure = Structure.ofModel(model);
const structures: Structure[] = [];
let structure: Structure = initial_structure;
// the list of asambly *?
const symmetry = ModelSymmetry.Provider.get(model);
if (symmetry && symmetry.assemblies.length !== 0) {
for (const a of symmetry.assemblies) {
const s = await StructureSymmetry.buildAssembly(initial_structure, a.id).runInContext(ctx);
structures.push(s);
}
const builder = Structure.Builder({ label: 'Membrane' });
let offsetInvariantId = 0;
for (const s of structures) {
let maxInvariantId = 0;
for (const u of s.units) {
const invariantId = u.invariantId + offsetInvariantId;
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
}
offsetInvariantId += maxInvariantId + 1;
}
structure = builder.getStructure();
for (let i = 0, il = structure.models.length; i < il; ++i) {
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
}
}
return new PSO.Molecule.Structure(structure, { label: a.label, description: `${a.description}` });
});
},
dispose({ b }) {
b?.data.customPropertyDescriptors.dispose();
}
});
const CreateTransformer = StateTransformer.builderFactory('cellPACK');
export const CreateCompartmentSphere = CreateTransformer({
name: 'create-compartment-sphere',
display: 'CompartmentSphere',
from: PSO.Root, // or whatever data source
to: PSO.Shape.Representation3D,
params: {
center: PD.Vec3(Vec3()),
radius: PD.Numeric(1),
label: PD.Text(`Compartment Sphere`)
}
})({
canAutoUpdate({ oldParams, newParams }) {
return true;
},
apply({ a, params }, plugin: PluginContext) {
return Task.create('Compartment Sphere', async ctx => {
const data = params;
const repr = MBRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => (MBParams));
await repr.createOrUpdate({ ...params, quality: 'custom', xrayShaded: true, doubleSided: true }, data).runInContext(ctx);
return new PSO.Shape.Representation3D({ repr, sourceData: a }, { label: data.label });
});
}
});

View File

@@ -1,109 +0,0 @@
/**
* Copyright (c) 2019-2021 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>
*/
import { CIF } from '../../mol-io/reader/cif';
import { parsePDB } from '../../mol-io/reader/pdb/parser';
import { AssetManager, Asset } from '../../mol-util/assets';
import { Structure } from '../../mol-model/structure';
import { Vec3 } from '../../mol-math/linear-algebra';
import { PluginContext } from '../../mol-plugin/context';
export async function parseCif(plugin: PluginContext, data: string | Uint8Array) {
const comp = CIF.parse(data);
const parsed = await plugin.runTask(comp);
if (parsed.isError) throw parsed;
return parsed.result;
}
export async function parsePDBfile(plugin: PluginContext, data: string, id: string) {
const comp = parsePDB(data, id);
const parsed = await plugin.runTask(comp);
if (parsed.isError) throw parsed;
return parsed.result;
}
async function downloadCif(plugin: PluginContext, url: string, isBinary: boolean, assetManager: AssetManager) {
const type = isBinary ? 'binary' : 'string';
const asset = await plugin.runTask(assetManager.resolve(Asset.getUrlAsset(assetManager, url), type));
return { cif: await parseCif(plugin, asset.data), asset };
}
async function downloadPDB(plugin: PluginContext, url: string, id: string, assetManager: AssetManager) {
const asset = await assetManager.resolve(Asset.getUrlAsset(assetManager, url), 'string').run();
return { pdb: await parsePDBfile(plugin, asset.data, id), asset };
}
export async function getFromPdb(plugin: PluginContext, pdbId: string, assetManager: AssetManager) {
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId}.bcif`, true, assetManager);
return { mmcif: cif.blocks[0], asset };
}
export async function getFromOPM(plugin: PluginContext, pdbId: string, assetManager: AssetManager) {
const asset = await plugin.runTask(assetManager.resolve(Asset.getUrlAsset(assetManager, `https://opm-assets.storage.googleapis.com/pdb/${pdbId.toLowerCase()}.pdb`), 'string'));
return { pdb: await parsePDBfile(plugin, asset.data, pdbId), asset };
}
export async function getFromCellPackDB(plugin: PluginContext, id: string, baseUrl: string, assetManager: AssetManager) {
if (id.toLowerCase().endsWith('.cif') || id.toLowerCase().endsWith('.bcif')) {
const isBinary = id.toLowerCase().endsWith('.bcif');
const { cif, asset } = await downloadCif(plugin, `${baseUrl}/other/${id}`, isBinary, assetManager);
return { mmcif: cif.blocks[0], asset };
} else {
const name = id.endsWith('.pdb') ? id.substring(0, id.length - 4) : id;
return await downloadPDB(plugin, `${baseUrl}/other/${name}.pdb`, name, assetManager);
}
}
export type IngredientFiles = { [name: string]: Asset.File }
export function getStructureMean(structure: Structure) {
let xSum = 0, ySum = 0, zSum = 0;
for (let i = 0, il = structure.units.length; i < il; ++i) {
const unit = structure.units[i];
const { elements } = unit;
const { x, y, z } = unit.conformation;
for (let j = 0, jl = elements.length; j < jl; ++j) {
const eI = elements[j];
xSum += x(eI);
ySum += y(eI);
zSum += z(eI);
}
}
const { elementCount } = structure;
return Vec3.create(xSum / elementCount, ySum / elementCount, zSum / elementCount);
}
export function getFloatValue(value: DataView, offset: number) {
// if the last byte is a negative value (MSB is 1), the final
// float should be too
const negative = value.getInt8(offset + 2) >>> 31;
// this is how the bytes are arranged in the byte array/DataView
// buffer
const [b0, b1, b2, exponent] = [
// get first three bytes as unsigned since we only care
// about the last 8 bits of 32-bit js number returned by
// getUint8().
// Should be the same as: getInt8(offset) & -1 >>> 24
value.getUint8(offset),
value.getUint8(offset + 1),
value.getUint8(offset + 2),
// get the last byte, which is the exponent, as a signed int
// since it's already correct
value.getInt8(offset + 3)
];
let mantissa = b0 | (b1 << 8) | (b2 << 16);
if (negative) {
// need to set the most significant 8 bits to 1's since a js
// number is 32 bits but our mantissa is only 24.
mantissa |= 255 << 24;
}
return mantissa * Math.pow(10, exponent);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -170,8 +170,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true);
}
private addMaterial(metalness: number, roughness: number, doubleSided: boolean, alpha: boolean) {
const hash = `${metalness}|${roughness}|${doubleSided}`;
private addMaterial(metalness: number, roughness: number, emissive: number, doubleSided: boolean, alpha: boolean) {
const hash = `${metalness}|${roughness}|${emissive}|${doubleSided}`;
if (!this.materialMap.has(hash)) {
this.materialMap.set(hash, this.materials.length);
this.materials.push({
@@ -180,6 +180,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
metallicFactor: metalness,
roughnessFactor: roughness
},
emissiveFactor: [emissive, emissive, emissive],
doubleSided,
alphaMode: alpha ? 'BLEND' : 'OPAQUE',
});
@@ -200,10 +201,11 @@ export class GlbExporter extends MeshExporter<GlbData> {
const instanceCount = values.uInstanceCount.ref.value;
const metalness = values.uMetalness.ref.value;
const roughness = values.uRoughness.ref.value;
const emissive = values.uEmissive.ref.value;
const doubleSided = values.uDoubleSided?.ref.value || values.hasReflection.ref.value;
const alpha = values.uAlpha.ref.value < 1;
const material = this.addMaterial(metalness, roughness, doubleSided, alpha);
const material = this.addMaterial(metalness, roughness, emissive, doubleSided, alpha);
let interpolatedColors: Uint8Array | undefined;
if (webgl && mesh && (colorType === 'volume' || colorType === 'volumeInstance')) {

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