Compare commits

...

709 Commits

Author SHA1 Message Date
dsehnal
33dc2015df 5.0.0-dev.4 2025-08-05 09:55:55 +02:00
dsehnal
fcf5ea420b npm audit 2025-08-05 09:54:34 +02:00
dsehnal
8d97327f8d MVS: fix passing custom primitive params 2025-08-05 09:53:58 +02:00
midlik
abc7ebba3e MVS: Fix MVSInlinePrimitiveData param type (#1592) 2025-08-03 17:48:19 +02:00
David Sehnal
73d593907e MVS cavnas molstar_postprocessing (#1598) 2025-08-03 12:04:53 +02:00
dsehnal
0dc05e1138 5.0.0-dev.3 2025-08-02 18:21:14 +02:00
David Sehnal
dd11cacae4 Markdown Commands and MVS improvements (#1597)
* add query command to markdown extensions

* fix typo

* better postprocessing param support in MVS

* molstar_mesh/label/line_params
2025-08-02 18:19:01 +02:00
David Sehnal
b503259758 another io-ts import fix (#1595) 2025-08-01 06:28:26 +02:00
zachcp
1e98741e16 Update field-schema.ts (#1594) 2025-08-01 05:38:41 +02:00
dsehnal
f879519700 5.0.0-dev.2 2025-07-31 18:31:41 +02:00
zachcp
c6e175e5da Update field-schema.ts (#1593)
Follow on from #1587  to make `JS` explicit.
2025-07-31 18:29:28 +02:00
dsehnal
add75bf9c9 5.0.0-dev.1 2025-07-28 16:37:17 +02:00
dsehnal
57cbcd5fbf npm audit 2025-07-28 16:34:08 +02:00
zachcp
0a33936e06 Update field-schema.ts to point directly to PathReporter (#1587) 2025-07-28 07:48:00 +02:00
Alexander Rose
7291025e09 Merge pull request #1585 from molstar/scene-scale
Scene scale
2025-07-27 17:18:57 -07:00
Alexander Rose
86da258280 Merge branch 'master' of https://github.com/molstar/molstar into scene-scale 2025-07-26 17:42:54 -07:00
Alexander Rose
477a80d1ca fix post-processing params hideIf logic 2025-07-26 17:40:14 -07:00
Alexander Rose
86b68018a9 add scene scaling support 2025-07-26 17:39:44 -07:00
Alexander Rose
da095d6ef9 handling move/dragt before resolving pickData breaks ray-picking 2025-07-26 16:46:08 -07:00
Alexander Rose
dc304b9e08 Merge pull request #1583 from molstar/fix-async-buffer
fix async buffer issues
2025-07-26 16:22:43 -07:00
Alexander Rose
c905fa17c4 tweak 2025-07-26 16:22:33 -07:00
Alexander Rose
a06c64e8e0 Merge pull request #1584 from molstar/pp-switch
add `enable` param for post-processing effects
2025-07-26 16:05:25 -07:00
Alexander Rose
f5441290dd Merge pull request #1567 from giagitom/box3d-spec
add tests for box3D nearestIntersectionWithRay3D
2025-07-26 16:04:59 -07:00
Alexander Rose
9f23124317 move ray box intersection code to Ray3D 2025-07-26 15:59:40 -07:00
dsehnal
8299cd638c tweaks 2025-07-26 20:37:42 +02:00
Alexander Rose
50cb08e74d Merge branch 'master' of https://github.com/molstar/molstar into fix-async-buffer 2025-07-26 07:55:21 -07:00
Alexander Rose
89552652ba Merge branch 'master' of https://github.com/molstar/molstar into pp-switch 2025-07-26 07:55:02 -07:00
Alexander Rose
37ce577813 fix text shader 2025-07-26 07:54:28 -07:00
Alexander Rose
4d9a003141 add enable param for post-processing effects
- If false, no effects are applied.
2025-07-26 07:45:59 -07:00
Alexander Rose
6f0311a53f fix async buffer issues
- mark pick-helper dirty when async pick failed
- add pixel-pack buffer wrapper
- recover pixel-pack buffer after context loss (pick buffer, hi-z pass)
2025-07-26 07:30:54 -07:00
Alexander Rose
bfd2d6b055 text shader: head rotation tweak 2025-07-26 07:23:49 -07:00
Alexander Rose
3072e60709 Merge pull request #1582 from molstar/revert-1581-fix-async-identify
Revert "fix async identify"
2025-07-26 07:22:33 -07:00
Alexander Rose
62ed8d10e3 Revert "fix async identify (#1581)"
This reverts commit 13d3c34864.
2025-07-26 07:22:12 -07:00
David Sehnal
13d3c34864 fix async identify (#1581) 2025-07-25 18:42:29 +02:00
David Sehnal
cac433efca MVS Stories: Add "Download MVS State" link (#1580) 2025-07-25 14:31:07 +02:00
dsehnal
b25ffe7151 Canvas3dInteractionHelper fix 2025-07-25 10:53:02 +02:00
David Sehnal
31074dc74c fix inv het rotation uniform (#1578) 2025-07-25 10:34:13 +02:00
giagitom
c98c01a076 fix names 2025-07-23 18:24:56 +02:00
giagitom
8966fc9396 refactor 2025-07-23 18:23:12 +02:00
dsehnal
fdbdc551e8 fix web component syntax 2025-07-22 15:25:44 +02:00
dsehnal
bb232ac3a4 pass format in mvs stories app 2025-07-22 14:37:51 +02:00
giagitom
735c25ef8d Merge branch 'master' of https://github.com/molstar/molstar into box3d-spec 2025-07-22 14:13:07 +02:00
Alexander Rose
298043313a head rotation handling tweaks 2025-07-20 22:07:37 -07:00
Alexander Rose
77cd181b91 add addCylinderFromRay3D helper function 2025-07-20 09:02:31 -07:00
Alexander Rose
b5bee042e8 add groupCount argument to Shape.create 2025-07-20 08:48:43 -07:00
Alexander Rose
4faf17ddc7 Merge pull request #1576 from molstar/headrotation
Add head rotation support
2025-07-20 08:45:15 -07:00
Alexander Rose
28774b2277 fix scale issues in cylinders & spheres shaders 2025-07-19 16:13:48 -07:00
Alexander Rose
6a7444f44e add head rotation support
- handle skybox
- handle sphere & text billboards
2025-07-19 16:13:00 -07:00
Alexander Rose
15bfa8416a Merge pull request #1575 from molstar/async-ray-picking
add async & ray picking
2025-07-19 15:16:01 -07:00
Alexander Rose
e6895ec833 cleanup, simplify AsyncPickData 2025-07-19 15:08:46 -07:00
Alexander Rose
2099ad728a add async & ray picking 2025-07-19 09:13:49 -07:00
dsehnal
72ae3fae65 5.0.0-dev.0 2025-07-19 09:30:04 +02:00
dsehnal
bb5ad78681 eslint fix 2025-07-19 09:28:44 +02:00
dsehnal
f10e88612f npm audit 2025-07-19 09:26:55 +02:00
David Sehnal
a2e582d4a9 update MVS Stories app and deploy scripts (#1574)
* update MVS Stories app and deploy scripts

* reorder changelog
2025-07-19 09:25:32 +02:00
David Sehnal
572874f4ae Rename SymmetryOperator.canonicalName to instanceId (#1571) 2025-07-19 07:46:43 +02:00
David Sehnal
b9c0347497 MVS: grid-slice volume representation, label improvements, state transitions via 3D interactions, instacing (#1570)
* MVS: grid-slice volume representation

* tweak

* type fix

* label tether support

* snapshot_key support

* custom MVSShapeRepresentation3D

* renaming

* structure and volume instancing

* fix mixin
2025-07-19 07:46:01 +02:00
midlik
089148198f MVS operator_name (#1561)
* Change symmetry operator naming

* MVS operator_name selector for inline component, color, label, tooltip

* MVS operator_name selector in annotations (component/color/label/tooltip_from_uri/source)

* Revert changes to operatorName, add canonicalOperatorName instead, rename MVS selector field operator_name -> instance_id

* Update CHANGELOG

* Remove polyfill.io in mkdocs

* MVS: MultilayerColorThemeName decide granularity smartly

* MVS: MultilayerColorThemeName refactor
2025-07-11 20:45:22 +02:00
giagitom
6fc04c3294 add tests for box3D nearestIntersectionWithRay3D 2025-07-07 18:46:06 +02:00
Alexander Rose
dc55577e22 chanelog 2025-07-06 15:28:43 -07:00
Alexander Rose
f7ba7c0511 add Ray3D and math fixes/improvements 2025-07-06 15:24:47 -07:00
Alexander Rose
ed5374fab9 improve volume visual group count update 2025-07-06 10:10:02 -07:00
Alexander Rose
9a04b4f0df instanced volume (#1557)
* wip, instanced volume

* add Orderset.isEmpty and Interval.offset

* add Box3D.addBox3D

* support volume instances

- add Volume.instances
- add Volume.InstanceIndex and Volume.SegmentIndex types
- volume loci improvements

* add volume-instance color theme

* add VolumeInstances xform

* breaking note

* trailing space

* remove setting that breaks ESlint in VSCode

* tweak angle param

* reuse volume visuals when only instance transforms change

* tweaks

---------

Co-authored-by: dsehnal <david.sehnal@gmail.com>
2025-07-06 19:04:03 +02:00
Alexander Rose
9350e539b6 Merge pull request #1566 from molstar/fix-group-count
Fix group count calculation on geometry update
2025-07-06 09:48:20 -07:00
Alexander Rose
c38377af46 Merge pull request #1564 from molstar/mol2-improvements
Mol2 Reader improvements
2025-07-05 16:57:00 -07:00
Alexander Rose
9804febd95 Merge branch 'master' into mol2-improvements 2025-07-05 16:56:51 -07:00
Alexander Rose
7936dc1840 Fix wrong instance index in calcMeshColorSmoothing 2025-07-05 15:53:08 -07:00
Alexander Rose
a033a8be36 Fix group count calculation on geometry update 2025-07-04 23:24:39 -07:00
Alexander Rose
4b84c6dcba fix typo MarchinCubes -> MarchingCubes 2025-07-04 23:22:38 -07:00
Alexander Rose
309d792fdb fix shader error when clipping flags are set without clip objects present 2025-07-04 17:51:59 -07:00
Alexander Rose
c437254680 add substructure spec 2025-07-04 16:15:55 -07:00
Alexander Rose
6fbf7c7a22 fix spec 2025-07-04 16:01:55 -07:00
Alexander Rose
86a7520b90 Mol2 Reader improvements
- Fix column count parsing
- Add support for substructure
2025-07-04 15:11:43 -07:00
David Sehnal
cd10043447 MVS: clip node support (#1553)
* MVS: clip node support

* rename transform to point_transform

* fix vec3/mat4 control overflow

* refactor mvs clipping

* unused var

* tweaks
2025-07-04 18:03:37 +02:00
David Sehnal
146e95cb23 Snapshot Markdown Improvements (#1555)
* basic markdown commands

* markdown renderers

* support markdown tables

* fix style

* indicate external links in markdown

* simplify the api

* load image from MVSX

* lint

* docs

* typo

* custom color palette support

* move manager to mol-plugin-state

* customize args parser

* better custom args parser support
2025-07-04 10:29:03 +02:00
David Sehnal
13b1e5d59c Async Viewer Init (#1394)
* async viewer init

* changelog

* tweak changelog

* make context init functions async
2025-07-01 09:52:45 +02:00
Alexander Rose
ae3efa53d6 Merge pull request #1556 from molstar/coarsegrained-unit-trait
Avoid calculating rings for coarse-grained structures
2025-06-29 22:16:32 -07:00
Alexander Rose
2e67fbe870 Merge branch 'master' into coarsegrained-unit-trait 2025-06-29 22:16:20 -07:00
dsehnal
56df6f82a7 docs tweak 2025-06-29 21:16:34 +02:00
Alexander Rose
fdd874b7a6 Merge pull request #1554 from giagitom/isosurface-fix
Fix isosurface compute shader normals when transformation matrix is applied to volume
2025-06-28 17:22:38 -07:00
Alexander Rose
f142c3ef1b lint 2025-06-28 17:20:04 -07:00
Alexander Rose
978b53e7d8 Avoid calculating rings for coarse-grained structures
- add `Unit.Traits.CoarseGrained`
2025-06-28 17:06:44 -07:00
Alexander Rose
2f3197479d Merge pull request #1550 from giagitom/illumination-fix
Fix outlines on opaque elements using illumination mode
2025-06-28 16:12:31 -07:00
Alexander Rose
6536d0ab91 Merge branch 'master' into illumination-fix 2025-06-28 16:12:09 -07:00
Alexander Rose
3bee224e7d Merge pull request #1549 from molstar/gl-refactor
WebGL related refactoring
2025-06-28 15:56:00 -07:00
Alexander Rose
3e63137977 Merge branch 'master' of https://github.com/molstar/molstar into gl-refactor 2025-06-28 15:53:29 -07:00
Alexander Rose
38d6bc6c27 tweak 2025-06-28 15:52:43 -07:00
Alexander Rose
fafe22d56b Apply suggestions from code review
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2025-06-28 15:45:32 -07:00
giagitom
a6a92bcf91 lint fix 2025-06-28 23:03:39 +02:00
giagitom
82c681f445 improve performances 2025-06-28 23:01:04 +02:00
giagitom
fbbd58b4db Fix isosurface compute shader normals when transformation matrix is applied to volume 2025-06-28 17:34:52 +02:00
David Sehnal
2dc13f082c Add custom extensions to MVS (#1547)
* Add custom extensions to MVS

* typo

* lint
2025-06-24 17:04:01 +02:00
midlik
ab5eb5993d MVS generic color themes (#1530)
* MVS: CategoricalPalette draft

* MVS: tweak param validation for union, nullable

* MVSAnnotationColorTheme: support categorical palette

* MVS: color theme categorical with list or mapping

* MVS: color theme categorical with named palette

* Add missing color lists

* Sort color lists

* MVS: color theme categorical tidyup

* MVS: color theme continuous params

* MVS: color theme continuous impl

* refactor

* MVS: color theme continuous - reverse, auto overflow_color

* MVS: color theme discrete

* file reorg

* MVS: param union does not need []

* MVS: refactor typing object params

* MVS: color theme - palette defaults in one place

* MVS: declare fields_remapping param

* MVS: implement fields_remapping param

* MVS: docs

* Update CHANGELOG

* MVS: rename fields_remapping -> field_remapping

* PR feedback

* MVS: Generic color themes - case_insensitive param

* MVS: SecondaryStructure named color dict

* Remove accidentaly added file

* Update color map descriptions

* Revert color scheme renaming, keep for v5

* Revert "Revert color scheme renaming, keep for v5"

This reverts commit 12e25c20fe.

* Added color list type "cyclical"

* Color palettes - show description in UI tooltips

* Fixed docstrings
2025-06-24 12:48:39 +02:00
Alexander Rose
2384003f5d add https support for dev server (#1548) 2025-06-24 12:41:07 +02:00
David Sehnal
3675c0afe0 Update Representation.Empty creation (#1546) 2025-06-24 12:40:50 +02:00
giagitom
d9bae488e9 Fix outlines on opaque elements using illumination mode 2025-06-23 17:58:53 +02:00
Alexander Rose
e31e5321ba refactor (abstract) webgl drawing buffer handling 2025-06-22 16:43:22 -07:00
Alexander Rose
8c7f8b8a56 remove unused WebGLContext.getDrawingBufferPixelData 2025-06-22 16:38:18 -07:00
Alexander Rose
e4dfb5148c add support for webgl multiview2 extension 2025-06-22 16:35:13 -07:00
Alexander Rose
39e2591b60 improve webgl error handling 2025-06-22 16:31:51 -07:00
David Sehnal
f8a5237024 Improve production build (#1542)
* production build using esbuild

* build browser tests with esbuild

* use tsc-alias

* remove webpack

* changelog

* update eslint to v9

* pr feedback

* update build

* include src map by default
2025-06-17 18:15:10 +02:00
MadCatX
6c2d5b9da7 Do not display NtC Tube and Confal pyramids Loci labels as verbatim text (#1543) 2025-06-16 10:45:08 +02:00
midlik
e128d85356 StringLike type include string explicitely (#1539) 2025-06-12 18:08:56 +02:00
Alexander Rose
08a929bb2f 4.18.0 2025-06-08 13:40:21 -07:00
Alexander Rose
5a54b3ef66 changelog 2025-06-08 13:37:31 -07:00
Alexander Rose
a0c897547a schema updates 2025-06-08 13:37:25 -07:00
Alexander Rose
89ce8394fd package updates 2025-06-08 13:34:08 -07:00
Alexander Rose
ea0331e95c Merge branch 'master' of https://github.com/molstar/molstar 2025-06-08 13:28:12 -07:00
Alexander Rose
9f220b55c2 fix mc scalar field diff (@giagitom) 2025-06-08 13:28:10 -07:00
Alexander Rose
acf248d58f Merge pull request #1535 from molstar/arbitrary-plane-sampling
Support sampling from arbitrary planes
2025-06-08 13:26:34 -07:00
Alexander Rose
c83b859766 type tweak 2025-06-08 13:26:21 -07:00
Alexander Rose
33a2564893 Merge branch 'master' into arbitrary-plane-sampling 2025-06-08 13:25:58 -07:00
David Sehnal
d409c4f5ea Fix SASS @import depraction warnings (#1534)
* refactor SASS to not use @import

* changelog

* typo
2025-06-08 09:33:39 +02:00
Alexander Rose
ab61e31230 header 2025-06-07 16:29:28 -07:00
Alexander Rose
ae9c2dd9d8 Support sampling from arbitrary planes
- structure plane and volume slice representations
2025-06-07 16:27:37 -07:00
David Sehnal
c17edb4928 isolatedModules and fix turbopack build (#1533)
* isolated modules tsconfig & fix errors

* fix mol-math imports

* fix turbopack builds

* fix typo

* tweak

* undo gl-shim change
2025-06-02 18:59:42 +02:00
Alexander Rose
528377eb47 Merge pull request #1532 from molstar/xray-picking
Support `pickingAlphaThreshold` when `xrayShaded` is enabled
2025-06-01 08:48:01 -07:00
Alexander Rose
c9819369d0 header 2025-05-31 11:33:18 -07:00
Alexander Rose
cdbbbfa6dd Support pickingAlphaThreshold when xrayShaded is enabled 2025-05-31 11:29:58 -07:00
David Sehnal
a1e31c79e9 MVS: FoV adjusted position Camera Info & MVSX assets in multi-snapshot states (#1531)
* FoV adjusted position Camera Info

* Fix MVSX file assets being disposed in multi-snapshot states

* pr feedback
2025-05-30 19:19:29 +02:00
midlik
e027fe46c1 MVS: Support for label_comp_id and auth_comp_id in annotations (#1529)
* MVS: Support for label_comp_id and auth_comp_id in annotations

* MVS: Primitives recognize empty substructures, distance_measurement refactor

* MVS: Primitives skipped when empty substructure, nicer default arrow caps

* MVS: Primitive angle_measurement added vector_radius param
2025-05-23 16:17:26 +02:00
dsehnal
05c4006e9d 4.17.0 2025-05-22 07:01:43 +02:00
dsehnal
191ea65c9d changelog 2025-05-22 06:58:02 +02:00
David Sehnal
3c1ee16376 remove salt bridge interaction kind (#1528) 2025-05-22 06:56:15 +02:00
David Sehnal
9ac34ee13b Add mvs-stories app (#1523)
* mvs-stories app

* update mvs-stories example

* fix build

* fix UI bug

* support search params in stories app

* merge fixes

* PR feedback

* customize build filenames

* mvs-stories loading state & dev build script fixes

* multiple context example
2025-05-22 06:49:17 +02:00
midlik
6778452d07 NodeJS ajaxGet support gzip (#1516)
* Retype string to StringLike in parsers

* Define minimal CustomString interface

* ChunkedBigString

* Test ChunkedBigString with cif2bcif

* benchmarking

* ChunkedBigString access optimization

* ChunkedBigString .length optimization

* ChunkedBigString.indexOf, tests

* ChunkedBigString remove [] in favor of charAt

* ChunkedBigString tidy up

* ChunkedBigString .substring optimization

* ChunkedBigString for browser

* ChunkedBigString for drag-and-drop

* ChunkedBigString fixes

* Simplify readFromFileInternal

* Correctly type DataResponse<'string'> as StringLike

* Update CHANGELOG

* PR feedback

* Workaround for ajaxGet in NodeJS when content gzipped

* Workaround for ajaxGet in NodeJS when content gzipped - allow aborting

* ajaxGetInternal_file_NodeJS - async read file

* Eliminate xhr2

* Remove xhr2 dependency

* Update file headers
2025-05-21 14:49:54 +02:00
Alexander Rose
7e01af1e0d 4.16.0 2025-05-20 21:07:34 -07:00
Alexander Rose
85469cbf28 changelog 2025-05-20 21:04:22 -07:00
Alexander Rose
299bdc72cd Merge pull request #1525 from molstar/mp4-export-fix-25-5-20
Fix camera interpolation during animation export
2025-05-20 21:01:41 -07:00
dsehnal
ae9f879139 use isContextLost flag instead of pausing/resuming animation 2025-05-21 05:53:26 +02:00
dsehnal
b50d83d6ea replace behavior subject with subject 2025-05-20 18:02:40 +02:00
dsehnal
2d99d8a1d0 do not pause animation during context loss 2025-05-20 17:32:45 +02:00
midlik
ea00cca1c8 MVS single state loading (#1524)
* MVS: Load single state as if multistate

* MVS: join keepCamera and keepSnapshotCamera options

* MVS: remove replaceExisting, addappendSnapshots option
2025-05-20 17:24:25 +02:00
midlik
27c3b4e698 Big strings (#1479)
* Retype string to StringLike in parsers

* Define minimal CustomString interface

* ChunkedBigString

* Test ChunkedBigString with cif2bcif

* benchmarking

* ChunkedBigString access optimization

* ChunkedBigString .length optimization

* ChunkedBigString.indexOf, tests

* ChunkedBigString remove [] in favor of charAt

* ChunkedBigString tidy up

* ChunkedBigString .substring optimization

* ChunkedBigString for browser

* ChunkedBigString for drag-and-drop

* ChunkedBigString fixes

* Simplify readFromFileInternal

* Correctly type DataResponse<'string'> as StringLike

* Update CHANGELOG

* PR feedback

* PR feedback 2
2025-05-20 11:55:20 +02:00
Alexander Rose
52942e7021 4.15.0 2025-05-19 19:31:09 -07:00
Alexander Rose
5904f694b5 changelog 2025-05-19 19:28:29 -07:00
Alexander Rose
92c0b82784 pckage updates 2025-05-19 19:28:16 -07:00
Alexander Rose
e1226fa384 type fix 2025-05-19 19:27:59 -07:00
Alexander Rose
ac2f7d1c38 lint 2025-05-19 19:27:48 -07:00
Alexander Rose
ae1742f68e Merge pull request #1520 from molstar/fix-contextlost-handling
WebGL ContextLost handling improvements
2025-05-18 14:47:36 -07:00
Alexander Rose
04e2da86fd typo 2025-05-17 16:39:06 -07:00
Alexander Rose
510182ff60 WebGL ContextLost handling improvements
- Fix missing framebuffer & drawbuffer re-attachments
- Fix missing cube texture re-initialization
- Fix missing extensions reset
- Fix timer clearing edge case
- Add reset support for geometry generated on he GPU
2025-05-17 16:36:20 -07:00
Alexander Rose
4e1da19bdd Merge pull request #1506 from sbittrich/master
IHM improvements: Enable assembly symmetry, disable volume streaming and validation report visualization
2025-05-17 13:46:26 -07:00
Alexander Rose
a3eae15446 Merge branch 'master' into master 2025-05-17 13:15:04 -07:00
David Sehnal
4334f4d1fa JSON CIF format and ligand editor example (#1510)
* wip data model and writer

* parser & test

* minimal editor example

* molstar_bond_site cif category support

* ligand graph, deletion, simple undo

* jest config

* bond editing and graph change summary

* readme

* ts config

* basic atom addition

* undo path aliases because tsc doesn't transform them

* tweak package json

* basic rgroup support

* mol parsing test and fixes

* refactoring

* molfile conversion

* refactoring and UI polish

* molfile export tweaks

* move jsonCifToMolfile

* tweak

* refactoring

* error reporting

* geometry edits

* hide selection controls

* refactoring

* tweaks

* changelog

* ligand graph tests

* SingleTaskQueue tweak

* revert Column changes

* pr feedback

* PR Feedback
2025-05-13 10:51:39 +02:00
Alexander Rose
e33ed54121 Merge pull request #1513 from midlik/fix-transparent-ssao-nodejs
Fix transparency rendering with occlusion in NodeJS
2025-05-10 08:51:41 -07:00
Adam Midlik
ae8f037192 Fix transparency rendering with occlusion in NodeJS 2025-05-09 14:12:31 +01:00
Alexander Rose
01271941dd Merge pull request #1509 from molstar/tweak-auto-quality-surface-res
adjust max resolution for auto quality
2025-05-08 22:08:33 -07:00
dsehnal
7f8be5b8c6 4.14.1 2025-05-09 07:02:52 +02:00
David Sehnal
2ab6e4b2e7 No error in Transformer.create (#1512)
* No error in Transformer.create

* header
2025-05-09 06:59:15 +02:00
Sebastian Bittrich
aa22840b12 Merge remote-tracking branch 'upstream/master'
# Conflicts:
#	CHANGELOG.md
2025-05-08 10:36:30 -07:00
Sebastian Bittrich
c1e33fac94 PR feedback: adjust assembly symmetry logic 2025-05-08 10:23:58 -07:00
Sebastian Bittrich
a7336095ca name IHM assemblies "deposited" 2025-05-07 09:47:26 -07:00
dsehnal
4a88546181 changelog 2025-05-07 16:08:17 +02:00
dsehnal
edbc70cf6e 4.14.0 2025-05-07 16:07:49 +02:00
dsehnal
c22ad2910c npm audit 2025-05-07 16:04:36 +02:00
dsehnal
28a2b52e3c changelog 2025-05-07 16:03:53 +02:00
David Sehnal
449d572ed5 Merge branch 'master' into tweak-auto-quality-surface-res 2025-05-07 14:50:09 +02:00
Gianluca Tomasello
470227af43 Avoid grid expansion when requiring unit cell on volume server (#1502)
* Avoid grid expansion when requiring unit cell on volume server

* Increment version and use specific changelog

* change header
2025-05-07 14:49:08 +02:00
dsehnal
a0ccf46939 remove extra import 2025-05-05 08:10:03 +02:00
dsehnal
0ce8931fc5 Fix switching representation type in Volume UI 2025-05-04 18:28:19 +02:00
David Sehnal
3ddb29fc6f Add format selection option to image export UI (#1504)
* Add format selection option to image export UI

* check if webp is supported
2025-05-04 14:54:17 +02:00
Alexander Rose
1a0c65df21 changelog 2025-05-03 08:26:01 -07:00
Alexander Rose
daad1923ea adjust max resolution for auto quality (#1501) 2025-05-03 08:22:56 -07:00
David Sehnal
f34f879cf1 MVS: support updating transform states (#1505)
* MVS: support updating transform states

* changelog

* changelog

* pr feedback

* util functions & is_hidden extension
2025-05-02 12:53:38 +02:00
Sebastian Bittrich
f47b76c8af Merge remote-tracking branch 'upstream/master'
# Conflicts:
#	CHANGELOG.md
2025-05-01 11:17:52 -07:00
Sebastian Bittrich
6ee9eb8b60 IHM: disable validation report 2025-05-01 11:16:53 -07:00
Sebastian Bittrich
915703a46d IHM: enable assembly symmetry 2025-05-01 11:13:46 -07:00
Sebastian Bittrich
61c3c19ae3 IHM: disable volume streaming 2025-05-01 11:11:50 -07:00
David Sehnal
6da9557531 Fix StructConn.residueCantorPairs (#1500) 2025-05-01 11:29:24 +02:00
Paul Lewallen
29e6d69d21 Update package.json (#1497)
Add engines field to specify required Node.js version (>=18.0.0)
2025-04-26 07:46:50 +02:00
David Sehnal
6b2b87e6c5 fix Viewer.loadTrajectory (#1496) 2025-04-24 10:44:59 +02:00
dsehnal
5299d5c0c4 changelog 2025-04-14 18:37:52 +02:00
dsehnal
7bab95f4cc 4.13.0 2025-04-14 18:04:43 +02:00
David Sehnal
35e78ce638 MVS Stories (#1484)
* ability to select a story

* rename to mvs-stories

* update kinase story

* fix ae example

* tweaks

* tweak
2025-04-14 18:00:37 +02:00
Chetan Mishra
3abbcb6949 added toggle for hiding screenshot controls (#1483)
* added toggle for hiding screenshot button

* whoops did the todolist but forgot to commit

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2025-04-14 14:38:47 +02:00
David Sehnal
c3fc893ad0 mvs: fix state builder for volumes (#1485) 2025-04-13 13:07:52 +02:00
midlik
80415a2771 MVS: support loading extensions when loading multistate files (#1470)
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2025-04-01 07:15:26 +02:00
etongfu
bfef69e2e4 Dev script update (#1473)
* feat: support host option for build-dev script

* feat: ust update the code related to the host

---------

Co-authored-by: tongfu.e <tongfu.e@xtalpi.com>
2025-04-01 07:14:39 +02:00
David Sehnal
a265a579be Update support for QA metrics (#1476)
* generalize ma_qa_metric support

* do not assume PAE is symmetric

* changelog

* Update src/extensions/model-archive/quality-assessment/prop.ts

Co-authored-by: Alexander Rose <alexander.rose@weirdbyte.de>

* Update src/extensions/model-archive/quality-assessment/prop.ts

Co-authored-by: Alexander Rose <alexander.rose@weirdbyte.de>

* lint

---------

Co-authored-by: Alexander Rose <alexander.rose@weirdbyte.de>
2025-03-31 18:51:18 +02:00
ludovic autin
c2af1b0b22 Colors from user and file (#1466)
* color, and granularity for selectrion.

* Load json colors per ingredient {"name":{"x":0,"y":0,"z":0}}

* change icon

* use functional components and simple function.
2025-03-28 18:51:42 +01:00
Sagar Pathak
b739876726 Update index.md (#1472)
Fix command typo: changed 'npm build' to 'npm run build'
2025-03-24 17:57:01 +01:00
David Sehnal
56851cc328 Improve struct_conn handling (#1468)
* improve struct_conn handling

* fix

* fix

* check same model

* Update src/mol-model-formats/structure/property/bonds/struct_conn.ts

Co-authored-by: Alexander Rose <alexander.rose@weirdbyte.de>

---------

Co-authored-by: Alexander Rose <alexander.rose@weirdbyte.de>
2025-03-24 16:49:11 +01:00
dsehnal
b719568555 mobile-friendly Kinase Story 2025-03-24 08:47:42 +01:00
Alexander Rose
f842f19912 Merge branch 'master' of https://github.com/molstar/molstar 2025-03-23 16:28:55 -07:00
Alexander Rose
d6b0dd910c guard against overly large grids 2025-03-23 16:28:52 -07:00
Alexander Rose
395eb8e8d0 Merge pull request #1454 from agdturner/pwa
For viewer to be installable as a Progressive Web App with a Service …
2025-03-23 16:28:03 -07:00
Alexander Rose
e4376c6737 try fix package-lock 2025-03-23 16:10:03 -07:00
Alexander Rose
6f23fcba8a Merge branch 'master' of https://github.com/molstar/molstar into pr/agdturner/1454 2025-03-23 16:06:17 -07:00
dsehnal
7d19bfdb9b Tweaks: kinase story edgecase fix, do not include volseg extension in Viewer by default 2025-03-17 09:30:30 +01:00
David Sehnal
6e4065f779 Interactions extension (#1463)
* move schema based element addressing from MVS to the core library

* proof of concept

* tweak

* initial visual

* tweak

* tweak structureElementLocationToSchemaItem

* show receptor residues as ball and stick

* wip data model

* custom interactions support

* add example

* wip visuals

* covalent links

* tweak

* fix bug

* cache unit features

* Add structureRef to Molecule.Structure.Selections

* optimize compute

* support multi-structure selections

* analyzeTrajectory support

* readme

* tweak

* headers

* docs

* Optimize StructureElementSchema & remove fromLoci conversion (not production ready for now, will be separate PR)

* move schema code to `StructureElement.Schema` and add fromExpression/Query/Schame functions to Loci and Bundle

* improve selection docs

* adjust covalent bond data model

* tweak fromExpression

* fix type

* remove hydrogen reference

* Fix MVS.rowsToExpression
2025-03-17 09:25:18 +01:00
Alexander Rose
c42a68b560 fix package-lock 2025-03-15 16:44:16 -07:00
Alexander Rose
c86d913b83 Merge branch 'master' of https://github.com/molstar/molstar into pr/agdturner/1454 2025-03-15 16:42:38 -07:00
Alexander Rose
0f6fa5fe15 simplify, add pwa files during deploy 2025-03-15 16:38:04 -07:00
Alexander Rose
f664cb02b1 Fix pickPadding and pickScale not updating PickHelper 2025-03-15 14:34:47 -07:00
David Sehnal
00a53de6e2 Use StructureElement.Bundle for measurements (#1453)
* Revert "Support measurements for coarse models"

This reverts commit a8a84e1dbf.

* add MultiStructureSelectionFromBundle and use it for measurements
2025-03-12 07:15:03 +01:00
Paul Pillot
b1ce5c158e fix: bond iterator starts from the same interbonds for every element (#1464)
This is causing an issue when there are multiple units having different inter-unit bonds: the `interBondCount` value is based on the `interBondIndices` length which is correct. But the iteration always starts from the `edges` index === 0. This is only correct for the first pairs of units. The iteration must be done over the `interBondIndices` to get the proper edges.

By returning indices for edges that do not correspond to the given element, the `otherIndex` property can be of bounds.
2025-03-12 06:41:59 +01:00
dsehnal
26826b61f9 fix lint error 2025-03-10 11:06:06 +01:00
Lukas Polak
f44f954a76 Implement ColorScale.createDiscrete, fix UI color palette bugs (#1459) 2025-03-09 10:53:10 +01:00
Alexander Rose
78aae8a2b4 pwa tweaks
- keep build process simple, write SW in JS
- support molstar version SW
- add `Viewer.loadFiles` to open supported files
- cleanup pwa icons
- add more file_handlers to pwa manifest
2025-03-08 15:00:23 -08:00
David Sehnal
92267d3264 MVS example tweaks (#1457)
* kinase story

* ihm-restraints

* fix typo

* tweak wwpdb links
2025-03-05 15:46:33 +01:00
Andy Turner
73ed45564e For viewer to be installable as a Progressive Web App with a Service Worker transpiled from TypeScript 2025-03-04 09:05:51 +00:00
David Sehnal
8bc2ebbeff Experimental esbuild support (#1452)
* wip esbuild

* esbuild all apps and examples

* wip dev build

* dev builds

* tweaks
2025-03-04 07:22:44 +01:00
Alexander Rose
5306d5d15a ignore renderables with empty draw count 2025-03-01 14:04:33 -08:00
Alexander Rose
3c3fb461c8 fix element-point visual not using child unit 2025-03-01 14:03:31 -08:00
dsehnal
62b3281282 fix webpack config 2025-03-01 19:20:34 +01:00
dsehnal
e85fadf15b tweak 2025-03-01 13:14:45 +01:00
dsehnal
a8a84e1dbf Support measurements for coarse models 2025-03-01 13:11:50 +01:00
David Sehnal
153599ef89 ihm-restraints example: show entity labels (#1450)
* ihm-restraints example: show entity labels

* noEntityLabels option
2025-03-01 08:37:51 +01:00
dsehnal
b67eda7cb5 4.12.1 2025-02-28 08:11:41 +01:00
dsehnal
8b431c50be npm fix 2025-02-28 08:08:58 +01:00
dsehnal
e195b048a1 4.12.0 2025-02-28 08:04:38 +01:00
dsehnal
ae5bb81b27 changelog 2025-02-28 08:01:21 +01:00
David Sehnal
8c04c57bc5 MVS Animation example (#1444)
* wip kinase story

* wip storyboard

* wip kinases

* import

* tweaks

* finish kinase story

* refactoring

* changelog

* tweaks

* tweak
2025-02-27 19:41:18 +01:00
David Sehnal
ec46a444f1 I/HM Example Improvements (#1448)
* add CoarseIndex

* IHM validation snapshots

* tweaks

* header
2025-02-25 06:44:48 +01:00
Alexander Rose
559e0326b6 fix image/slice group handling
- correctly update group count
- ignore undefined group when picking
- ensure group cover anti-aliased slice part
2025-02-22 11:06:01 -08:00
Sebastian Bittrich
82b93bc2a8 prim: fix arrow orientation and behavior along [0,-1,0] (#1447) 2025-02-22 09:11:59 +01:00
David Sehnal
62f940bc48 MVS: angle primitive (#1446) 2025-02-21 18:44:23 +01:00
David Sehnal
4e0be8e7b4 MVS: Volume Server support + Support carbohydrate representation + Camera section in Screenshot / State (#1445)
* MVS: Volume Server support + Camera section in Screenshot / State

* support carbohydrate repr
2025-02-21 07:51:10 +01:00
David Sehnal
128502edf0 MVS: Basic volumetric data support (#1443)
* mvs: basic volumetric data support

* headers
2025-02-19 09:03:04 +01:00
David Sehnal
aad4d4a86c MVS: IHM support (#1433)
* MVS: IHM support

* pr feedback

* fix cyclic import

* ihm.overlaps-seq-id-range

* Add ihm-restraints example
2025-02-18 08:18:18 +01:00
Alexander Rose
9bc7e27243 Merge pull request #1419 from molstar/slice-rotate
Add support for rotating `slice` representation around an axis & structure plane representation
2025-02-17 18:05:05 -08:00
Alexander Rose
a5111356c1 Add transform property to clip objects 2025-02-17 17:59:14 -08:00
Alexander Rose
9b11f7ffde use defaultColor for NaN values 2025-02-16 13:30:11 -08:00
Alexander Rose
93ce6d2807 Merge branch 'master' of https://github.com/molstar/molstar into slice-rotate 2025-02-16 13:27:56 -08:00
Alexander Rose
5c9d5d3a3d fix Vec3 prop check in setUpdateState 2025-02-16 13:27:09 -08:00
David Sehnal
f40307db39 Remove static uses of ColorTheme and SizeTheme fields (#1442) 2025-02-16 18:17:01 +01:00
Alexander Rose
4e6000fa6c Merge branch 'master' of https://github.com/molstar/molstar into slice-rotate 2025-02-15 16:59:16 -08:00
Alexander Rose
26e5817bf2 slice & plane-image improvements
- support trimming & rotation for boxes with non 90 deg angles
- cleanup & document params
2025-02-15 16:57:35 -08:00
Alexander Rose
8469be80d0 fix uniform slice coloring out of bounds 2025-02-15 16:40:05 -08:00
David Sehnal
029edc95c8 MVS: Additional Primitives (#1437)
* ellipsis

* box

* improve ellipsis

* improve ellipsis

* arrow primitive

* tweak arrow

* ellipsoid

* changelog

* pr feedback
2025-02-12 16:33:06 +01:00
David Sehnal
dd9aaf055f Components example (#1425)
* components example, initial code

* refactoring

* readme

* usage example

* changelog

* tweak
2025-02-11 18:08:15 +01:00
Alexander Rose
fcfb2d940c Merge branch 'master' of https://github.com/molstar/molstar into slice-rotate 2025-02-09 14:06:29 -08:00
Alexander Rose
f5b5109d0f Improve logic when to cull in renderer 2025-02-01 19:13:59 -08:00
Sebastian Bittrich
ca99c800f1 fix PDBj structure data URL (#1430) 2025-01-31 08:13:52 +01:00
Alexander Rose
dbd5570370 4.11.0 2025-01-26 11:10:06 -08:00
Alexander Rose
3f805c7a82 changelog 2025-01-26 11:07:06 -08:00
Alexander Rose
12c71dc5ba Handle Firefox's limit on vertex ids per draw 2025-01-25 16:04:34 -08:00
Alexander Rose
2fe3a926aa package updates 2025-01-25 15:59:44 -08:00
Alexander Rose
60c2096575 scss tweak 2025-01-25 15:53:52 -08:00
Alexander Rose
f4e9df5e4d schema updates 2025-01-25 15:26:06 -08:00
Alexander Rose
c304b82772 changelog 2025-01-25 15:18:39 -08:00
Alexander Rose
9edd171350 Merge pull request #1421 from molstar/volume-dot
add volume dot representation
2025-01-25 15:16:36 -08:00
Alexander Rose
f7d1bd7c04 PR tweaks 2025-01-25 14:27:59 -08:00
Alexander Rose
7422c255ab Merge branch 'master' of https://github.com/molstar/molstar into volume-dot 2025-01-25 14:26:44 -08:00
Alexander Rose
5497215784 Merge pull request #1423 from giagitom/fix-cartoon-tubular-helices
Fix tubular helices issue
2025-01-25 14:25:40 -08:00
giagitom
577bf1c77c Fix tubular helices issue 2025-01-22 16:27:40 +01:00
David Sehnal
c9bddccaf7 MVS: Initial support for customizable representation parameters (#1417)
* MVS: Initial support for customizable representation parameters

* pr feedback
2025-01-21 13:53:24 +01:00
midlik
ac292f9267 Sequence panel focus marker (#1392)
* Sequence view show focused residues

* wip

* Factor out MarkerColors

* Marker array supports "focus" marker type

* Sequence panel - separate focus marker from highlight and select

* Revert changes in mol-util/marker-actions.ts

* Simplify sequence.tsx

* Sequence panel markers follow renderer highlight colors

* Sequence panel focused range in bold

* Focus add with Ctrl, extend with Shift

* Update CHANGELOG
2025-01-21 13:15:06 +01:00
Alexander Rose
f0b8d75b10 add volume dot representation
- add volume-value size theme
2025-01-19 17:47:21 -08:00
Alexander Rose
0dacbcb3bc fix vertex based themes for spheres shader 2025-01-19 17:45:52 -08:00
Alexander Rose
0789241ea3 Add plane structure representation
- Can be colored with any structure theme
- Can be colored with the `external-volume` theme
- Can show atoms as a cutout
- Supports principal axes and bounding box as a reference frame
2025-01-18 19:36:49 -08:00
Alexander Rose
ddb0799dc4 Merge branch 'master' of https://github.com/molstar/molstar into slice-rotate 2025-01-18 17:30:36 -08:00
Alexander Rose
cbfa341fa3 Merge pull request #1411 from midlik/quick-styles
Quick styles
2025-01-18 16:57:03 -08:00
Alexander Rose
1c19bd90df Merge branch 'master' into quick-styles 2025-01-18 16:56:56 -08:00
Alexander Rose
300e5c8985 Merge pull request #1407 from sbittrich/master
Rebrand PDB-Dev as PDB-IHM
2025-01-18 16:55:24 -08:00
Alexander Rose
0861a78db6 Merge branch 'master' into master 2025-01-18 16:55:08 -08:00
Alexander Rose
a8e403ad85 Add support for rotating slice representation around an axis
- Add default color support for palette based themes
- Add support for trimming `image` geometry to a box
- Improve/fix iso-level support of `slice` representation
2025-01-18 16:45:22 -08:00
Ventura Rivera
4e350496b2 Sequence Viewer Mode Customization (#1412)
* adding flags for viewport selection controls

* adding logic to remove/keep selection controls

* adding viewport selection control feature

* package updates

* moving selectionTool options out of viewport

* updating selection controls with new properties

* updating property name to match nomenclature

* adding option for custom selection controls

* adding custom selection controls

* minor property name update

* adding property for granularity options

* reassigning StructureSelectionParams.granularity.options if custom granularity options

* adding entry for custom granularity options

* reusing Loci.Granularity to specify valid strings for granularityOptions

* Update src/mol-plugin-ui/spec.ts

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>

* merging molstar/molstar:main into ventura-rivera/molstar:main

* moving granularityOption logic into componentDidMount

* moving granularityOption logic to componentDidMount and creating structureSelectionParams state

* adding modeOptions and defaultMode properties to sequenceViewer

* adding logic to customize mode controls

* CHANGELOG

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2025-01-18 10:59:07 +01:00
Alexander Rose
dd21ddcc80 math helpers 2025-01-17 22:47:42 -08:00
Alexander Rose
a88121f779 add Quat uniform mapping to vec4 2025-01-17 22:47:27 -08:00
Adam Midlik
1ab91d1979 Quick Styles: stateless UI 2025-01-17 11:17:14 +00:00
Adam Midlik
267788388d Quick Styles: UI subsection headers 2025-01-17 10:11:52 +00:00
Adam Midlik
43c0333be3 Quick Styles: postpone UI update while animation running 2025-01-14 10:33:25 +00:00
JonStargaryen
3b90a269b0 rebranding from PDB-Dev to PDB-IHM 2025-01-13 11:21:00 -08:00
Ventura Rivera
4aa5e1d7fc Structure Selection Granularity Options Customization (#1410)
* adding flags for viewport selection controls

* adding logic to remove/keep selection controls

* adding viewport selection control feature

* package updates

* moving selectionTool options out of viewport

* updating selection controls with new properties

* updating property name to match nomenclature

* adding option for custom selection controls

* adding custom selection controls

* minor property name update

* adding property for granularity options

* reassigning StructureSelectionParams.granularity.options if custom granularity options

* adding entry for custom granularity options

* reusing Loci.Granularity to specify valid strings for granularityOptions

* Update src/mol-plugin-ui/spec.ts

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>

* merging molstar/molstar:main into ventura-rivera/molstar:main

* moving granularityOption logic into componentDidMount

* moving granularityOption logic to componentDidMount and creating structureSelectionParams state

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2025-01-13 16:39:25 +01:00
Adam Midlik
679db48938 Merge branch 'master' into quick-styles 2025-01-13 15:09:42 +00:00
Adam Midlik
2f96b42df7 Updated CHANGELOG 2025-01-13 15:08:12 +00:00
Adam Midlik
dd6f3bd76e Increase scrollbar visibility 2025-01-13 14:41:38 +00:00
Adam Midlik
f1b7e478c7 Remove unused stuff 2025-01-13 13:32:02 +00:00
Adam Midlik
416442aa27 entity-id color theme overrideWater param 2025-01-13 11:59:36 +00:00
Alexander Rose
a5f65b6e6f use value for uniform coloring of slices
- recovers previous behavior
2025-01-12 18:10:14 -08:00
Alexander Rose
938ac0cc8f fix image rendering issues with marking
- handle pixels without a group
- take fog into account
2025-01-11 14:47:06 -08:00
Alexander Rose
7dacf60478 plane mesh helpers 2025-01-11 14:40:16 -08:00
Alexander Rose
9cdb8a3a92 Merge pull request #1399 from molstar/improve-volume-coloring
improve volume coloring
2025-01-11 14:35:17 -08:00
Alexander Rose
242982e661 Merge branch 'master' into improve-volume-coloring 2025-01-11 14:34:58 -08:00
Alexander Rose
6da20a6989 Fix Plane3D.projectPoint 2025-01-11 09:21:33 -08:00
Alexander Rose
f27b651230 tweak spec.components.selectionTools.hide names 2025-01-11 09:17:30 -08:00
Alexander Rose
7c818c0cc9 add location related comments 2025-01-11 08:58:59 -08:00
Adam Midlik
e7d7ba26b0 Quick Styles: Add cartoon 2025-01-10 16:16:36 +00:00
Adam Midlik
7e64121059 Quick styles: two-row UI 2025-01-10 15:59:20 +00:00
Adam Midlik
894bba1d3a Quick styles: Stylized independent from preset 2025-01-10 11:14:23 +00:00
Adam Midlik
d9db775fe8 Toggle button different hover color when on and off 2025-01-10 11:07:13 +00:00
Ventura Rivera
a7fbc7b4c4 Remove viewport selection controls (#1408)
* adding flags for viewport selection controls

* adding logic to remove/keep selection controls

* adding viewport selection control feature

* package updates

* moving selectionTool options out of viewport

* updating selection controls with new properties

* updating property name to match nomenclature

* adding option for custom selection controls

* adding custom selection controls

* minor property name update
2025-01-10 11:06:14 +01:00
Alexander Rose
c0596298d6 Add support for position-location to volume-value color theme 2025-01-04 09:26:30 -08:00
Alexander Rose
8f32dde599 review tweaks 2025-01-04 09:25:01 -08:00
Alexander Rose
4d8f00900d improve volume coloring
- Add `volume-data` theme that colors positions by volume data
- Add support for color themes to `slice` representation
- Improve/fix palette support in volume color themes
2025-01-03 17:07:13 -08:00
Alexander Rose
9f3c617945 Merge pull request #1397 from molstar/color-external-structure
add external-structure theme
2025-01-02 14:45:50 -08:00
Alexander Rose
f920188cdc Merge pull request #1398 from ventura-rivera/master
fix hyperlink typo
2025-01-02 14:45:33 -08:00
Ventura Rivera
68b73503bb update changelog with doc updates 2025-01-02 12:59:35 -08:00
Ventura Rivera
e776138ecd fix hyperlink typo 2025-01-02 12:55:42 -08:00
Alexander Rose
bbacd5a9dd Merge branch 'master' into color-external-structure 2024-12-31 13:53:04 -08:00
Alexander Rose
289ecef1d7 add approxNearest to lookup3d 2024-12-31 13:31:26 -08:00
Alexander Rose
52fc3ef750 Merge pull request #1396 from molstar/float-volume-data
Support float and half-float data type
2024-12-29 18:41:53 -08:00
Alexander Rose
dffe40ac1d simplify isosurface property handling 2024-12-29 17:54:23 -08:00
Alexander Rose
f834e39ce4 add external-structure theme
- colors any geometry by structure properties
2024-12-28 17:38:49 -08:00
Alexander Rose
2bc9c6fb57 Support float and half-float data type
- direct-volume rendering
- GPU isosurface extraction
2024-12-28 14:29:17 -08:00
Alexander Rose
6e42c11f5e comments regarding webgl1 consistent bit plane counts 2024-12-28 13:08:05 -08:00
midlik
d48feeaa94 Mvs union params (#1388)
* MVS: define union params

* MVS: include defaults in param schema

* MVS: removed mvs-defaults

* MVS: union params validation

* MVS: use new param schema

* MVS: Nicely format ColorName

* MVS: primitive uses UnionParamsSchema

* MVS: mesh remove triangle_groups

* MVS: remove dead code

* MVS: reorg files and add docs for parameter system

* MVS: print-schema for union params

* MVS: remove line_colors

* MVS: Rename many primitives params

* MVS: update primitive params descriptions

* MVS: Refactor primitive Builders

* Volumes and segmentations: avoid parsing non-success response

* MVS: refactor primitive params types

* MVS: avoid repeating MVS defaults in primitives.ts

* MVS: update builder

* MVS: primitive params docstrings
2024-12-18 18:54:39 +01:00
David Sehnal
fd0ca75fc1 Volume UI improvements (#1379)
* improvements to Volumes UI

* support wheel scroll on sliders

* headers

* changelog
2024-12-17 17:55:06 +01:00
Alexander Rose
a270dcb5f5 error handling in deploy script 2024-12-15 13:49:54 -08:00
Alexander Rose
917de1175c 4.10.0 2024-12-15 10:05:40 -08:00
Alexander Rose
65945fb904 changelog 2024-12-15 10:01:58 -08:00
Alexander Rose
3b7afc6037 package updates 2024-12-15 10:01:15 -08:00
Alexander Rose
05d9ca6e68 Merge pull request #1386 from molstar/fix-handle-resize
wip, fix handle resize
2024-12-15 09:52:09 -08:00
Alexander Rose
12ee0e0f38 Fix resize handling in tests/browser 2024-12-15 09:42:03 -08:00
Alexander Rose
dbd29e749e Merge branch 'master' of https://github.com/molstar/molstar into fix-handle-resize 2024-12-15 08:52:57 -08:00
Alexander Rose
a8085111dc add support for more webgl extensions
- EXT_render_snorm
- WEBGL_render_shared_exponent
- EXT_texture_norm16
- EXT_depth_clamp
2024-12-14 11:59:18 -08:00
Alexander Rose
93798554ac Use adjoint matrix to transform normals in shaders 2024-12-14 11:55:47 -08:00
Alexander Rose
ce07c52d9f Fix addIndexPairBonds quadratic runtime case 2024-12-14 11:53:07 -08:00
Alexander Rose
fb7a247f6c Fix units transform data not fully updated when structure child changes 2024-12-14 11:51:10 -08:00
Alexander Rose
e9dfe6322d changelog 2024-12-14 11:50:27 -08:00
Alexander Rose
079187326a wip, fix handle resize 2024-12-12 21:58:15 -08:00
midlik
4dc9d037a4 MolViewSpec animations (#1348)
* MVS: Interfaces for multistate specs (wip)

* MVS: Multistate loading PoC

* MVS: Multistate loading PoC with camera

* Focus node in MOLJ: dirty impl

* PluginCommands.Camera.FocusObject

* Minor changes

* Refactoring for FocusObject

* Refactor getFocusBoundingSphere

* Move stuff to src/mol-plugin-state/manager/focus-camera/focus-object.ts

* MVS: molstarTreeToEntry include focus

* MVS: multi-state with focus

* MVS: multi-state with implicit camera

* MVS: multi-state works with video rendering

* Fix is_iOS() for NodeJS

* MVS: multi-state with canvas node

* Added PluginStateSnapshotManager.EntryParams.descriptionFormat

* MVS: mvs-render can generate MP4

* MVS: Remove dead code

* Update CHANGELOG

* MVS: Rename linger_duration_ms, transition_duration_ms

* MVS: focus node, radius* params

* PluginState.Snapshot.camera.focus allow multiple targets

* MVS: support multiple focus nodes

* MVS: Rename param radius_extend -> radius_extent

* MVS: support focus node on root

* MVS: Synchonize metadata format with backend

* MVS: change "transparency" to "opacity"

* Updated CHANGELOG
2024-12-12 13:06:23 +01:00
Alexander Rose
f36ad9ac28 Merge pull request #1380 from molstar/fix-gap-marking-consecutive
fix marking of consecutive gap elements
2024-12-09 19:21:34 -08:00
Alexander Rose
6d392de628 Merge branch 'master' into fix-gap-marking-consecutive 2024-12-09 19:21:03 -08:00
Alexander Rose
d7cd957b42 fix missing deflate header if CompressionStream is available 2024-12-09 19:15:13 -08:00
Alexander Rose
de36612bf1 changelog 2024-12-09 19:13:45 -08:00
Yakov Pechersky
d5154bcff2 Support React 19 (#1382)
By loosening `peerDependencies`
2024-12-09 15:51:34 +01:00
Alexander Rose
b44a6fa660 fix marking of consecutive gap elements 2024-12-08 16:33:58 -08:00
David Sehnal
5cc28c9471 Add ModelWithCoordinates transform (#1378)
* Add ModelWithCoordinates transform

* PR feedback
2024-12-08 18:21:13 +01:00
Alexander Rose
b42a6d4636 Merge pull request #1373 from giagitom/illumination-outlines-fix
Fix outlines on transparent background using illuminartion mode
2024-12-07 18:10:49 -08:00
Alexander Rose
efd405f44b Merge branch 'master' into illumination-outlines-fix 2024-12-07 18:10:14 -08:00
Alexander Rose
4b3932e9e2 4.9.1 2024-12-05 08:55:57 -08:00
Alexander Rose
dcb8eca29a changelog 2024-12-05 08:53:54 -08:00
midlik
ac0177aef5 Fix iOS check (#1376) 2024-12-05 10:37:15 +01:00
giagitom
316013aafd Fix transparent depth artifacts using illumination mode 2024-12-03 16:05:57 +01:00
giagitom
040d83e8d4 Fix outlines on transparent background using illuminartion mode 2024-12-03 15:26:36 +01:00
Alexander Rose
b31ed50b3a 4.9.0 2024-12-01 13:38:17 -08:00
Alexander Rose
2a9c4db97f lint 2024-12-01 13:34:35 -08:00
Alexander Rose
fbeda779ac changelog 2024-12-01 13:32:37 -08:00
Alexander Rose
89e60cfde9 Merge pull request #1372 from molstar/immutablejs5
update to immutable-js 5
2024-12-01 13:27:26 -08:00
Dominik Tichy
0845f5fd75 Fix: missing partial charges (#1368)
* fix: color missing charges green

* fix: save missing charges as undefined

* chore: updated changelog

* fix: corrected check of missing values

* fix: lint

* fix: wrong logic

* fix: removed unused functions

* fix: unecessary assignment of undefined
2024-12-01 19:38:47 +01:00
dsehnal
918b67482f update to immutable-js 5 2024-12-01 15:41:20 +01:00
Alexander Rose
3ff3ea2912 schema updates 2024-11-30 15:37:39 -08:00
Alexander Rose
b2e1d069ba add missing bytes member to File_NodeJs 2024-11-30 15:37:32 -08:00
Alexander Rose
0a409c6fdf fix missing params assignment in shape repr 2024-11-30 15:37:02 -08:00
Alexander Rose
5ce552d2cc package updates 2024-11-30 15:36:30 -08:00
Alexander Rose
8bda510378 fix wrong repr params assignment 2024-11-28 21:09:41 -08:00
midlik
ad1923f57b Servers object storage (#1355)
* ModelServer: support for GS

* VolumeServer: simple support for GS

* fetch_GS abort

* file reorg

* ModelServer/VolumeServer: dynamic import @google-cloud/storage

* ModelServer/VolumeServer: update docs

* ModelServer/VolumeServer: handle HTTP and GS errors

* ModelServer/VolumeServer: update docs

* ModelServer/VolumeServer: update docs 2

* Minor tweaks
2024-11-28 12:06:02 +01:00
Sebastian Bittrich
ba38fe2474 Membrane Server to generate MolViewSpec data (#1338)
* membrane server wip

* cleanup

* desc

* cl
2024-11-25 19:12:40 +01:00
Alexander Rose
c53b651472 Merge pull request #1359 from molstar/snapshot-manager-fix-current
PluginStateSnapshotManager.syncCurrent fix
2024-11-23 10:29:37 -08:00
Alexander Rose
2eb4f77504 Merge branch 'master' into snapshot-manager-fix-current 2024-11-23 10:29:29 -08:00
Alexander Rose
c09f30a135 Merge pull request #1353 from giagitom/x-ray-fix
Xray shading fix for high XrayEdgeFalloff
2024-11-23 10:06:41 -08:00
Alexander Rose
c60c52f563 Merge pull request #1349 from papillot/infer-valence-from-protonated-ligand
Do not infer implicit Hs when explicit Hs are set #1257
2024-11-23 10:05:51 -08:00
dsehnal
7e67678dcd PluginStateSnapshotManager.syncCurrent fix 2024-11-23 08:55:48 +01:00
giagitom
4ee33c9dcd Use clamp 2024-11-20 18:51:19 +01:00
giagitom
8a0d5eb366 Xray shading fix for high XrayEdgeFalloff 2024-11-20 15:56:22 +01:00
Paul Pillot
e18a3b452a Do not infer implicitH when explicitH are set
Instead of detecting the protonation at the level of the atom only, the protonation of the unit is detected first. If protonation is known, then the assignment of implicit hydrogens is not needed.
This avoids cases where a nitrogen on a protonated ligand gets assigned a positive charge when it has no hydrogens, because the code was assuming that protonation was unknown in that case.
2024-11-18 17:24:28 -05:00
Alexander Rose
38a508fd87 Merge pull request #1328 from sbittrich/master
Membrane orientation: improve `isApplicable` check and error handling
2024-11-16 19:26:45 -08:00
Alexander Rose
0b1fd14e09 Merge pull request #1337 from giagitom/inprove-tubular-helices
Inprove tubular helices
2024-11-16 19:22:23 -08:00
Alexander Rose
b883ddd10e Fix transform data not updated when structure child changes 2024-11-16 12:47:30 -08:00
Simeon Borko
30557d13ca Refactor value swapping in molstar-math to fix SWC (Next.js) build (#1345) (#1346)
Co-authored-by: Simeon Borko <simeon.borko@recetox.muni.cz>
2024-11-16 08:08:28 +01:00
David Sehnal
85b72ae3b0 StructConn.isExhaustive fix (#1342) 2024-11-13 18:30:40 +01:00
JonStargaryen
2ed165f9a5 extend check to all models 2024-11-11 08:39:56 -08:00
giagitom
8c5388a6ea Cleanup 2024-11-11 17:25:39 +01:00
giagitom
703ef6c273 Enable double rounded cap on tubular helices 2024-11-11 17:16:17 +01:00
giagitom
0a1c5537d2 fix trace iterator for single residue tubular helices 2024-11-11 17:14:56 +01:00
Alexander Rose
e65f5b270e Merge pull request #1331 from giagitom/postprocessing-update-fix
Fix outlines on volume and surface reps that do not disappearing
2024-11-10 09:46:48 -08:00
Alexander Rose
9185c4592f Merge pull request #1329 from sbittrich/express
Update to express v5
2024-11-10 09:44:39 -08:00
Alexander Rose
fbe44bfab7 Merge branch 'master' into express 2024-11-10 09:44:07 -08:00
Alexander Rose
f4d44621d6 improve inter unit bond performance
- use numbers instead of strings in maps
- bespoke `eachStructureGroupsBond` to iterate
2024-11-10 09:35:44 -08:00
Alexander Rose
05a87fded9 Fix bonds not shown with ignoreHydrogens on (#1315)
- Better handle mmCIF files with no entities defined by using `label_asym_id`
- Show bonds in water chains when `ignoreHydorgensVariant` is `non-polar`
2024-11-10 09:33:25 -08:00
Alexander Rose
195f7284b5 fix transparent SSAO for image rendering 2024-11-10 09:27:43 -08:00
Alexander Rose
c4a900e2ea fix occupancy check using wrong index for inter-unit bond computation 2024-11-10 09:26:03 -08:00
giagitom
e1eb686355 Fix outlines on volume and surface reps that do not disappearing 2024-11-07 14:51:15 +01:00
Sebastian Bittrich
54b4a01cc3 update to express v5 (#1311) 2024-11-05 15:42:06 -08:00
Sebastian Bittrich
f68a01183d consider coarse-grained models 2024-11-05 10:24:08 -08:00
Sebastian Bittrich
057d605135 merge 2024-11-05 10:07:42 -08:00
Sebastian Bittrich
a391bbf786 cleanup 2024-11-05 10:04:54 -08:00
Sebastian Bittrich
fdc1054060 membrane orientation: improve isApplicable check and error handling (closes #1316) 2024-11-05 10:04:30 -08:00
Alexander Rose
b4238f574a fix incudeParent handling for structure-based visuals 2024-11-04 21:10:10 -08:00
Alexander Rose
965c6a37a9 Merge pull request #1325 from molstar/structure-visuals
Structure visuals
2024-11-03 21:27:16 -08:00
Alexander Rose
35a9056368 Add more structure-based visuals
-  to avoid too many (small) render-objects
- `structure-intra-bond`, `structure-ellipsoid-mesh`, `structure-element-point`, `structure-element-cross`
2024-11-02 17:39:04 -07:00
Alexander Rose
fd96973e82 Add Structure.intraUnitBondMapping 2024-11-02 17:38:22 -07:00
Alexander Rose
8812b0d264 Merge pull request #1320 from bergwerf/delete_fence_sync
Set fenceSync to null after deleteSync.
2024-11-02 11:13:55 -07:00
Alexander Rose
597c0dbbe1 Merge branch 'master' into delete_fence_sync 2024-11-02 11:11:24 -07:00
Alexander Rose
768d7a2a4d make ViewportScreenshotHelper.download return a promise 2024-11-02 11:10:29 -07:00
Alexander Rose
30ec53ffa4 Fix operator key-based IndexPairBonds assignment
- Don't add bonds twice
- Add `IndexPairs.bySameOperator` to avoid looping over all bonds for each unit
2024-11-02 11:09:16 -07:00
Dirk Arnez
b79ffd9cfc - fixed linting issues at glb-export example (#1322) 2024-11-01 14:11:41 +01:00
Herman Bergwerf
cc7f88fd53 Set fenceSync to null after deleteSync. 2024-10-31 11:27:53 +01:00
Dirk Arnez
57c84d0159 - add glb export example (#1314)
* - add glb export example

* Update src/examples/glb-export/index.ts

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2024-10-30 08:42:36 +01:00
Alexander Rose
4daf409337 4.8.0 2024-10-27 14:42:49 -07:00
Alexander Rose
a17e886ab9 changelog 2024-10-27 14:23:12 -07:00
Alexander Rose
ebb9046184 webpack: no fallback for vm package 2024-10-27 14:21:24 -07:00
Alexander Rose
cb41c0c7f9 package updates 2024-10-27 14:09:26 -07:00
Alexander Rose
bdbc9eab64 schema updates 2024-10-27 14:09:15 -07:00
Alexander Rose
5f76620ef5 Merge pull request #1308 from molstar/operator-bonds
Improve performance of `IndexPairBonds` assignment
2024-10-27 13:58:55 -07:00
Alexander Rose
b5c1c4d32e Merge branch 'master' into operator-bonds 2024-10-27 13:58:12 -07:00
Alexander Rose
6e4777355a Merge pull request #1309 from molstar/simplify-text-frag
remove extra anti-aliasing from text shader
2024-10-27 13:57:37 -07:00
David Sehnal
7526535a8b Basic support for Predicted Aligned Error parsing and plotting (#1302)
* proof of concept

* typos

* plot interactivity

* centerline

* better axis labels, darkmode support

* plot label

* data source selection

* interactivity

* better plot labels

* make pae overpaint ghosts

* config option

* update labels

* standalone mode and AFDB example

* fix interactivity

* pr feedback

* changelog
2024-10-27 07:36:22 +01:00
Alexander Rose
2bd84b7e7c remove extra anti-aliasing from text shader 2024-10-26 22:15:19 -07:00
Alexander Rose
84e292b3e2 Merge pull request #1293 from corredD/lammps_data
Lammps data
2024-10-26 17:34:30 -07:00
Alexander Rose
152cef9c5b tweaks & cleanup 2024-10-26 17:23:36 -07:00
Alexander Rose
d76bc583c0 Merge branch 'master' of https://github.com/molstar/molstar into pr/corredD/1293 2024-10-26 17:03:55 -07:00
Alexander Rose
071fb21dd0 Merge pull request #1252 from giagitom/transparent-ssao
Transparent ssao
2024-10-26 14:25:14 -07:00
Alexander Rose
db8943bcfb tweaks & cleanup 2024-10-26 14:16:41 -07:00
Alexander Rose
2d1ce14f2e fix marking not applied in illumination pass 2024-10-26 13:54:31 -07:00
Alexander Rose
33760b0d37 Improve performance of IndexPairBonds assignment when operator keys are available 2024-10-26 13:49:09 -07:00
giagitom
1aa6f30780 Optimize outlines 2024-10-23 13:01:04 +02:00
Ludovic Autin
86c8dd5d74 Merge commit 'c37a7ebf791afb8aa60ee8170d28a2bf156e7609' into lammps_data 2024-10-22 23:26:28 -07:00
Alexander Rose
1435a5e6e6 fix calling renderBlendedTransparent twice 2024-10-22 22:26:22 -07:00
Alexander Rose
c123e55a8d Merge branch 'master' of https://github.com/molstar/molstar into pr/giagitom/1252 2024-10-22 22:19:01 -07:00
Alexander Rose
c37a7ebf79 remove unused file 2024-10-22 22:17:25 -07:00
Alexander Rose
00e228a834 fix bloom in illumination mode 2024-10-22 22:17:16 -07:00
Ludovic Autin
55f40738f2 revert registry order. 2024-10-22 21:31:25 -07:00
Ludovic Autin
4ffd69750f Merge commit '12add4d66b93438eb53e6406811b8daea83bc907' into lammps_data 2024-10-22 21:30:03 -07:00
David Sehnal
295608baae fix Safari sequence view (#1305) 2024-10-22 20:09:15 +02:00
giagitom
4429b7185f assign material color fix 2024-10-22 19:30:30 +02:00
giagitom
84fadc2e5c Rename internal properties 2024-10-22 19:20:36 +02:00
giagitom
0b3bd885ca Fixes and removing includeTransparent property 2024-10-22 19:13:21 +02:00
giagitom
51d9eda168 Outlines fixes 2024-10-21 18:27:51 +02:00
giagitom
abe10d5c7c changed name and fixed behavior of transparentAlphaThreshold 2024-10-21 15:35:26 +02:00
giagitom
e7da2333fe Add transparentAlphaThreshold to disable transparent ssao on low transparency scenes (disabled on viewport screenshot) 2024-10-21 14:55:19 +02:00
Alexander Rose
3899a95c97 add scene.transparencyMin 2024-10-21 12:12:38 +02:00
Alexander Rose
12add4d66b me: add support for 4-character PDB ID in pdb-dev loader 2024-10-20 22:34:24 -07:00
Ludovic Autin
e16c073639 renaming 2024-10-19 14:32:04 -07:00
Ludovic Autin
3c5dc56bb2 follow @arose recommandation. 2024-10-19 14:18:52 -07:00
Alexander Rose
ad2106e6f6 use moleculeId as asym_id 2024-10-18 23:10:11 -07:00
Alexander Rose
dd5aa061b8 moleculeType -> moleculeId 2024-10-18 23:09:38 -07:00
Alexander Rose
f69ad14296 add scaling factor where it made sense 2024-10-18 23:08:48 -07:00
Alexander Rose
277254b78e fix atom style type error 2024-10-18 23:08:24 -07:00
ludovic autin
3c4f2806e7 David's recommandation 2024-10-18 16:44:51 -07:00
ludovic autin
79612833d4 Merge remote-tracking branch 'upstream/master' into lammps_data 2024-10-18 16:44:02 -07:00
midlik
b4772e0cb9 Fix binarySearchPredIndexRange (#1264)
* Fix binarySearchPredIndexRange

* Tests

* binarySearchPredIndexRange - avoid param reassignment

* Update CHANGELOG
2024-10-18 16:12:27 +02:00
David Sehnal
003c5a9437 MVS: Primitives MVP (#1300)
* inline primitives (mesh, line)

* tweak

* resolved positions

* wip labels

* tooltips

* refactor primitives

* primitives from URI

* primitives focus support todo

* focusable render objects

* default label color

* move code

* label primitive

* default shape provider params

* refactoring

* support primitive instancing

* tweak

* mvs refs support

* changelog

* header

* streamline primitives from uri

* group manager and pass node data to shape

* better position typing and resolution

* imports

* fix bug

* lint

* support mesh wireframe

* lines primitive
2024-10-18 15:00:57 +02:00
ludovic autin
ff9fb450fa added unitStyle information, reorganize the io parser. Fix spelling 2024-10-17 12:25:36 -07:00
ludovic autin
136e996e4f Merge remote-tracking branch 'upstream/master' into lammps_data 2024-10-17 12:24:11 -07:00
Alexander Rose
a93b53c413 fix for "cleanup illumination pass" 2024-10-16 22:28:27 -07:00
ludovic autin
0f25421db1 selection helper/shortcut to handle atom types ( coveres regular atom types C,N,O etc.. and custom atom type like in lammps eg . 1-200 etc...) 2024-10-16 15:37:19 -07:00
ludovic autin
cde3a73bba Merge remote-tracking branch 'upstream/master' into lammps_data 2024-10-16 15:34:10 -07:00
midlik
c19130c9eb MVS transparency and additional properties (#1289)
* MVS: Transparency support

* MVS: Data model supporting additional properties

* MVS: Builder supporting additional properties

* MVS: Loading extensions, NonCovalentInteractionsExtension

* MVS: Refactor, update CHANGELOG

* MVS: Rename additional_properties -> custom

* MVS: Add "ref" to nodes

* MVS: Fix missing dealing with ref

* MVS: Builder supporting custom and ref

* MVS: minor refactor

* StateBuilder support adding tags

* MVS: Add node ref as tag

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2024-10-16 18:17:25 +02:00
giagitom
54c8801951 - Disable transparent ssao if nothing transparent to render
- Fix transparent color texture not cleared when scene.opacityAverage = 1
2024-10-15 16:22:25 +02:00
giagitom
8371a3e349 Merge remote-tracking branch 'upstream/master' into transparent-ssao 2024-10-15 14:55:20 +02:00
Alexander Rose
cca289728c add sample/blur timer mark to ssao pass 2024-10-14 23:04:59 -07:00
Alexander Rose
d9a44daa5d cleanup illumination pass
- reuse buffers from draw pass
- remove superfluous anti aliasing render
2024-10-14 22:50:45 -07:00
Alexander Rose
48ee9ef8cb fix Scene.opacityAverage calculation never 1 2024-10-14 22:46:47 -07:00
ludovic autin
ba84081888 Merge remote-tracking branch 'upstream/master' into lammps_data 2024-10-14 11:21:14 -07:00
ludovic autin
45d8059ed2 removed scale options. Fix transformation when coordinates are scaled. 2024-10-14 11:20:28 -07:00
giagitom
6e2d8653ec Fix transparent outline size 2024-10-14 17:01:30 +02:00
Alexander Rose
cca6210076 rename includeTransparency -> includeTransparent
- to be same as in outline pass
2024-10-13 22:37:28 -07:00
Alexander Rose
9f926757b2 relax ssao blur background check 2024-10-13 22:32:00 -07:00
Alexander Rose
87d83d8f9e more ssao timers 2024-10-13 22:31:15 -07:00
Alexander Rose
d16076b170 cleanup 2024-10-13 22:30:38 -07:00
Alexander Rose
cccdc53fd0 merge master 2024-10-13 22:26:56 -07:00
Alexander Rose
a312799361 Merge pull request #1297 from molstar/fix-backfaces-visible
fix backfaces visible using blended transparency on impostors
2024-10-13 13:46:39 -07:00
Alexander Rose
60c81e79ba illumination ssao fixes & cleanup 2024-10-12 09:34:32 -07:00
Alexander Rose
bd22db4252 fix backfaces visible using blended transparency on impostors 2024-10-12 09:26:15 -07:00
Alexander Rose
36b5a9e181 only request draw after interaction in illumination mode 2024-10-12 09:08:00 -07:00
ludovic autin
809cca5261 lammps can dump normalize coordinates (sx or usx), to be able to handle it, parse the bounding box, and apply the transformation in that case 2024-10-11 14:53:43 -07:00
ludovic autin
7a81ea3ba1 missing arguments 2024-10-10 13:25:41 -07:00
ludovic autin
afa51b4416 lammps is a multiscale simulation engine, coordinate scale option help bringing everything at the properscale in the viewport. I put the option in the OpenFiles state. 2024-10-10 13:21:02 -07:00
ludovic autin
95792dd3c8 Merge remote-tracking branch 'upstream/master' into lammps_data 2024-10-10 13:09:00 -07:00
Paul Pillot
e2bc15ac6b Make Loci.isSubset() strict on units comparisons (#1294)
Loci.isSubest could return true for a Loci that is a superset of the reference Loci. This was happening when the second Loci contains units that are not matched in the reference Loci. See #1292
2024-10-10 12:20:41 +02:00
ludovic autin
4e565808c6 more flexible parsing, can handle different atom_style. 2024-10-09 17:21:47 -07:00
ludovic autin
b2e2b46280 Merge remote-tracking branch 'upstream/master' into lammps_data 2024-10-08 11:52:11 -07:00
ludovic autin
462e675237 trailing space 2024-10-08 11:51:35 -07:00
ludovic autin
6e77b4ce71 added my name 2024-10-08 11:29:13 -07:00
ludovic autin
e8bd67c069 lammpstrj string format no boundary coordinates 2024-10-08 11:21:43 -07:00
Yakov Pechersky
fe502539f9 Remove remaining deprecated SASS lighten call (#1291) 2024-10-08 14:38:23 +02:00
ludovic autin
fe5afa8935 rename to lammps_data, so we can have a lammps_traj parser 2024-10-07 17:02:49 -07:00
ludovic autin
20452e762b renaming 2024-10-07 16:45:33 -07:00
ludovic autin
bc2d19338b naive lammps data parser. Lammps data are initial conformation before simulation or are dumped from a trajectory. 2024-10-07 16:11:47 -07:00
giagitom
719e141dd9 Include ssao transparent -> opaque interactions on ssgi 2024-10-07 19:38:34 +02:00
giagitom
5d9d01d251 Merge remote-tracking branch 'upstream/master' into transparent-ssao 2024-10-07 16:02:54 +02:00
Alexander Rose
39ad2f0719 changelog 2024-10-06 16:18:44 -07:00
Alexander Rose
4f06f724a4 Merge pull request #1290 from molstar/transparency-fixes
Transparency related fixes
2024-10-06 16:15:07 -07:00
Alexander Rose
d5a4b266dd wip, supporting transparent ssao with ssgi 2024-10-05 10:44:35 -07:00
Alexander Rose
e1d92a58be fix missing pre-multiplied alpha for blended & wboit with no fog 2024-10-05 10:21:30 -07:00
Alexander Rose
05ff705c25 fix direct-volume for dpoit with fog off and transparent background on 2024-10-05 10:20:40 -07:00
Alexander Rose
f1cfb29a03 renderer cleanup, remove duplicated/unused code 2024-10-05 10:08:44 -07:00
Alexander Rose
d2f354d949 only set depthTextureSupport when given 2024-10-05 10:04:28 -07:00
David Sehnal
481c6926e7 iOS transparency (#1288)
* iOS transparency

* headers

* changelog
2024-10-04 19:29:12 +02:00
giagitom
f15da87e13 Merge remote-tracking branch 'upstream/master' into transparent-ssao 2024-10-03 16:29:39 +02:00
Xavier M
c34aaf7c31 Add doc page to describe how to access component data (#1283) 2024-10-03 13:53:06 +02:00
Alexander Rose
fb6815bb7d 4.7.1 2024-09-30 20:49:18 -07:00
Alexander Rose
9c78dc76e1 changelog 2024-09-30 20:47:04 -07:00
Alexander Rose
62a0a40a49 Merge pull request #1281 from molstar/auto-res-mode-1
add resolutionMode.auto
2024-09-30 19:47:17 -07:00
Alexander Rose
8d61fa17c8 add resolution mode get param and config item 2024-09-30 19:41:47 -07:00
dsehnal
a460869d4a add resolutionMode.auto 2024-09-30 19:25:12 +02:00
Alexander Rose
a9e0d8236c 4.7.0 2024-09-29 11:12:30 -07:00
Alexander Rose
fc47276fc3 changelog 2024-09-29 11:08:07 -07:00
Huiyu CAI
c60334b97b allow nested components (#1227)
* allow nested components

* header and CHANGELOG edits

* fix header edit

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2024-09-29 20:05:41 +02:00
Alexander Rose
36d58d0ff0 Merge pull request #1273 from pechersky/sass-color
Move away from deprecated SASS explicit color by using `invert` and `change`
2024-09-29 10:30:40 -07:00
Alexander Rose
73529a890b update mesoscale-explorer style.scss 2024-09-29 10:25:44 -07:00
Yakov Pechersky
b9e88d61a1 master lock 2024-09-29 02:17:22 -04:00
Yakov Pechersky
04bfe71131 Merge branch 'master' into sass-color 2024-09-29 02:10:56 -04:00
Yakov Pechersky
b16c51825a clean up imports 2024-09-29 02:09:44 -04:00
Yakov Pechersky
12630dd9f5 fix lighten-darken -- different in light vs dark/blue 2024-09-29 02:05:43 -04:00
Yakov Pechersky
880b73a3c4 Revert "remove color-lower-contrast"
This reverts commit 12ff3aad93.
2024-09-29 02:00:09 -04:00
Yakov Pechersky
63e7ba57bc Revert "remove color-increase-contrast"
This reverts commit 745d8b80d7.
2024-09-29 01:59:20 -04:00
Alexander Rose
bc2d8a4ce1 schema updates 2024-09-28 19:44:51 -07:00
Alexander Rose
9f951dbeac package updates 2024-09-28 19:18:12 -07:00
Alexander Rose
cba1c23b4d Merge pull request #1277 from molstar/me-color-style-fixes
ME: color and style fixes
2024-09-28 18:39:16 -07:00
Alexander Rose
d63663a2ea changelog 2024-09-28 18:32:58 -07:00
Alexander Rose
41c5ebf1f3 fix entities of root group not updated 2024-09-28 18:31:10 -07:00
Alexander Rose
757cf0cd13 fix shinyDof 2024-09-28 16:49:49 -07:00
Alexander Rose
ad8d07cfaa me: only change style not color in quick-styles ui 2024-09-28 14:51:54 -07:00
giagitom
d9f7aafd72 remove unised variable 2024-09-28 15:37:52 +02:00
Alexander Rose
e0b307d1a8 Merge pull request #1276 from molstar/revert-1275-me_preset
Revert "fix preset coloring in ME"
2024-09-26 23:08:04 -07:00
Alexander Rose
729306f142 Revert "adding 'ent:' break the preset (#1275)"
This reverts commit 8568656d44.
2024-09-26 23:07:54 -07:00
giagitom
dc7f745dbe Improve postrpocessing frag and fix outlines blending issues 2024-09-26 18:52:58 +02:00
ludovic autin
8568656d44 adding 'ent:' break the preset (#1275) 2024-09-26 12:35:15 +02:00
giagitom
4dea8849be Fix dpoit blending issue when postprocessing is off 2024-09-26 11:21:39 +02:00
giagitom
a2056d31bf Improvements and transparency blend mode fix 2024-09-25 22:24:33 +02:00
giagitom
c14344d465 - Removing separatedTransparency option
- Removing includeOpacity option
2024-09-24 12:09:00 +02:00
Yakov Pechersky
b7ba8322d1 CHANGELOG 2024-09-23 15:02:57 -04:00
Yakov Pechersky
818a0dac0d npm update sass 2024-09-23 13:45:54 -04:00
Yakov Pechersky
3f96ba92ce reset lock 2024-09-23 13:45:30 -04:00
Yakov Pechersky
b356f217ab imports where necessary 2024-09-23 13:40:10 -04:00
Yakov Pechersky
a968fb0984 update sass dep 2024-09-23 13:24:13 -04:00
Yakov Pechersky
745d8b80d7 remove color-increase-contrast
use `color.adjust` instead
2024-09-23 13:20:50 -04:00
Yakov Pechersky
12ff3aad93 remove color-lower-contrast
use `color.adjust` instead
2024-09-23 13:20:02 -04:00
Alexander Rose
e8501b73a5 illumination params description 2024-09-23 09:51:50 -07:00
Yakov Pechersky
9c07da6de6 also set alpha using api 2024-09-23 12:35:53 -04:00
Yakov Pechersky
8c2e58b67c Move away from deprecate SASS explicit color by using invert
In building, saw
```
Deprecation Warning: The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0.

More info: https://sass-lang.com/d/legacy-js-api
```
Instead of using `color.channel(..., "red")`, we really were deconstructing rgb by channel just to invert.
So use the underlying SASS api directly.
2024-09-23 12:30:51 -04:00
giagitom
7242494123 Try to handle blended transparency 2024-09-23 12:49:19 +02:00
giagitom
adfea9f336 Merge branch 'master' of https://github.com/molstar/molstar into transparent-ssao 2024-09-23 11:12:02 +02:00
Alexander Rose
80d7649dbb illumination params description 2024-09-22 19:29:43 -07:00
David Sehnal
6e63bb4283 ErrorContext util & usage in MVS (#1254)
* error context util & usage in MVS

* changelog
2024-09-22 10:12:00 +02:00
Alexander Rose
ba7a4137fe changelog 2024-09-21 14:56:41 -07:00
Alexander Rose
2ca0a4291b Merge pull request #1269 from molstar/resolution-mode
Resolution mode
2024-09-21 14:54:24 -07:00
Alexander Rose
32a1a35a96 remove debug code 2024-09-21 10:36:45 -07:00
Alexander Rose
df129d8ce3 Merge branch 'master' of https://github.com/molstar/molstar into resolution-mode 2024-09-21 10:24:03 -07:00
Alexander Rose
c346da9f6d Merge pull request #1253 from molstar/illumination
Illumination mode
2024-09-21 10:23:38 -07:00
Alexander Rose
de15a3d05d add resolutionMode parameter to Canvas3DContext 2024-09-21 10:22:57 -07:00
Alexander Rose
392b42f6f0 fix impostor sphere interior normal when using orthographic projection 2024-09-21 10:10:28 -07:00
Alexander Rose
46a9b587b4 handle 0 denoise threshold 2024-09-21 10:08:30 -07:00
giagitom
1b4d42cc1e Avoid rendering textures used by getMappedDepth when ssao multiScale is off 2024-09-19 19:50:29 +02:00
giagitom
7c8f6255c5 Merge branch 'master' of https://github.com/molstar/molstar into transparent-ssao 2024-09-19 19:09:37 +02:00
Alexander Rose
3334a636c5 Merge branch 'master' of https://github.com/molstar/molstar into illumination 2024-09-15 14:48:26 -07:00
Alexander Rose
94d52dddda fix me color update 2024-09-15 14:48:10 -07:00
Alexander Rose
b35a73b50f tweak params 2024-09-15 14:01:33 -07:00
Alexander Rose
7fde6a810d Merge branch 'master' of https://github.com/molstar/molstar into illumination 2024-09-15 13:42:45 -07:00
Alexander Rose
843eae1e49 Merge pull request #1262 from molstar/shadow-multiple-lights
fix shadows with multiple lights
2024-09-15 13:41:29 -07:00
Alexander Rose
8513183684 Merge branch 'master' of https://github.com/molstar/molstar into illumination 2024-09-14 14:35:41 -07:00
Alexander Rose
790bebf302 normalize direct light for shadows 2024-09-14 14:24:30 -07:00
Alexander Rose
0fb76261e8 spec & avoid compiling tracing shaders when unsupported 2024-09-14 13:58:51 -07:00
Alexander Rose
53c69640b7 spec 2024-09-14 13:46:36 -07:00
Alexander Rose
d70cef8ad3 fix shadows with multiple lights 2024-09-14 13:42:47 -07:00
Alexander Rose
a84a23cbcc remove console.log 2024-09-14 13:27:46 -07:00
Alexander Rose
736f2dc657 ssao tweaks
- linear filter always supported for uint8
- don't use mapped depth in non-multiscale to avoid issues on some mobile devices
2024-09-14 13:17:49 -07:00
Alexander Rose
06295fd586 add shader precision formats info to webgl context 2024-09-14 13:16:00 -07:00
Alexander Rose
e9f4d95dc3 Merge pull request #1260 from molstar/xtc-parser-fix
fix no-compression xtc parser
2024-09-14 13:14:33 -07:00
Alexander Rose
223e3b6fbf Merge branch 'master' into xtc-parser-fix 2024-09-14 13:14:26 -07:00
Alexander Rose
16c967b674 changelog 2024-09-14 13:14:01 -07:00
Alexander Rose
a6a1f0621e improve viewport-screenshot tracing
- make sample count independent of performance for reproducible quality
2024-09-14 13:11:49 -07:00
Alexander Rose
60cb722343 avoid compiling tracing shaders when unsupported 2024-09-14 13:08:54 -07:00
Alexander Rose
2569fe9577 improve direct light shadows 2024-09-14 13:07:25 -07:00
Alexander Rose
be717133ef fix tracing on mobile
- specify precision highp int
2024-09-14 13:06:06 -07:00
Alexander Rose
231d585236 always wait for gpu in canvas3d.render 2024-09-14 13:04:53 -07:00
etongfu
098faf129c Fix mol2 status_bit read error (#1251)
* fix: Fix mol2 status_bit read error

* feat: Retrieve the number of columns using `streaming tokenizer` and remove readLineElements

* fix: remove useless code

* fix: fix status_bits array read error

* chore: remove dataLines code.

* chore: Add contributor information

---------

Co-authored-by: tongfu.e <tongfu.e@xtalpi.com>
2024-09-11 18:58:14 +02:00
dsehnal
0b39ad8341 fix no-compression xtc parser 2024-09-10 19:00:30 +02:00
Alexander Rose
c0117c41e6 Merge branch 'master' of https://github.com/molstar/molstar into illumination 2024-09-08 19:32:30 -07:00
Alexander Rose
0ce41e989a Merge pull request #1255 from giagitom/enable-depth-xray
Enable dXrayShaded define when using depth variant
2024-09-08 16:58:29 -07:00
Alexander Rose
b6885a0d76 improve parameter adjustment 2024-09-07 23:07:42 -07:00
Alexander Rose
125120fcab avoid rendering illumination iteration when previous gpu are not complete 2024-09-07 21:39:02 -07:00
Alexander Rose
2147a5c3fb add automatically adjusted denoise threshold 2024-09-07 11:41:09 -07:00
Alexander Rose
8c7d5b9585 remove extra timer.mark 2024-09-07 11:33:33 -07:00
Alexander Rose
aa4c36885d make userInteractionReleaseMs a param 2024-09-07 11:31:23 -07:00
Alexander Rose
4ee4788378 make "glow" from overly lit bounce rays optional 2024-09-07 11:28:57 -07:00
Alexander Rose
47aea2b12f pause canvas3d while rendering screenshot 2024-09-07 11:23:41 -07:00
Alexander Rose
490bc82ee6 properly wait for gpu to finish when rendering image 2024-09-07 11:22:21 -07:00
Alexander Rose
0d24c636a3 improve webgl timer, support optional note 2024-09-07 11:21:26 -07:00
Alexander Rose
5a81b4f375 add illumination option to viewer app 2024-09-07 11:09:59 -07:00
Alexander Rose
73b90ffb5c fix normal for approximate sphere 2024-09-07 11:09:22 -07:00
giagitom
02e795b265 Update headers 2024-09-05 01:46:31 +02:00
giagitom
325aa74331 changelog 2024-09-05 01:45:22 +02:00
giagitom
1efe2eb329 Enable dXrayShaded define when using depth variant 2024-09-05 01:37:13 +02:00
giagitom
1ba00c7fa8 Reverting changes 2024-09-05 01:32:19 +02:00
giagitom
1bfc2fe511 Ssao initialization fixes 2024-09-04 12:56:17 +02:00
dsehnal
1e895f3c8c only render first iteration of illumination when actively interacting 2024-09-03 18:18:23 +02:00
dsehnal
028c283043 undo image pass render tweak 2024-09-03 18:06:41 +02:00
dsehnal
144ed51100 fallback to standard rendering when interacting with the structure 2024-09-03 17:16:56 +02:00
Alexander Rose
e3c2ec4561 fix image.frag 2024-09-02 22:48:34 -07:00
Alexander Rose
84dd957983 temp 2024-09-02 22:38:18 -07:00
Alexander Rose
1093a4f6ad lint 2024-09-02 22:37:13 -07:00
Alexander Rose
c4fdc43aa0 changelog 2024-09-02 14:32:25 -07:00
Alexander Rose
15da722af5 add illumination pass 2024-09-02 14:24:27 -07:00
Alexander Rose
eec2d2a720 add tracing pass 2024-09-02 14:23:09 -07:00
giagitom
1766fad6f7 Enable ssao on transparency by default 2024-09-02 15:40:29 +02:00
giagitom
d4775812ad Fixes 2024-09-02 15:00:03 +02:00
giagitom
6cf887d44d remove comments 2024-09-02 09:16:56 +02:00
giagitom
bbb2bee2ae changelog 2024-09-01 21:10:39 +02:00
giagitom
73763b444e Merge remote-tracking branch 'upstream/master' into transparent-ssao 2024-09-01 21:07:05 +02:00
giagitom
9508e01e59 Add transparent ssao support 2024-09-01 19:10:59 +02:00
Alexander Rose
375db11e9b add visual density parameter to estimate object thickness 2024-08-30 13:56:56 -07:00
Alexander Rose
b1b1972684 add bloom to simple-settings ui 2024-08-29 18:54:47 -07:00
Alexander Rose
ce0d4cbc4e fix renderer.clearDepth 2024-08-29 18:52:39 -07:00
Alexander Rose
127d9bc94e me: increase ultra graphics mode thresholds 2024-08-29 18:48:20 -07:00
Alexander Rose
860df1a898 Merge pull request #1249 from molstar/fix-1245
Fix handling of PDB files
2024-08-29 18:44:33 -07:00
Alexander Rose
51b36e90f0 Merge branch 'master' into fix-1245 2024-08-29 18:44:24 -07:00
Alexander Rose
48b19e149b Merge pull request #1250 from JonStargaryen/master
Sequence Panel: Improve visuals of unmodeled sequence positions
2024-08-29 14:05:08 -07:00
Sebastian Bittrich
5a87d9dbf5 Seq Panel: adjust font color of missing positions (closes #1248) 2024-08-29 13:38:54 -07:00
Sebastian Bittrich
c07b4ba550 Seq Panel: default cursor for missing positions 2024-08-29 13:11:53 -07:00
Alexander Rose
8a99e3e3fd load no default tour 2024-08-29 10:31:06 -07:00
Alexander Rose
571f54f4e6 lint 2024-08-29 10:28:19 -07:00
Alexander Rose
15cd7b9c13 Fix handling of PDB files
- that have chains with same id separated by TER record
2024-08-29 10:22:16 -07:00
Alexander Rose
0d21b399b5 4.6.0 2024-08-28 16:26:11 -07:00
Alexander Rose
94ad0bf75c changelog 2024-08-28 16:22:54 -07:00
Alexander Rose
2c44286ca5 schema update 2024-08-28 16:20:33 -07:00
Alexander Rose
23705727ac package updates 2024-08-28 16:19:44 -07:00
Alexander Rose
0a173d230c Merge pull request #1242 from molstar/library-docs
Using esbuild documentation
2024-08-28 15:01:27 -07:00
Alexander Rose
f8987af0e8 Merge pull request #1247 from corredD/revision_p
PR for Revision
2024-08-28 14:58:17 -07:00
Alexander Rose
e046b80bf2 use now from util 2024-08-28 14:57:34 -07:00
Ludovic Autin
f8d6f1d010 timer for loadURL 2024-08-28 12:15:50 -07:00
ludovic autin
579190b9ce embedding.html page example 2024-08-28 11:09:42 -07:00
dsehnal
e44e29eb9f using esbuild docs 2024-08-27 11:35:10 +02:00
ludovic autin
589cec24e5 more timer. For some reason it doesnt report anything with MG model 2024-08-26 16:46:37 -07:00
ludovic autin
fd999953f9 default MOL loading if no url model given
timingmode for loading mesoscale model ( in loadURL function )
2024-08-26 16:34:16 -07:00
Alexander Rose
523dfe7928 Merge branch 'master' of https://github.com/molstar/molstar 2024-08-24 15:01:12 -07:00
Alexander Rose
b2f26e6b1d fix timer: no resolve when capturing stats 2024-08-24 14:59:39 -07:00
Alexander Rose
dc45bf3915 optimize camera.getPixelSize 2024-08-24 14:59:30 -07:00
Sebastian Bittrich
96e22e25cf ModelServer & VolumeServer: add health-check (#1233)
* VolumeServer: add health-check

* ModelServer: add health-check
2024-08-23 12:49:17 +02:00
Alexander Rose
051beb3c3c Merge pull request #1230 from molstar/compression-api
compression-api
2024-08-19 22:38:05 -07:00
Alexander Rose
2ba3d67520 Merge branch 'master' into compression-api 2024-08-19 22:37:57 -07:00
Alexander Rose
cd30d9c1a3 Merge pull request #1231 from molstar/cg-improvements
improve coarse-grained models handling
2024-08-19 22:37:02 -07:00
Alexander Rose
7d32aa8276 Merge pull request #1224 from molstar/screenshot-task
wrap screenshot & image generation in a Task
2024-08-19 22:36:11 -07:00
Alexander Rose
f837b46da1 Merge branch 'master' into screenshot-task 2024-08-19 22:36:03 -07:00
Alexander Rose
c6107ff694 fix hasCompressionStreamSupport 2024-08-19 22:31:36 -07:00
Alexander Rose
2e7228f88b improve coarse-grained models handling
- Add Zhang-Skolnick secondary-structure assignment method which handles coarse-grained models (#49)
- Calculate bonds for coarse-grained models
2024-08-18 13:44:57 -07:00
Alexander Rose
e8825eac5d fix cartoon representation not updated when secondary structure changes 2024-08-18 13:36:22 -07:00
Alexander Rose
1a88126af8 add pdbx_structure_determination_methodology mmcif field and Model helpers 2024-08-18 13:32:57 -07:00
Alexander Rose
c4a6eba448 compression-api 2024-08-17 19:20:48 -07:00
Alexander Rose
fc7e9501b2 add more coloring options to cartoon theme 2024-08-17 16:35:25 -07:00
Alexander Rose
1dfd52db43 add formal-charge color theme 2024-08-17 16:35:07 -07:00
Alexander Rose
5510b28656 fix polymer-gap visual coloring with cartoon theme 2024-08-17 16:34:26 -07:00
Alexander Rose
e94abdb159 Merge pull request #1120 from giagitom/tubular-alpha-helices-round-cap
Add round caps on tubular alpha helices
2024-08-17 16:07:21 -07:00
Alexander Rose
7015607244 tweaks 2024-08-17 16:05:44 -07:00
Alexander Rose
7ff37d7dcc Merge pull request #1228 from JonStargaryen/master
Support for AlphaFold DB BinaryCIF files and 4-character PDB-Dev IDs
2024-08-17 15:52:59 -07:00
Sebastian Bittrich
3abc2da106 cl 2024-08-16 14:32:50 -07:00
Sebastian Bittrich
f9c498177a PDB-Dev: support 4-char PDB ID 2024-08-16 14:31:37 -07:00
Sebastian Bittrich
872c6483be AFDB: bcif support 2024-08-16 13:55:10 -07:00
Sebastian Bittrich
53288e4e9d typo 2024-08-16 13:27:59 -07:00
giagitom
d6b045594c Round cap fixes 2024-08-14 15:04:29 +02:00
giagitom
aa86111de7 Merge branch 'master' of https://github.com/molstar/molstar into tubular-alpha-helices-round-cap 2024-08-12 10:18:27 +02:00
Alexander Rose
040473388e wrap screenshot & image generation in a Task 2024-08-10 15:38:44 -07:00
Alexander Rose
f474615729 typo 2024-08-10 15:37:06 -07:00
Alexander Rose
92559e456e passes refactor 2024-08-10 15:35:44 -07:00
Alexander Rose
b2434ea0d0 Merge pull request #1220 from molstar/bond-visibility
Avoid calculating bonds for water units when `ignoreHydrogens` is on
2024-08-10 12:38:27 -07:00
Alexander Rose
6cf0ce5574 Merge branch 'master' into bond-visibility 2024-08-10 12:38:20 -07:00
Alexander Rose
518a40f0ba defer sphere3d cloning 2024-08-10 12:36:42 -07:00
Alexander Rose
387e87bfda Merge pull request #1222 from molstar/entity-id-multi-model
Improve entity-id coloring for structures with multiple models from the same source
2024-08-10 12:03:44 -07:00
Alexander Rose
4fac2a5cd6 fix changelog dates 2024-08-10 12:01:42 -07:00
Alexander Rose
f5f3ea84d4 Improve entity-id coloring for structures with multiple models from the same source 2024-08-03 12:51:45 -07:00
Alexander Rose
4b3d470dde Avoid calculating bonds for water units when ignoreHydrogens is on
- Add `Water` trait to `Unit`
- Remove unsued code from `Structure.ofModel`
- Try reuse boundary in element-cross visual
2024-08-03 12:00:27 -07:00
Alexander Rose
8513a44e8c Improved prmtop format support (CTITLE, %COMMENT) 2024-08-03 11:53:22 -07:00
Alexander Rose
84b54d97df Fix missing Sequence UI update on state object removal 2024-08-03 11:49:09 -07:00
Alexander Rose
34606f258e tweak objectForEach type 2024-08-03 11:47:08 -07:00
Alexander Rose
2c10dd46a0 4.5.0 2024-07-28 17:34:12 -07:00
Alexander Rose
d4c80fc995 changelog 2024-07-28 17:30:50 -07:00
Alexander Rose
e1c00f65a5 package updates 2024-07-28 17:29:36 -07:00
Alexander Rose
012bc9e8e8 Merge pull request #1209 from molstar/fix-traj-color-update
ensure color-theme gets correctly updated
2024-07-28 17:27:00 -07:00
Alexander Rose
a99083107c Merge pull request #1207 from giagitom/separed-postprocessing-passes
Separed postprocessing passes
2024-07-28 17:23:15 -07:00
Alexander Rose
7e93bb0dda update filter in schema 2024-07-28 17:20:44 -07:00
giagitom
9735cce043 Merge branch 'master' of https://github.com/molstar/molstar into separed-postprocessing-passes 2024-07-28 12:53:06 +02:00
Alexander Rose
78e1d76f5e Merge branch 'master' into fix-traj-color-update 2024-07-25 22:14:54 -07:00
Alexander Rose
18b1492d54 schema update
- force missing auth_seq_id fields to be int
2024-07-25 22:13:10 -07:00
Alexander Rose
6116b2fea5 Merge pull request #1211 from molstar/ssao-filter
use texture linear filter for SSAO when available
2024-07-25 22:11:10 -07:00
Alexander Rose
6ef8fd2b64 handle missing theme updates
- Fix trajectory-index color-theme not always updated (#896)
 - Fix bond cylinders not updated on size-theme change with `adjustCylinderLength` enabled (#1215)
2024-07-25 22:08:39 -07:00
giagitom
9319805d36 Fix textue size not updating correctly 2024-07-24 11:58:21 +02:00
Alexander Rose
5027ad37d7 use texture linear filter for SSAO when available 2024-07-22 20:55:02 -07:00
Alexander Rose
70bd0c25c4 schema update
- add struct_mon_prot_cis mmcif category
2024-07-22 20:52:50 -07:00
Alexander Rose
1a5c7f5437 cleanup 2024-07-21 14:18:35 -07:00
Alexander Rose
4a9505c334 Merge branch 'master' of https://github.com/molstar/molstar into fix-traj-color-update 2024-07-20 13:40:41 -07:00
Alexander Rose
b43ec9ed45 Merge branch 'master' of https://github.com/molstar/molstar 2024-07-20 13:37:46 -07:00
Alexander Rose
eb9c6d542b package updates 2024-07-20 13:37:44 -07:00
Alexander Rose
2ec0911821 Merge pull request #1201 from rjdirisio/further-sass-syntax-fixes
Continue fixing syntax for Sass deprecation of special semantics
2024-07-20 13:31:28 -07:00
Alexander Rose
bbb34c8a27 ensure color-theme gets correctly updated
- fixes trajectory-index color-theme (#896)
2024-07-20 13:30:26 -07:00
giagitom
1bcb8d6486 Merge remote-tracking branch 'upstream/master' into separed-postprocessing-passes 2024-07-17 16:29:31 +02:00
giagitom
630b5ca203 Separated postprocessing passes 2024-07-17 16:16:53 +02:00
Alexander Rose
5b2ed784e1 Merge pull request #1204 from corredD/patch-1
Update states.tsx docs url
2024-07-16 21:02:18 -07:00
Alexander Rose
11c9a83ee7 Merge pull request #1205 from corredD/ME_fix_loadUrl
Load URL now behave like drag/drop.
2024-07-16 21:00:39 -07:00
ludovic autin
2d1b61647a Load URL now behave like drag/drop. Fix some error loading state snapshot tours 2024-07-16 11:46:25 -07:00
ludovic autin
fa3797a738 Update states.tsx docs url 2024-07-16 10:40:43 -07:00
Alexander Rose
fc60c0c980 Merge pull request #1191 from bergwerf/aromatic_links
Add unadjusted position for aromatic link dashes
2024-07-14 10:42:34 -07:00
Alexander Rose
372ca20980 simplify render-structure-grid test 2024-07-14 10:35:35 -07:00
Alexander Rose
b0aad9f1ff add missing adjust arg 2024-07-14 10:15:35 -07:00
Alexander Rose
40e45adbb0 Merge branch 'master' of https://github.com/molstar/molstar into pr/bergwerf/1191 2024-07-14 10:05:40 -07:00
Alexander Rose
5b43a2cee9 me ui header tweaks 2024-07-14 10:03:10 -07:00
Alexander Rose
a319a0daa8 update me deploy path 2024-07-13 18:44:19 -07:00
Alexander Rose
79f812d0e1 update me deploy path 2024-07-13 18:01:34 -07:00
Ryan DiRisio
45611a25a5 Continue fixing syntax 2024-07-12 12:25:57 -04:00
Ryan DiRisio
77be659915 Update misc.scss msp-no-webgl syntax to be in line with latest sass requirements (#1199)
* wrap args in ampersand for msp-no-webgl

* update changelog

---------

Co-authored-by: Ryan DiRisio <rdirisio@treeline.bio>
2024-07-12 16:54:40 +02:00
Herman Bergwerf
5986250ed9 Make Trackball spin in radians per second (#1193)
* Make Trackball spin in radians per second.

* Add line to changelog.
2024-07-08 18:03:02 +02:00
Herman Bergwerf
8156c672b0 Fix formatting. 2024-07-08 15:59:59 +02:00
Herman Bergwerf
a443512102 Merge branch 'master' into aromatic_links 2024-07-08 15:52:14 +02:00
Herman Bergwerf
4b921319a8 Improve pinch event (#1192)
* Improve pinch event.

* Apply feedback.

* Update changelog and headers.

* Add me to contributers.
2024-07-08 15:39:00 +02:00
Herman Bergwerf
6329820a87 Add line to changelog. 2024-07-08 15:14:26 +02:00
Herman Bergwerf
91e4b0c3d6 Add me to file headers. 2024-07-08 15:11:53 +02:00
Herman Bergwerf
7666617857 Alter LinkBuilderProps.position signature. 2024-07-08 15:10:04 +02:00
Herman Bergwerf
19be1090b3 Add unadjusted position for aromatic link dashes. 2024-07-07 16:39:38 +02:00
Alexander Rose
354438052e Merge pull request #1176 from papillot/hbonds_with_explicit_hydrogens
Take into account explicit hydrogens when creating and representing Hydrogen bonds (regular and weak)
2024-07-06 12:03:05 -07:00
Alexander Rose
b72444b213 Merge branch 'master' of https://github.com/molstar/molstar into pr/papillot/1176 2024-07-06 11:55:51 -07:00
Alexander Rose
179078f45c remove unused postprocessing code (#1189) 2024-07-06 11:48:53 -07:00
Alexander Rose
1dbc23fe91 dof fixes
- handle pixel ratios =! 1
- ensure transparent depths is available
2024-07-06 11:29:31 -07:00
Paul Pillot
ff4dec9fea code styling, typos 2024-07-01 09:43:10 +02:00
Paul Pillot
6ea51c07b4 update changelog, author information 2024-07-01 09:32:11 +02:00
Paul Pillot
f4cebb9195 manage weak hydrogen bonds position
Previous code was filtering on `polar` hydrogens when looking for an anchorage.
This was not necessary as an acceptor for a regular HBond has only polar hydrogens.
It was removed to make the same code compliant with weak C-H bonds.
2024-07-01 09:27:46 +02:00
Paul Pillot
cbe5f0dc7c Add ignoreHydrogens parameter to geometry options
This is different from the "Hydrogens: Show All/Hide All/Only Polar" as it is used for filtering out which contacts are represented, while the latter is used to control the display (point to the heavy atoms when H are hidden).
In other words, by considering the H atoms in the geometric constraints, some candidate H bonds are discarded (e.g. because the donor does not have H atoms in the right orientation), while the visibility of hydrogens parameters will still show the same number of hydrogen bonds, just not pointing to the same atoms.
2024-07-01 09:27:46 +02:00
Paul Pillot
0ac8b565b5 Use explicit hydrogens to calculate angles
Donor: at least one hydrogen on the donor must be within the donor angle deviation towards the acceptor (angle H-X-A < 45º)

Acceptor: use the closest H on the donor to compute the angles. Ensure that every H atom on the acceptor complies with the ideal angle (no bonded atom from acceptor should be in the way).
2024-07-01 09:27:46 +02:00
Paul Pillot
4f38d4d943 relocate hbond on hydrogens from donors only
This avoids hbonds between 2 hydrogen atoms
2024-07-01 09:27:46 +02:00
Alexander Rose
0af84eb6b5 4.4.1 2024-06-30 15:53:58 -07:00
Alexander Rose
aa2d19478b changelog 2024-06-30 15:51:28 -07:00
Alexander Rose
e035b834a6 update package-lock 2024-06-30 15:43:34 -07:00
giagitom
d2192d609a Fix initial and final caps 2024-05-15 15:58:22 +02:00
Gianluca Tomasello
46ad8f495f Merge branch 'master' into tubular-alpha-helices-round-cap 2024-05-14 19:22:20 +02:00
giagitom
65b2b69a64 Smooth normals 2024-05-14 19:20:36 +02:00
giagitom
3220ab6118 Add round caps on tubular alpha helices 2024-05-07 17:04:18 +02:00
679 changed files with 61099 additions and 30666 deletions

View File

@@ -1,4 +0,0 @@
node_modules/*
build/*
docs/site/*
lib/*

View File

@@ -1,122 +0,0 @@
{
"env": {
"browser": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeatures": {
"impliedStrict": true
}
},
"rules": {
"indent": "off",
"arrow-parens": [
"off",
"as-needed"
],
"brace-style": "off",
"comma-spacing": "off",
"space-infix-ops": "off",
"comma-dangle": "off",
"eqeqeq": [
"error",
"smart"
],
"import/order": "off",
"no-eval": "warn",
"no-new-wrappers": "warn",
"no-trailing-spaces": "error",
"no-unsafe-finally": "warn",
"no-var": "error",
"spaced-comment": "error",
"semi": "warn",
"no-restricted-syntax": [
"error",
{
"selector": "ExportDefaultDeclaration",
"message": "Default exports are not allowed"
}
],
"no-throw-literal": "error",
"key-spacing": "error",
"object-curly-spacing": ["error", "always"],
"array-bracket-spacing": "error",
"space-in-parens": "error",
"computed-property-spacing": "error",
"prefer-const": ["error", {
"destructuring": "all",
"ignoreReadBeforeAssign": false
}],
"space-before-function-paren": "off",
"func-call-spacing": "off",
"no-multi-spaces": "error",
"block-spacing": "error",
"keyword-spacing": "off",
"space-before-blocks": "error",
"semi-spacing": "error",
"no-constant-binary-expression": "error"
},
"overrides": [
{
"files": ["**/*.ts", "**/*.tsx"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["tsconfig.json", "tsconfig.commonjs.json"],
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/class-name-casing": "off",
"@typescript-eslint/indent": [
"error",
4
],
"@typescript-eslint/member-delimiter-style": [
"off",
{
"multiline": {
"delimiter": "none",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"@typescript-eslint/prefer-namespace-keyword": "warn",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": true
}
],
"@typescript-eslint/semi": [
"off",
null
],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/brace-style": [
"error",
"1tbs", { "allowSingleLine": true }
],
"@typescript-eslint/comma-spacing": "error",
"@typescript-eslint/space-infix-ops": "error",
"@typescript-eslint/space-before-function-paren": ["error", {
"anonymous": "always",
"named": "never",
"asyncArrow": "always"
}],
"@typescript-eslint/func-call-spacing": ["error"],
"@typescript-eslint/keyword-spacing": ["error"]
}
}
]
}

7
.gitignore vendored
View File

@@ -1,4 +1,5 @@
build/
deploy/
lib/
docs/site/
@@ -11,4 +12,8 @@ tsconfig.commonjs.tsbuildinfo
*.sublime-workspace
.idea
.DS_Store
.DS_Store
tmp/
dev.pem
dev-key.pem

View File

@@ -6,9 +6,4 @@
"*.vert.ts": "glsl",
"*.gql.ts": "graphql"
},
"eslint.options": {
"overrideConfig": {
"ignorePatterns": ["webpack.config.js", "scripts/*"],
},
}
}

View File

@@ -4,8 +4,390 @@ 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]
- [Breaking] Renamed some color schemes ('inferno' -> 'inferno-no-black', 'magma' -> 'magma-no-black', 'turbo' -> 'turbo-no-black', 'rainbow' -> 'simple-rainbow')
- [Breaking] `Box3D.nearestIntersectionWithRay` -> `Ray3D.intersectBox3D`
- [Breaking] `Plane3D.distanceToSpher3D` -> `distanceToSphere3D` (fix spelling)
- [Breaking] fix typo `MarchinCubes` -> `MarchingCubes`
- [Breaking] `PluginContext.initViewer/initContainer/mount` are now async and have been renamed to include `Async` postfix
- [Breaking] Add `Volume.instances` support and a `VolumeInstances` transform to dynamically assign it
- This change is breaking because all volume objects require the `instances` field now.
- [Breaking] `Canvas3D.identify` now expects `Vec2` or `Ray3D`
- Update production build to use `esbuild`
- Emit explicit paths in `import`s in `lib/`
- Fix outlines on opaque elements using illumination mode
- Change `Representation.Empty` to a lazy property to avoid issue with some bundlers
- MolViewSpec extension:
- Generic color schemes (`palette` parameter for color_from_* nodes)
- Annotation field remapping (`field_remapping` parameter for color_from_* nodes)
- Representation node: support custom property `molstar_reprepresentation_params`
- Primitives node: support custom property `molstar_mesh/label/line_params`
- Canvas node: support custom property `molstar_postprocessing` with the ability to customize outline, depth of field, bloom, shadow, occlusion (SSAO), and fog
- `clip` node support for structure and volume representations
- `grid_slice` representation support for volumes
- Support tethers and background for primitive labels
- Support `snapshot_key` parameter on primitives that enables transition between states via clicking on 3D objects
- Inline selectors and MVS annotations support `instance_id`
- Support `matrix` on transform params
- Add `instance` node type
- Support transforming and instancing of structures, components, and volumes
- Added new color schemes, synchronized with D3.js ('inferno', 'magma', 'turbo', 'rainbow', 'sinebow', 'warm', 'cool', 'cubehelix-default', 'category-10', 'observable-10', 'tableau-10')
- Snapshot Markdown improvements
- Add `MarkdownExtensionManager` (`PluginContext.managers.markdownExtensions`)
- Support custom markdown commands to control the plugin via the `[link](!command)` pattern
- Support rendering custom elements via the `![alt](!parameters)` pattern
- Support tables
- Support loading images from MVSX files
- Indicate external links with ⤴
- Avoid calculating rings for coarse-grained structures
- Fix isosurface compute shader normals when transformation matrix is applied to volume
- Symmetry operator naming for spacegroup symmetry - parenthesize multi-character indices (1_111-1 -> 1_(11)1(-1))
- Add `SymmetryOperator.instanceId` that corresponds to a canonical operator name (e.g. ASM-1, ASM-X0-1 for assemblies, 1_555, 1_(11)1(-1) for crystals)
- Mol2 Reader
- Fix column count parsing
- Add support for substructure
- Fix shader error when clipping flags are set without clip objects present
- Fix wrong group count calculation on geometry update (#1562)
- Fix wrong instance index in `calcMeshColorSmoothing`
- Add `Ray3D` object and helpers
- Volume slice representation: add `relativeX/Y/Z` options for dimension
- Add `StructureInstances` transform
- `mvs-stories` app
- Add `story-id` URL arg support
- Add "Download MVS State" link
- Add ray-based picking
- Render narrow view of scene scene from ray origin & direction to a few pixel sized viewport
- Cast ray on every input as opposed to the standard "whole screen" picking
- Can be enabled with new `Canvas3dInteractionHelperParams.convertCoordsToRay` param
- Allows to have input methods that are 3D pointers in the scene
- Add `ray: Ray3D` property to `DragInput`, `ClickInput`, and `MoveInput`
- Add async, non-blocking picking (only WebGL2)
- Refactor `Canvas3dInteractionHelper` internals to use async picking for move events
- Add `enable` param for post-processing effects. If false, no effects are applied.
## [v4.4.0] - 2023-06-30
## [v4.18.0] - 2025-06-08
- MolViewSpec extension:
- Support for label_comp_id and auth_comp_id in annotations
- Geometric primitives - do not render if position refers to empty substructure
- Primitive arrow - nicer default cap size (relative to tube_radius)
- Primitive angle_measurement - added vector_radius param
- Fix MVSX file assets being disposed in multi-snapshot states
- Add `mol-utils/camera.ts` with `fovAdjustedPosition` and `fovNormalizedCameraPosition`
- Show FOV normalized position in `CameraInfo` UI and use it in "Copy MVS State"
- Support static resources in `AssetManager`
- General:
- Use `isolatedModules` tsconfig flag
- Fix TurboPack build when using ES6 modules
- Support `pickingAlphaThreshold` when `xrayShaded` is enabled
- Support sampling from arbitrary planes for structure plane and volume slice representations
- Refactor SCSS to not use `@import` (fixes deprecation warnings)
## [v4.17.0] - 2025-05-22
- Remove `xhr2` dependency for NodeJS, use `fetch`
- Add `mvs-stories` app included in the `molstar` NPM package
- Use the app in the corresponding example
- Interactions extension: remove `salt-bridge` interaction kind (since `ionic` is supported too)
## [v4.16.0] - 2025-05-20
- Load potentially big text files as `StringLike` to bypass string size limit
- MolViewSpec extension:
- Load single-state MVS as if it were multi-state with one state
- Merged `loadMVS` options `keepCamera` and `keepSnapshotCamera` -> `keepCamera`
- Removed `loadMVS` option `replaceExisting` (is now default)
- Added `loadMVS` option `appendSnapshots`
- Fix camera not being interpolated in MP4 export due to updates in WebGL ContextLost handling
## [v4.15.0] - 2025-05-19
- IHM improvements:
- Disable volume streaming
- Disable validation report visualization
- Enable assembly symmetry for integrative models
- Fix transparency rendering with occlusion in NodeJS
- mmCIF Support
- Add custom `molstar_bond_site` category that enables serializing explicit bonds by referencing `atom_site.id`
- Add `includeCategoryNames`, `keepAtomSiteId`, `exportExplicitBonds`, `encoder` properties to `to_mmCIF` exporter
- Add support for attachment points property (`M APO`) to the MOL V2000 parser
- Add `json-cif` extension that should pave way towards structure editing capabilities in Mol\*
- JSON-based encoding of the CIF data format
- `JSONCifLigandGraph` that enables editing of small molecules via modifying `atom_site` and `molstar_bond_site` categories
- Add `ligand-editor` example that showcases possible use-cases of the `json-cif` extension
- Breaking (minor): Changed `atom_site.id` indexing to 1-based in `mol-model-formats/structure/mol.ts::getMolModels`.
- WebGL ContextLost handling
- Fix missing framebuffer & drawbuffer re-attachments
- Fix missing cube texture re-initialization
- Fix missing extensions reset
- Fix timer clearing edge case
- Add reset support for geometry generated on the GPU
## [v4.14.1] - 2025-05-09
- Do not raise error when creating duplicate state transformers and print console warning instead
## [v4.14.0] - 2025-05-07
- Fix `Viewer.loadTrajectory` when loading a topology file
- Fix `StructConn.residueCantorPairs` to not include identity pairs
- Add format selection option to image export UI (PNG, WebP, JPEG)
- Add `StateBuilder.To.updateState`
- MVS:
- Support updating transform states
- Add support for `is_hidden` custom state as an extension
- Add `queryMVSRef` and `createMVSRefMap` utility functions
- Adjust max resolution of surfaces for auto quality (#1501)
- Fix switching representation type in Volume UI
- VolumeServer: Avoid grid expansion when requiring unit cell (avoids including an extra layer of cells outside the unit cell query box)
## [v4.13.0] - 2025-04-14
- Support `--host` option for build-dev.mjs script
- Add `Viewer.loadFiles` to open supported files
- Support installing the viewer as a Progressive Web App (PWA)
- `ihm-restraints` example: show entity labels
- Fix `element-point` visual not using child unit
- Ignore `renderables` with empty draw count
- Add experimental support for `esbuild` for development
- Use `npm run dev` for faster development builds
- Use `StructureElement.Bundle` instead of expressions to serialize measurement elements
- Fixes measurements not being supported for coarse models
- Implementation of `ColorScale.createDiscrete` (#1458)
- Add `ColorScale.createDiscrete` to the `uncertainty` color theme
- Fix color palette shown in the UI (for non-gradient palettes)
- Fix colors description in the UI (when using custom thresholds)
- Fix an edge case in the UI when the user deletes all colors from the color list
- Add `interactions` extension and a corresponding example that utilizes it
- Add element source index to default atomic granularity hover labels
- Add `StructureElement.Schema` based on corresponding MolViewSpec implementation that allows data-driven selection of structural elements
- Add `StructureElement.Loci/Bundle.fromExpression/Query/Schema` helper functions
- Add `addLinkCylinderMesh` (from `createLinkCylinderMesh`)
- Add `Unit.transientCache` and `Unit.getCopy`
- Fix `ElementBondIterator` indices mapping logic for inter-unit bonds
- Fix `pickPadding` and `pickScale` not updating `PickHelper`
- MolViewSpec extension: support loading extensions when loading multistate files
- Do not add bonds for pairs of residues that have a `struct_conn` entry
- Improved `ma_qa_metric` support
- Parse all local metrics
- Ability to select alternate metrics in the pLDDT/qmean themes
- Do not assume PAE plot is symmetric
- Added `PluginConfig.Viewport.ShowScreenshotControls` to control visibility of screenshot controls
- Fix MolViewSpec builder for volumes.
- Generalize `mvs-kinase-story` example to `mvs-stories`
- Add TATA-binding protein story
- Improve the Kinase story
- Fix alpha orbitals example
## [v4.12.0] - 2025-02-28
- Fix PDBj structure data URL
- Improve logic when to cull in renderer
- Add `atom.ihm.has-seq-id` and `atom.ihm.overlaps-seq-id-range` symbol to the query language
- MolViewSpec extension:
- Add box, arrow, ellipse, ellipsoid, angle primitives
- Add basic support for volumetric data (map, Volume Server)
- Add support for `molstar_color_theme_name` custom extension
- Better IH/M support:
- Support `coarse` components
- Support `spacefill` representation
- Support `carbohydrate` representation
- Support for `custom.molstar_use_default_coloring` property on Color node.
- Use `atom.ihm.has-seq-id` and `atom.ihm.overlaps-seq-id-range` for matching `label_seq_id` locations to support querying coarse elements.
- Add ihm-restraints example
- Add `mvs-kinase-story` example
- Remove static uses of `ColorTheme` and `SizeTheme` fields. Should resolvent "undefined" errors in certain builds
- Add `transform` property to clip objects
- Add support for trimming `image` geometry to a box
- Improve/fix iso-level support of `slice` representation
- Add support for rotating `slice` representation around an axis
- Add default color support for palette based themes
- Add `plane` structure representation
- Can be colored with any structure theme
- Can be colored with the `external-volume` theme
- Can show atoms as a cutout
- Supports principal axes and bounding box as a reference frame
- Add `Camera` section to "Screenshot / State" controls
- Add `CoarseIndex` for fast lookup of coarse elements
## [v4.11.0] - 2025-01-26
- Fix for tubular helices issue (Fixes #1422)
- Volume UI improvements
- Render all volume entries instead of selecting them one-by-one
- Toggle visibility of all volumes
- More accessible iso value control
- Support wheel event on sliders
- MolViewSpec extension:
- Add validation for discriminated union params
- Primitives: remove triangle_colors, line_colors, have implicit grouping instead; rename many parameters
- UI configuration options
- Support removal of independent selection controls in the viewport
- Support custom selection controls
- Support for custom granularity dropdown options
- Support for custom Sequence Viewer mode options
- Add `external-structure` theme that colors any geometry by structure properties
- Support float and half-float data type for direct-volume rendering and GPU isosurface extraction
- Minor documentation updates
- Add support for position-location to `volume-value` color theme
- Add support for color themes to `slice` representation
- Improve/fix palette support in volume color themes
- Fix `Plane3D.projectPoint`
- Fix marking related `image` rendering issues
- Handle pixels without a group
- Take fog into account
- MolViewSpec extension: Initial support for customizable representation parameters
- Quick Styles section reorganized
- UI color improvements (scrollbar contrast, toggle button hover color)
- Add `overrideWater` param for entity-id color theme
- Renames PDB-Dev to PDB-IHM and adjusts data source
- Fix vertex based themes for spheres shader
- Add volume dot representation
- Add volume-value size theme
- Sequence panel: Mark focused loci (bold+underline)
- Change modifier key behavior in Normal Mode (default = select only, Ctrl/Cmd = add to selection, Shift = extend last selected range)
- Handle Firefox's limit on vertex ids per draw (#1116)
- Fix behavior of `Vec3.makeRotation(out, a, b)` when `a ≈ -b`
## [v4.10.0] - 2024-12-15
- Add `ModelWithCoordinates` decorator transform.
- Fix outlines on transparent background using illumination mode (#1364)
- Fix transparent depth texture artifacts using illumination mode
- Fix marking of consecutive gap elements (#876)
- Allow React 19 in dependencies
- Fix missing deflate header if `CompressionStream` is available
- Fix is_iOS check for NodeJS
- Added PluginCommands.Camera.FocusObject
- Plugin state snapshot can have instructions to focus objects (PluginState.Snapshot.camera.focus)
- MolViewSpec extension: Support for multi-state files (animations)
- Fix units transform data not fully updated when structure child changes
- Fix `addIndexPairBonds` quadratic runtime case
- Use adjoint matrix to transform normals in shaders
- Fix resize handling in `tests/browser`
## [v4.9.1] - 2024-12-05
- Fix iOS check when running on Node
## [v4.9.0] - 2024-12-01
- Fix artifacts when using xray shading with high xrayEdgeFalloff values
- Enable double rounded capping on tubular helices
- Fix single residue tubular helices not showing up
- Fix outlines on volume and surface reps that do not disappear (#1326)
- Add example `glb-export`
- Membrane orientation: Improve `isApplicable` check and error handling (#1316)
- Fix set fenceSync to null after deleteSync.
- Fix operator key-based `IndexPairBonds` assignment
- Don't add bonds twice
- Add `IndexPairs.bySameOperator` to avoid looping over all bonds for each unit
- Add `Structure.intraUnitBondMapping`
- Add more structure-based visuals to avoid too many (small) render-objects
- `structure-intra-bond`, `structure-ellipsoid-mesh`, `structure-element-point`, `structure-element-cross`
- Upgrade to express v5 (#1311)
- Fix occupancy check using wrong index for inter-unit bond computation (@rxht, #1321)
- Fix transparent SSAO for image rendering, e.g., volumne slices (#1332)
- Fix bonds not shown with `ignoreHydrogens` on (#1315)
- Better handle mmCIF files with no entities defined by using `label_asym_id`
- Show bonds in water chains when `ignoreHydorgensVariant` is `non-polar`
- Add MembraneServer API, generating data to be consumed in the context of MolViewSpec
- Fix `StructConn.isExhaustive` for partial models (e.g., returned by the model server)
- Refactor value swapping in molstar-math to fix SWC (Next.js) build (#1345)
- Fix transform data not updated when structure child changes
- Fix `PluginStateSnapshotManager.syncCurrent` to work as expected on re-loaded states.
- Fix do not compute implicit hydrogens when unit is explicitly protonated (#1257)
- ModelServer and VolumeServer: support for input files from Google Cloud Storage (gs://)
- Fix color of missing partial charges for SB partial charges extension
## [v4.8.0] - 2024-10-27
- Add SSAO support for transparent geometry
- Fix SSAO color not updating
- Improve blending of overlapping outlines from transparent & opaque geometries
- Default to `blended` transparency on iOS due to `wboit` not being supported.
- Fix direct-volume with fog off (and on with `dpoit`) and transparent background on (#1286)
- Fix missing pre-multiplied alpha for `blended` & `wboit` with no fog (#1284)
- Fix backfaces visible using blended transparency on impostors (#1285)
- Fix StructureElement.Loci.isSubset() only considers common units (#1292)
- Fix `Scene.opacityAverage` calculation never 1
- Fix bloom in illumination mode
- Fix `findPredecessorIndex` bug when repeating values
- MolViewSpec: Support for transparency and custom properties
- MolViewSpec: MVP Support for geometrical primitives (mesh, lines, line, label, distance measurement)
- Mesoscale Explorer: Add support for 4-character PDB IDs (e.g., 8ZZC) in PDB-IHM/PDB-Dev loader
- Fix Sequence View in Safari 18
- Improve performance of `IndexPairBonds` assignment when operator keys are available
- ModelArchive QualityAssessment extension:
- Add support for ma_qa_metric_local_pairwise mmCIF category
- Add PAE plot component
- Add new AlphaFoldDB-PAE example app
- Add support for LAMMPS data and dump formats
- Remove extra anti-aliasing from text shader (fixes #1208 & #1306)
## [v4.7.1] - 2024-09-30
- Improve `resolutionMode` (#1279)
- Add `auto` that picks `scaled` for mobile devices and `native` elsewhere
- Add `resolution-mode` Viewer GET param
- Add `PluginConfig.General.ResolutionMode` config item
## [v4.7.0] - 2024-09-29
- Add illumination mode
- Path-traced SSGI
- Automatic thickness (estimate)
- Base thickness as max(backface depth) - min(frontface depth)
- Per object density factor to adjust thickness
- Progressively trace samples to keep viewport interactive
- Toggle on/off by pressing "G"
- `illumination` Viewer GET param
- Enables dXrayShaded define when rendering depth
- Fix handling of PDB files that have chains with same id separated by TER record (#1245)
- Sequence Panel: Improve visuals of unmodeled sequence positions (#1248)
- Fix no-compression xtc parser (#1258)
- Mol2 Reader: Fix mol2 status_bit read error (#1251)
- Fix shadows with multiple lights
- Fix impostor sphere interior normal when using orthographic projection
- Add `resolutionMode` parameter to `Canvas3DContext`
- `scaled`, divides by `devicePixelRatio`
- `native`, no changes
- Add `CustomProperty.Context.errorContext` to support reporting errors during loading of custom properties (#1254)
- Use in MolViewSpec extension
- Mesoscale Explorer: fix color & style issues
- Remove use of deprecated SASS explicit color functions
- Allow "Components" section to display nested components created by "Apply Action > Selection".
## [v4.6.0] - 2024-08-28
- Add round-caps option on tubular alpha helices
- Fix missing Sequence UI update on state object removal (#1219)
- Improved prmtop format support (CTITLE, %COMMENT)
- Avoid calculating bonds for water units when `ignoreHydrogens` is on
- Add `Water` trait to `Unit`
- Improve entity-id coloring for structures with multiple models from the same source (#1221)
- Wrap screenshot & image generation in a `Task`
- AlphaFold DB: Add BinaryCIF support when fetching data
- PDB-IHM/PDB-Dev: Add support for 4-character PDB IDs (e.g., 8ZZC)
- Fix polymer-gap visual coloring with cartoon theme
- Add formal-charge color theme (#328)
- Add more coloring options to cartoon theme
- Use `CompressionStream` Browser API when available
- Add `pdbx_structure_determination_methodology` mmcif field and `Model` helpers
- Fix cartoon representation not updated when secondary structure changes
- Add Zhang-Skolnick secondary-structure assignment method which handles coarse-grained models (#49)
- Calculate bonds for coarse-grained models
- VolumeServer: Add `health-check` endpoint + `healthCheckPath` config prop to report service health
- ModelServer: Add `health-check` endpoint + `healthCheckPath` config prop to report service health
## [v4.5.0] - 2024-07-28
- Separated postprocessing passes
- Take into account explicit hydrogens when computing hydrogen bonds
- Fix DoF with pixel ratios =! 1
- Fix DoF missing transparent depth
- Fix trackball pinch zoom and add pan
- Fix aromatic link rendering when `adjustCylinderLength` is true
- Change trackball animate spin speed unit to radians per second
- Fix `mol-plugin-ui/skin/base/components/misc.scss` syntax to be in line with latest Sass syntax
- Handle missing theme updates
- Fix trajectory-index color-theme not always updated (#896)
- Fix bond cylinders not updated on size-theme change with `adjustCylinderLength` enabled (#1215)
- Use `OES_texture_float_linear` for SSAO when available
## [v4.4.1] - 2024-06-30
- Clean `solidInterior` transparent cylinders
- Create a transformer to deflate compressed data
@@ -40,14 +422,14 @@ Note that since we don't clearly distinguish between a public and private interf
- Add `doNotDisposeCanvas3DContext` option to `PluginContext.dispose`
- Remove support for density data from edmaps.rcsb.org
## [v4.3.0] - 2023-05-26
## [v4.3.0] - 2024-05-26
- Fix State Snapshots export animation (#1140)
- Add depth of field (dof) postprocessing effect
- Add `SbNcbrTunnels` extension for for visualizing tunnels in molecular structures from ChannelsDB (more info in [tunnels.md](./docs/docs/extensions/tunnels.md))
- Fix edge case in minimizing RMSD transform computation
## [v4.2.0] - 2023-05-04
## [v4.2.0] - 2024-05-04
- Add emissive material support
- Add bloom post-processing
@@ -65,7 +447,7 @@ Note that since we don't clearly distinguish between a public and private interf
- Fix SSAO artifacts (@corredD, #1082)
- Fix bumpiness artifacts (#1107, #1084)
## [v4.1.0] - 2023-03-31
## [v4.1.0] - 2024-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
@@ -76,13 +458,13 @@ Note that since we don't clearly distinguish between a public and private interf
- 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
## [v4.0.1] - 2024-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
## [v4.0.0] - 2024-02-04
- Add Mesoscale Explorer app for investigating large systems
- [Breaking] Remove `cellpack` extension (superseded by Mesoscale Explorer app)
@@ -118,7 +500,7 @@ Note that since we don't clearly distinguish between a public and private interf
- 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
## [v3.45.0] - 2024-02-03
- Add color interpolation to impostor cylinders
- MolViewSpec components are applicable only when the model has been loaded from MolViewSpec
@@ -132,7 +514,7 @@ Note that since we don't clearly distinguish between a public and private interf
- 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
## [v3.44.0] - 2024-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

View File

@@ -84,7 +84,16 @@ Wipes the `build` and `lib` directories and `.tsbuildinfo` files.
Runs the cleanup script prior to building the project, forcing a full rebuild of the project.
Use these commands to resolve occassional build failures which may arise after some dependency updates. Once done, `npm run build` should work again. Note that full rebuilds take more time to complete.
Use these commands to resolve occasional build failures which may arise after some dependency updates. Once done, `npm run build` should work again. Note that full rebuilds take more time to complete.
### Develop with `esbuild`
Experimental support for faster builds with `esbuild`
- `npm run dev:all` - watch mode for all apps and examples
- `npm run dev:viewer` - watch mode for viewer
- `npm run dev:apps` - watch mode for all apps
- `npm run dev:examples` - watch mode for all examples
- `npm run dev -- -a <app name 1> <app name 2> -e <example name 1> ...` - watch mode for specified apps/examples. `-a`/`-e` with without any names will build everything.
### Build for production:
NODE_ENV=production npm run build
@@ -103,7 +112,6 @@ From the root of the project:
and navigate to `build/viewer`
### Code generation
**CIF schemas**
@@ -182,9 +190,14 @@ To get syntax highlighting for shader files add the following to Visual Code's s
npm publish
## Deploy
To prepare apps and demos for https://molstar.org deploy, run:
npm run test
npm run build
node ./scripts/deploy.js # currently updates the viewer on molstar.org/viewer
npm run deploy:local
To commit these changes remotely to the `molstar/molstar.github.io` repo:
npm run deploy:remote
## Contributing
Just open an issue or make a pull request. All contributions are welcome.

View File

@@ -263,6 +263,7 @@ software.version
struct.entry_id
struct.title
struct.pdbx_descriptor
struct.pdbx_structure_determination_methodology
struct_asym.id
struct_asym.pdbx_blank_PDB_chainid_flag
@@ -367,18 +368,43 @@ struct_site.details
struct_site_gen.id
struct_site_gen.site_id
struct_site_gen.pdbx_num_res
struct_site_gen.label_comp_id
struct_site_gen.auth_asym_id
struct_site_gen.auth_atom_id
struct_site_gen.auth_comp_id
struct_site_gen.auth_seq_id
struct_site_gen.details
struct_site_gen.label_alt_id
struct_site_gen.label_asym_id
struct_site_gen.label_atom_id
struct_site_gen.label_comp_id
struct_site_gen.label_seq_id
struct_site_gen.pdbx_auth_ins_code
struct_site_gen.auth_comp_id
struct_site_gen.auth_asym_id
struct_site_gen.auth_seq_id
struct_site_gen.label_atom_id
struct_site_gen.label_alt_id
struct_site_gen.pdbx_num_res
struct_site_gen.symmetry
struct_site_gen.details
struct_site_keywords.site_id
struct_site_keywords.text
struct_mon_prot_cis.pdbx_id
struct_mon_prot_cis.auth_asym_id
struct_mon_prot_cis.auth_comp_id
struct_mon_prot_cis.auth_seq_id
struct_mon_prot_cis.label_alt_id
struct_mon_prot_cis.label_asym_id
struct_mon_prot_cis.label_comp_id
struct_mon_prot_cis.label_seq_id
struct_mon_prot_cis.pdbx_PDB_ins_code
struct_mon_prot_cis.pdbx_PDB_ins_code_2
struct_mon_prot_cis.pdbx_PDB_model_num
struct_mon_prot_cis.pdbx_auth_asym_id_2
struct_mon_prot_cis.pdbx_auth_comp_id_2
struct_mon_prot_cis.pdbx_auth_ins_code
struct_mon_prot_cis.pdbx_auth_ins_code_2
struct_mon_prot_cis.pdbx_auth_seq_id_2
struct_mon_prot_cis.pdbx_label_asym_id_2
struct_mon_prot_cis.pdbx_label_comp_id_2
struct_mon_prot_cis.pdbx_label_seq_id_2
struct_mon_prot_cis.pdbx_omega_angle
symmetry.entry_id
symmetry.cell_setting
@@ -850,6 +876,17 @@ ma_qa_metric_local.metric_value
ma_qa_metric_local.model_id
ma_qa_metric_local.ordinal_id
ma_qa_metric_local_pairwise.ordinal_id
ma_qa_metric_local_pairwise.model_id
ma_qa_metric_local_pairwise.label_asym_id_1
ma_qa_metric_local_pairwise.label_comp_id_1
ma_qa_metric_local_pairwise.label_seq_id_1
ma_qa_metric_local_pairwise.label_asym_id_2
ma_qa_metric_local_pairwise.label_comp_id_2
ma_qa_metric_local_pairwise.label_seq_id_2
ma_qa_metric_local_pairwise.metric_id
ma_qa_metric_local_pairwise.metric_value
ma_software_group.group_id
ma_software_group.ordinal_id
ma_software_group.software_id
1 atom_sites.entry_id
263 struct_conn.ptnr1_label_seq_id struct_conn.ptnr1_label_comp_id
264 struct_conn.ptnr1_label_atom_id struct_conn.ptnr1_label_seq_id
265 struct_conn.pdbx_ptnr1_label_alt_id struct_conn.ptnr1_label_atom_id
266 struct_conn.pdbx_ptnr1_label_alt_id
267 struct_conn.pdbx_ptnr1_PDB_ins_code
268 struct_conn.pdbx_ptnr1_standard_comp_id
269 struct_conn.ptnr1_symmetry
368 pdbx_reference_entity_link.details symmetry.space_group_name_H-M
369 pdbx_reference_entity_list.prd_id pdbx_molecule.instance_id
370 pdbx_reference_entity_list.ref_entity_id pdbx_molecule.prd_id
371 pdbx_reference_entity_list.component_id pdbx_molecule.asym_id
372 pdbx_reference_entity_list.type pdbx_molecule_features.prd_id
373 pdbx_molecule_features.name
374 pdbx_molecule_features.type
375 pdbx_molecule_features.class
376 pdbx_molecule_features.details
377 pdbx_reference_entity_list.details pdbx_reference_entity_link.prd_id
378 pdbx_reference_entity_link.link_id
379 pdbx_reference_entity_link.link_class
380 pdbx_reference_entity_poly_link.prd_id pdbx_reference_entity_link.ref_entity_id_1
381 pdbx_reference_entity_poly_link.ref_entity_id pdbx_reference_entity_link.entity_seq_num_1
382 pdbx_reference_entity_poly_link.link_id pdbx_reference_entity_link.comp_id_1
pdbx_reference_entity_poly_link.atom_id_1
pdbx_reference_entity_poly_link.comp_id_1
pdbx_reference_entity_poly_link.entity_seq_num_1
pdbx_reference_entity_poly_link.atom_id_2
383 pdbx_reference_entity_poly_link.comp_id_2 pdbx_reference_entity_link.atom_id_1
384 pdbx_reference_entity_poly_link.entity_seq_num_2 pdbx_reference_entity_link.ref_entity_id_2
385 pdbx_reference_entity_link.entity_seq_num_2
386 pdbx_reference_entity_link.comp_id_2
387 pdbx_reference_entity_link.atom_id_2
388 pdbx_reference_entity_link.value_order
389 pdbx_reference_entity_link.component_1
390 pdbx_reference_entity_link.component_2
391 pdbx_reference_entity_link.details
392 pdbx_reference_entity_list.prd_id
393 pdbx_reference_entity_list.ref_entity_id
394 pdbx_reference_entity_list.component_id
395 pdbx_reference_entity_list.type
396 pdbx_reference_entity_list.details
397 pdbx_reference_entity_poly_link.prd_id
398 pdbx_reference_entity_poly_link.ref_entity_id
399 pdbx_reference_entity_poly_link.link_id
400 pdbx_reference_entity_poly_link.atom_id_1
401 pdbx_reference_entity_poly_link.comp_id_1
402 pdbx_reference_entity_poly_link.entity_seq_num_1
403 pdbx_reference_entity_poly_link.atom_id_2
404 pdbx_reference_entity_poly_link.comp_id_2
405 pdbx_reference_entity_poly_link.entity_seq_num_2
406 pdbx_reference_entity_poly_link.value_order
407 pdbx_reference_entity_poly_link.component_id
408 pdbx_reference_entity_poly_link.value_order pdbx_struct_assembly.id
409 pdbx_reference_entity_poly_link.component_id pdbx_struct_assembly.details
410 pdbx_struct_assembly.id pdbx_struct_assembly.method_details
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892

15
data/pwa/README.md Normal file
View File

@@ -0,0 +1,15 @@
The files in this directory are used for deploying the Mol* Viewer as a PWA (Progressive Web App) at https://molstar.org/viewer/. They may serve as an example for creating your own PWA but wont work as-is. See `/script/deploy.js` for where these files are copied and how they are transformed during deployment.
## PWA features
- The Service Worker will cache static resources so the Viewer can be used without internet access. This works without installing, i.e., also in Firefox.
- Once installed, file types listed in the Manifest can be opened from, e.g., the Windows File Explorer.
## Notes for development
In Chrome you can see a list of installed PWAs at chrome://apps/. A right-click opens a menu with an option uninstall.
The Chrome Dev Tools have a section 'Application' to inspect and manage PWA aspects like the Manifest and Service Workers.

BIN
data/pwa/logo-144.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,39 @@
{
"id": "https://molstar.org/viewer/",
"name": "Mol* Viewer",
"short_name": "Mol*",
"description": "Mol* Viewer: a modern web app for 3D visualization and analysis of large biomolecular structures.",
"start_url": "./index.html",
"theme_color": "#eeece7",
"background_color": "#eeece7",
"display": "standalone",
"icons": [
{
"src": "favicon.ico",
"sizes": "48x48"
},
{
"src": "logo-144.png",
"sizes": "144x144"
}
],
"file_handlers": [
{
"action": "./index.html",
"accept": {
"application/vnd.molstar": [".molx", ".molj"],
"text/plain": [
".mol", ".mol2", ".sdf", ".sd", ".pdb", ".ent", ".pdbqt", ".cif", ".mcif", ".mmcif", ".xyz", ".gro", ".lammpstrj",
".cub", ".cube", ".dx"
],
"application/octet-stream": [
".bcif",
".dxbin", ".ccp4", ".mrc", ".map", ".dsn6", ".brix"
]
}
}
],
"launch_handler": {
"client_mode": ["auto"]
}
}

44
data/pwa/pwa.js Normal file
View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Andy Turner <agdturner@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
window.addEventListener('molstarViewerCreated', e => {
const viewer = e.detail.viewer;
// Handle incoming files
if ('launchQueue' in window) {
launchQueue.setConsumer((launchParams) => {
if (!launchParams.files.length) return;
const files = [];
for (const fileHandle of launchParams.files) {
files.push(fileHandle.getFile());
}
Promise.all(files).then((files) => {
viewer.loadFiles(files);
});
});
}
});
// Register Progressive Web App service worker.
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('./sw.js')
.then(function (registration) {
// Registration was successful
if (molstar.isDebugMode) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}
}, function (err) {
// registration failed :(
if (molstar.isDebugMode) {
console.error('ServiceWorker registration failed: ', err);
}
});
});
}

60
data/pwa/sw.js Normal file
View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Andy Turner <agdturner@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
/** version from package.json, to be filled in during deployment */
const VERSION = '__MOLSTAR_VERSION__';
const CACHE_NAME = `molstar-viewer-${VERSION}`;
// The static resources that the app needs to function.
const APP_STATIC_RESOURCES = [
'favicon.ico',
'index.html',
'molstar.css',
'molstar.js',
'manifest.webmanifest',
'logo-144.png',
'pwa.js'
];
async function cacheStaticResources() {
const cache = await caches.open(CACHE_NAME);
await cache.addAll(APP_STATIC_RESOURCES);
await self.skipWaiting(); // Ensures the new service worker takes control immediately.
}
async function deleteOldCaches() {
const keys = await caches.keys();
await Promise.all(
keys.map((key) => {
if (key !== CACHE_NAME) {
return caches.delete(key);
}
}),
);
await self.clients.claim(); // Ensures the new service worker takes control immediately.
}
async function respondWithCacheFirst(request) {
// Try to match the request with the cache
const cachedResponse = await caches.match(request);
return cachedResponse || fetch(request);
}
self.addEventListener('install', (event) => {
// console.log(`Service Worker version ${VERSION} installed.`);
event.waitUntil(cacheStaticResources());
});
self.addEventListener('activate', (event) => {
// console.log(`Service Worker version ${VERSION} activated.`);
event.waitUntil(deleteOldCaches());
});
self.addEventListener('fetch', (event) => {
event.respondWith(respondWithCacheFirst(event.request));
});

View File

@@ -29,7 +29,7 @@ node lib/commonjs/servers/model/server --sourceMap pdb-bcif '/opt/data/bcif/${id
| `--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 |
| `--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. Supported protocols: http://, https://, gs:// |
```sh
node lib/commonjs/servers/model/server [-h] [-v]

View File

@@ -24,7 +24,7 @@ npm install
Afterwards, build the project source:
```
npm run build-tsc
npm run build:lib
```
and run the server by
@@ -66,7 +66,7 @@ To achieve this, use the ``pack`` application (``node lib/commonjs/servers/volum
### 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.
The program ``lib/commonjs/servers/volume/query`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
### Navigating the Source Code
@@ -105,7 +105,7 @@ node lib/commonjs/servers/volume/server --idMap x-ray '/opt/data/xray/${id}.mdb'
| `--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. |
| `--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. - If using URL, it can be http://, https://, gs:// or file:// protocol.|
| `--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. |

View File

@@ -0,0 +1,4 @@
# Interactions extension
The Interactions extension enables computing or providing custom interactions between multiple selections/structures.
For usage, see the [example source code](https://github.com/molstar/molstar/tree/master/src/examples/interactions).

View File

@@ -94,7 +94,7 @@ The extension uses several transformations to process and visualize tunnel data:
To help users understand how to use these transformations in practice, include detailed examples:
### Visualizing Multiple Tunnels
This example ([runVisualizeTunnels](../../../src/extensions/sb-ncbr/tunnels/examples.ts#L19)) demonstrates how to visualize multiple tunnels from a fetched dataset.
This example (see `src/extensions/sb-ncbr/tunnels/examples.ts#L19`) demonstrates how to visualize multiple tunnels from a fetched dataset.
```typescript
update.toRoot()
.apply(TunnelsFromRawData, { data: tunnels })
@@ -104,7 +104,7 @@ update.toRoot()
```
### Visualizing a Single Tunnel
This example ([runVisualizeTunnel](../../../src/extensions/sb-ncbr/tunnels/examples.ts#L46)) shows how to visualize a single tunnel.
This example (see `src/extensions/sb-ncbr/tunnels/examples.ts#L46`) shows how to visualize a single tunnel.
```typescript
update.toRoot()
.apply(TunnelFromRawData, {

View File

@@ -25,7 +25,7 @@ import { PluginContext } from 'molstar/lib/mol-plugin/context';
git clone https://github.com/molstar/molstar.git
cd molstar
npm install
npm build
npm run build
```
--------------------

View File

@@ -0,0 +1,59 @@
# Exporting components
Export components data can be useful to reproduce the same view in a different visualization software.
To do that, one would need to loop over all components, extract its selection (for example by using atom indices) and its representations (type, coloring and sizing).
### Getting assets / molecular files
```js
for (const { asset, file } of plugin.managers.asset.assets) {
const isFile = asset.asset.kind === 'url'
console.log(asset.asset.id)
console.log(isFile)
const data = await file.arrayBuffer()
}
```
### Getting components per structure
```js
import { PluginStateObject as PSO } from 'molstar/lib/mol-plugin-state/objects';
//...
const componentManager = plugin.managers.structure.component;
for (const structure of componentManager.currentStructures) {
if (!structure.properties) {
continue;
}
const cell = plugin.state.data.select(structure.properties.cell.transform.ref)[0];
if (!cell || !cell.obj) {
continue;
}
const structureData = (cell.obj as PSO.Molecule.Structure).data;
for (const component of structure.components) {
if (!component.cell.obj) {
continue;
}
// For each component in each structure, display the content of the selection
Structure.eachAtomicHierarchyElement(component.cell.obj.data, {
atom: location => console.log(location.element)
});
for (const rep of component.representations) {
// For each representation of the component, display its type
console.log(rep.cell?.transform?.params?.type?.name)
// Also display the color for each atom
const colorThemeName = rep.cell.transform.params?.colorTheme.name;
const colorThemeParams = rep.cell.transform.params?.colorTheme.params;
const theme = plugin.representation.structure.themes.colorThemeRegistry.create(
colorThemeName || '',
{ structure: structureData },
colorThemeParams
) as ColorTheme<typeof colorThemeParams>;
Structure.eachAtomicHierarchyElement(component.cell.obj.data, {
atom: loc => console.log(theme.color(loc, false))
});
}
}
}
```

View File

@@ -22,7 +22,7 @@
* Discontinuous chains, i.e. gaps in the sequence (3sn6)
* Lots of sheets (1cbs)
* DNA (2np2, 1d66)
* C-alpha only (2rcj)
* C-alpha only (2RCJ, 6ZIG, 5AJ2)
* Not cyclic, but termini are backbone-only and within distance but seqIds are not compatible (6SW3)
* Close backbone atoms but not linked (e.g. 4HIV)
* Non-standard residues

View File

@@ -0,0 +1,193 @@
# Building a Custom Library
This page goes over creating a custom Mol\* based library usable inside a `<script>` tag in an HTML page.
## Setup
- Create a new npm/yarn package
- Install `molstar` and `esbuild` packages
```
mkdir molstar-lib
cd molstar-lib
npm init
npm install molstar
npm install esbuild --save-dev
```
## Example Library Code
Create new file `src/index.ts` (or `.js` if you don't want to use TypeScript):
```ts
import { DefaultPluginSpec, PluginSpec } from 'molstar/lib/mol-plugin/spec';
import { PluginContext } from 'molstar/lib/mol-plugin/context';
export async function initViewer(element: string | HTMLDivElement, options?: { spec?: PluginSpec }) {
const parent = typeof element === 'string' ? document.getElementById(element)! as HTMLDivElement : element;
const canvas = document.createElement('canvas') as HTMLCanvasElement;
parent.appendChild(canvas);
const spec = options?.spec ?? DefaultPluginSpec();
const plugin = new PluginContext(spec);
await plugin.init();
plugin.initViewer(canvas, parent);
return plugin;
}
export async function loadStructure(
plugin: PluginContext,
url: string,
options?: { format?: string, isBinary?: boolean }
) {
const data = await plugin.builders.data.download(
{ url, isBinary: options?.isBinary }
);
const trajectory = await plugin.builders.structure.parseTrajectory(
data,
options?.format ?? 'mmcif' as any
);
const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
return preset;
}
```
## Building the Library
Add new commands to the `scripts` section of the `package.json` file
```json
"scripts": {
"build": "esbuild src/index.ts --bundle --outfile=./build/js/index.js --global-name=molstarLib",
"watch": "esbuild src/index.ts --bundle --outfile=./build/js/index.js --global-name=molstarLib --watch"
}
```
and run the command `npm run build` (or `watch` for interactive development experience). This will create `build/js/index.js` file which can be imported with a `<script>` tag and the exported functions called view the `molstarLib` prefix (you can customize this parameter).
## Using the Library
Create file `build/index.html`:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* Library Example</title>
</head>
<style>
#viewer {
position: absolute;
width: 800px;
height: 600px;
}
</style>
<script type="text/javascript" src="./js/index.js"></script>
<body>
<div id="viewer"></div>
<script type="text/javascript">
async function init() {1
const plugin = await molstarLib.initViewer("viewer");
await molstarLib.loadStructure(
plugin,
"https://models.rcsb.org/4hhb.bcif",
{ isBinary: true }
);
}
init();
</script>
</body>
</html>
```
After opening `index.html` in a browser, you should see
![lib-example](lib-example.png)
## Using Mol* React UI
The above example does not make use of the default Mol\* React UI and any UI components are therefore the author's responsibility. The below examples show how to (re)use the Mol\* React UI.
- Create `src/ui.tsx`:
```tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { DefaultPluginUISpec, PluginUISpec } from 'molstar/lib/mol-plugin-ui/spec';
import { PluginUIContext } from 'molstar/lib/mol-plugin-ui/context';
import { Plugin } from 'molstar/lib/mol-plugin-ui/plugin';
export async function initViewerUI(element: string | HTMLDivElement, options?: { spec?: PluginUISpec }) {
const parent = typeof element === 'string' ? document.getElementById(element)! as HTMLDivElement : element;
const spec = { ...DefaultPluginUISpec(), ...options?.spec };
const plugin = new PluginUIContext(spec);
await plugin.init();
createRoot(parent).render(<Plugin plugin={plugin} />)
return plugin;
}
export async function loadStructure(plugin: PluginUIContext, url: string, options?: { format?: string, isBinary?: boolean }) {
const data = await plugin.builders.data.download({ url, isBinary: options?.isBinary });
const trajectory = await plugin.builders.structure.parseTrajectory(data, options?.format ?? 'mmcif' as any);
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
}
```
- Create `src/style.scss`:
```scss
@use '../node_modules/molstar/lib/mol-plugin-ui/skin/light.scss';
```
- Create `build/ui.html`:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* UI Library Example</title>
</head>
<link rel="stylesheet" type="text/css" href="css/style.css" />
<style>
#viewer {
position: absolute;
inset: 0;
}
</style>
<script type="text/javascript" src="./js/ui.js"></script>
<body>
<div id="viewer"></div>
<script type="text/javascript">
async function init() {
const plugin = await molstarLib.initViewerUI("viewer", {
spec: {
layout: {
initial: {
isExpanded: true,
showControls: true,
},
},
}
});
await molstarLib.loadStructure(plugin, "https://models.rcsb.org/4hhb.bcif", { isBinary: true });
}
init();
</script>
</body>
</html>
```
- Install `sass`: `npm install sass -save-dev` (or use [`esbuild` plugin](https://www.npmjs.com/package/esbuild-sass-plugin) and `import` the scss file in `ui.tsx`)
- Add scripts to `package.json`:
```json
"build-ui": "esbuild src/ui.tsx --bundle --outfile=./build/js/ui.js --global-name=molstarLib",
"css": "sass src/style.scss ./build/css/style.css"
```
- Run `npm run build-ui` and `npm run css` (skip if using `esbuild-sass-plugin`)
- Opening `build/ui.html`:
![ui-example](ui-example.png)

View File

@@ -6,7 +6,7 @@
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.
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-ui/context.ts#L12) 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`.
@@ -247,7 +247,7 @@ async function init() {
const canvas = <HTMLCanvasElement> document.getElementById('molstar-canvas');
const parent = <HTMLDivElement> document.getElementById('molstar-parent');
if (!plugin.initViewer(canvas, parent)) {
if (!(await plugin.initViewer(canvas, parent))) {
console.error('Failed to init Mol*');
return;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

View File

@@ -0,0 +1,102 @@
# Markdown Extension Manager
The `markdownExtensions` manager in `PluginContext.manager` allows customizing
the `Markdown` React component to enable executing commands and rendering custom content.
The main use case of this is enriching [MolViewSpec](`https://molstar.org/mol-view-spec`) support.
## API
- `PluginContext.manager.markdownExtensions.register*` functions can be used to register extensions and state/data resolvers to make the the manager work with plugin extension
- `PluginContext.manager.markdownExtensions.remove*` can be used to dynamically remove the above
## Commands
Extends Markdown Hyperlink syntax to support expressions of the form `[title](!c1=v1&c2=v2&...)` into an executable command. The command can be executed either on click, mouse enter, or mouse leave.
Generally, the command should be URL encoded, e.g., `a b` => `a%20b` (in JS, `encodeURIComponent`, in Python `urllib.parse.quote_plus/urlencode`).
### Built-in Commands
- `center-camera` - Centers the camera
- `apply-snapshot=key` - Loads snapshots with the provided key
- `focus-refs=ref1,ref2,...` - On click, focuses nodes with the provided refs
- `highlight-refs=ref1,ref2,...` - On mouse over, highlights the provided refs
- `query=...&lang=...&action=highlight,focus&focus-radius=...`
- `query` is an expression (e.g., `resn HEM` when using PyMol syntax)
- (optional) `lang` is one of `mol-script` (default), `pymol`, `vmd`, `jmol`
- (optional) `action` is an array of `highlight` (default), `focus` (multiple actions can be specified)
- (optional) `focus-radius` is extra distance applied when focusing the selection (default is `3`)
- Example: `[HEM](!query%3Dresn%20HEM%26lang%3Dpymol%26action%3Dhighlight%2Cfocus)` highlights or focuses the HEM residue (the command must be URL encoded because it contains spaces and possibly other special characters)
## Custom Content
Extends Markdown Image syntax to support expressions of the form `![alt](!c1=v1&c2=v2&...)` to render custom elements instead.
### Built-in Custom Content
- `color-swatch=color` - Renders a box with the provided color
- Color palettes:
- `color-palette-name=name` - Renders a gradient with the provided named color palette (see `mol-util/color/lists.ts` for supported color schemes)
- `color-palette-colors=color1,color2` - Renders a gradient with the provided colors
- `color-palette-width=CCS-value` - Specifies the width of the element, defaults to `150px`
- `color-palette-height=CCS-value` - Specified the height of the element, defaults to `0.5em`
- `color-palette-discrete` - Renders discrete color list instead of interpolating
## Example
```markdown
### Highlight/Focus:
- ![blue](!color-swatch=blue) [polymer](!highlight-refs=polymer&focus-refs=polymer)
- ![blue](!color-swatch=red) [ligand](!highlight-refs=ligand&focus-refs=ligand)
- [both](!highlight-refs=polymer,ligand&focus-refs=polymer,ligand)
### Color Palettes
|name|visual|
|---:|---|
|viridis|![viridis](!color-palette-name=viridis)|
|rainbow (discrete)|![simple-rainbow](!color-palette-name=simple-rainbow&color-palette-discrete)|
|custom|![custom](!color-palette-colors=red,#00ff00,rgb(0,0,255))|
### Camera controls
- [center](!center-camera)
### Image embedded in MVSX file
![mvsx image](logo.png)
```
This works with the MolViewSpec state built by:
```py
import molviewspec as mvs
builder = mvs.create_builder()
assets = {
"1cbs.cif": "https://files.wwpdb.org/download/1cbs.cif",
"logo.png": "https://molstar.org/img/molstar-logo.png",
}
model = (
builder.download(url="1cbs.cif")
.parse(format="mmcif")
.model_structure()
)
(
model.component(selector="polymer")
.representation(ref="polymer")
.color(color="blue")
)
(
model.component(selector="ligand")
.representation(ref="ligand")
.color(color="red")
)
mvsx = mvs.MVSX(
data=builder.get_state(
description="""...""" # inline the code above
),
assets=assets
)
```

View File

@@ -70,7 +70,6 @@ This relies on the concept of `Expression` which is basically a intermediate rep
### 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'
@@ -82,8 +81,8 @@ export function select_multiple() {
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]])
'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))
@@ -98,13 +97,52 @@ Inspect these examples to get a better feeling for this syntax: `https://github.
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)
'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);
```
## Query Functions
Instead of building expressions, query functions can be created directly, e.g.:
```ts
import { atoms } from 'mol-model/structure/query/queries/generators';
const query = atoms({
residueTest: ctx => {
const seqId = StructureProperties.residue.label_seq_id(ctx.element);
return seqId > 10 && seqId < 25;
},
});
const selection = query(new QueryContext(structure));
// ...
```
## Selection Schema
For simple selections, the `StructureElement.Schema` can be used to reference elements within a protein structure using mmCIF `atom_site` field names, e.g.:
```ts
const ala121: StructureElement.Schema = { label_asym_id: 'A', label_seq_id: 121 };
const residues: StructureElement.Schema = {
items: {
auth_asym_id: ['A', 'B'],
auth_seq_id: [10, 11],
}
};
const loci = StructureElement.Loci.fromSchema(structure, residues);
```
Usually, a code editor such as VS Code will auto-suggest all the available field names.
## Helper Functions
Given an `Expression`, `QueryFn`, or `StructureElement.Schema` it is possible to use `fromExpression/Query/Schema` functions on `StructureElement.Loci` and `StructureElement.Bundle`.

View File

@@ -0,0 +1,45 @@
# Assign custom conformation to a Model
This document shows how to update model conformation dynamically using the `ModelWithCoordinates` transforms. If this does not work well with your particular use case, it is suggested to write a custom version of `ModelWithCoordinates` with similar usage as outlined in this document.
```ts
async function animateFirstXCoordinateExample(plugin: PluginContext, url: string, format: BuiltInTrajectoryFormat) {
// Load data
const _data = await plugin.builders.data.download({ url });
const trajectory = await plugin.builders.structure.parseTrajectory(_data, format);
const hierarchy = await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
if (!hierarchy) return;
// Insert ModelWithCoordinates cell to be updated in the loop bellow
const coordinatesNode = await plugin.build().to(hierarchy!.model).insert(ModelWithCoordinates).commit();
const x0 = hierarchy!.model.data!.atomicConformation.x[0];
let xOffset = 0;
async function animateFirstXCoord() {
// Normally, the whole conformation would come from an API/library call, but here we fake it:
const { x, y, z } = hierarchy!.model.data!.atomicConformation;
const nextX = [...(x as number[])];
nextX[0] = x0 + xOffset;
xOffset += 0.05;
if (xOffset > 1) xOffset = 0;
// Construct new coodinate frame from the data and commit the update.
// Rest of the state tree will reconcile automatically.
await plugin.build().to(coordinatesNode).update({
atomicCoordinateFrame: {
elementCount: x.length,
time: { value: 0, unit: 'step' },
xyzOrdering: { isIdentity: true },
x: nextX,
y,
z,
}
}).commit();
requestAnimationFrame(animateFirstXCoord);
}
animateFirstXCoord();
}
// animateFirstXCoordinateExample('https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/2244/record/SDF/?record_type=3d', 'sdf');
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

View File

@@ -25,20 +25,23 @@ markdown_extensions:
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
- Custom Library: 'plugin/custom-library.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'
- Managers:
- Markdown Extensions: 'plugin/managers/markdown-extensions.md'
- State Transforms:
- Custom Trajectory: 'plugin/transforms/custom-trajectory.md'
- Custom Conformation: 'plugin/transforms/custom-conformation.md'
- Data Access Tools:
- 'data-access-tools/model-server.md'
- Volume Server:
@@ -54,6 +57,8 @@ nav:
- MolViewSpec: 'extensions/mvs/index.md'
- wwPDB StructConn: 'extensions/struct-conn.md'
- Tunnels: 'extensions/tunnels.md'
- Interactions: 'extensions/interactions.md'
- Misc:
- Interesting PDB entries: misc/interesting-pdb-entries.md
repo_url: https://github.com/molstar/docs
- Exporting component data: misc/exporting-components.md
repo_url: https://github.com/molstar/docs

110
eslint.config.mjs Normal file
View File

@@ -0,0 +1,110 @@
import { defineConfig } from "eslint/config";
import globals from "globals";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
export default defineConfig([{
ignores: [
"node_modules/*",
"build/*",
"deploy/*",
"docs/site/*",
"lib/*",
"eslint.config.mjs",
"build.mjs",
]
},{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
ecmaVersion: 2018,
sourceType: "module",
parserOptions: {
ecmaFeatures: {
impliedStrict: true,
},
},
},
rules: {
indent: "off",
"arrow-parens": ["off", "as-needed"],
"brace-style": ["error", "1tbs", {
allowSingleLine: true,
}],
"comma-spacing": "off",
"space-infix-ops": "off",
"comma-dangle": "off",
eqeqeq: ["error", "smart"],
"import/order": "off",
"no-eval": "warn",
"no-extend-native": "warn",
"no-new-wrappers": "warn",
"no-trailing-spaces": "error",
"no-unsafe-finally": "warn",
"no-self-compare": "warn",
"no-var": "error",
"spaced-comment": "error",
semi: "warn",
"no-restricted-syntax": ["error", {
selector: "ExportDefaultDeclaration",
message: "Default exports are not allowed",
}],
"no-throw-literal": "error",
"key-spacing": "error",
"object-curly-spacing": ["error", "always"],
"array-bracket-spacing": "error",
"space-in-parens": "error",
"computed-property-spacing": "error",
"prefer-const": ["error", {
destructuring: "all",
ignoreReadBeforeAssign: false,
}],
"space-before-function-paren": "off",
"func-call-spacing": "off",
"no-multi-spaces": "error",
"block-spacing": "error",
"keyword-spacing": "warn",
"space-before-blocks": "error",
"semi-spacing": "error",
"no-constant-binary-expression": "error",
},
}, {
files: ["**/*.ts", "**/*.tsx"],
plugins: {
"@typescript-eslint": typescriptEslint,
},
languageOptions: {
parser: tsParser,
ecmaVersion: 5,
sourceType: "module",
parserOptions: {
project: ["tsconfig.eslint.json"],
},
},
rules: {
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/class-name-casing": "off",
"@typescript-eslint/member-delimiter-style": ["off", {
multiline: {
delimiter: "none",
requireLast: true,
},
singleline: {
delimiter: "semi",
requireLast: false,
},
}],
"@typescript-eslint/prefer-namespace-keyword": "warn",
"@typescript-eslint/semi": ["off", null],
},
}]);

View File

@@ -0,0 +1,456 @@
forcefield_3904
RDKit 3D
49 52 0 0 0 0 0 0 0 0999 V2000
7.1950 23.7840 6.1780 C 0 0 0 0 0 0 0 0 0 0 0 0
5.7810 23.6440 6.1320 O 0 0 0 0 0 0 0 0 0 0 0 0
5.0380 24.7500 6.4550 C 0 0 0 0 0 0 0 0 0 0 0 0
4.8270 25.8570 5.6380 C 0 0 0 0 0 0 0 0 0 0 0 0
4.0460 26.9150 6.0970 C 0 0 0 0 0 0 0 0 0 0 0 0
3.4760 26.8670 7.3710 C 0 0 0 0 0 0 0 0 0 0 0 0
3.6720 25.7630 8.2160 C 0 0 0 0 0 0 0 0 0 0 0 0
3.0720 25.7780 9.4800 N 0 0 0 0 0 0 0 0 0 0 0 0
1.8090 25.4470 9.9650 C 0 0 0 0 0 0 0 0 0 0 0 0
0.8080 26.1730 9.4160 N 0 0 0 0 0 0 0 0 0 0 0 0
-0.4770 25.9480 9.8040 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.5110 26.6810 9.2540 O 0 0 0 0 0 0 0 0 0 0 0 0
-1.0950 27.9270 8.6960 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.1890 28.9860 8.9110 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.6300 30.4110 8.7600 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.7130 31.4650 8.9780 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.4040 31.2910 10.3270 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.9520 29.8780 10.4990 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.8690 28.8240 10.2810 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.7400 24.9710 10.7610 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.8850 24.4940 11.3660 N 0 0 0 0 0 0 0 0 0 0 0 0
-1.4690 23.5570 12.1930 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.1090 23.4080 12.1480 N 0 0 0 0 0 0 0 0 0 0 0 0
0.3700 24.3040 11.2380 C 0 0 0 0 0 0 0 0 0 0 0 0
1.6480 24.4860 10.8910 N 0 0 0 0 0 0 0 0 0 0 0 0
4.4600 24.7130 7.7330 C 0 0 0 0 0 0 0 0 0 0 0 0
7.5263 23.8168 7.2264 H 0 0 0 0 0 0 0 0 0 0 0 0
7.6649 22.9280 5.6716 H 0 0 0 0 0 0 0 0 0 0 0 0
7.4879 24.7155 5.6716 H 0 0 0 0 0 0 0 0 0 0 0 0
5.2751 25.8961 4.6342 H 0 0 0 0 0 0 0 0 0 0 0 0
3.8776 27.7913 5.4538 H 0 0 0 0 0 0 0 0 0 0 0 0
2.8616 27.7104 7.7192 H 0 0 0 0 0 0 0 0 0 0 0 0
3.6420 26.0930 10.2520 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.1673 28.2555 9.1874 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.9189 27.8009 7.6175 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.9466 28.8274 8.1294 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.8310 30.5615 9.5009 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.2424 30.5222 7.7366 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.2524 32.4632 8.9392 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.4689 31.3496 8.1873 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.6781 31.4924 11.1285 H 0 0 0 0 0 0 0 0 0 0 0 0
-4.2475 31.9954 10.3749 H 0 0 0 0 0 0 0 0 0 0 0 0
-4.3529 29.7723 11.5179 H 0 0 0 0 0 0 0 0 0 0 0 0
-4.7404 29.7213 9.7481 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.1096 28.9239 11.0705 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.3427 27.8318 10.3148 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.1334 22.9663 12.8408 H 0 0 0 0 0 0 0 0 0 0 0 0
0.4510 22.7600 12.6820 H 0 0 0 0 0 0 0 0 0 0 0 0
4.6312 23.8345 8.3724 H 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0
2 3 1 0
3 4 2 0
4 5 1 0
5 6 2 0
6 7 1 0
7 8 1 0
8 9 1 0
9 10 2 0
10 11 1 0
11 12 1 0
12 13 1 0
13 14 1 0
14 15 1 0
15 16 1 0
16 17 1 0
17 18 1 0
18 19 1 0
11 20 2 0
20 21 1 0
21 22 2 0
22 23 1 0
23 24 1 0
24 25 2 0
7 26 2 0
26 3 1 0
25 9 1 0
19 14 1 0
24 20 1 0
1 27 1 0
1 28 1 0
1 29 1 0
4 30 1 0
5 31 1 0
6 32 1 0
8 33 1 0
13 34 1 0
13 35 1 0
14 36 1 0
15 37 1 0
15 38 1 0
16 39 1 0
16 40 1 0
17 41 1 0
17 42 1 0
18 43 1 0
18 44 1 0
19 45 1 0
19 46 1 0
22 47 1 0
23 48 1 0
26 49 1 0
M END
> <ligandCode> (1)
forcefield_3904
> <ligandName> (1)
klr_22
$$$$
forcefield_3905
RDKit 3D
49 52 0 0 0 0 0 0 0 0999 V2000
6.5460 25.1350 3.8360 N 0 0 0 0 0 0 0 0 0 0 0 0
5.2550 25.5560 3.9960 C 0 0 0 0 0 0 0 0 0 0 0 0
4.5630 25.8360 3.0240 O 0 0 0 0 0 0 0 0 0 0 0 0
4.7190 25.6170 5.3820 C 0 0 0 0 0 0 0 0 0 0 0 0
4.0120 26.7570 5.7730 C 0 0 0 0 0 0 0 0 0 0 0 0
3.4880 26.8570 7.0690 C 0 0 0 0 0 0 0 0 0 0 0 0
3.6630 25.8340 8.0050 C 0 0 0 0 0 0 0 0 0 0 0 0
3.1640 25.8720 9.3110 N 0 0 0 0 0 0 0 0 0 0 0 0
1.9510 25.5250 9.9030 C 0 0 0 0 0 0 0 0 0 0 0 0
0.8900 26.1930 9.3960 N 0 0 0 0 0 0 0 0 0 0 0 0
-0.3560 25.9480 9.8850 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.4490 26.6220 9.3800 O 0 0 0 0 0 0 0 0 0 0 0 0
-1.1050 27.7730 8.6070 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.7010 29.0180 9.2810 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.5340 30.2520 8.3770 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.5490 31.3650 8.6570 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.0430 31.3400 10.0950 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.7320 30.0170 10.4330 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.1730 28.8170 9.6630 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.5160 25.0110 10.9050 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.5980 24.5310 11.6140 N 0 0 0 0 0 0 0 0 0 0 0 0
-1.0930 23.6490 12.4490 C 0 0 0 0 0 0 0 0 0 0 0 0
0.2630 23.5390 12.3140 N 0 0 0 0 0 0 0 0 0 0 0 0
0.6490 24.4020 11.3310 C 0 0 0 0 0 0 0 0 0 0 0 0
1.8910 24.6070 10.8850 N 0 0 0 0 0 0 0 0 0 0 0 0
4.3890 24.7010 7.5990 C 0 0 0 0 0 0 0 0 0 0 0 0
4.9170 24.5890 6.3060 C 0 0 0 0 0 0 0 0 0 0 0 0
7.0340 24.6220 4.5560 H 0 0 0 0 0 0 0 0 0 0 0 0
6.8360 24.9760 2.8790 H 0 0 0 0 0 0 0 0 0 0 0 0
3.8658 27.5811 5.0592 H 0 0 0 0 0 0 0 0 0 0 0 0
2.9275 27.7591 7.3554 H 0 0 0 0 0 0 0 0 0 0 0 0
3.7800 26.2290 10.0270 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.0105 27.8693 8.5543 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.5056 27.6720 7.5875 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.1446 29.1858 10.2149 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.5231 30.6584 8.5280 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.7027 29.9170 7.3429 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.0722 32.3370 8.4626 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.4156 31.2068 7.9983 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.1848 31.4776 10.7693 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.7762 32.1504 10.2201 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.6091 29.8266 11.5094 H 0 0 0 0 0 0 0 0 0 0 0 0
-4.7879 30.1213 10.1428 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.2575 27.9194 10.2932 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.7539 28.7064 8.7355 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.6897 23.0698 13.1690 H 0 0 0 0 0 0 0 0 0 0 0 0
0.8800 22.9330 12.8360 H 0 0 0 0 0 0 0 0 0 0 0 0
4.5467 23.8809 8.3150 H 0 0 0 0 0 0 0 0 0 0 0 0
5.4866 23.6927 6.0193 H 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0
2 3 2 0
2 4 1 0
4 5 2 0
5 6 1 0
6 7 2 0
7 8 1 0
8 9 1 0
9 10 2 0
10 11 1 0
11 12 1 0
12 13 1 0
13 14 1 0
14 15 1 0
15 16 1 0
16 17 1 0
17 18 1 0
18 19 1 0
11 20 2 0
20 21 1 0
21 22 2 0
22 23 1 0
23 24 1 0
24 25 2 0
7 26 1 0
26 27 2 0
27 4 1 0
25 9 1 0
19 14 1 0
24 20 1 0
1 28 1 0
1 29 1 0
5 30 1 0
6 31 1 0
8 32 1 0
13 33 1 0
13 34 1 0
14 35 1 0
15 36 1 0
15 37 1 0
16 38 1 0
16 39 1 0
17 40 1 0
17 41 1 0
18 42 1 0
18 43 1 0
19 44 1 0
19 45 1 0
22 46 1 0
23 47 1 0
26 48 1 0
27 49 1 0
M END
> <ligandCode> (2)
forcefield_3905
> <ligandName> (2)
1oiy-1
$$$$
forcefield_14264
RDKit 3D
50 53 0 0 0 0 0 0 0 0999 V2000
4.9220 23.4040 3.0090 N 0 0 0 0 0 0 0 0 0 0 0 0
5.8970 24.7200 3.4040 S 0 0 0 0 0 6 0 0 0 0 0 0
7.2120 24.2200 3.7260 O 0 0 0 0 0 0 0 0 0 0 0 0
5.6670 25.6980 2.3670 O 0 0 0 0 0 0 0 0 0 0 0 0
5.1310 25.2890 4.8970 C 0 0 0 0 0 0 0 0 0 0 0 0
4.3720 26.4580 4.8910 C 0 0 0 0 0 0 0 0 0 0 0 0
3.7730 26.8990 6.0760 C 0 0 0 0 0 0 0 0 0 0 0 0
3.9290 26.1970 7.2780 C 0 0 0 0 0 0 0 0 0 0 0 0
3.3430 26.5790 8.4890 N 0 0 0 0 0 0 0 0 0 0 0 0
2.1400 26.2820 9.1290 C 0 0 0 0 0 0 0 0 0 0 0 0
1.0830 26.9670 8.6370 N 0 0 0 0 0 0 0 0 0 0 0 0
-0.1520 26.7740 9.1730 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.2430 27.4630 8.6800 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.9280 28.7750 8.2140 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.2060 29.6320 8.2420 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.2120 29.0810 9.2730 C 0 0 0 0 0 0 0 0 0 0 0 0
-4.1810 30.1570 9.7450 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.4520 31.3060 10.4440 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.0250 31.5210 9.9360 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.8770 31.1190 8.4740 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.3060 25.8730 10.2240 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.3770 25.4460 10.9810 N 0 0 0 0 0 0 0 0 0 0 0 0
-0.8720 24.5730 11.8270 C 0 0 0 0 0 0 0 0 0 0 0 0
0.4750 24.4190 11.6520 N 0 0 0 0 0 0 0 0 0 0 0 0
0.8530 25.2430 10.6320 C 0 0 0 0 0 0 0 0 0 0 0 0
2.0880 25.3990 10.1410 N 0 0 0 0 0 0 0 0 0 0 0 0
4.7090 25.0260 7.2550 C 0 0 0 0 0 0 0 0 0 0 0 0
5.3130 24.5730 6.0780 C 0 0 0 0 0 0 0 0 0 0 0 0
4.1960 23.3080 3.7210 H 0 0 0 0 0 0 0 0 0 0 0 0
5.4460 22.5390 2.8810 H 0 0 0 0 0 0 0 0 0 0 0 0
4.2447 27.0304 3.9603 H 0 0 0 0 0 0 0 0 0 0 0 0
3.1666 27.8167 6.0634 H 0 0 0 0 0 0 0 0 0 0 0 0
3.8380 27.2640 9.0420 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.1661 29.2261 8.8668 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.5358 28.7227 7.1876 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.6885 29.5690 7.2555 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.7847 28.2635 8.8106 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.6467 28.7218 10.1456 H 0 0 0 0 0 0 0 0 0 0 0 0
-4.7231 30.5555 8.8747 H 0 0 0 0 0 0 0 0 0 0 0 0
-4.8753 29.7014 10.4664 H 0 0 0 0 0 0 0 0 0 0 0 0
-4.0232 32.2317 10.2804 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.3803 31.0434 11.5098 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.7667 32.5851 10.0403 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.3511 30.8895 10.5336 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.5603 31.7316 7.8675 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.8284 31.2816 8.1841 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.4619 24.0333 12.5825 H 0 0 0 0 0 0 0 0 0 0 0 0
1.0910 23.8110 12.1720 H 0 0 0 0 0 0 0 0 0 0 0 0
4.8468 24.4538 8.1843 H 0 0 0 0 0 0 0 0 0 0 0 0
5.9261 23.6598 6.0846 H 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0
2 3 2 0
2 4 2 0
2 5 1 0
5 6 2 0
6 7 1 0
7 8 2 0
8 9 1 0
9 10 1 0
10 11 2 0
11 12 1 0
12 13 1 0
13 14 1 0
14 15 1 0
15 16 1 0
16 17 1 0
17 18 1 0
18 19 1 0
19 20 1 0
12 21 2 0
21 22 1 0
22 23 2 0
23 24 1 0
24 25 1 0
25 26 2 0
8 27 1 0
27 28 2 0
28 5 1 0
26 10 1 0
20 15 1 0
25 21 1 0
1 29 1 0
1 30 1 0
6 31 1 0
7 32 1 0
9 33 1 0
14 34 1 0
14 35 1 0
15 36 1 0
16 37 1 0
16 38 1 0
17 39 1 0
17 40 1 0
18 41 1 0
18 42 1 0
19 43 1 0
19 44 1 0
20 45 1 0
20 46 1 0
23 47 1 0
24 48 1 0
27 49 1 0
28 50 1 0
M END
> <ligandCode> (3)
forcefield_14264
> <ligandName> (3)
1h1s
$$$$
forcefield_14265
RDKit 3D
50 53 0 0 0 0 0 0 0 0999 V2000
5.9560 25.0880 4.1850 N 0 0 0 0 0 0 0 0 0 0 0 0
5.6880 24.2300 5.6100 S 0 0 0 0 0 6 0 0 0 0 0 0
4.9010 23.0800 5.2270 O 0 0 0 0 0 0 0 0 0 0 0 0
6.9470 24.1160 6.3060 O 0 0 0 0 0 0 0 0 0 0 0 0
4.6500 25.3510 6.5050 C 0 0 0 0 0 0 0 0 0 0 0 0
4.3190 26.5790 5.9340 C 0 0 0 0 0 0 0 0 0 0 0 0
3.4970 27.4490 6.6410 C 0 0 0 0 0 0 0 0 0 0 0 0
3.0220 27.0950 7.9100 C 0 0 0 0 0 0 0 0 0 0 0 0
3.3680 25.8770 8.5100 C 0 0 0 0 0 0 0 0 0 0 0 0
2.9060 25.4620 9.7630 N 0 0 0 0 0 0 0 0 0 0 0 0
1.6520 25.1280 10.2760 C 0 0 0 0 0 0 0 0 0 0 0 0
0.6590 25.9320 9.8350 N 0 0 0 0 0 0 0 0 0 0 0 0
-0.6150 25.7270 10.2650 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.6410 26.5420 9.8330 O 0 0 0 0 0 0 0 0 0 0 0 0
-1.2650 27.5510 8.8940 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.4150 28.9670 9.4830 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.0650 29.8960 8.4420 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.7130 31.1080 9.0990 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.8410 30.6980 10.0490 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.6490 29.3080 10.6570 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.1750 28.9600 10.8270 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.8720 24.6910 11.1610 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.0060 24.2140 11.7870 N 0 0 0 0 0 0 0 0 0 0 0 0
-1.5910 23.2030 12.5210 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.2430 23.0050 12.3980 N 0 0 0 0 0 0 0 0 0 0 0 0
0.2320 23.9470 11.5330 C 0 0 0 0 0 0 0 0 0 0 0 0
1.5000 24.1080 11.1380 N 0 0 0 0 0 0 0 0 0 0 0 0
4.1890 25.0030 7.7800 C 0 0 0 0 0 0 0 0 0 0 0 0
6.4350 24.4790 3.5180 H 0 0 0 0 0 0 0 0 0 0 0 0
5.1080 25.4920 3.7910 H 0 0 0 0 0 0 0 0 0 0 0 0
4.7020 26.8549 4.9404 H 0 0 0 0 0 0 0 0 0 0 0 0
3.2187 28.4186 6.2023 H 0 0 0 0 0 0 0 0 0 0 0 0
2.3612 27.7900 8.4488 H 0 0 0 0 0 0 0 0 0 0 0 0
3.5640 25.4960 10.5280 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.2150 27.3950 8.6054 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.9312 27.4715 8.0223 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.4134 29.3591 9.7135 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.2920 30.2413 7.7397 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.8482 29.3296 7.9168 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.9484 31.6568 9.6685 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.1423 31.7400 8.3076 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.8921 31.4320 10.8667 H 0 0 0 0 0 0 0 0 0 0 0 0
-4.7682 30.6682 9.4579 H 0 0 0 0 0 0 0 0 0 0 0 0
-4.1367 29.2806 11.6426 H 0 0 0 0 0 0 0 0 0 0 0 0
-4.0960 28.5721 9.9724 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.7122 29.6974 11.4994 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.1140 27.9439 11.2440 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.2486 22.5915 13.1563 H 0 0 0 0 0 0 0 0 0 0 0 0
0.3120 22.2980 12.8550 H 0 0 0 0 0 0 0 0 0 0 0 0
4.4733 24.0335 8.2150 H 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0
2 3 2 0
2 4 2 0
2 5 1 0
5 6 2 0
6 7 1 0
7 8 2 0
8 9 1 0
9 10 1 0
10 11 1 0
11 12 2 0
12 13 1 0
13 14 1 0
14 15 1 0
15 16 1 0
16 17 1 0
17 18 1 0
18 19 1 0
19 20 1 0
20 21 1 0
13 22 2 0
22 23 1 0
23 24 2 0
24 25 1 0
25 26 1 0
26 27 2 0
9 28 2 0
28 5 1 0
27 11 1 0
21 16 1 0
26 22 1 0
1 29 1 0
1 30 1 0
6 31 1 0
7 32 1 0
8 33 1 0
10 34 1 0
15 35 1 0
15 36 1 0
16 37 1 0
17 38 1 0
17 39 1 0
18 40 1 0
18 41 1 0
19 42 1 0
19 43 1 0
20 44 1 0
20 45 1 0
21 46 1 0
21 47 1 0
24 48 1 0
25 49 1 0
28 50 1 0
M END
> <ligandCode> (4)
forcefield_14265
> <ligandName> (4)
1oiu
$$$$

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

31697
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "4.4.0",
"version": "5.0.0-dev.4",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -10,29 +10,30 @@
"bugs": {
"url": "https://github.com/molstar/molstar/issues"
},
"engines": {
"node": ">=18.0.0"
},
"scripts": {
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"test": "npm install --no-save \"gl@^6.0.2\" && npm run lint && jest",
"jest": "jest",
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
"clean": "node ./scripts/clean.js",
"clean": "node ./scripts/clean.js --all",
"clean:build": "node ./scripts/clean.js --build",
"build": "npm run build:apps && npm run build:lib",
"build:apps": "node ./scripts/build.mjs -a -e --prd",
"build:lib": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\" && npm run build:lib-extra",
"build:lib-extra": "node scripts/write-version.mjs && cpx \"src/**/*.{scss,html,ico,jpg}\" lib/ && cpx \"src/**/*.{scss,html,ico,jpg}\" lib/commonjs/ && tsc-alias -p tsconfig.json",
"rebuild": "npm run clean && npm run build",
"build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
"build-tsc": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\"",
"build-extra": "cpx \"src/**/*.{scss,html,ico,jpg}\" lib/",
"build-webpack": "webpack --mode production --config ./webpack.config.production.js",
"build-webpack-viewer": "webpack --mode production --config ./webpack.config.viewer.js",
"watch": "concurrently -c \"green,green,gray,gray\" --names \"tsc,srv,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-servers\" \"npm:watch-extra\" \"npm:watch-webpack\"",
"watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
"watch-viewer-debug": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer-debug\"",
"watch-tsc": "tsc --watch --incremental",
"watch-servers": "tsc --build tsconfig.commonjs.json --watch --incremental",
"watch-extra": "cpx \"src/**/*.{scss,html,ico,jpg}\" lib/ --watch",
"watch-webpack": "webpack -w --mode development --stats minimal",
"watch-webpack-viewer": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.js",
"watch-webpack-viewer-debug": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.debug.js",
"dev": "node ./scripts/build.mjs",
"dev:all": "node ./scripts/build.mjs -a -e -bt",
"dev:viewer": "node ./scripts/build.mjs -a viewer",
"dev:apps": "node ./scripts/build.mjs -a",
"dev:examples": "node ./scripts/build.mjs -e",
"dev:browser-tests": "node ./scripts/build.mjs -bt",
"serve": "http-server -p 1338 -g",
"deploy:local": "npm run clean:build && npm run build:apps && node ./scripts/deploy.js --local",
"deploy:remote": "npm run clean:build && npm run build:apps && node ./scripts/deploy.js",
"model-server": "node lib/commonjs/servers/model/server.js",
"model-server-watch": "nodemon --watch lib lib/commonjs/servers/model/server.js",
"volume-server-test": "node lib/commonjs/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
@@ -43,7 +44,8 @@
},
"files": [
"lib/",
"build/viewer/"
"build/viewer/",
"build/mvs-stories/"
],
"bin": {
"cif2bcif": "lib/commonjs/cli/cif2bcif/index.js",
@@ -108,79 +110,85 @@
"Cai Huiyu <szmun.caihy@gmail.com>",
"Ryan DiRisio <rjdiris@gmail.com>",
"Dušan Veľký <dvelky@mail.muni.cz>",
"Neli Fonseca <neli@ebi.ac.uk>"
"Neli Fonseca <neli@ebi.ac.uk>",
"Paul Pillot <paul.pillot@tandemai.com>",
"Herman Bergwerf <post@hbergwerf.nl>",
"Eric E <etongfu@outlook.com>",
"Xavier Martinez <xavier.martinez.xm@gmail.com>",
"Alex Chan <smalldirkalex@gmail.com>",
"Simeon Borko <simeon.borko@gmail.com>",
"Ventura Rivera <venturaxrivera@gmail.com>",
"Andy Turner <agdturner@gmail.com>",
"Lukáš Polák <admin@lukaspolak.cz>",
"Chetan Mishra <chetan.s115@gmail.com>",
"Zach Charlop-Powers <zach.charlop.powers@gmail.com>"
],
"license": "MIT",
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/cors": "^2.8.19",
"@types/gl": "^6.0.5",
"@types/jest": "^29.5.14",
"@types/pngjs": "^6.0.5",
"@types/jest": "^29.5.12",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.14.1",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@typescript-eslint/eslint-plugin": "^8.34.0",
"@typescript-eslint/parser": "^8.34.0",
"benchmark": "^2.1.4",
"concurrently": "^8.2.2",
"cpx2": "^7.0.1",
"crypto-browserify": "^3.12.0",
"concurrently": "^9.1.2",
"cpx2": "^8.0.0",
"css-loader": "^7.1.2",
"eslint": "^8.57.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^11.2.0",
"esbuild": "^0.25.5",
"esbuild-sass-plugin": "^3.3.1",
"eslint": "^9.29.0",
"fs-extra": "^11.3.0",
"http-server": "^14.1.1",
"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.3.1",
"react-dom": "^18.3.1",
"sass": "^1.77.6",
"sass-loader": "^14.2.1",
"simple-git": "^3.25.0",
"stream-browserify": "^3.0.0",
"style-loader": "^4.0.0",
"ts-jest": "^29.1.5",
"typescript": "^5.5.2",
"webpack": "^5.92.1",
"webpack-cli": "^5.1.4"
"sass": "^1.89.1",
"simple-git": "^3.28.0",
"ts-jest": "^29.3.4",
"tsc-alias": "^1.8.16",
"typescript": "^5.8.3"
},
"dependencies": {
"@types/argparse": "^2.0.16",
"@types/argparse": "^2.0.17",
"@types/benchmark": "^2.1.5",
"@types/compression": "1.7.5",
"@types/express": "^4.17.21",
"@types/node": "^18.19.39",
"@types/node-fetch": "^2.6.11",
"@types/compression": "1.8.1",
"@types/express": "^5.0.3",
"@types/node": "^18.19.111",
"@types/node-fetch": "^2.6.12",
"@types/swagger-ui-dist": "3.30.5",
"argparse": "^2.0.1",
"body-parser": "^1.20.2",
"compression": "^1.7.4",
"compression": "^1.8.0",
"cors": "^2.8.5",
"express": "^4.19.2",
"express": "^5.1.0",
"h264-mp4-encoder": "^1.0.12",
"immer": "^10.1.1",
"immutable": "^4.3.6",
"io-ts": "^2.2.21",
"immutable": "^5.1.2",
"io-ts": "^2.2.22",
"node-fetch": "^2.7.0",
"react-markdown": "^9.0.1",
"rxjs": "^7.8.1",
"swagger-ui-dist": "^5.17.14",
"tslib": "^2.6.3",
"util.promisify": "^1.1.2",
"xhr2": "^0.2.1"
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
"rxjs": "^7.8.2",
"swagger-ui-dist": "^5.24.0",
"tslib": "^2.8.1",
"util.promisify": "^1.1.3"
},
"peerDependencies": {
"@google-cloud/storage": "^7.14.0",
"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"
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
},
"peerDependenciesMeta": {
"@google-cloud/storage": {
"optional": true
},
"canvas": {
"optional": true
},

307
scripts/build.mjs Normal file
View File

@@ -0,0 +1,307 @@
/**
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Eric E <etongfu@@outlook.com>
*/
import * as esbuild from 'esbuild';
import * as fs from 'fs';
import * as path from 'path';
import * as argparse from 'argparse';
import { sassPlugin } from 'esbuild-sass-plugin';
import * as os from 'os';
const Apps = [
// Apps
{ kind: 'app', name: 'viewer' },
{ kind: 'app', name: 'docking-viewer' },
{ kind: 'app', name: 'mesoscale-explorer' },
{ kind: 'app', name: 'mvs-stories', globalName: 'mvsStories', filename: 'mvs-stories.js' },
// Examples
{ kind: 'example', name: 'proteopedia-wrapper' },
{ kind: 'example', name: 'basic-wrapper' },
{ kind: 'example', name: 'lighting' },
{ kind: 'example', name: 'alpha-orbitals' },
{ kind: 'example', name: 'alphafolddb-pae' },
{ kind: 'example', name: 'mvs-stories' },
{ kind: 'example', name: 'ihm-restraints' },
{ kind: 'example', name: 'interactions' },
{ kind: 'example', name: 'ligand-editor' },
];
function findApp(name, kind) {
return Apps.find(a => a.name === name && a.kind === kind);
}
function mkDir(dir) {
try {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
} catch (error) {
console.error(`Failed to create directory ${dir}:`, error);
process.exit(1);
}
}
function handleFileError(error, operation, path) {
console.error(`Failed to ${operation} ${path}:`, error);
process.exit(1);
}
function fileLoaderPlugin(options) {
mkDir(options.out);
return {
name: 'file-loader',
setup(build) {
build.onLoad({ filter: /\.jpg$/ }, async (args) => {
try {
const name = path.basename(args.path);
mkDir(path.resolve(options.out, 'images'));
await fs.promises.copyFile(args.path, path.resolve(options.out, 'images', name));
return {
contents: `images/${name}`,
loader: 'text',
};
} catch (error) {
handleFileError(error, 'copy', args.path);
}
});
build.onLoad({ filter: /\.(html|ico)$/ }, async (args) => {
const name = path.basename(args.path);
await fs.promises.copyFile(args.path, path.resolve(options.out, name));
return {
contents: '',
loader: 'empty',
};
});
},
};
}
function examplesCssRenamePlugin({ root }) {
return {
name: 'example-css-rename',
setup(build) {
build.onEnd(async () => {
if (fs.existsSync(path.resolve(root, 'index.css'))) {
await fs.promises.rename(
path.resolve(root, 'index.css'),
path.resolve(root, 'molstar.css')
);
}
});
}
};
}
function resolveEntryPath(path) {
if (!fs.existsSync(path)) {
return path + 'x'; // fallback to .tsx
}
return path;
}
function getPaths(app) {
if (app.kind === 'app') {
return {
prefix: `./build/${app.name}`,
entry: resolveEntryPath(`./src/apps/${app.name}/index.ts`),
outfile: `./build/${app.name}/${app.filename || 'molstar.js'}`,
};
}
if (app.kind === 'example') {
return {
prefix: `./build/examples/${app.name}`,
entry: resolveEntryPath(`./src/examples/${app.name}/index.ts`),
outfile: `./build/examples/${app.name}/${app.filename || 'index.js'}`,
};
}
if (app.kind === 'browser-test') {
return {
prefix: `./build/tests/browser`,
entry: resolveEntryPath(`./src/tests/browser/${app.name}.ts`),
outfile: `./build/tests/browser/${app.name}.js`,
};
}
throw new Error(`Unknown app kind: ${app.kind}`);
}
async function createBundle(app) {
const { name, kind } = app;
const { prefix, entry, outfile } = getPaths(app);
const ctx = await esbuild.context({
entryPoints: [entry],
tsconfig: './tsconfig.json',
bundle: true,
minify: isProduction,
minifyIdentifiers: false,
sourcemap: includeSourceMap,
globalName: app.globalName || 'molstar',
outfile,
plugins: [
fileLoaderPlugin({ out: prefix }),
sassPlugin({
type: 'css',
silenceDeprecations: ['import'],
logger: {
warn: (msg) => console.warn(msg),
debug: () => { },
}
}),
...(kind === 'example' ? [examplesCssRenamePlugin({ root: prefix })] : []),
],
external: ['crypto', 'fs', 'path', 'stream'],
loader: {
},
color: true,
logLevel: 'info',
define: {
'process.env.DEBUG': JSON.stringify(process.env.DEBUG || false),
__MOLSTAR_PLUGIN_VERSION__: JSON.stringify(VERSION),
__MOLSTAR_BUILD_TIMESTAMP__: `${TIMESTAMP}`,
},
});
await ctx.rebuild();
if (!isProduction) await ctx.watch();
}
function findBrowserTests(names) {
const dir = path.resolve('./src', 'tests', 'browser');
let files = fs.readdirSync(dir).filter(file => file.endsWith('.ts')).map(file => file.replace('.ts', ''));
if (names.length) {
files = files.filter(file => names.includes(file));
}
return files.map(name => ({ kind: 'browser-test', name }));
}
const argParser = new argparse.ArgumentParser({
add_help: true,
description: 'Mol* Build'
});
argParser.add_argument('--prd', {
help: 'Create a production build.',
required: false,
action: 'store_true',
});
argParser.add_argument('--no-src-map', {
help: 'Do not include source map.',
required: false,
action: 'store_true',
});
argParser.add_argument('--apps', '-a', {
help: 'Apps to build.',
required: false,
nargs: '*',
});
argParser.add_argument('--examples', '-e', {
help: 'Examples to build.',
required: false,
nargs: '*',
});
argParser.add_argument('--browser-tests', '-bt', {
help: 'Browser Tests to build.',
required: false,
nargs: '*',
});
argParser.add_argument('--port', '-p', {
help: 'Port.',
required: false,
default: 1338,
type: 'int',
});
argParser.add_argument('--host', {
help: 'Show all available host addresses.',
required: false,
action: 'store_true',
});
const args = argParser.parse_args();
const isProduction = !!args.prd;
const includeSourceMap = !args.no_src_map;
const VERSION = isProduction ? JSON.parse(fs.readFileSync('./package.json', 'utf8')).version : '(dev build)';
const TIMESTAMP = Date.now();
const apps = (!args.apps ? [] : (args.apps.length ? args.apps.map(a => findApp(a, 'app')).filter(a => a) : Apps.filter(a => a.kind === 'app')));
const examples = (!args.examples ? [] : (args.examples.length ? args.examples.map(e => findApp(e, 'example')).filter(a => a) : Apps.filter(a => a.kind === 'example')));
const browserTests = (!args.browser_tests ? [] : findBrowserTests(args.browser_tests));
console.log('Apps:', apps.map(a => a.name));
console.log('Examples:', examples.map(e => e.name));
console.log('Browser Tests', browserTests.map(e => e.name));
console.log('');
function getLocalIPs() {
const interfaces = os.networkInterfaces();
const ips = [];
for (const name of Object.keys(interfaces)) {
for (const iface of interfaces[name]) {
// Skip internal and non-IPv4 addresses
if (iface.internal || iface.family !== 'IPv4') continue;
ips.push(iface.address);
}
}
return ips;
}
async function main() {
const promises = [];
console.log(isProduction ? 'Building apps...' : 'Initial build...');
for (const app of apps) promises.push(createBundle(app));
for (const example of examples) promises.push(createBundle(example));
for (const browserTest of browserTests) promises.push(createBundle(browserTest));
await Promise.all(promises);
if (isProduction) {
console.log('Done.');
process.exit(0);
}
console.log('Initial build complete.');
const certfile = './dev.pem';
const keyfile = './dev-key.pem';
const sslEnabled = fs.existsSync(certfile) && fs.existsSync(keyfile);
const protocol = sslEnabled ? 'https' : 'http';
const ctx = await esbuild.context({});
ctx.serve({
servedir: './',
port: args.port,
host: '0.0.0.0', // Always listen on all interfaces
certfile: sslEnabled ? certfile : undefined,
keyfile: sslEnabled ? keyfile : undefined,
});
console.log('');
console.log(`Server URL: ${protocol}://localhost:${args.port}`);
if (args.host) {
console.log('Available host addresses:');
const ips = getLocalIPs();
ips.forEach(ip => console.log(` ${protocol}://${ip}:${args.port}`));
}
console.log('');
console.log('Watching for changes...');
console.log('');
console.log('Press Ctrl+C to stop.');
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -6,6 +6,7 @@
const fs = require('fs');
const path = require('path');
const argparse = require('argparse');
function removeDir(dirPath) {
for (const ent of fs.readdirSync(dirPath)) {
@@ -24,11 +25,29 @@ function remove(entryPath) {
fs.unlinkSync(entryPath);
}
const toClean = [
path.resolve(__dirname, '../build'),
path.resolve(__dirname, '../lib'),
path.resolve(__dirname, '../tsconfig.tsbuildinfo'),
];
const argParser = new argparse.ArgumentParser({
add_help: true,
description: 'Clean Script'
});
argParser.add_argument('--build', { required: false, action: 'store_true' });
argParser.add_argument('--lib', { required: false, action: 'store_true' });
argParser.add_argument('--all', { required: false, action: 'store_true' });
const args = argParser.parse_args();
const toClean = [];
if (args.build || args.all) {
toClean.push(path.resolve(__dirname, '../build'));
toClean.push(path.resolve(__dirname, '../deploy/data'));
}
if (args.lib || args.all) {
toClean.push(
path.resolve(__dirname, '../lib'),
path.resolve(__dirname, '../tsconfig.tsbuildinfo'),
);
}
console.log('\n###', 'cleaning', toClean.join(', '));
toClean.forEach(ph => {
if (fs.existsSync(ph)) {

View File

@@ -1,22 +1,35 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
*/
const git = require('simple-git');
const path = require('path');
const fs = require("fs");
const fse = require("fs-extra");
const argparse = require('argparse');
const VERSION = require(path.resolve(__dirname, '../package.json')).version;
const MVS_STORIES_VERSION = require(path.resolve(__dirname, '../src/apps/mvs-stories/version.ts')).VERSION;
const remoteUrl = "https://github.com/molstar/molstar.github.io.git";
const dataDir = path.resolve(__dirname, '../data/');
const buildDir = path.resolve(__dirname, '../build/');
const deployDir = path.resolve(buildDir, 'deploy/');
const localPath = path.resolve(deployDir, 'molstar.github.io/');
const deployDir = path.resolve(__dirname, '../deploy/');
const localPath = path.resolve(deployDir, 'data/');
const repositoryPath = path.resolve(deployDir, 'molstar.github.io/');
const analyticsTag = /<!-- __MOLSTAR_ANALYTICS__ -->/g;
const analyticsCode = `<!-- Cloudflare Web Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "c414cbae2d284ea995171a81e4a3e721"}'></script><!-- End Cloudflare Web Analytics --><iframe src="https://web3dsurvey.com/collector-iframe.html" style="width: 1px; height: 1px;"></iframe>`;
const manifestTag = /<!-- __MOLSTAR_MANIFEST__ -->/g;
const manifestCode = `<link rel="manifest" href="./manifest.webmanifest">`;
const pwaTag = /<!-- __MOLSTAR_PWA__ -->/g;
const pwaCode = `<script src='./pwa.js'></script>`;
function log(command, stdout, stderr) {
if (command) {
console.log('\n###', command);
@@ -31,39 +44,117 @@ function addAnalytics(path) {
fs.writeFileSync(path, result, 'utf8');
}
function addManifest(path) {
const data = fs.readFileSync(path, 'utf8');
const result = data.replace(manifestTag, manifestCode);
fs.writeFileSync(path, result, 'utf8');
}
function addPwa(path) {
const data = fs.readFileSync(path, 'utf8');
const result = data.replace(pwaTag, pwaCode);
fs.writeFileSync(path, result, 'utf8');
}
function addVersion(path) {
const data = fs.readFileSync(path, 'utf8');
const result = data.replace('__MOLSTAR_VERSION__', VERSION);
fs.writeFileSync(path, result, 'utf8');
}
function copyViewer() {
console.log('\n###', 'copy viewer files');
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/');
const viewerBuildPath = path.resolve(buildDir, 'viewer/');
const viewerDeployPath = path.resolve(localPath, 'viewer/');
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true });
addAnalytics(path.resolve(viewerDeployPath, 'index.html'));
addManifest(path.resolve(viewerDeployPath, 'index.html'));
addPwa(path.resolve(viewerDeployPath, 'index.html'));
const pwaDataPath = path.resolve(dataDir, 'pwa/');
fse.copySync(pwaDataPath, viewerDeployPath, { overwrite: true });
addVersion(path.resolve(viewerDeployPath, 'sw.js'));
}
function copyMe() {
console.log('\n###', 'copy me files');
const meBuildPath = path.resolve(buildDir, '../build/mesoscale-explorer/');
const meDeployPath = path.resolve(localPath, 'me/');
const meBuildPath = path.resolve(buildDir, 'mesoscale-explorer/');
const meDeployPath = path.resolve(localPath, 'me/viewer/');
fse.copySync(meBuildPath, meDeployPath, { overwrite: true });
addAnalytics(path.resolve(meDeployPath, 'index.html'));
}
function copyMVSStories() {
console.log('\n###', 'copy MVS stories files');
const mvsStoriesBuildPath = path.resolve(buildDir, 'mvs-stories/');
const mvsStoriesDeployPath = path.resolve(localPath, `stories-viewer/v${MVS_STORIES_VERSION}/`);
fse.copySync(mvsStoriesBuildPath, mvsStoriesDeployPath, { overwrite: true });
addAnalytics(path.resolve(mvsStoriesDeployPath, 'index.html'));
// TODO: add PWA
// addManifest(path.resolve(mvsStoriesDeployPath, 'index.html'));
// addPwa(path.resolve(mvsStoriesDeployPath, 'index.html'));
}
function copyDemo(name) {
console.log('\n###', `copy demo files for ${name}`);
const demoBuildPath = path.resolve(buildDir, `examples/${name}/`);
const demoDeployPath = path.resolve(localPath, `demos/${name}/`);
fse.copySync(demoBuildPath, demoDeployPath, { overwrite: true });
addAnalytics(path.resolve(demoDeployPath, 'index.html'));
}
function copyDemos() {
console.log('\n###', 'copy demos files');
const lightingBuildPath = path.resolve(buildDir, '../build/examples/lighting/');
const lightingDeployPath = path.resolve(localPath, 'demos/lighting/');
fse.copySync(lightingBuildPath, lightingDeployPath, { overwrite: true });
addAnalytics(path.resolve(lightingDeployPath, 'index.html'));
const orbitalsBuildPath = path.resolve(buildDir, '../build/examples/alpha-orbitals/');
const orbitalsDeployPath = path.resolve(localPath, 'demos/alpha-orbitals/');
fse.copySync(orbitalsBuildPath, orbitalsDeployPath, { overwrite: true });
addAnalytics(path.resolve(orbitalsDeployPath, 'index.html'));
copyDemo('lighting');
copyDemo('alpha-orbitals');
copyDemo('mvs-stories');
}
function copyFiles() {
copyViewer();
copyMe();
copyDemos();
try {
copyViewer();
copyMe();
copyMVSStories();
copyDemos();
} catch (e) {
console.error(e);
}
}
function copyToRepository() {
console.log('\n###', 'copy repository files');
fse.copySync(localPath, repositoryPath, { overwrite: true });
}
function syncRepository() {
console.log('\n###', 'sync repository');
if (!fs.existsSync(path.resolve(repositoryPath, '.git/'))) {
console.log('\n###', 'clone repository');
git()
.outputHandler(log)
.clone(remoteUrl, repositoryPath)
.fetch(['--all'])
.exec(copyToRepository);
} else {
console.log('\n###', 'update repository');
git()
.outputHandler(log)
.fetch(['--all'])
.reset(['--hard', 'origin/master'])
.exec(copyToRepository);
}
}
function commit() {
console.log('\n###', 'commit changes');
git()
.outputHandler(log)
.add(['-A'])
.commit(`Updated Apps and Demos
- Mol* version: ${VERSION}
- MVS Stories version: ${MVS_STORIES_VERSION}`)
.push();
}
if (!fs.existsSync(localPath)) {
@@ -71,26 +162,28 @@ if (!fs.existsSync(localPath)) {
fs.mkdirSync(localPath, { recursive: true });
}
process.chdir(localPath);
const argParser = new argparse.ArgumentParser({
add_help: true,
description: 'Mol* Deploy'
});
argParser.add_argument('--local',{
help: 'Do not commit to remote repository.',
required: false,
action: 'store_true',
});
const args = argParser.parse_args();
if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
console.log('\n###', 'clone repository');
git()
.outputHandler(log)
.clone(remoteUrl, localPath)
.fetch(['--all'])
.exec(copyFiles)
.add(['-A'])
.commit('updated viewer & demos')
.push();
} else {
console.log('\n###', 'update repository');
git()
.outputHandler(log)
.fetch(['--all'])
.reset(['--hard', 'origin/master'])
.exec(copyFiles)
.add(['-A'])
.commit('updated viewer & demos')
.push();
}
copyFiles();
if (args.local) {
process.exit(0);
}
if (!fs.existsSync(repositoryPath)) {
console.log('\n###', 'create repositoryPath');
fs.mkdirSync(repositoryPath, { recursive: true });
}
process.chdir(repositoryPath);
syncRepository();
commit();

16
scripts/write-version.mjs Normal file
View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as fs from 'fs';
const VERSION = JSON.parse(fs.readFileSync('./package.json', 'utf8')).version;
const TIMESTAMP = Date.now();
const file = `export var PLUGIN_VERSION = '${VERSION}';\nexport var PLUGIN_VERSION_DATE = new Date(${TIMESTAMP})`;
const files = ['./lib/mol-plugin/version.js', './lib/commonjs/mol-plugin/version.js'];
for (const f of files) {
if (!fs.existsSync(f)) continue;
fs.writeFileSync(f, file);
}

View File

@@ -27,7 +27,7 @@ import { ObjectKeys } from '../../mol-util/type-helpers';
import './index.html';
import { ShowButtons, StructurePreset, ViewportComponent } from './viewport';
require('mol-plugin-ui/skin/light.scss');
import '../../mol-plugin-ui/skin/light.scss';
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setDebugMode, setProductionMode } from '../../mol-util/debug';

View File

@@ -44,16 +44,6 @@ function occlusionStyle(plugin: PluginContext) {
},
postprocessing: {
...plugin.canvas3d!.props.postprocessing,
occlusion: { name: 'on', params: {
blurKernelSize: 15,
blurDepthBias: 0.5,
multiScale: { name: 'off', params: {} },
radius: 5,
bias: 0.8,
samples: 32,
resolutionScale: 1,
color: Color(0x000000),
} },
outline: { name: 'on', params: {
scale: 1.0,
threshold: 0.33,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022-2023 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>
*/
@@ -25,7 +25,7 @@ import { MesoFocusLoci } from './behavior/camera';
import { GraphicsMode, MesoscaleState } from './data/state';
import { MesoSelectLoci } from './behavior/select';
import { Transparency } from '../../mol-gl/webgl/render-item';
import { LoadModel, loadExampleEntry, loadPdb, loadPdbDev, loadUrl, openState } from './ui/states';
import { LoadModel, loadExampleEntry, loadPdb, loadPdbIhm, loadUrl, openState } from './ui/states';
import { Asset } from '../../mol-util/assets';
import { AnimateCameraSpin } from '../../mol-plugin-state/animation/built-in/camera-spin';
import { AnimateCameraRock } from '../../mol-plugin-state/animation/built-in/camera-rock';
@@ -47,6 +47,7 @@ export type ExampleEntry = {
export type MesoscaleExplorerState = {
examples?: ExampleEntry[],
graphicsMode: GraphicsMode,
illumination: boolean,
stateRef?: string,
driver?: any,
stateCache: { [k: string]: any },
@@ -78,11 +79,13 @@ const DefaultMesoscaleExplorerOptions = {
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
resolutionMode: PluginConfig.General.ResolutionMode.defaultValue,
illumination: false,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
viewportShowSelectionMode: false,
viewportShowSelectionMode: true,
viewportShowAnimation: false,
viewportShowTrajectoryControls: false,
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
@@ -117,8 +120,15 @@ export class MesoscaleExplorer {
await loadPdb(this.plugin, id);
}
/**
* @deprecated Scheduled for removal in v5. Use {@link loadPdbIhm | loadPdbIhm(id: string)} instead.
*/
async loadPdbDev(id: string) {
await loadPdbDev(this.plugin, id);
await this.loadPdbIhm(id);
}
async loadPdbIhm(id: string) {
await loadPdbIhm(this.plugin, id);
}
static async create(elementOrId: string | HTMLElement, options: Partial<MesoscaleExplorerOptions> = {}) {
@@ -140,7 +150,7 @@ export class MesoscaleExplorer {
PluginSpec.Behavior(MesoFocusLoci),
PluginSpec.Behavior(MesoSelectLoci),
PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci),
...o.extensions.map(e => Extensions[e]),
],
animations: [
@@ -185,6 +195,7 @@ export class MesoscaleExplorer {
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
[PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat],
[PluginConfig.General.PowerPreference, o.powerPreference],
[PluginConfig.General.ResolutionMode, o.resolutionMode],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
@@ -213,9 +224,9 @@ export class MesoscaleExplorer {
onBeforeUIRender: async plugin => {
let examples: MesoscaleExplorerState['examples'] = undefined;
try {
examples = await plugin.fetch({ url: './examples/list.json', type: 'json' }).run();
examples = await plugin.fetch({ url: '../examples/list.json', type: 'json' }).run();
// extend the array with file tour.json if it exists
const tour = await plugin.fetch({ url: './examples/tour.json', type: 'json' }).run();
const tour = await plugin.fetch({ url: '../examples/tour.json', type: 'json' }).run();
if (tour) {
examples = examples?.concat(tour);
}
@@ -226,6 +237,7 @@ export class MesoscaleExplorer {
(plugin.customState as MesoscaleExplorerState) = {
examples,
graphicsMode: o.graphicsMode,
illumination: o.illumination,
driver: o.driver,
stateCache: {},
};

View File

@@ -73,7 +73,7 @@ export const MesoSelectLoci = PluginBehavior.create<MesoSelectLociProps>({
this.ctx.managers.interactivity.lociSelects.deselectAll();
return;
}
const loci = Loci.normalize(current.loci, modifiers.control ? 'entity' : 'chain');
const loci = Loci.normalize(current.loci, modifiers.control ? 'entity' : this.ctx.managers.interactivity.props.granularity);
this.ctx.managers.interactivity.lociSelects.toggle({ loci }, false);
if (StructureElement.Loci.is(current.loci)) {
const cell = this.ctx.helpers.substructureParent.get(current.loci.structure);
@@ -116,7 +116,7 @@ export const MesoSelectLoci = PluginBehavior.create<MesoSelectLociProps>({
if (modifiers.control) {
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EveryLoci }, false);
} else {
const loci = Loci.normalize(current.loci, 'chain');
const loci = Loci.normalize(current.loci, this.ctx.managers.interactivity.props.granularity);
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci }, false);
}
}

View File

@@ -12,7 +12,7 @@ import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/
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, updateColors } from '../state';
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) {
@@ -96,12 +96,12 @@ export async function createCellpackHierarchy(plugin: PluginContext, trajectory:
const compRoot = await state.build()
.toRoot()
.applyOrUpdateTagged('group:comp:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `comp:`, label: 'compartment', color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:comp:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: 'comp:', label: 'compartment', color: { type: 'custom', illustrative: false, 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', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:func:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: 'func:', label: 'function', color: { type: 'custom', illustrative: false, 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) {
@@ -159,7 +159,7 @@ export async function createCellpackHierarchy(plugin: PluginContext, trajectory:
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', illustrative: false, value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `comp:${p}`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
.apply(MesoscaleGroup, { ...groupParams, root: parent === compRoot, index: colorIdx, tag: `comp:${n}`, label, color: { type: 'generate', illustrative: false, value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: [`group:comp:${n}`, `comp:${p}`], state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit({ revertOnError: true });
compGroups.set(n, group);
}
@@ -171,7 +171,7 @@ export async function createCellpackHierarchy(plugin: PluginContext, trajectory:
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', illustrative: false, value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'func:', state: { isCollapsed: true, isHidden: groupParams.hidden } })
.apply(MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `func:${f}`, label: f, color: { type: 'custom', illustrative: false, value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: [`group:func:${f}`, 'func:'], state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit({ revertOnError: true });
funcGroups.set(f, group);
}
@@ -201,9 +201,6 @@ export async function createCellpackHierarchy(plugin: PluginContext, trajectory:
.apply(StructureRepresentation3D, getSpacefillParams(color, sizeFactor, graphicsMode), { tags: [`comp:${n}`, `func:${f}`] });
}
await build.commit();
const values = { type: 'group-generate', value: ColorNames.white, lightness: 0, alpha: 1 };
const options = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 }, celShaded: true, };
await updateColors(plugin, values, options);
} catch (e) {
console.error(e);
plugin.log.error(e);

View File

@@ -11,7 +11,7 @@ import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/
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 { GraphicsMode, MesoscaleGroup, MesoscaleState, getGraphicsModeProps, getMesoscaleGroupParams } 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';
@@ -192,12 +192,13 @@ export async function createGenericHierarchy(plugin: PluginContext, file: Asset.
const t = isBinary ? d : utf8Read(d, 0, d.length);
const file = Asset.File(new File([t], ent.file));
const color = ColorNames.skyblue;
const color = (ent.color) ? Color.fromRgb(ent.color[0], ent.color[1], ent.color[2]) : ColorNames.skyblue;
const sizeFactor = ent.sizeFactor || 1;
const tags = ent.groups.map(({ id, root }) => `${root}:${id}`);
const instances = ent.instances && getAssetInstances(ent.instances);
const description = ent.description;
const label = ent.label || ent.file.split('.')[0];
build = build
.toRoot()
@@ -250,10 +251,6 @@ export async function createGenericHierarchy(plugin: PluginContext, file: Asset.
}
}
await build.commit();
const values = { type: 'group-generate', value: ColorNames.white, lightness: 0, alpha: 1 };
const options = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 }, celShaded: true, };
await updateColors(plugin, values, options);
} catch (e) {
console.error(e);
plugin.log.error(e);
@@ -304,6 +301,7 @@ type GenericEntity = {
file: string
label?: string
description?: string
color?: number[]
groups: {
/** reference to `${GenericGroup.id}` */
id: string,

View File

@@ -13,7 +13,7 @@ import { StateObjectRef, StateObjectSelector, StateBuilder } from '../../../../m
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, updateColors } from '../state';
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) {
@@ -114,7 +114,7 @@ export async function createMmcifHierarchy(plugin: PluginContext, trajectory: St
const entRoot = await state.build()
.toRoot()
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: 'ent:', label: 'entity', color: { type: 'custom', illustrative: false, 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) => {
@@ -172,9 +172,6 @@ export async function createMmcifHierarchy(plugin: PluginContext, trajectory: St
.apply(StructureRepresentation3D, getSpacefillParams(color, scaleFactor, graphicsMode, clipVariant), { tags: [`ent:${t}`] });
}
await build.commit();
const values = { type: 'group-generate', value: ColorNames.white, lightness: 0, alpha: 1 };
const options = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 }, celShaded: true, };
await updateColors(plugin, values, options);
} catch (e) {
console.error(e);
plugin.log.error(e);

View File

@@ -11,7 +11,7 @@ import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/
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, updateColors } from '../state';
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';
@@ -97,12 +97,12 @@ export async function createPetworldHierarchy(plugin: PluginContext, trajectory:
const group = await state.build()
.toRoot()
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'generate', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'generate', illustrative: false, 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', illustrative: false, value: ColorNames.lightgrey, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
.apply(MesoscaleGroup, { ...groupParams, index: undefined, tag: `ent:mem`, label: 'Membrane', color: { type: 'uniform', illustrative: false, value: ColorNames.lightgrey, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: ['group:ent:mem', 'ent:', '__no_group_color__'], state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit();
const colors = getDistinctBaseColors(other.length, 0);
@@ -115,18 +115,15 @@ export async function createPetworldHierarchy(plugin: PluginContext, trajectory:
build = build
.to(cell)
.apply(StructureFromPetworld, membrane[i])
.apply(StructureRepresentation3D, getSpacefillParams(ColorNames.lightgrey, graphicsMode), { tags: [`ent:mem`] });
.apply(StructureRepresentation3D, getSpacefillParams(ColorNames.lightgrey, graphicsMode), { tags: ['ent:mem', '__no_group_color__'] });
}
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:`] });
.apply(StructureRepresentation3D, getSpacefillParams(colors[i], graphicsMode), { tags: ['ent:'] });
}
await build.commit();
const values = { type: 'group-generate', value: ColorNames.white, lightness: 0, alpha: 1 };
const options = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 }, celShaded: true, };
await updateColors(plugin, values, options);
} catch (e) {
console.error(e);
plugin.log.error(e);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -12,7 +12,7 @@ import { Color } from '../../../mol-util/color';
import { Spheres } from '../../../mol-geo/geometry/spheres/spheres';
import { Clip } from '../../../mol-util/clip';
import { escapeRegExp, stringToWords } from '../../../mol-util/string';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { ParamMapping } from '../../../mol-util/param-mapping';
import { EntityNode } from '../ui/entities';
import { DistinctColorsProps, distinctColors } from '../../../mol-util/color/distinct';
@@ -211,7 +211,8 @@ export function getClipObjects(values: SimpleClipProps, boundingSphere: Sphere3D
invert: values.invert,
position,
scale,
rotation: values.rotation
rotation: values.rotation,
transform: Mat4.identity(),
}];
}
@@ -339,10 +340,10 @@ export function getLodLevels(graphicsMode: Exclude<GraphicsMode, 'custom'>): Lod
];
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 },
{ minDistance: 1, maxDistance: 5000, overlap: 0, stride: 1, scaleBias: 1 },
{ minDistance: 5000, maxDistance: 10000, overlap: 0, stride: 10, scaleBias: 3 },
{ minDistance: 10000, maxDistance: 30000, overlap: 0, stride: 50, scaleBias: 2.5 },
{ minDistance: 30000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2 },
];
default:
assertUnreachable(graphicsMode);
@@ -471,7 +472,7 @@ export function getAllGroups(plugin: PluginContext, tag?: string) {
return _getAllGroups(plugin, tag, []);
}
export function getAllLeafGroups(plugin: PluginContext, tag: string | undefined) {
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 => {
@@ -505,7 +506,7 @@ function getFilterMatcher(filter: string) {
: new RegExp(escapeRegExp(filter), 'gi');
}
export function getFilteredEntities(plugin: PluginContext, tag: string | undefined, filter: string | undefined) {
export function getFilteredEntities(plugin: PluginContext, tag: string, filter: string) {
if (!filter) return getEntities(plugin, tag);
const matcher = getFilterMatcher(filter);
return getEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
@@ -523,7 +524,7 @@ export function getAllEntities(plugin: PluginContext, tag?: string) {
return _getAllEntities(plugin, tag, []);
}
export function getAllFilteredEntities(plugin: PluginContext, tag: string | undefined, filter: string | undefined) {
export function getAllFilteredEntities(plugin: PluginContext, tag: string, filter: string) {
if (!filter) return getAllEntities(plugin, tag);
const matcher = getFilterMatcher(filter);
return getAllEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
@@ -553,14 +554,43 @@ export function getEntityDescription(plugin: PluginContext, cell: StateObjectCel
return d;
}
export async function updateStyle(plugin: PluginContext, options: { ignoreLight: boolean, material: Material, celShaded: boolean, illustrative: boolean }) {
const update = plugin.state.data.build();
const { ignoreLight, material, celShaded, illustrative } = options;
export async function updateColors(plugin: PluginContext, values: PD.Values, options?: PD.Values, tag?: string, filter?: string) {
const entities = getAllEntities(plugin);
for (let j = 0; j < entities.length; ++j) {
update.to(entities[j]).update(old => {
if (old.type) {
const value = old.colorTheme.name === 'illustrative'
? old.colorTheme.params.style.params.value
: old.colorTheme.params.value;
const lightness = old.colorTheme.name === 'illustrative'
? old.colorTheme.params.style.params.lightness
: old.colorTheme.params.lightness;
if (illustrative) {
old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value, lightness } } } };
} else {
old.colorTheme = { name: 'uniform', params: { value, lightness } };
}
old.type.params.ignoreLight = ignoreLight;
old.type.params.material = material;
old.type.params.celShaded = celShaded;
}
});
}
await update.commit();
};
export async function updateColors(plugin: PluginContext, values: PD.Values, tag: string, filter: string) {
const update = plugin.state.data.build();
const { type, illustrative, value, shift, lightness, alpha, emissive } = values;
const doLighting = (options !== undefined);
const { ignoreLight, materialStyle: material, celShaded } = options ? options : { ignoreLight: true, materialStyle: { metalness: 0, roughness: 0.2, bumpiness: 0 }, celShaded: false };
if (type === 'group-generate' || type === 'group-uniform') {
const groups = getAllLeafGroups(plugin, tag);
const leafGroups = getAllLeafGroups(plugin, tag);
const rootLeafGroups = getRoots(plugin).filter(g => g.params?.values.tag === tag && getEntities(plugin, g.params?.values.tag).length > 0);
const groups = [...leafGroups, ...rootLeafGroups];
const baseColors = getDistinctBaseColors(groups.length, shift);
for (let i = 0; i < groups.length; ++i) {
@@ -585,11 +615,6 @@ export async function updateColors(plugin: PluginContext, values: PD.Values, opt
old.type.params.alpha = alpha;
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
old.type.params.emissive = emissive;
if (doLighting) {
old.type.params.ignoreLight = ignoreLight;
old.type.params.material = material;
old.type.params.celShaded = celShaded;
}
} else if (old.coloring) {
old.coloring.params.color = c;
old.coloring.params.lightness = lightness;
@@ -629,11 +654,6 @@ export async function updateColors(plugin: PluginContext, values: PD.Values, opt
old.type.params.alpha = alpha;
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
old.type.params.emissive = emissive;
if (doLighting) {
old.type.params.ignoreLight = ignoreLight;
old.type.params.material = material;
old.type.params.celShaded = celShaded;
}
} else if (old.coloring) {
old.coloring.params.color = c;
old.coloring.params.lightness = lightness;
@@ -668,16 +688,3 @@ export function expandAllGroups(plugin: PluginContext) {
}
};
export async function updateReprParams(plugin: PluginContext, options: PD.Values) {
const update = plugin.state.data.build();
const { ignoreLight, materialStyle: material, celShaded } = options;
const entities = getAllEntities(plugin);
for (let j = 0; j < entities.length; ++j) {
update.to(entities[j]).update(old => {
old.type.params.ignoreLight = ignoreLight;
old.type.params.material = material;
old.type.params.celShaded = celShaded;
});
}
await update.commit();
}

View File

@@ -0,0 +1,79 @@
<!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">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
overflow: hidden;
}
#controls {
display: flex;
justify-content: center;
width: 800px;
margin-bottom: 10px;
z-index: 1;
}
button {
margin: 5px;
padding: 10px 20px;
font-size: 14px;
cursor: pointer;
}
#viewer-container {
width: 100%;
height: 600px;
border: 1px solid #ccc;
position: relative;
}
</style>
<link rel="stylesheet" type="text/css" href="./molstar.css" />
</head>
<body>
<div id="controls">
<button onclick="loadExample('cellpack-hiv1')">Load HIV-1 Example</button>
<button onclick="loadExample('machineryoflife-tour')">Load Machinery of Life Tour</button>
<button onclick="loadExample('petworld-synvesicle')">Load Synaptic Vesicle Example</button>
</div>
<div id="viewer-container">
<div id="meso-viewer" style="position: relative; width: 100%; height: 400px;"></div>
</div>
<script src="./molstar.js"></script>
<script type="text/javascript">
let mesoExplorer;
function loadExample(example) {
if (mesoExplorer) {
mesoExplorer.loadExample(example);
}
}
molstar.MesoscaleExplorer.create('meso-viewer', {
layoutShowControls: false,
viewportShowExpand: false,
layoutIsExpanded: false,
powerPreference: 'high-performance',
graphicsMode: 'quality'
}).then(me => {
mesoExplorer = me;
me.loadExample('cellpack-hiv1'); // Load the default example on page load
window.addEventListener('unload', () => {
me.dispose();
});
});
</script>
</body>
</html>

View File

@@ -4,7 +4,7 @@
<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">
<link rel="stylesheet" href="./extras/driver.css"/>
<link rel="stylesheet" href="../extras/driver.css"/>
<title>Mol* Mesoscale Explorer</title>
<style>
* {
@@ -39,7 +39,7 @@
</head>
<body>
<div id="app"></div>
<script src="./extras/driver.js.iife.js"></script>
<script src="../extras/driver.js.iife.js"></script>
<script type="text/javascript" src="./molstar.js"></script>
<script type="text/javascript">
const driver = window.driver ? window.driver.js.driver() : undefined;
@@ -60,6 +60,8 @@
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
var powerPreference = getParam('power-preference', '[^&]+').trim().toLowerCase();
var graphicsMode = getParam('graphics-mode', '[^&]+').trim().toLowerCase();
var illumination = getParam('illumination', '[^&]+').trim() === '1';
var resolutionMode = getParam('resolution-mode', '[^&]+').trim().toLowerCase();
molstar.MesoscaleExplorer.create('app', {
layoutShowControls: !hideControls,
@@ -68,6 +70,8 @@
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
powerPreference: powerPreference || 'high-performance',
graphicsMode: graphicsMode || 'quality',
illumination: illumination,
resolutionMode: resolutionMode || 'auto',
driver: driver
}).then(me => {
var example = getParam('example', '[^&]+').trim();
@@ -89,12 +93,17 @@
return;
}
var pdbdev = getParam('pdbdev', '[^&]+').trim();
if (pdbdev) {
me.loadPdbDev(pdbdev);
var pdbihm = getParam('pdbihm', '[^&]+').trim();
if (pdbihm) {
me.loadPdbIhm(pdbihm);
return;
}
// support for deprecated pdb-dev param
var pdbdev = getParam('pdbdev', '[^&]+').trim();
if (pdbdev) {
me.loadPdbIhm(pdbdev);
return;
}
window.addEventListener('unload', () => {
// to aid GC
me.dispose();

View File

@@ -6,5 +6,5 @@
import './favicon.ico';
import './index.html';
require('./style.scss');
import './style.scss';
export * from './app';

View File

@@ -1,30 +1,28 @@
$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;
@use "sass:color";
// used in LOG
$log-message: #0CCA5D;
$log-info: #5E3673;
$log-warning: #FCC937;
$log-error: #FD354B;
@use '../../mol-plugin-ui/skin/base/colors' with (
$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,
$logo-background: rgba(0,0,0,0.75);
// used in LOG
$log-message: #0CCA5D,
$log-info: #5E3673,
$log-warning: #FCC937,
$log-error: #FD354B,
@function color-lower-contrast($color, $amount) {
@return darken($color, $amount);
}
$logo-background: rgba(0,0,0,0.75),
@function color-increase-contrast($color, $amount) {
@return lighten($color, $amount);
}
$color-adjust-sign: -1,
);
@import 'mol-plugin-ui/skin/base/base';
@import 'mol-plugin-ui/skin/base/variables';
@use '../../mol-plugin-ui/skin/base/base';
@use '../../mol-plugin-ui/skin/base/vars' as *;
a {
color: $font-color;
@@ -35,7 +33,7 @@ a {
.msp-snapshot-description-me {
background: rgba(red($default-background), green($default-background), blue($default-background), 0.5);
background: color.change($default-background, $alpha: 0.5, $space: rgb);
position: absolute;
height: 50vh; // 50% of the viewport height

View File

@@ -29,6 +29,7 @@ import { Sphere3D } from '../../../mol-math/geometry';
import { MesoFocusLoci } from '../behavior/camera';
import Markdown from 'react-markdown';
import { combineLatest } from 'rxjs';
import { ColorLoaderControls } from './states';
function centerLoci(plugin: PluginContext, loci: Loci, durationMs = 250) {
const { canvas3d } = plugin;
@@ -394,7 +395,10 @@ export function MesoViewportSnapshotDescription() {
{showInfo}{increasePoliceSize}{decreasePoliceSize}
</div>
<div id='snapinfo' className={`msp-snapshot-description-me ${isShown ? 'shown' : 'hidden'}`} style={{ fontSize: `${textSize}px` }}>
{<Markdown skipHtml={false} components={{ a: MesoMarkdownAnchor }}>{e.description}</Markdown>}
{e.descriptionFormat === 'plaintext'
&& e.description
|| <Markdown skipHtml={false} components={{ a: MesoMarkdownAnchor }}>{e.description}</Markdown>
}
</div>
</>
);
@@ -616,6 +620,7 @@ class Node<P extends {}, S extends { isDisabled: boolean }> extends PluginUIComp
}
}
export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean, action?: 'color' | 'clip' | 'root', isDisabled: boolean }> {
state = {
isCollapsed: !!this.props.cell.state.isCollapsed,
@@ -749,7 +754,7 @@ export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean,
};
updateRoot = async (values: PD.Values) => {
await updateColors(this.plugin, values, undefined, this.cell.params?.values.tag, this.props.filter);
await updateColors(this.plugin, values, this.cell.params?.values.tag, this.props.filter);
const update = this.plugin.state.data.build();
@@ -878,12 +883,13 @@ export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean,
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} />;
const loadColorButton = (depth === 0) && <ColorLoaderControls plugin={this.plugin} />;
return <>
<div className={`msp-flex-row`} style={{ margin: `1px 5px 1px ${depth * 10 + 5}px` }}>
{expand}
{label}
{root || color}
{loadColorButton}
{clip}
{visibility}
</div>

View File

@@ -4,10 +4,8 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import Markdown from 'react-markdown';
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
import { MmcifProvider } from '../../../mol-plugin-state/formats/trajectory';
import { StructureComponentManager } from '../../../mol-plugin-state/manager/structure/component';
import { PluginStateObject } from '../../../mol-plugin-state/objects';
import { Button, ExpandGroup, IconButton } from '../../../mol-plugin-ui/controls/common';
import { GetAppSvg, HelpOutlineSvg, MagicWandSvg, TourSvg, Icon, OpenInBrowserSvg } from '../../../mol-plugin-ui/controls/icons';
@@ -26,9 +24,14 @@ 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, updateColors } from '../data/state';
import { getAllEntities, getEntityLabel, MesoscaleState, MesoscaleStateObject, setGraphicsCanvas3DProps, updateStyle } from '../data/state';
import { isTimingMode } from '../../../mol-util/debug';
import { now } from '../../../mol-util/now';
import { readFromFile } from '../../../mol-util/data-source';
function adjustPluginProps(ctx: PluginContext) {
const customState = ctx.customState as MesoscaleExplorerState;
ctx.managers.interactivity.setProps({ granularity: 'chain' });
ctx.canvas3d?.setProps({
multiSample: { mode: 'off' },
@@ -82,12 +85,12 @@ function adjustPluginProps(ctx: PluginContext) {
blurDepthBias: 0.5,
resolutionScale: 1,
color: Color(0x000000),
transparentThreshold: 0.4,
}
},
shadow: {
name: 'on',
params: {
bias: 0.6,
maxDistance: 80,
steps: 3,
tolerance: 1.0,
@@ -101,8 +104,13 @@ function adjustPluginProps(ctx: PluginContext) {
color: Color(0x000000),
includeTransparent: false,
}
}
}
},
},
illumination: {
enabled: customState.illumination,
firstStepSize: 0.1,
rayDistance: 1024,
},
});
const { graphics } = MesoscaleState.get(ctx);
@@ -165,14 +173,36 @@ export async function loadExampleEntry(ctx: PluginContext, entry: ExampleEntry)
}
export async function loadUrl(ctx: PluginContext, url: string, type: 'molx' | 'molj' | 'cif' | 'bcif') {
let startTime = 0;
if (isTimingMode) {
startTime = now();
}
if (type === 'molx' || type === 'molj') {
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.OpenUrl(ctx, { url, type });
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;
} else {
await reset(ctx);
const isBinary = type === 'bcif';
const data = await ctx.builders.data.download({ url, isBinary });
await createHierarchy(ctx, data.ref);
}
if (isTimingMode) {
const endTime = now();
// Calculate the elapsed time
const timeTaken = endTime - startTime;
console.log(`Model loaded in ${timeTaken} milliseconds`);
}
}
export async function loadPdb(ctx: PluginContext, id: string) {
@@ -182,21 +212,53 @@ export async function loadPdb(ctx: PluginContext, id: string) {
await createHierarchy(ctx, data.ref);
}
export async function loadPdbDev(ctx: PluginContext, id: string) {
export async function loadPdbIhm(ctx: PluginContext, id: string) {
await reset(ctx);
const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
const url = `https://pdb-dev.wwpdb.org/bcif/${nId.toUpperCase()}.bcif`;
let url: string;
// 4 character PDB id, TODO: support extended PDB ID
if (id.match(/^[1-9][A-Z0-9]{3}$/i) !== null) {
url = `https://pdb-ihm.org/bcif/${id.toLowerCase()}.bcif`;
} else {
const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
url = `https://pdb-ihm.org/bcif/${nId.toUpperCase()}.bcif`;
}
const data = await ctx.builders.data.download({ url, isBinary: true });
await createHierarchy(ctx, data.ref);
}
async function loadColors(ctx: PluginContext, file: File) {
const colorData = await ctx.runTask(readFromFile(file, 'json'));
const update = ctx.state.data.build();
const allEntities = getAllEntities(ctx);
for (const entityCell of allEntities) {
const label = getEntityLabel(ctx, entityCell);
const tags = entityCell.transform.tags;
const fullname = (tags?.[0].replace('comp:', '') ?? '') + '.' + label;
// test each tag, siwtch to uniform color
if (fullname in colorData) {
const { x, y, z } = colorData[fullname];
const color = Color.fromRgb(x, y, z);
update.to(entityCell).update(old => {
if (old.type) {
old.colorTheme = { name: 'uniform', params: { value: color, lightness: old.colorTheme.params.lightness } };
old.type.params.color = color;
} else if (old.coloring) {
old.coloring.params.color = color;
}
});
}
}
await update.commit();
}
//
export const LoadDatabase = StateAction.build({
display: { name: 'Database', description: 'Load from Database' },
params: (a, ctx: PluginContext) => {
return {
source: PD.Select('pdb', PD.objectToOptions({ pdb: 'PDB', pdbDev: 'PDB-Dev' })),
source: PD.Select('pdb', PD.objectToOptions({ pdb: 'PDB', pdbIhm: 'PDB-IHM' })),
entry: PD.Text(''),
};
},
@@ -204,8 +266,8 @@ export const LoadDatabase = StateAction.build({
})(({ params }, ctx: PluginContext) => Task.create('Loading from database...', async taskCtx => {
if (params.source === 'pdb') {
await loadPdb(ctx, params.entry);
} else if (params.source === 'pdbDev') {
await loadPdbDev(ctx, params.entry);
} else if (params.source === 'pdbIhm') {
await loadPdbIhm(ctx, params.entry);
}
}));
@@ -264,7 +326,6 @@ export const LoadModel = StateAction.build({
}
}));
//
export class DatabaseControls extends PluginUIComponent {
componentDidMount() {
@@ -302,6 +363,30 @@ export class ExampleControls extends PluginUIComponent {
}
}
export function ColorLoaderControls({ plugin }: { plugin: PluginContext }) {
const triggerLoadColors = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = async (e) => {
const input = e.target as HTMLInputElement;
if (!input.files || !input.files[0]) return;
const file = input.files[0];
await loadColors(plugin, new File([file], file.name));
};
input.click();
};
return (
<IconButton
svg={OpenInBrowserSvg}
title="Load Colors"
onClick={triggerLoadColors}
small
/>
);
}
export async function openState(ctx: PluginContext, file: File) {
const customState = ctx.customState as MesoscaleExplorerState;
delete customState.stateRef;
@@ -391,7 +476,7 @@ export class ExplorerInfo extends PluginUIComponent<{}, { isDisabled: boolean, s
driver.setSteps([
// Left panel
{ element: '#explorerinfo', popover: { title: 'Explorer Header Info', description: 'This section displays the explorer header with version information, documentation access, and tour navigation. Use the right and left arrow keys to navigate the tour.', side: 'left', align: 'start' } },
{ element: '#database', popover: { title: 'Import from PDB', description: 'Load structures directly from PDB and PDB-DEV databases.', side: 'bottom', align: 'start' } },
{ element: '#database', popover: { title: 'Import from PDB', description: 'Load structures directly from PDB and PDB-IHM databases.', side: 'bottom', align: 'start' } },
{ element: '#loader', popover: { title: 'Import from File', description: 'Load local files (.molx, .molj, .zip, .cif, .bcif) using this option.', side: 'bottom', align: 'start' } },
{ element: '#example', popover: { title: 'Example Models and Tours', description: 'Select from a range of example models and tours provided.', side: 'left', align: 'start' } },
{ element: '#session', popover: { title: 'Session Management', description: 'Download the current session in .molx format.', side: 'top', align: 'start' } },
@@ -421,7 +506,7 @@ export class ExplorerInfo extends PluginUIComponent<{}, { isDisabled: boolean, s
openHelp = () => {
// open a new page with the documentation
window.open('https://molstar.org/me/docs', '_blank');
window.open('https://molstar.org/me-docs/', '_blank');
};
toggleHelp = () => {
@@ -440,19 +525,17 @@ export class ExplorerInfo extends PluginUIComponent<{}, { isDisabled: boolean, s
const driver = (this.plugin.customState as MesoscaleExplorerState).driver;
if (!driver) return;
const legend = `## Welcome to Mol* Mesoscale Explorer`;
const help = <IconButton svg={HelpOutlineSvg} toggleState={false} small onClick={this.openHelp} title='Open the Documentation' />;
const tour = <IconButton svg={TourSvg} toggleState={false} small onClick={this.toggleHelp} title='Start the interactive tour' />;
return <>
<div id='explorerinfo' style={{ paddingLeft: 4 }} className='msp-help-text'>
<Markdown>{legend}</Markdown>
<div id='explorerinfo' style={{ display: 'flex', alignItems: 'center', padding: '4px 0 4px 8px' }} className='msp-help-text'>
<h2 style={{ flexGrow: 1 }}>Mol* Mesoscale Explorer</h2>
{tour}{help}
</div>
</>;
}
}
export class MesoQuickStylesControls extends CollapsableControls {
defaultState() {
return {
@@ -470,65 +553,18 @@ export class MesoQuickStylesControls extends CollapsableControls {
}
export class MesoQuickStyles extends PluginUIComponent {
state = {
celShaded: false,
};
default_color_values = {
type: 'group-generate',
illustrative: false,
value: [1, 1, 1, 1],
variability: 20,
shift: 0,
lightness: 0,
alpha: 1,
emissive: 0
};
illustrative_color_values = {
type: 'group-generate',
illustrative: true,
value: [1, 1, 1, 1],
variability: 20,
shift: 0,
lightness: 0,
alpha: 1,
emissive: 0
};
async default() {
if (!this.plugin.canvas3d) return;
const p = this.plugin.canvas3d.props;
this.plugin.canvas3d.setProps({
renderer: {
exposure: 1.1,
},
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,
blurDepthBias: 0.5,
resolutionScale: 1,
color: Color(0x000000),
}
},
...p.postprocessing,
shadow: {
name: 'on',
params: {
bias: 0.6,
maxDistance: 80,
steps: 3,
tolerance: 1.0,
@@ -546,49 +582,26 @@ export class MesoQuickStyles extends PluginUIComponent {
dof: { name: 'off', params: {} },
}
});
const loptions = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 } };
const options = { ...loptions, celShaded: false, };
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
await updateColors(this.plugin, this.default_color_values, options);
await updateStyle(this.plugin, {
ignoreLight: true,
material: { metalness: 0, roughness: 1.0, bumpiness: 0 },
celShaded: false,
illustrative: false,
});
}
async celshading() {
if (!this.plugin.canvas3d) return;
const p = this.plugin.canvas3d.props;
this.plugin.canvas3d.setProps({
renderer: {
exposure: 1.5,
},
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.5,
blurKernelSize: 11,
blurDepthBias: 0.5,
resolutionScale: 1,
color: Color(0x000000),
}
},
...p.postprocessing,
shadow: {
name: 'on',
params: {
bias: 0.4,
maxDistance: 256,
steps: 64,
tolerance: 1.0,
@@ -598,49 +611,26 @@ export class MesoQuickStyles extends PluginUIComponent {
dof: { name: 'off', params: {} },
}
});
// ignore Light
const loptions = { ignoreLight: false, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 } };
const options = { ...loptions, celShaded: true, };
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
await updateColors(this.plugin, this.default_color_values, options);
await updateStyle(this.plugin, {
ignoreLight: false,
material: { metalness: 0, roughness: 1.0, bumpiness: 0 },
celShaded: true,
illustrative: false,
});
}
async stylizedDof() {
async shinyDof() {
if (!this.plugin.canvas3d) return;
const p = this.plugin.canvas3d.props;
this.plugin.canvas3d.setProps({
renderer: {
exposure: 1.1,
},
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.3,
blurKernelSize: 11,
blurDepthBias: 0.5,
resolutionScale: 1,
color: Color(0x000000),
}
},
...p.postprocessing,
shadow: {
name: 'on',
params: {
bias: 0.4,
maxDistance: 256,
steps: 64,
tolerance: 1.0,
@@ -660,49 +650,26 @@ export class MesoQuickStyles extends PluginUIComponent {
}
}
});
// ignore Light
const loptions = { ignoreLight: false, materialStyle: { metalness: 0, roughness: 0.2, bumpiness: 0 } };
const options = { ...loptions, celShaded: false };
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
await updateColors(this.plugin, this.default_color_values, options);
await updateStyle(this.plugin, {
ignoreLight: false,
material: { metalness: 0, roughness: 0.2, bumpiness: 0 },
celShaded: false,
illustrative: false,
});
}
async illustrative() {
if (!this.plugin.canvas3d) return;
const p = this.plugin.canvas3d.props;
this.plugin.canvas3d.setProps({
renderer: {
exposure: 1.5,
},
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.5,
blurKernelSize: 11,
blurDepthBias: 0.5,
resolutionScale: 1,
color: Color(0x000000),
}
},
...p.postprocessing,
shadow: {
name: 'on',
params: {
bias: 0.4,
maxDistance: 256,
steps: 64,
tolerance: 1.0,
@@ -720,93 +687,48 @@ export class MesoQuickStyles extends PluginUIComponent {
dof: { name: 'off', params: {} },
}
});
// ignore Light
const loptions = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 } };
const options = { ...loptions, celShaded: false, };
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
await updateColors(this.plugin, this.illustrative_color_values, options);
await updateStyle(this.plugin, {
ignoreLight: true,
material: { metalness: 0, roughness: 1.0, bumpiness: 0 },
celShaded: false,
illustrative: true,
});
}
async shiny() {
if (!this.plugin.canvas3d) return;
const p = this.plugin.canvas3d.props;
this.plugin.canvas3d.setProps({
renderer: {
exposure: 1.5,
},
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.3,
blurKernelSize: 11,
blurDepthBias: 0.5,
resolutionScale: 1,
color: Color(0x000000),
}
},
...p.postprocessing,
shadow: { name: 'off', params: {} },
outline: { name: 'off', params: {} },
dof: { name: 'off', params: {} },
}
});
// ignore Light
const loptions = { ignoreLight: false, materialStyle: { metalness: 0, roughness: 0.2, bumpiness: 0 } };
const options = { ...loptions, celShaded: false };
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
await updateColors(this.plugin, this.default_color_values, options);
await updateStyle(this.plugin, {
ignoreLight: false,
material: { metalness: 0, roughness: 0.2, bumpiness: 0 },
celShaded: false,
illustrative: false,
});
}
async stylized() {
if (!this.plugin.canvas3d) return;
const p = this.plugin.canvas3d.props;
this.plugin.canvas3d.setProps({
renderer: {
exposure: 1.1,
},
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.3,
blurKernelSize: 11,
blurDepthBias: 0.5,
resolutionScale: 1,
color: Color(0x000000),
}
},
...p.postprocessing,
shadow: {
name: 'on',
params: {
bias: 0.4,
maxDistance: 256,
steps: 64,
tolerance: 1.0,
@@ -824,11 +746,12 @@ export class MesoQuickStyles extends PluginUIComponent {
dof: { name: 'off', params: {} },
}
});
// ignore Light
const loptions = { ignoreLight: false, materialStyle: { metalness: 0, roughness: 0.2, bumpiness: 0 } };
const options = { ...loptions, celShaded: false };
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
await updateColors(this.plugin, this.illustrative_color_values, options);
await updateStyle(this.plugin, {
ignoreLight: false,
material: { metalness: 0, roughness: 0.2, bumpiness: 0 },
celShaded: false,
illustrative: true,
});
}
render() {
@@ -851,7 +774,7 @@ export class MesoQuickStyles extends PluginUIComponent {
<Button noOverflow title='Enable shiny material, outline, and illustrative colors' onClick={() => this.stylized()} style={{ width: 'auto' }}>
Shiny-Illustrative
</Button>
<Button noOverflow title='Enable DOF and shiny material' onClick={() => this.stylizedDof()} style={{ width: 'auto' }}>
<Button noOverflow title='Enable DOF and shiny material' onClick={() => this.shinyDof()} style={{ width: 'auto' }}>
Shiny-DOF
</Button>
</div>

View File

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

View File

@@ -0,0 +1,2 @@
import './snapshot-markdown';
import './viewer';

View File

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

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { MolViewSpec } from '../../../extensions/mvs/behavior';
import { loadMVSData } from '../../../extensions/mvs/components/formats';
import { MVSData } from '../../../extensions/mvs/mvs-data';
import { StringLike } from '../../../mol-io/common/string-like';
import { PluginComponent } from '../../../mol-plugin-state/component';
import { createPluginUI } from '../../../mol-plugin-ui';
import { renderReact18 } from '../../../mol-plugin-ui/react18';
import { DefaultPluginUISpec } from '../../../mol-plugin-ui/spec';
import { PluginCommands } from '../../../mol-plugin/commands';
import { PluginConfig } from '../../../mol-plugin/config';
import { PluginContext } from '../../../mol-plugin/context';
import { PluginSpec } from '../../../mol-plugin/spec';
import { getMVSStoriesContext, MVSStoriesContext } from '../context';
export class MVSStoriesViewerModel extends PluginComponent {
readonly context: MVSStoriesContext;
plugin?: PluginContext = undefined;
async mount(root: HTMLElement) {
const spec = DefaultPluginUISpec();
this.plugin = await createPluginUI({
target: root,
render: renderReact18,
spec: {
...spec,
layout: {
initial: {
isExpanded: false,
showControls: false,
controlsDisplay: 'landscape',
},
},
components: {
remoteState: 'none',
viewport: {
snapshotDescription: EmptyDescription,
}
},
behaviors: [
...spec.behaviors,
PluginSpec.Behavior(MolViewSpec)
],
config: [
[PluginConfig.Viewport.ShowAnimation, false],
]
}
});
this.subscribe(this.context.commands, async (cmd) => {
if (!cmd || !this.plugin) return;
try {
this.context.state.isLoading.next(true);
if (cmd.kind === 'load-mvs') {
let loadedData: MVSData | StringLike | Uint8Array | undefined;
if (cmd.url) {
const data = await this.plugin.runTask(this.plugin.fetch({ url: cmd.url, type: cmd.format === 'mvsx' ? 'binary' : 'string' }));
loadedData = await loadMVSData(this.plugin, data, cmd.format ?? 'mvsj', { sourceUrl: cmd.url });
} else if (cmd.data) {
loadedData = await loadMVSData(this.plugin, cmd.data, cmd.format ?? 'mvsj');
}
if (StringLike.is(loadedData) || loadedData instanceof Uint8Array) {
this.context.state.currentStoryData.next(loadedData as string | Uint8Array);
} else if (loadedData) {
this.context.state.currentStoryData.next(JSON.stringify(loadedData));
}
}
} catch (e) {
console.error(e);
PluginCommands.Toast.Show(
this.plugin,
{ key: '<mvsload>', title: 'Error', message: e?.message ? `${e?.message}` : `${e}`, timeoutMs: 10000 }
);
} finally {
this.context.state.isLoading.next(false);
}
});
const viewers = this.context.state.viewers.value;
const next = [...viewers, { name: this.options?.name, model: this }];
this.context.state.viewers.next(next);
}
constructor(private options?: { context?: { name?: string, container?: object }, name?: string }) {
super();
this.context = getMVSStoriesContext(options?.context);
const viewers = this.context.state.viewers.value;
const index = viewers.findIndex(v => v.name === options?.name);
if (index >= 0) {
const next = [...viewers];
next[index].model.dispose();
next.splice(index, 0);
this.context.state.viewers.next(next);
}
}
}
function EmptyDescription() {
return <></>;
}
export class MVSStoriesViewer extends HTMLElement {
private model: MVSStoriesViewerModel | undefined = undefined;
async connectedCallback() {
this.model = new MVSStoriesViewerModel({
name: this.getAttribute('name') ?? undefined,
context: { name: this.getAttribute('context-name') ?? undefined },
});
await this.model.mount(this);
}
disconnectedCallback() {
this.model?.dispose();
this.model = undefined;
}
constructor() {
super();
}
}
window.customElements.define('mvs-stories-viewer', MVSStoriesViewer);

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,121 @@
<!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>Molecular Stories</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#viewer {
position: absolute;
left: 0;
top: 0;
right: 34%;
bottom: 0;
}
#controls {
position: absolute;
left: 66%;
top: 0;
right: 0;
bottom: 0;
padding: 16px;
padding-bottom: 20px;
border: 1px solid #ccc;
border-left: none;
background: #F6F5F3;
z-index: -2;
display: flex;
flex-direction: column;
gap: 16px;
}
#links {
position: absolute;
bottom: 4px;
right: 8px;
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 0.6rem;
z-index: -1;
color: #666;
}
#links a {
color: #666;
text-decoration: none;
}
@media (orientation:portrait) {
#viewer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 40%;
}
#controls {
position: absolute;
left: 0;
top: 60%;
right: 0;
bottom: 0;
border-top: none;
}
.msp-viewport-controls-buttons {
display: none;
}
}
</style>
<link rel="stylesheet" type="text/css" href="mvs-stories.css" />
<script type="text/javascript" src="mvs-stories.js"></script>
</head>
<body>
<!-- the context-name parameter is optional and useful when embedding multiple stories in a single page -->
<div id="viewer">
<mvs-stories-viewer context-name="story1" ></mvs-stories-viewer>
</div>
<div id="controls">
<mvs-stories-snapshot-markdown context-name="story1" style="flex-grow: 1;" ></mvs-stories-snapshot-markdown>
</div>
<div id="links">
<a href="#" id="mvs-data">Download MVS State</a> | <a href="https://github.com/molstar/molstar/tree/master/src/apps/mvs-stories" id="mvs-data" target="_blank" rel="noopener noreferrer">Source Code</a>
</div>
<script>
var urlParams = new URLSearchParams(window.location.search);
var storyId = urlParams.get('story-id');
var storyUrl = urlParams.get('story-url');
var format = urlParams.get('data-format');
// For testing purposes:
// if (!storyUrl) {
// storyUrl = 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/kinase-story.mvsj';
// }
if (storyId) {
mvsStories.loadFromID(storyId, { format: format || 'mvsj', contextName: 'story1' });
} else if (storyUrl) {
mvsStories.loadFromURL(storyUrl, { format: format || 'mvsj', contextName: 'story1' });
}
document.getElementById('mvs-data').addEventListener('click', (e) => {
e.preventDefault();
mvsStories.downloadCurrentStory({ contextName: 'story1' });
});
</script>
<!-- __MOLSTAR_ANALYTICS__ -->
</body>
</html>

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { getMVSStoriesContext } from './context';
import './elements';
import { MVSData } from '../../extensions/mvs/mvs-data';
import { download } from '../../mol-util/download';
import './favicon.ico';
import '../../mol-plugin-ui/skin/light.scss';
import './styles.scss';
import './index.html';
export function getContext(name?: string) {
return getMVSStoriesContext({ name });
}
export function loadFromURL(url: string, options?: { format: 'mvsx' | 'mvsj', contextName?: string }) {
setTimeout(() => {
getContext(options?.contextName).dispatch({
kind: 'load-mvs',
format: options?.format ?? 'mvsj',
url,
});
}, 0);
}
export function loadFromData(data: MVSData | string | Uint8Array, options?: { format: 'mvsx' | 'mvsj', contextName?: string }) {
setTimeout(() => {
getContext(options?.contextName).dispatch({
kind: 'load-mvs',
format: options?.format ?? 'mvsj',
data,
});
}, 0);
}
function getStoryUrlFromId(id: string, format: 'mvsx' | 'mvsj' = 'mvsj') {
return `https://stories.molstar.org/api/story/${id}/data`;
}
export function loadFromID(id: string, options?: { format?: 'mvsx' | 'mvsj', contextName?: string }) {
loadFromURL(
getStoryUrlFromId(id, options?.format),
{ format: options?.format ?? 'mvsj', contextName: options?.contextName },
);
}
export function downloadCurrentStory(options?: { contextName?: string, filename?: string }) {
const story = getContext(options?.contextName).state.currentStoryData.value;
if (!story) return;
const isMVSJ = typeof story === 'string';
const filename = `${options?.filename ?? 'story'}.${isMVSJ ? 'mvsj' : 'mvsx'}`;
download(
new Blob([typeof story === 'string' ? story : story.buffer], { type: isMVSJ ? 'application/json' : 'application/octet-stream' }),
filename
);
};
export { MVSData };

View File

@@ -0,0 +1,66 @@
# MolViewSpec Stories App
An app that defines `mvs-stories-snapshot-markdown` and `mvs-stories-viewer` web components that can be used to view MolViewSpec molecular stories.
See the [mvs-stories](../../examples/mvs-stories) example that includes specific stories.
### Usage
- Get `mvs-stories.css` and `mvs-stories.js` from `build/mvs-stories` and include these to your HTML page
```html
<link rel="stylesheet" type="text/css" href="mvs-stories.css" />
<script type="text/javascript" src="mvs-stories.js"></script>
```
Can also use `https://cdn.jsdelivr.net/npm/molstar@latest/build/mvs-stories/mvs-stories.js` (and `.css`). `latest` can be substituted by specific version.
- Place the components in your page wrapper in `<div>` elements to set up positioning:
```html
<div class="viewer">
<mvs-stories-viewer />
</div>
<div class="snapshot">
<mvs-stories-snapshot-markdown />
</div>
```
- Load MolViewSpec state:
```html
<script>
mvsStories.loadFromURL('https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj');
</script>
```
- See [index.html](./index.html) for full example of how to embed the app.
- For interactive development build (for production use `npm run build`) of the example that immediately reflects changes use:
```bash
npm run dev -- -a mvs-stories
```
### Multiple Stories on a Single Page
To support multiple instances of stories, use the `context-name='unique-name'` attribute on the `mvs-` components together with `loadFromURL/Data(..., { contextName: 'unique-name' })`.
For example (simplified to not include layout):
```html
<div>
<mvs-stories-viewer context-name="1" />
<mvs-stories-snapshot-markdown context-name="1" />
</div>
<div>
<mvs-stories-viewer context-name="2" />
<mvs-stories-snapshot-markdown context-name="2" />
</div>
<script>
mvsStories.loadFromURL('1.mvsj', { format: 'mvsj', contextName: '1' });
mvsStories.loadFromURL('2.mvsj', { format: 'mvsj', contextName: '2' });
</script>
```

View File

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

View File

@@ -0,0 +1,7 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
export const VERSION = 1;

View File

@@ -1,9 +1,10 @@
/**
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Neli Fonseca <neli@ebi.ac.uk>
* @author Adam Midlik <midlik@gmail.com>
*/
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
@@ -11,19 +12,18 @@ import { Backgrounds } from '../../extensions/backgrounds';
import { DnatcoNtCs } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { GeometryExport } from '../../extensions/geo-export';
import { MAQualityAssessment, QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
import { MAQualityAssessment, MAQualityAssessmentConfig, QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
import { ModelExport } from '../../extensions/model-export';
import { Mp4Export } from '../../extensions/mp4-export';
import { MolViewSpec } from '../../extensions/mvs/behavior';
import { loadMVSX } from '../../extensions/mvs/components/formats';
import { loadMVS } from '../../extensions/mvs/load';
import { loadMVSData, loadMVSX } from '../../extensions/mvs/components/formats';
import { loadMVS, MolstarLoadingExtension } from '../../extensions/mvs/load';
import { MVSData } from '../../extensions/mvs/mvs-data';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBValidationReport } from '../../extensions/rcsb';
import { AssemblySymmetry, AssemblySymmetryConfig } from '../../extensions/assembly-symmetry';
import { SbNcbrPartialCharges, SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider, SbNcbrTunnels } from '../../extensions/sb-ncbr';
import { 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';
@@ -47,7 +47,7 @@ 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';
import { PluginConfig, PluginConfigItem } from '../../mol-plugin/config';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { PluginSpec } from '../../mol-plugin/spec';
import { PluginState } from '../../mol-plugin/state';
@@ -57,16 +57,17 @@ import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers';
import { OpenFiles } from '../../mol-plugin-state/actions/file';
import { StringLike } from '../../mol-io/common/string-like';
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { consoleStats, setDebugMode, setProductionMode, setTimingMode } from '../../mol-util/debug';
export { consoleStats, setDebugMode, setProductionMode, setTimingMode, isProductionMode, isDebugMode, isTimingMode } from '../../mol-util/debug';
const CustomFormats = [
['g3d', G3dProvider] as const
];
export const ExtensionMap = {
'volseg': PluginSpec.Behavior(Volseg),
'backgrounds': PluginSpec.Behavior(Backgrounds),
'dnatco-ntcs': PluginSpec.Behavior(DnatcoNtCs),
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
@@ -105,6 +106,8 @@ const DefaultViewerOptions = {
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
resolutionMode: PluginConfig.General.ResolutionMode.defaultValue,
illumination: false,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
@@ -118,10 +121,11 @@ const DefaultViewerOptions = {
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
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,
config: [] as [PluginConfigItem, any][],
};
type ViewerOptions = typeof DefaultViewerOptions;
@@ -182,6 +186,7 @@ export class Viewer {
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
[PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat],
[PluginConfig.General.PowerPreference, o.powerPreference],
[PluginConfig.General.ResolutionMode, o.resolutionMode],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
@@ -196,10 +201,10 @@ export class Viewer {
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
[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],
...(o.config ?? []),
]
};
@@ -217,6 +222,7 @@ export class Viewer {
plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
}
});
plugin.canvas3d?.setProps({ illumination: { enabled: o.illumination } });
return new Viewer(plugin);
}
@@ -280,14 +286,21 @@ export class Viewer {
}));
}
/**
* @deprecated Scheduled for removal in v5. Use {@link loadPdbIhm | loadPdbIhm(pdbIhm: string)} instead.
*/
loadPdbDev(pdbDev: string) {
return this.loadPdbIhm(pdbDev);
}
loadPdbIhm(pdbIhm: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'pdb-dev' as const,
name: 'pdb-ihm' as const,
params: {
provider: {
id: pdbDev,
id: pdbIhm,
encoding: 'bcif',
},
options: params.source.params.options,
@@ -318,7 +331,10 @@ export class Viewer {
source: {
name: 'alphafolddb' as const,
params: {
id: afdb,
provider: {
id: afdb,
encoding: 'bcif'
},
options: {
...params.source.params.options,
representation: 'preset-structure-representation-ma-quality-assessment-plddt'
@@ -478,7 +494,8 @@ export class Viewer {
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
const provider = plugin.dataFormats.get(params.model.format);
model = await provider!.parse(plugin, data);
const parsed = await provider!.parse(plugin, data);
model = parsed.topology;
}
const data = params.coordinates.kind === 'coordinates-data'
@@ -500,10 +517,10 @@ export class Viewer {
return { model, coords, preset };
}
async loadMvsFromUrl(url: string, format: 'mvsj' | 'mvsx', options?: { replaceExisting?: boolean, keepCamera?: boolean }) {
async loadMvsFromUrl(url: string, format: 'mvsj' | 'mvsx', options?: { appendSnapshots?: boolean, keepCamera?: boolean, extensions?: MolstarLoadingExtension<any>[] }) {
if (format === 'mvsj') {
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'string' }));
const mvsData = MVSData.fromMVSJ(data);
const mvsData = MVSData.fromMVSJ(StringLike.toString(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' }));
@@ -519,26 +536,24 @@ export class Viewer {
/** 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 });
}));
loadMvsData(data: string | Uint8Array, format: 'mvsj' | 'mvsx', options?: { appendSnapshots?: boolean, keepCamera?: boolean, extensions?: MolstarLoadingExtension<any>[] }) {
return loadMVSData(this.plugin, data, format, options);
}
loadFiles(files: File[]) {
const sessions = files.filter(f => {
const fn = f.name.toLowerCase();
return fn.endsWith('.molx') || fn.endsWith('.molj');
});
if (sessions.length > 0) {
return PluginCommands.State.Snapshots.OpenFile(this.plugin, { file: sessions[0] });
} else {
throw new Error(`Unknown MolViewSpec format: ${format}`);
return this.plugin.runTask(this.plugin.state.data.applyAction(OpenFiles, {
files: files.map(f => Asset.File(f)),
format: { name: 'auto', params: {} },
visuals: true
}));
}
}
@@ -607,5 +622,10 @@ export const ViewerAutoPreset = StructureRepresentationPresetProvider({
export const PluginExtensions = {
wwPDBStructConn: wwPDBStructConnExtensionFunctions,
mvs: { MVSData, loadMVS },
mvs: { MVSData, loadMVS, loadMVSData },
modelArchive: {
qualityAssessment: {
config: MAQualityAssessmentConfig
}
}
};

View File

@@ -35,6 +35,7 @@
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
<!-- __MOLSTAR_MANIFEST__ -->
</head>
<body>
<div id="app"></div>
@@ -63,6 +64,8 @@
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 illumination = getParam('illumination', '[^&]+').trim() === '1';
var resolutionMode = getParam('resolution-mode', '[^&]+').trim().toLowerCase();
// console.log('Available extensions: ', Object.keys(molstar.ExtensionMap));
@@ -83,6 +86,8 @@
preferWebgl1: preferWebgl1,
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
powerPreference: powerPreference || 'high-performance',
illumination: illumination,
resolutionMode: resolutionMode || 'auto'
}).then(viewer => {
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
@@ -107,8 +112,11 @@
var pdb = getParam('pdb', '[^&]+').trim();
if (pdb) viewer.loadPdb(pdb);
var pdbIhm = getParam('pdb-ihm', '[^&]+').trim();
if (pdbIhm) viewer.loadPdbIhm(pdbIhm);
// support for deprecated pdb-dev param
var pdbDev = getParam('pdb-dev', '[^&]+').trim();
if (pdbDev) viewer.loadPdbDev(pdbDev);
if (pdbDev) viewer.loadPdbIhm(pdbDev);
var emdb = getParam('emdb', '[^&]+').trim();
if (emdb) viewer.loadEmdb(emdb);
@@ -123,8 +131,12 @@
// to aid GC
viewer.dispose();
});
const event = new CustomEvent("molstarViewerCreated", { detail: { viewer } });
window.dispatchEvent(event);
});
</script>
<!-- __MOLSTAR_PWA__ -->
<!-- __MOLSTAR_ANALYTICS__ -->
</body>
</html>

View File

@@ -8,5 +8,5 @@
import './embedded.html';
import './favicon.ico';
import './index.html';
require('mol-plugin-ui/skin/light.scss');
import '../../mol-plugin-ui/skin/light.scss';
export * from './app';

View File

@@ -15,6 +15,7 @@ import { classifyFloatArray, classifyIntArray } from '../../mol-io/common/binary
import { BinaryEncodingProvider } from '../../mol-io/writer/cif/encoder/binary';
import { Category } from '../../mol-io/writer/cif/encoder';
import { ReaderResult } from '../../mol-io/reader/result';
import { utf8ReadLong } from '../../mol-io/common/utf8';
function showProgress(p: Progress) {
process.stdout.write(`\r${new Array(80).join(' ')}`);
@@ -31,14 +32,10 @@ async function readFile(ctx: RuntimeContext, filename: string): Promise<ReaderRe
if (isGz) input = await unzipAsync(input);
return await CIF.parseBinary(new Uint8Array(input)).runInContext(ctx);
} else {
let str: string;
if (isGz) {
const data = await unzipAsync(await readFileAsync(filename));
str = data.toString('utf8');
} else {
str = await readFileAsync(filename, 'utf8');
}
return await CIF.parseText(str).runInContext(ctx);
const data = isGz ? await unzipAsync(await readFileAsync(filename)) : await readFileAsync(filename);
const str = utf8ReadLong(data);
const cif = await CIF.parseText(str).runInContext(ctx);
return cif;
}
}

View File

@@ -234,13 +234,19 @@ const FORCE_INT_FIELDS = [
'_atom_site.id',
'_atom_site.auth_seq_id',
'_atom_site_anisotrop.id',
'_atom_site_anisotrop.pdbx_auth_seq_id',
'_pdbx_struct_mod_residue.auth_seq_id',
'_pdbx_unobs_or_zero_occ_residues.auth_seq_id',
'_struct_conf.beg_auth_seq_id',
'_struct_conf.end_auth_seq_id',
'_struct_conn.ptnr1_auth_seq_id',
'_struct_conn.ptnr2_auth_seq_id',
'_struct_sheet_range.beg_auth_seq_id',
'_struct_sheet_range.end_auth_seq_id',
'_struct_site.pdbx_auth_seq_id',
'_struct_site_gen.auth_seq_id',
'_struct_mon_prot_cis.auth_seq_id',
'_struct_mon_prot_cis.pdbx_auth_seq_id_2',
];
/**

View File

@@ -13,8 +13,8 @@ import { UniqueArray } from '../../mol-data/generic';
const LIPIDS_DIR = path.resolve(__dirname, '../../../../build/lipids/');
const MARTINI_LIPIDS_PATH = path.resolve(LIPIDS_DIR, 'martini_lipids.itp');
const MARTINI_LIPIDS_URL = 'http://www.cgmartini.nl/images/parameters/lipids/Collections/martini_v2.0_lipids_all_201506.itp';
const MARTINI_LIPIDS_PATH = path.resolve(LIPIDS_DIR, 'martini_lipids_v3.itp');
const MARTINI_LIPIDS_URL = 'https://cgmartini-library.s3.ca-central-1.amazonaws.com/1_Downloads/ff_parameters/martini3/martini_v3.0.0_phospholipids_v1.itp';
async function ensureAvailable(path: string, url: string) {
if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
@@ -32,6 +32,7 @@ async function ensureAvailable(path: string, url: string) {
async function ensureLipidsAvailable() { await ensureAvailable(MARTINI_LIPIDS_PATH, MARTINI_LIPIDS_URL); }
const extraLipids = ['DMPC'];
const v2lipids = ['DAPC', 'DBPC', 'DFPC', 'DGPC', 'DIPC', 'DLPC', 'DNPC', 'DOPC', 'DPPC', 'DRPC', 'DTPC', 'DVPC', 'DXPC', 'DYPC', 'LPPC', 'PAPC', 'PEPC', 'PGPC', 'PIPC', 'POPC', 'PRPC', 'PUPC', 'DAPE', 'DBPE', 'DFPE', 'DGPE', 'DIPE', 'DLPE', 'DNPE', 'DOPE', 'DPPE', 'DRPE', 'DTPE', 'DUPE', 'DVPE', 'DXPE', 'DYPE', 'LPPE', 'PAPE', 'PGPE', 'PIPE', 'POPE', 'PQPE', 'PRPE', 'PUPE', 'DAPS', 'DBPS', 'DFPS', 'DGPS', 'DIPS', 'DLPS', 'DNPS', 'DOPS', 'DPPS', 'DRPS', 'DTPS', 'DUPS', 'DVPS', 'DXPS', 'DYPS', 'LPPS', 'PAPS', 'PGPS', 'PIPS', 'POPS', 'PQPS', 'PRPS', 'PUPS', 'DAPG', 'DBPG', 'DFPG', 'DGPG', 'DIPG', 'DLPG', 'DNPG', 'DOPG', 'DPPG', 'DRPG', 'DTPG', 'DVPG', 'DXPG', 'DYPG', 'LPPG', 'PAPG', 'PGPG', 'PIPG', 'POPG', 'PRPG', 'DAPA', 'DBPA', 'DFPA', 'DGPA', 'DIPA', 'DLPA', 'DNPA', 'DOPA', 'DPPA', 'DRPA', 'DTPA', 'DVPA', 'DXPA', 'DYPA', 'LPPA', 'PAPA', 'PGPA', 'PIPA', 'POPA', 'PRPA', 'PUPA', 'DPP', 'DPPI', 'PAPI', 'PIPI', 'POP', 'POPI', 'PUPI', 'PVP', 'PVPI', 'PADG', 'PIDG', 'PODG', 'PUDG', 'PVDG', 'APC', 'CPC', 'IPC', 'LPC', 'OPC', 'PPC', 'TPC', 'UPC', 'VPC', 'BNSM', 'DBSM', 'DPSM', 'DXSM', 'PGSM', 'PNSM', 'POSM', 'PVSM', 'XNSM', 'DPCE', 'DXCE', 'PNCE', 'XNCE'];
async function run(out: string) {
await ensureLipidsAvailable();
@@ -50,11 +51,15 @@ async function run(out: string) {
UniqueArray.add(lipids, v, v);
}
for (const v of v2lipids) {
UniqueArray.add(lipids, v, v);
}
const lipidNames = JSON.stringify(lipids.array);
if (out) {
const output = `/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated lipid params file. Names extracted from Martini FF lipids itp.
*

View File

@@ -12,7 +12,6 @@
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';
@@ -32,9 +31,9 @@ function parseArguments(): Args {
/** Main workflow for printing MolViewSpec tree schema. */
function main(args: Args) {
if (args.markdown) {
console.log(treeSchemaToMarkdown(MVSTreeSchema, MVSDefaults));
console.log(treeSchemaToMarkdown(MVSTreeSchema));
} else {
console.log(treeSchemaToString(MVSTreeSchema, MVSDefaults));
console.log(treeSchemaToString(MVSTreeSchema));
}
}

View File

@@ -5,9 +5,14 @@
* @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
* From Molstar NPM package:
* npm install molstar canvas gl jpeg-js pngjs
* npx mvs-render -i examples/mvs/1cbs.mvsj -o ../outputs/1cbs.png --size 800x600 --molj
* From Molstar source code:
* npm install
* npm install --no-save canvas gl jpeg-js pngjs // these packages are not listed in Mol* dependencies for performance reasons
* npm run build
* node lib/commonjs/cli/mvs/mvs-render -i examples/mvs/1cbs.mvsj -o ../outputs/1cbs.png --size 800x600 --molj
*/
import { ArgumentParser } from 'argparse';
@@ -29,6 +34,7 @@ import { onelinerJsonString } from '../../mol-util/json';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
// MolViewSpec must be imported after HeadlessPluginContext
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';
@@ -46,6 +52,7 @@ interface Args {
output: string[],
size: { width: number, height: number },
molj: boolean,
no_extensions: boolean,
}
/** Return parsed command line arguments for `main` */
@@ -55,6 +62,7 @@ function parseArguments(): Args {
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)` });
parser.add_argument('-n', '--no-extensions', { action: 'store_true', help: `Do not apply builtin MVS-loading extensions (not a part of standard MVS specification)` });
const args = parser.parse_args();
try {
const parts = args.size.split('x');
@@ -92,13 +100,17 @@ async function main(args: Args): Promise<void> {
} else {
throw new Error(`Input file name must end with .mvsj or .mvsx: ${input}`);
}
await loadMVS(plugin, mvsData, { sanityChecks: true, replaceExisting: true, sourceUrl: sourceUrl });
await loadMVS(plugin, mvsData, { sanityChecks: true, sourceUrl: sourceUrl, extensions: args.no_extensions ? [] : undefined });
fs.mkdirSync(path.dirname(output), { recursive: true });
if (args.molj) {
await plugin.saveStateSnapshot(withExtension(output, '.molj'));
}
await plugin.saveImage(output);
if (output.toLowerCase().endsWith('.mp4')) {
await plugin.saveAnimation(output);
} else {
await plugin.saveImage(output);
}
checkState(plugin);
}
await plugin.clear();
@@ -110,6 +122,7 @@ async function createHeadlessPlugin(args: Pick<Args, 'size'>): Promise<HeadlessP
const externalModules: ExternalModules = { gl, pngjs, 'jpeg-js': jpegjs };
const spec = DefaultPluginSpec();
spec.behaviors.push(PluginSpec.Behavior(MolViewSpec));
spec.behaviors.push(PluginSpec.Behavior(Mp4Export));
const headlessCanvasOptions = defaultCanvas3DParams();
const canvasOptions = {
...PD.getDefaultValues(Canvas3DParams),

View File

@@ -43,7 +43,7 @@ function paramInfo(param: PD.Any, offset: number): string {
}
}
function oToS(options: readonly (readonly [string, string] | readonly [string, string, string | undefined])[]) {
function oToS(options: readonly PD.SelectOption<any>[]) {
return options.map(o => `'${o[0]}'`).join(', ');
}

View File

@@ -47,8 +47,8 @@
<div id="app"></div>
<div id='controls'></div>
<div id='sponsor'>
<a href='https://www.entos.ai/envision' target="_blank" rel="noopener">
<svg class="makeStyles-root-46" viewBox="0 0 190 36" xmlns="http://www.w3.org/2000/svg"><path d="M32.2591 28.6707C32.2591 32.3914 29.2421 35.407 25.5214 35.407C22.0752 35.407 19.2338 32.8206 18.8325 29.4831V29.4775C18.8143 29.3312 18.8018 29.1835 18.7934 29.0344C18.7934 29.0316 18.7921 29.0274 18.7921 29.0246V29.0177C18.7865 28.902 18.7837 28.7864 18.7837 28.6707C18.7837 26.2557 20.0532 24.1389 21.9609 22.9503C21.9623 22.9489 21.9651 22.9489 21.9665 22.9475C22.0933 22.8666 22.2243 22.7914 22.3581 22.7203C22.3581 22.7203 22.3595 22.7203 22.3595 22.7189C23.3029 22.2173 24.3787 21.933 25.5214 21.933C29.2421 21.933 32.2591 24.9486 32.2591 28.6707Z"></path><path d="M25.5214 14.0692C29.2421 14.0692 32.2591 11.0522 32.2591 7.33146C32.2591 3.61074 29.2421 0.59375 25.5214 0.59375C22.0529 0.59375 19.1962 3.21637 18.8255 6.58592C18.8185 6.67092 18.8116 6.75454 18.8018 6.83815C18.7893 7.00119 18.7837 7.16563 18.7837 7.33146C18.7837 9.73669 20.0434 11.8465 21.94 13.038C22.0891 13.116 22.2355 13.201 22.3776 13.2916C22.3783 13.2923 22.379 13.2926 22.3797 13.293C22.3804 13.2933 22.3811 13.2937 22.3818 13.2944C23.3196 13.7891 24.3871 14.0692 25.5214 14.0692Z"></path><path d="M19.3645 12.4113C20.2926 12.4113 21.1694 12.638 21.94 13.038C20.0434 11.8465 18.7837 9.73669 18.7837 7.33146C18.7837 7.16563 18.7893 7.00119 18.8018 6.83815C18.4688 9.76455 16.1385 12.0866 13.2065 12.4044C13.8545 13.1193 14.3785 13.9484 14.745 14.857C15.7497 13.3798 17.4443 12.4113 19.3645 12.4113Z"></path><path d="M14.7312 21.1249V21.1236C14.1279 20.2331 13.7767 19.1587 13.7767 18.0007V17.9728C13.7767 15.3084 12.2285 13.0035 9.9835 11.911C9.98141 11.9103 9.97967 11.9096 9.97793 11.9089C9.97619 11.9082 9.97444 11.9075 9.97235 11.9068C9.96817 11.904 9.96538 11.9026 9.9612 11.9012C9.95981 11.9012 9.95981 11.8998 9.95981 11.8998C9.9417 11.8915 9.92394 11.8831 9.90618 11.8747C9.8884 11.8664 9.87063 11.858 9.85251 11.8497C9.82046 11.8343 9.78701 11.819 9.75357 11.8051L9.74521 11.8009C8.91745 11.4372 8.0019 11.2351 7.03898 11.2351C3.31826 11.2351 0.30127 14.2521 0.30127 17.9728C0.30127 21.6935 3.31826 24.7105 7.03898 24.7105C7.98797 24.7105 8.89098 24.514 9.71037 24.1601C9.71246 24.1594 9.7142 24.1583 9.71594 24.1573C9.71768 24.1562 9.71943 24.1552 9.72152 24.1545C9.8107 24.1169 9.8985 24.0765 9.9849 24.0333L9.98629 24.0319C10.7625 23.6919 11.6181 23.5037 12.5197 23.5037C12.7524 23.5037 12.9824 23.5163 13.2081 23.54C13.2082 23.5399 13.2081 23.54 13.2081 23.54C15.0168 23.7365 16.5971 24.695 17.6185 26.0885C17.9195 25.1688 18.3752 24.3201 18.9563 23.5732C17.1964 23.4464 15.6635 22.5058 14.7312 21.1249Z"></path><g clip-path="url(#clip0)"><path d="M106.391 18.0021C106.391 11.3724 101.039 6 94.4389 6H88.4585C81.8581 6 76.5061 11.3724 76.5061 18.0021V30.0042H81.2845V18.0021C81.2845 14.0268 84.4941 10.8008 88.4585 10.8008H94.4347C98.395 10.8008 101.609 14.0226 101.609 18.0021V30.0042H106.391V18.0021Z"></path><path d="M149.432 6H142.258C135.653 6 130.301 11.3724 130.301 18.0021C130.301 24.6319 135.653 30.0042 142.258 30.0042H149.432C156.036 30.0042 161.388 24.6319 161.388 18.0021C161.388 11.3724 156.032 6 149.432 6ZM149.432 25.1992H142.258C138.297 25.1992 135.084 21.9774 135.084 17.9979C135.084 14.0183 138.293 10.7966 142.258 10.7966H149.432C153.392 10.7966 156.606 14.0183 156.606 17.9979C156.606 21.9774 153.392 25.1992 149.432 25.1992Z"></path><path d="M74.1151 25.1992H58.5736C55.4526 25.1992 52.804 23.1924 51.8171 20.3983H74.1151V17.9979C74.1151 17.1808 74.1868 16.3807 74.3175 15.5975H51.8171C52.804 12.8033 55.4526 10.7966 58.5736 10.7966H76.0383C77.1475 8.87458 78.6911 7.22773 80.5299 6H58.5736C51.969 6 46.6169 11.3724 46.6169 18.0021C46.6169 24.6276 51.969 30 58.5736 30H74.1151V25.1992Z"></path><path d="M120.74 6H115.958H102.369C104.212 7.22773 105.751 8.87458 106.861 10.8008H115.958V30H120.74V10.8008H129.838C130.947 8.87458 132.486 7.22773 134.329 6H120.74Z"></path><path d="M182.906 15.6017H169.756C168.436 15.6017 167.365 14.5264 167.365 13.2013C167.365 11.8762 168.436 10.8008 169.756 10.8008H188.882V6H169.756C165.796 6 162.582 9.22173 162.582 13.2013C162.582 17.1808 165.791 20.4025 169.756 20.4025H182.906C184.226 20.4025 185.297 21.4779 185.297 22.803C185.297 24.1281 184.226 25.2034 182.906 25.2034H161.852C160.743 27.1297 159.199 28.7765 157.361 30.0042H182.906C186.866 30.0042 190.08 26.7825 190.08 22.803C190.08 18.8234 186.866 15.6017 182.906 15.6017Z"></path></g><defs><clipPath id="clip0"><rect width="190" height="24" fill="white" transform="translate(0 6)"></rect></clipPath></defs></svg>
<a href='https://www.iambic-envision.com/' target="_blank" rel="noopener">
<svg viewBox="0 0 190 36" xmlns="http://www.w3.org/2000/svg"><path d="M32.2591 28.6707C32.2591 32.3914 29.2421 35.407 25.5214 35.407C22.0752 35.407 19.2338 32.8206 18.8325 29.4831V29.4775C18.8143 29.3312 18.8018 29.1835 18.7934 29.0344C18.7934 29.0316 18.7921 29.0274 18.7921 29.0246V29.0177C18.7865 28.902 18.7837 28.7864 18.7837 28.6707C18.7837 26.2557 20.0532 24.1389 21.9609 22.9503C21.9623 22.9489 21.9651 22.9489 21.9665 22.9475C22.0933 22.8666 22.2243 22.7914 22.3581 22.7203C22.3581 22.7203 22.3595 22.7203 22.3595 22.7189C23.3029 22.2173 24.3787 21.933 25.5214 21.933C29.2421 21.933 32.2591 24.9486 32.2591 28.6707Z"></path><path d="M25.5214 14.0692C29.2421 14.0692 32.2591 11.0522 32.2591 7.33146C32.2591 3.61074 29.2421 0.59375 25.5214 0.59375C22.0529 0.59375 19.1962 3.21637 18.8255 6.58592C18.8185 6.67092 18.8116 6.75454 18.8018 6.83815C18.7893 7.00119 18.7837 7.16563 18.7837 7.33146C18.7837 9.73669 20.0434 11.8465 21.94 13.038C22.0891 13.116 22.2355 13.201 22.3776 13.2916C22.3783 13.2923 22.379 13.2926 22.3797 13.293C22.3804 13.2933 22.3811 13.2937 22.3818 13.2944C23.3196 13.7891 24.3871 14.0692 25.5214 14.0692Z"></path><path d="M19.3645 12.4113C20.2926 12.4113 21.1694 12.638 21.94 13.038C20.0434 11.8465 18.7837 9.73669 18.7837 7.33146C18.7837 7.16563 18.7893 7.00119 18.8018 6.83815C18.4688 9.76455 16.1385 12.0866 13.2065 12.4044C13.8545 13.1193 14.3785 13.9484 14.745 14.857C15.7497 13.3798 17.4443 12.4113 19.3645 12.4113Z"></path><path d="M14.7312 21.1249V21.1236C14.1279 20.2331 13.7767 19.1587 13.7767 18.0007V17.9728C13.7767 15.3084 12.2285 13.0035 9.9835 11.911C9.98141 11.9103 9.97967 11.9096 9.97793 11.9089C9.97619 11.9082 9.97444 11.9075 9.97235 11.9068C9.96817 11.904 9.96538 11.9026 9.9612 11.9012C9.95981 11.9012 9.95981 11.8998 9.95981 11.8998C9.9417 11.8915 9.92394 11.8831 9.90618 11.8747C9.8884 11.8664 9.87063 11.858 9.85251 11.8497C9.82046 11.8343 9.78701 11.819 9.75357 11.8051L9.74521 11.8009C8.91745 11.4372 8.0019 11.2351 7.03898 11.2351C3.31826 11.2351 0.30127 14.2521 0.30127 17.9728C0.30127 21.6935 3.31826 24.7105 7.03898 24.7105C7.98797 24.7105 8.89098 24.514 9.71037 24.1601C9.71246 24.1594 9.7142 24.1583 9.71594 24.1573C9.71768 24.1562 9.71943 24.1552 9.72152 24.1545C9.8107 24.1169 9.8985 24.0765 9.9849 24.0333L9.98629 24.0319C10.7625 23.6919 11.6181 23.5037 12.5197 23.5037C12.7524 23.5037 12.9824 23.5163 13.2081 23.54C13.2082 23.5399 13.2081 23.54 13.2081 23.54C15.0168 23.7365 16.5971 24.695 17.6185 26.0885C17.9195 25.1688 18.3752 24.3201 18.9563 23.5732C17.1964 23.4464 15.6635 22.5058 14.7312 21.1249Z"></path><g clip-path="url(#clip0)"><path d="M106.391 18.0021C106.391 11.3724 101.039 6 94.4389 6H88.4585C81.8581 6 76.5061 11.3724 76.5061 18.0021V30.0042H81.2845V18.0021C81.2845 14.0268 84.4941 10.8008 88.4585 10.8008H94.4347C98.395 10.8008 101.609 14.0226 101.609 18.0021V30.0042H106.391V18.0021Z"></path><path d="M149.432 6H142.258C135.653 6 130.301 11.3724 130.301 18.0021C130.301 24.6319 135.653 30.0042 142.258 30.0042H149.432C156.036 30.0042 161.388 24.6319 161.388 18.0021C161.388 11.3724 156.032 6 149.432 6ZM149.432 25.1992H142.258C138.297 25.1992 135.084 21.9774 135.084 17.9979C135.084 14.0183 138.293 10.7966 142.258 10.7966H149.432C153.392 10.7966 156.606 14.0183 156.606 17.9979C156.606 21.9774 153.392 25.1992 149.432 25.1992Z"></path><path d="M74.1151 25.1992H58.5736C55.4526 25.1992 52.804 23.1924 51.8171 20.3983H74.1151V17.9979C74.1151 17.1808 74.1868 16.3807 74.3175 15.5975H51.8171C52.804 12.8033 55.4526 10.7966 58.5736 10.7966H76.0383C77.1475 8.87458 78.6911 7.22773 80.5299 6H58.5736C51.969 6 46.6169 11.3724 46.6169 18.0021C46.6169 24.6276 51.969 30 58.5736 30H74.1151V25.1992Z"></path><path d="M120.74 6H115.958H102.369C104.212 7.22773 105.751 8.87458 106.861 10.8008H115.958V30H120.74V10.8008H129.838C130.947 8.87458 132.486 7.22773 134.329 6H120.74Z"></path><path d="M182.906 15.6017H169.756C168.436 15.6017 167.365 14.5264 167.365 13.2013C167.365 11.8762 168.436 10.8008 169.756 10.8008H188.882V6H169.756C165.796 6 162.582 9.22173 162.582 13.2013C162.582 17.1808 165.791 20.4025 169.756 20.4025H182.906C184.226 20.4025 185.297 21.4779 185.297 22.803C185.297 24.1281 184.226 25.2034 182.906 25.2034H161.852C160.743 27.1297 159.199 28.7765 157.361 30.0042H182.906C186.866 30.0042 190.08 26.7825 190.08 22.803C190.08 18.8234 186.866 15.6017 182.906 15.6017Z"></path></g><defs><clipPath id="clip0"><rect width="190" height="24" fill="white" transform="translate(0 6)"></rect></clipPath></defs></svg>
<div>
Entos Envision
</div>

View File

@@ -24,7 +24,7 @@ import { ParamDefinition } from '../../mol-util/param-definition';
import { mountControls } from './controls';
import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
import './index.html';
require('mol-plugin-ui/skin/light.scss');
import '../../mol-plugin-ui/skin/light.scss';
import { setDebugMode, setTimingMode, consoleStats } from '../../mol-util/debug';

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* AlphaFold DB Predicted Aligned Error Example</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}
#app {
position: absolute;
top: 20px;
left: 20px;
width: 640px;
height: 480px;
}
#plot {
position: absolute;
left: 680px;
top: 20px;
width: 480px;
height: 480px;
}
#controls {
position: absolute;
left: 20px;
top: 520px;
font-family: sans-serif;
font-size: smaller;
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
<script type="text/javascript" src="./index.js"></script>
</head>
<body>
<div id='controls'>
<input type='text' id='af-id' value='Q8W3K0' />
<button id='af-load'>Load</button>
</div>
<div id='app'></div>
<div id='plot'></div>
<script>
AlphaFoldPAEExample.init({ pluginContainerId: 'app', plotContainerId: 'plot' }).then(example => {
example.load('Q8W3K0')
});
function $(id) { return document.getElementById(id); }
$('af-load').onclick = () => AlphaFoldPAEExample.load($('af-id').value)
</script>
<!-- __MOLSTAR_ANALYTICS__ -->
</body>
</html>

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { createRoot } from 'react-dom/client';
import { Viewer } from '../../apps/viewer/app';
import { MAPairwiseScorePlot } from '../../extensions/model-archive/quality-assessment/pairwise/ui';
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
import { Model, ResidueIndex } from '../../mol-model/structure';
import './index.html';
import '../../mol-plugin-ui/skin/light.scss';
export class AlphaFoldPAEExample {
viewer: Viewer;
plotContainerId: string;
async init(options: { pluginContainerId: string, plotContainerId: string }) {
this.plotContainerId = options.plotContainerId;
this.viewer = await Viewer.create(options.pluginContainerId, {
layoutIsExpanded: false,
layoutShowControls: false,
layoutShowLeftPanel: false,
layoutShowLog: false,
});
return this;
}
async load(afId: string) {
const id = afId.trim().toUpperCase();
const plotRoot = createRoot(document.getElementById(this.plotContainerId)!);
plotRoot.render(<div>Loading...</div>);
await this.viewer.plugin.clear();
await this.viewer.loadAlphaFoldDb(id);
try {
const req = await fetch(`https://alphafold.ebi.ac.uk/files/AF-${id}-F1-predicted_aligned_error_v4.json`);
const json = await req.json();
const model = this.viewer.plugin.managers.structure.hierarchy.current.models[0]?.cell.obj?.data!;
const metric = pairwiseMetricFromAlphaFoldDbJson(model, json)!;
plotRoot.render(
<div className='msp-plugin' style={{ background: 'white' }}>
<MAPairwiseScorePlot plugin={this.viewer.plugin} pairwiseMetric={metric} model={model} />
</div>
);
} catch (err) {
plotRoot.render(<div>Error: {String(err)}</div>);
}
}
}
function pairwiseMetricFromAlphaFoldDbJson(model: Model, data: any): QualityAssessment.Pairwise | undefined {
if (!Array.isArray(data) || !data[0]?.predicted_aligned_error) return undefined;
const { residues, residueAtomSegments, atomSourceIndex } = model.atomicHierarchy;
const sortedResidueIndices = new Array(residues._rowCount).fill(0).map((_, i) => i);
sortedResidueIndices.sort((a, b) => {
const idxA = atomSourceIndex.value(residueAtomSegments.offsets[a]);
const idxB = atomSourceIndex.value(residueAtomSegments.offsets[b]);
return idxA - idxB;
});
const metricData = data[0].predicted_aligned_error as number[][];
const metric: QualityAssessment.Pairwise = {
id: 0,
name: 'AlphaFold DB PAE',
residueRange: [0 as ResidueIndex, (residues._rowCount - 1) as ResidueIndex],
valueRange: [0, data[0].max_predicted_aligned_error],
values: {}
};
for (let i = 0; i < metricData.length; i++) {
const rA = sortedResidueIndices[i];
if (typeof rA !== 'number') continue;
const row = metricData[i];
const xs: any = (metric.values[rA as ResidueIndex] = {});
for (let j = 0; j < row.length; j++) {
const rB = sortedResidueIndices[j];
if (typeof rB !== 'number') continue;
xs[rB] = row[j];
}
}
return metric;
}
(window as any).AlphaFoldPAEExample = new AlphaFoldPAEExample();

View File

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

View File

@@ -22,7 +22,7 @@ import { CustomToastMessage } from './controls';
import { CustomColorThemeProvider } from './custom-theme';
import './index.html';
import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition';
require('mol-plugin-ui/skin/light.scss');
import '../../mol-plugin-ui/skin/light.scss';
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }

View File

@@ -0,0 +1,97 @@
/**
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alex Chan <smalldirkalex@gmail.com>
*
* Thanks to @author Adam Midlik <midlik@gmail.com> for the example code ../image-renderer and https://github.com/midlik/surface-calculator i can make reference to,
*
* Example command-line application generating and exporting PubChem SDF structures
* Build: npm install --no-save gl // these packages are not listed in dependencies for performance reasons
* npm run build
* Run: node lib/commonjs/examples/glb-export 2519 ../outputs_2519/
*/
import { ArgumentParser } from 'argparse';
import fs from 'fs';
import path from 'path';
import gl from 'gl';
import { Task } from '../../mol-task';
import { Download } from '../../mol-plugin-state/transforms/data';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { GlbExporter } from '../../extensions/geo-export/glb-exporter';
import { Box3D } from '../../mol-math/geometry';
import { ModelFromTrajectory, StructureFromModel, TrajectoryFromSDF } from '../../mol-plugin-state/transforms/model';
import { StructureRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { HeadlessPluginContext } from '../../mol-plugin/headless-plugin-context';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import { ExternalModules } from '../../mol-plugin/util/headless-screenshot';
import { setFSModule } from '../../mol-util/data-source';
setFSModule(fs);
// cid `2519` for Caffeine
interface Args {
cid: string,
outDirectory: string
}
function parseArguments(): Args {
const parser = new ArgumentParser({ description: 'Example command-line application exporting .glb file of SDF structures from PubChem' });
parser.add_argument('cid', { help: 'PubChem identifier' });
parser.add_argument('outDirectory', { help: 'Directory for outputs' });
const args = parser.parse_args();
return { ...args };
}
async function main() {
const args = parseArguments();
const root = 'https://pubchem.ncbi.nlm.nih.gov/rest';
const url = `${root}/pug/compound/cid/${args.cid}/sdf?record_type=3d`;
console.log('PubChem CID:', args.cid);
console.log('Source URL:', url);
console.log('Outputs:', args.outDirectory);
// Create a headless plugin
const externalModules: ExternalModules = { gl };
const plugin = new HeadlessPluginContext(externalModules, DefaultPluginSpec());
await plugin.init();
// Download and visualize data in the plugin
const update = plugin.build();
const structure = await update.toRoot()
.apply(Download, { url, isBinary: false })
.apply(TrajectoryFromSDF)
.apply(ModelFromTrajectory)
.apply(StructureFromModel)
.apply(StructureRepresentation3D, {
type: { name: 'ball-and-stick', params: { size: 'physical' } },
colorTheme: { name: 'element-symbol', params: { carbonColor: { name: 'element-symbol', params: {} } } },
sizeTheme: { name: 'physical', params: {} },
})
.commit();
const meshes = structure.data!.repr.renderObjects.filter(obj => obj.type === 'mesh') as GraphicsRenderObject<'mesh'>[];
const boundingSphere = plugin.canvas3d?.boundingSphereVisible!;
const boundingBox = Box3D.fromSphere3D(Box3D(), boundingSphere);
const renderObjectExporter = new GlbExporter(boundingBox);
await plugin.runTask(Task.create('Export Geometry', async ctx => {
for (let i = 0, il = meshes.length; i < il; ++i) {
await renderObjectExporter.add(meshes[i], plugin.canvas3d?.webgl!, ctx);
}
const blob = await renderObjectExporter.getBlob(ctx);
const buffer = await blob.arrayBuffer();
await fs.promises.writeFile(path.join(args.outDirectory, `${args.cid}.glb`), Buffer.from(buffer));
}));
// Cleanup
await plugin.clear();
plugin.dispose();
}
main();

View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* IHM Restraints Example</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}
#viewer {
position: absolute;
left: 20px;
top: 20px;
width: 640px;
height: 480px;
}
#links {
position: absolute;
bottom: 8px;
left: 8px;
font-family: sans-serif;
font-size: 0.8rem;
z-index: 2;
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
<script type="text/javascript" src="./index.js"></script>
</head>
<body>
<div id="viewer"></div>
<div id="links">
<a href="#" id="mvs-data" filename="ihm-restraints.mvsj">Download MVS State</a> | <a href="https://github.com/molstar/molstar/tree/master/src/examples/ihm-restraints" id="mvs-data" target="_blank" rel="noopener noreferrer">Source Code</a>
</div>
<script>
loadIHMRestraints(document.getElementById('viewer')).then(state => {
document.getElementById('mvs-data').addEventListener('click', (e) => {
e.preventDefault();
const data = JSON.stringify(state, null, 2);
molStarDownload(new Blob([data], { type: 'application/json' }), 'ihm-restraints.mvsj');
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,366 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { MolViewSpec } from '../../extensions/mvs/behavior';
import { loadMVS } from '../../extensions/mvs/load';
import { MVSData_States, Snapshot } from '../../extensions/mvs/mvs-data';
import { createMVSBuilder } from '../../extensions/mvs/tree/mvs/mvs-builder';
import { parseCifText } from '../../mol-io/reader/cif/text/parser';
import { Vec3 } from '../../mol-math/linear-algebra';
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
import { Model } from '../../mol-model/structure';
import { CoarseElementKey, CoarseElementReference } from '../../mol-model/structure/model/properties/coarse';
import { createPluginUI } from '../../mol-plugin-ui';
import { renderReact18 } from '../../mol-plugin-ui/react18';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginContext } from '../../mol-plugin/context';
import { PluginSpec } from '../../mol-plugin/spec';
import { Task } from '../../mol-task';
import { ajaxGet } from '../../mol-util/data-source';
import { download } from '../../mol-util/download';
import './index.html';
import '../../mol-plugin-ui/skin/light.scss';
async function createViewer(root: HTMLElement) {
const spec = DefaultPluginUISpec();
const plugin = await createPluginUI({
target: root,
render: renderReact18,
spec: {
...spec,
layout: {
initial: {
isExpanded: true,
showControls: false
}
},
components: {
remoteState: 'none',
},
behaviors: [
...spec.behaviors,
PluginSpec.Behavior(MolViewSpec)
],
config: [
[PluginConfig.Viewport.ShowAnimation, false],
[PluginConfig.Viewport.ShowTrajectoryControls, false],
]
}
});
return plugin;
}
interface IHMRestraintInfo {
e1: CoarseElementKey & { label_comp_id: string },
e2: CoarseElementKey & { label_comp_id: string },
a: Vec3,
b: Vec3,
restraintType: 'harmonic' | 'upper bound' | 'lower bound',
threshold: number,
satisfied: boolean,
distance: number,
}
interface IHMStructureInfo {
entity_labels: [id: string | undefined, label: string | undefined][],
model_restraints: IHMRestraintInfo[][],
}
function getCoarseElementPosition(e: CoarseElementReference, model: Model, position: Vec3) {
if (!e.kind) Vec3.set(position, 0, 0, 0);
const { x, y, z } = model.coarseConformation[e.kind!];
const idx = e.index;
Vec3.set(position, x[idx], y[idx], z[idx]);
}
const _elementRef = CoarseElementReference();
function resolvePosition(model: Model, key: CoarseElementKey, position: Vec3) {
if (model.coarseHierarchy.index.findElement(key, _elementRef)) {
getCoarseElementPosition(_elementRef, model, position);
return true;
}
const rI = model.atomicHierarchy.index.findResidueLabel(key);
if (rI < 0) return false;
const atomStart = model.atomicHierarchy.residueAtomSegments.offsets[rI];
const atomEnd = model.atomicHierarchy.residueAtomSegments.offsets[rI + 1];
const atomId = model.atomicHierarchy.atoms.label_atom_id;
let aI = atomStart;
// Find CA otherwise use the first atom.
// Possible future improvement: use the atom closest to the center of mass of the residue.
for (; aI < atomEnd; aI++) {
if (atomId.value(aI) === 'CA') break;
}
if (aI === atomEnd) aI = atomStart;
const { x, y, z } = model.atomicConformation;
Vec3.set(position, x[aI], y[aI], z[aI]);
return true;
}
const HarmonicRestraintTolerance = 0.1;
async function parseInfo(plugin: PluginContext, url: string): Promise<IHMStructureInfo> {
const data = await plugin.runTask(ajaxGet(url)) as string;
const parsed = await plugin.runTask(parseCifText(data));
if (parsed.isError) {
console.error(parsed);
return { entity_labels: [], model_restraints: [] };
}
const trajectory = await plugin.runTask(trajectoryFromMmCIF(parsed.result.blocks[0], parsed.result));
const dataBlocks = parsed.result.blocks;
const ihm_cross_link_restraint = dataBlocks[0].categories['ihm_cross_link_restraint'];
const entity_id_1 = ihm_cross_link_restraint.getField('entity_id_1')!;
const asym_id_1 = ihm_cross_link_restraint.getField('asym_id_1')!;
const seq_id_1 = ihm_cross_link_restraint.getField('seq_id_1')!;
const comp_id_1 = ihm_cross_link_restraint.getField('comp_id_1')!;
const entity_id_2 = ihm_cross_link_restraint.getField('entity_id_2')!;
const asym_id_2 = ihm_cross_link_restraint.getField('asym_id_2')!;
const seq_id_2 = ihm_cross_link_restraint.getField('seq_id_2')!;
const comp_id_2 = ihm_cross_link_restraint.getField('comp_id_2')!;
const restraint_type = ihm_cross_link_restraint.getField('restraint_type')!;
const threshold = ihm_cross_link_restraint.getField('distance_threshold')!;
const e1key = CoarseElementKey();
const e2key = CoarseElementKey();
const a = Vec3.zero();
const b = Vec3.zero();
const entity_labels: IHMStructureInfo['entity_labels'] = [];
const entity = dataBlocks[0].categories['entity'];
const entity_id = entity.getField('id');
const pdbx_description = entity.getField('pdbx_description');
for (let i = 0; i < entity.rowCount; i++) {
entity_labels.push([entity_id?.str(i), pdbx_description?.str(i)]);
}
const model_restraints: IHMRestraintInfo[][] = [];
for (let modelIndex = 0; modelIndex < trajectory.frameCount; modelIndex++) {
const _model = trajectory.getFrameAtIndex(modelIndex);
const model = Task.is(_model) ? await plugin.runTask(_model) : _model;
const restraints: IHMRestraintInfo[] = [];
model_restraints.push(restraints);
for (let i = 0; i < ihm_cross_link_restraint.rowCount; i++) {
e1key.label_entity_id = entity_id_1.str(i);
e1key.label_asym_id = asym_id_1.str(i);
e1key.label_seq_id = seq_id_1.int(i);
e2key.label_entity_id = entity_id_2.str(i);
e2key.label_asym_id = asym_id_2.str(i);
e2key.label_seq_id = seq_id_2.int(i);
if (!resolvePosition(model, e1key, a) || !resolvePosition(model, e2key, b)) {
continue;
}
const restraintType: 'harmonic' | 'upper bound' | 'lower bound' = restraint_type.str(i)?.toLowerCase() as any;
const thresholdValue = threshold.float(i);
const distance = Vec3.distance(a, b);
let satisfied = true;
if (restraintType === 'harmonic') {
const thresholdValue = threshold.float(i);
satisfied = distance >= (1 - HarmonicRestraintTolerance) * thresholdValue && distance <= (1 + HarmonicRestraintTolerance) * thresholdValue;
} else if (restraintType === 'upper bound') {
satisfied = distance <= thresholdValue;
} else if (restraintType === 'lower bound') {
satisfied = distance >= thresholdValue;
}
restraints.push({
e1: { ...e1key, label_comp_id: comp_id_1.str(i) },
e2: { ...e2key, label_comp_id: comp_id_2.str(i) },
a: Vec3.clone(a),
b: Vec3.clone(b),
restraintType,
threshold: thresholdValue,
satisfied,
distance,
});
}
}
return { entity_labels, model_restraints };
}
function baseStructure(url: string, modelIndex: number, info: IHMStructureInfo, options?: { noEntityLabels?: boolean }) {
const builder = createMVSBuilder();
const structure = builder
.download({ url })
.parse({ format: 'mmcif' })
.modelStructure({ model_index: modelIndex });
structure
.component({ selector: 'coarse' })
.representation({ type: 'spacefill' })
.color({ custom: { molstar_use_default_coloring: true } })
.opacity({ opacity: 0.51 });
structure
.component({ selector: 'polymer' })
.representation({ type: 'cartoon' })
.color({ custom: { molstar_use_default_coloring: true } })
.opacity({ opacity: 0.51 });
if (!options?.noEntityLabels) {
const primitives = structure.primitives();
for (const [label_entity_id, text] of info.entity_labels) {
if (!text) continue;
primitives
.label({ position: { label_entity_id }, text, label_size: 16, label_color: '#cccccc' });
}
}
return [builder, structure] as const;
}
function drawConstraints([, structure]: ReturnType<typeof baseStructure>, restraints: IHMRestraintInfo[], options: {
filter: (r: IHMRestraintInfo) => boolean,
color: (r: IHMRestraintInfo) => any,
radius?: (r: IHMRestraintInfo) => number,
tooltip: (r: IHMRestraintInfo) => string | undefined,
}) {
const primitives = structure.primitives();
for (const r of restraints) {
if (!options.filter(r)) continue;
const radius = options.radius?.(r) ?? 1;
primitives.tube({
start: r.a as any,
end: r.b as any,
color: options.color(r) || 'white',
tooltip: options.tooltip(r),
radius: radius,
dash_length: radius,
});
}
}
function restraintTooltip(r: IHMRestraintInfo) {
return `
- Element 1: ${r.e1.label_entity_id} ${r.e1.label_asym_id} ${r.e1.label_seq_id} ${r.e1.label_comp_id}
- Element 2: ${r.e2.label_entity_id} ${r.e2.label_asym_id} ${r.e2.label_seq_id} ${r.e2.label_comp_id}
- Distance: ${r.distance.toFixed(2)} Å
- Threshold: ${r.threshold.toFixed(2)} Å
- Constraint: ${r.restraintType}
- Satisfied: ${r.satisfied ? 'Yes' : 'No'}
`;
}
export async function loadIHMRestraints(root: HTMLElement, url?: string) {
url ??= 'https://pdb-ihm.org/cif/8zz1.cif';
const plugin = await createViewer(root);
const info = await parseInfo(plugin, url);
const modelIndex = 0;
const restraints = info.model_restraints[modelIndex];
const nVialoted = restraints.filter(r => !r.satisfied).length;
const nSatisfied = restraints.length - nVialoted;
const snapshots: Snapshot[] = [];
let mvs = baseStructure(url, modelIndex, info);
drawConstraints(mvs, restraints, {
filter: r => true,
color: r => r.e1.label_entity_id === r.e2.label_entity_id && r.e1.label_asym_id === r.e2.label_asym_id ? 'yellow' : 'blue',
radius: r => 1,
tooltip: restraintTooltip,
});
snapshots.push(mvs[0].getSnapshot({
title: 'All Restraints',
linger_duration_ms: 5000,
description: `
### All Restraints
- Yellow: Intra-chain restraints
- Blue: Inter-chain restraints
`,
}));
mvs = baseStructure(url, modelIndex, info);
drawConstraints(mvs, restraints, {
filter: r => true,
color: r => r.satisfied ? 'green' : 'red',
radius: r => 1,
tooltip: restraintTooltip,
});
snapshots.push(mvs[0].getSnapshot({
title: 'Restraint Validation',
linger_duration_ms: 5000,
description: `
### Restraint Validation
- Red: ${nVialoted} Violated restraints
- Green: ${nSatisfied} Satisfied restraints
`,
}));
mvs = baseStructure(url, modelIndex, info);
drawConstraints(mvs, restraints, {
filter: r => !r.satisfied,
color: r => r.satisfied ? 'green' : 'red',
radius: r => 1,
tooltip: restraintTooltip,
});
snapshots.push(mvs[0].getSnapshot({
title: 'Violated Restraints',
linger_duration_ms: 5000,
description: `
### Violated Restraints
${nVialoted} restraints are violated.
`,
}));
mvs = baseStructure(url, modelIndex, info);
drawConstraints(mvs, restraints, {
filter: r => r.satisfied,
color: r => r.satisfied ? 'green' : 'red',
radius: r => 1,
tooltip: restraintTooltip,
});
snapshots.push(mvs[0].getSnapshot({
title: 'Satisfied Restraints',
linger_duration_ms: 5000,
description: `
### Satisfied Restraints
${nSatisfied} restraints are satisfied.
`,
}));
const data: MVSData_States = {
kind: 'multiple',
snapshots,
metadata: {
title: 'I/HM Restraints',
version: '1.0',
timestamp: new Date().toISOString(),
}
};
await loadMVS(plugin, data, { sanityChecks: true, keepCamera: true });
return data;
}
(window as any).loadIHMRestraints = loadIHMRestraints;
(window as any).molStarDownload = download;

View File

@@ -0,0 +1,35 @@
# I/HM Restraints Example
This example illustrates:
- Using Mol* to parse CIF files and extract custom data
- Using MolViewSpec to visualize I/HM structure along with annotated crosslink restraints
### Usage
- Clone Mol* GitHub repo and build it.
```bash
git clone https://github.com/molstar/molstar.git
cd molstar
npm install
npm build
```
- Get `molstar.css` and `index.js` from `build/examples/ihm-restraints` and include these to your HTML page in a similar fashion to [index.html](./index.html):
```html
<link rel="stylesheet" type="text/css" href="molstar.css" />
<script type="text/javascript" src="index.js"></script>
...
<div id="viewer"></div>
...
<script>
loadIHMRestraints(document.getElementById('viewer'))
</script>
```
- For interactive development build (for production use `npm run build`) of the example that immediately reflects changes use:
```bash
npm run dev -- -e ihm-restraints
```

View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* Interactions Example</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}
#viewer {
position: absolute;
left: 20px;
top: 20px;
width: 640px;
height: 480px;
}
#controls {
position: absolute;
left: 40%;
bottom: 8px;
font-family: sans-serif;
padding: 8px;
border: 1px dotted #aaa;
background: rgba(255, 255, 255, 0.5);
}
#controls select {
margin-left: 4px;
padding: 4px 8px;
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
</head>
<body>
<div id="viewer"></div>
<div id="controls">
</div>
<script type="text/javascript" src="./index.js"></script>
<script>
initInteractionsExample('viewer', 'controls');
</script>
</body>
</html>

View File

@@ -0,0 +1,382 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { createRoot } from 'react-dom/client';
import { BehaviorSubject } from 'rxjs';
import { InteractionElementSchema, InteractionKind, StructureInteractionElement, StructureInteractions } from '../../extensions/interactions/model';
import { ComputeContacts, CustomInteractions, InteractionsShape } from '../../extensions/interactions/transforms';
import { MolViewSpec } from '../../extensions/mvs/behavior';
import { ResidueIndex, Structure, StructureElement, StructureProperties, StructureQuery } from '../../mol-model/structure';
import { atoms } from '../../mol-model/structure/query/queries/generators';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { MultiStructureSelectionFromBundle, StructureSelectionFromBundle } from '../../mol-plugin-state/transforms/model';
import { ShapeRepresentation3D, StructureRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { createPluginUI } from '../../mol-plugin-ui';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { renderReact18 } from '../../mol-plugin-ui/react18';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginContext } from '../../mol-plugin/context';
import { PluginSpec } from '../../mol-plugin/spec';
import '../../mol-plugin-ui/skin/light.scss';
import './index.html';
import { Task } from '../../mol-task';
import { computeContacts } from '../../extensions/interactions/compute';
async function createViewer(root: HTMLElement) {
const spec = DefaultPluginUISpec();
const plugin = await createPluginUI({
target: root,
render: renderReact18,
spec: {
...spec,
layout: {
initial: {
isExpanded: true,
showControls: false
}
},
components: {
remoteState: 'none',
},
behaviors: [
...spec.behaviors,
PluginSpec.Behavior(MolViewSpec)
],
config: [
[PluginConfig.Viewport.ShowAnimation, false],
]
}
});
return plugin;
}
async function createBindingSiteRepresentation(plugin: PluginContext, interactions: StructureInteractions[], receptors: Map<string, Structure>) {
const contactBundles = getBindingSiteBundles(interactions.flatMap(e => e.elements), receptors);
const update = plugin.build();
for (const [ref, bundle] of contactBundles) {
update.to(ref)
.apply(StructureSelectionFromBundle, { bundle, label: 'Binding Site' })
.apply(StructureRepresentation3D, {
type: { name: 'ball-and-stick', params: { sizeFactor: 0.2 } },
colorTheme: { name: 'element-symbol', params: { carbonColor: { name: 'element-symbol', params: {} } } },
});
}
await update.commit();
}
function getBindingSiteBundles(interactions: StructureInteractionElement[], receptors: Map<string, Structure>) {
const residueIndices = new Map<string, Set<ResidueIndex>>();
const loc = StructureElement.Location.create();
const add = (ref: string, loci: StructureElement.Loci) => {
if (!receptors.has(ref)) return;
let set: Set<ResidueIndex>;
if (residueIndices.has(ref)) {
set = residueIndices.get(ref)!;
} else {
set = new Set<ResidueIndex>();
residueIndices.set(ref, set);
}
StructureElement.Loci.forEachLocation(loci, l => {
set.add(StructureProperties.residue.key(l));
}, loc);
};
for (const e of interactions) {
add(e.aStructureRef!, e.a);
add(e.bStructureRef!, e.b);
}
const bundles: [ref: string, bundle: StructureElement.Bundle][] = [];
for (const [ref, indices] of Array.from(residueIndices.entries())) {
if (indices.size === 0) continue;
const loci = StructureQuery.loci(
atoms({
residueTest: e => indices.has(StructureProperties.residue.key(e.element))
}),
receptors.get(ref)!,
);
if (StructureElement.Loci.isEmpty(loci)) continue;
bundles.push([ref, StructureElement.Bundle.fromLoci(loci)]);
}
return bundles;
}
async function loadComputedExample(
plugin: PluginContext,
{ receptorUrl, ligandUrl }: { receptorUrl: [url: string, format: BuiltInTrajectoryFormat], ligandUrl: [url: string, format: BuiltInTrajectoryFormat] },
options: { receptor_label_asym_id: string | undefined, analyzeTrajectory?: boolean }
) {
await plugin.clear();
// Set up the receptor and ligand structures
const receptorData = await plugin.builders.data.download({ url: receptorUrl[0] });
const receptorTrajectory = await plugin.builders.structure.parseTrajectory(receptorData, receptorUrl[1]);
const receptor = await plugin.builders.structure.hierarchy.applyPreset(receptorTrajectory, 'default', { representationPreset: 'polymer-cartoon' });
const ligandData = await plugin.builders.data.download({ url: ligandUrl[0] });
const ligandTrajectory = await plugin.builders.structure.parseTrajectory(ligandData, ligandUrl[1]);
const ligand = await plugin.builders.structure.hierarchy.applyPreset(ligandTrajectory, 'default', { representationPreset: 'atomic-detail' });
// Compute the interactions
const update = plugin.build();
const receptorRef = receptor?.structure.ref!;
const ligandRef = ligand?.structure.ref!;
const refs = [receptorRef, ligandRef];
const interactionsRef = update.toRoot()
.apply(MultiStructureSelectionFromBundle, {
selections: [
{ key: 'a', ref: receptorRef, bundle: StructureElement.Schema.toBundle(receptor?.structure.data!, { label_asym_id: options.receptor_label_asym_id }) },
{ key: 'b', ref: ligandRef, bundle: StructureElement.Schema.toBundle(ligand?.structure.data!, { }) },
],
isTransitive: true,
label: 'Label'
}, { dependsOn: refs })
.apply(ComputeContacts);
interactionsRef.apply(InteractionsShape).apply(ShapeRepresentation3D);
await update.commit();
if (!options.analyzeTrajectory) {
console.log('Interactions', interactionsRef.selector.data?.interactions);
// Create ball and stick representations for the binding site and focus on the ligand
await createBindingSiteRepresentation(
plugin,
[interactionsRef.selector.data?.interactions!],
new Map([[receptorRef, receptor?.structure.data!]])
);
} else {
const trajectoryInteractions: StructureInteractions[] = [];
const receptorLoci = StructureElement.Schema.toLoci(receptor?.structure.data!, { label_asym_id: options.receptor_label_asym_id });
for (let fI = 0; fI < ligandTrajectory.data!.frameCount; fI++) {
const model = await Task.resolveInContext(ligandTrajectory.data!.getFrameAtIndex(fI));
const structure = Structure.ofModel(model);
const currentInteractions = await plugin.runTask(Task.create('Compute Contacts', ctx => {
return computeContacts(ctx, [
{ structureRef: receptorRef, loci: receptorLoci },
{ structureRef: ligandRef, loci: Structure.toStructureElementLoci(structure) },
]);
}));
trajectoryInteractions.push(currentInteractions);
}
console.log('Interactions', trajectoryInteractions);
await createBindingSiteRepresentation(
plugin,
trajectoryInteractions,
new Map([[receptorRef, receptor?.structure.data!]])
);
}
PluginCommands.Camera.FocusObject(plugin, {
targets: [{
targetRef: ligand?.representation.representations.all.ref
}]
});
}
async function loadCustomExample(plugin: PluginContext) {
await plugin.clear();
// Set up the receptor and ligand structures
const receptorData = await plugin.builders.data.download({ url: '../../../examples/ace2.pdbqt' });
const receptorTrajectory = await plugin.builders.structure.parseTrajectory(receptorData, 'pdbqt');
const receptor = await plugin.builders.structure.hierarchy.applyPreset(receptorTrajectory, 'default');
const ligandData = await plugin.builders.data.download({ url: '../../../examples/ace2-hit.mol2' });
const ligandTrajectory = await plugin.builders.structure.parseTrajectory(ligandData, 'mol2');
const ligand = await plugin.builders.structure.hierarchy.applyPreset(ligandTrajectory, 'default', { representationPreset: 'atomic-detail' });
// Compute the interactions
const update = plugin.build();
const receptorRef = receptor?.representation.components.polymer.ref!;
const ligandRef = ligand?.representation.components.all.ref!;
const refs = [receptorRef, ligandRef];
const interactionsRef = update.toRoot().apply(CustomInteractions, {
interactions: [
{
kind: 'hydrogen-bond',
aStructureRef: receptorRef,
a: { auth_seq_id: 353, auth_atom_id: 'N' },
bStructureRef: ligandRef,
b: { atom_index: 9 },
}
]
}, { dependsOn: refs });
interactionsRef.apply(InteractionsShape).apply(ShapeRepresentation3D);
await update.commit();
console.log('Interactions', interactionsRef.selector.data?.interactions);
// Create ball and stick representations for the binding site and focus on the ligand
await createBindingSiteRepresentation(
plugin,
[interactionsRef.selector.data?.interactions!],
new Map([[receptorRef, receptor?.representation.components.polymer.data]])
);
PluginCommands.Camera.FocusObject(plugin, {
targets: [{
targetRef: ligand?.representation.representations.all.ref
}]
});
}
async function loadTestAllExample(plugin: PluginContext) {
await plugin.clear();
// Set up the receptor and ligand structures
const receptorData = await plugin.builders.data.download({ url: '../../../examples/ace2.pdbqt' });
const receptorTrajectory = await plugin.builders.structure.parseTrajectory(receptorData, 'pdbqt');
const receptor = await plugin.builders.structure.hierarchy.applyPreset(receptorTrajectory, 'default');
const ligandData = await plugin.builders.data.download({ url: '../../../examples/ace2-hit.mol2' });
const ligandTrajectory = await plugin.builders.structure.parseTrajectory(ligandData, 'mol2');
const ligand = await plugin.builders.structure.hierarchy.applyPreset(ligandTrajectory, 'default', { representationPreset: 'atomic-detail' });
// Compute the interactions
const update = plugin.build();
const receptorRef = receptor?.representation.components.polymer.ref!;
const ligandRef = ligand?.representation.components.all.ref!;
const refs = [receptorRef, ligandRef];
const basic = (kind: InteractionKind, atom_index: number | number[], description?: string): InteractionElementSchema => {
return {
kind,
aStructureRef: receptorRef,
a: { auth_seq_id: 354, auth_atom_id: 'N' },
bStructureRef: ligandRef,
b: Array.isArray(atom_index) ? { items: { atom_index } } : { atom_index },
description,
};
};
const covalent = (degree: number, atom_index: number): InteractionElementSchema => {
return {
kind: 'covalent',
degree: degree === -1 ? 'aromatic' : Math.abs(degree) as 1 | 2 | 3 | 4,
aStructureRef: receptorRef,
a: { auth_seq_id: 354, auth_atom_id: 'N' },
bStructureRef: ligandRef,
b: { atom_index }
};
};
const interactionsRef = update.toRoot().apply(CustomInteractions, {
interactions: [
basic('unknown', 1),
basic('ionic', 2),
basic('pi-stacking', 3),
basic('cation-pi', 4),
basic('halogen-bond', 5),
basic('hydrogen-bond', 6),
basic('weak-hydrogen-bond', 7),
basic('hydrophobic', 8),
basic('metal-coordination', 9),
covalent(1, 10),
covalent(2, 11),
covalent(3, 12),
covalent(-1, 13), // aromatic
basic('unknown', [0, 1, 2, 3, 13, 14], 'Testing centroid for atom set'),
]
}, { dependsOn: refs });
interactionsRef.apply(InteractionsShape).apply(ShapeRepresentation3D);
await update.commit();
console.log('Interactions', interactionsRef.selector.data?.interactions);
// Create ball and stick representations for the binding site and focus on the ligand
await createBindingSiteRepresentation(
plugin,
[interactionsRef.selector.data?.interactions!],
new Map([[receptorRef, receptor?.representation.components.polymer.data]])
);
PluginCommands.Camera.FocusObject(plugin, {
targets: [{
targetRef: ligand?.representation.representations.all.ref
}]
});
}
const Examples = {
'Computed (1iep)': (plugin: PluginContext) => loadComputedExample(plugin, {
receptorUrl: ['https://files.rcsb.org/download/1IEP.cif', 'mmcif'],
ligandUrl: ['https://models.rcsb.org/v1/1iep/atoms?label_asym_id=G&copy_all_categories=false', 'mmcif']
}, { receptor_label_asym_id: 'A' }),
'Computed (ACE2)': (plugin: PluginContext) => loadComputedExample(plugin, {
receptorUrl: ['../../../examples/ace2.pdbqt', 'pdbqt'],
ligandUrl: ['../../../examples/ace2-hit.mol2', 'mol2']
}, { receptor_label_asym_id: 'B' }),
'Computed (multiple)': (plugin: PluginContext) => loadComputedExample(plugin, {
receptorUrl: ['../../../examples/docking/receptor_1.pdb', 'pdb'],
ligandUrl: ['../../../examples/docking/ligands_1.sdf', 'sdf']
}, { receptor_label_asym_id: undefined, analyzeTrajectory: true }),
'Custom': loadCustomExample,
'Synthetic': loadTestAllExample
};
function SelectExampleUI({ state, load }: {
state: BehaviorSubject<{ name?: keyof typeof Examples, isLoading?: boolean }>,
load: (name: keyof typeof Examples) => void
}) {
const current = useBehavior(state);
return <div>
Select Example:{' '}
<select value={current.name} onChange={e => load(e.target.value as any)} disabled={current.isLoading}>
{Object.keys(Examples).map(k => <option key={k} value={k}>{k}</option>)}
</select>
</div>;
}
async function init(viewer: HTMLElement | string, controls: HTMLElement | string, defaultExample: keyof typeof Examples = 'Computed (1iep)') {
const root = typeof viewer === 'string' ? document.getElementById(viewer)! : viewer;
const plugin = await createViewer(root);
const state = new BehaviorSubject<{ name?: keyof typeof Examples, isLoading?: boolean }>({});
const loadExample = async (name: keyof typeof Examples) => {
state.next({ name, isLoading: true });
try {
await Examples[name](plugin);
state.next({ name });
} catch (e) {
console.error(e);
state.next({});
}
};
createRoot(
typeof controls === 'string' ? document.getElementById(controls)! : controls
).render(<SelectExampleUI state={state} load={loadExample} />);
loadExample(defaultExample);
return { plugin, loadExample };
}
(window as any).initInteractionsExample = init;

View File

@@ -0,0 +1,16 @@
# Interactions Example
An example showcasing features of the `interactions` extension, including:
- Computing interactions on demand using Mol*'s built-in interaction computation code
- Triggering interaction computation separately for multiple ligands stored in a single SDF file
- Specifying interaction manually from custom data source
- Synthetic example showing all supported interaction types
To run development build locally from the root `molstar` directory (after `npm install`):
```bash
npm run dev -- -e interactions
```
and navigate to `build/examples/interactions` in the hosted server linked in the script output.

View File

@@ -0,0 +1,219 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { JSONCifLigandGraph, JSONCifLigandGraphAtom, JSONCifLigandGraphBondProps } from '../../extensions/json-cif/ligand-graph';
import { Quat, Vec3 } from '../../mol-math/linear-algebra';
import { VdwRadius } from '../../mol-model/structure/model/properties/atomic';
import { ElementSymbol } from '../../mol-model/structure/model/types';
import { attachRGroup, RGroupName } from './r-groups';
export const TopologyEdits = {
setElement: async (graph: JSONCifLigandGraph, atomIds: number[], type_symbol: string) => {
for (const id of atomIds) {
graph.modifyAtom(id, { type_symbol });
}
},
addElement: async (graph: JSONCifLigandGraph, parentId: number, type_symbol: string) => {
const p = graph.getAtom(parentId);
if (!p) return;
const c = graph.getAtomCoords(p);
const dir = approximateAddAtomDirection(graph, p);
const r = 2 / 5 * (VdwRadius(ElementSymbol(p.row.type_symbol ?? 'C')) + VdwRadius(ElementSymbol(type_symbol)));
const newAtom = graph.addAtom({
...p.row,
// NOTE: this is not correct for editing protein atoms
// as they should have atom names from CCD, or at least the should be
// unique. This should be fine for small ligand editing.
auth_atom_id: type_symbol,
label_atom_id: type_symbol,
type_symbol,
Cartn_x: c[0] + dir[0] * r,
Cartn_y: c[1] + dir[1] * r,
Cartn_z: c[2] + dir[2] * r
});
graph.addOrUpdateBond(p, newAtom, { value_order: 'sing', type_id: 'covale' });
return newAtom;
},
removeAtoms: async (graph: JSONCifLigandGraph, atomIds: number[]) => {
for (const id of atomIds) {
graph.removeAtom(id);
}
},
removeBonds: async (graph: JSONCifLigandGraph, atomIds: number[]) => {
for (let i = 0; i < atomIds.length; ++i) {
for (let j = i + 1; j < atomIds.length; ++j) {
graph.removeBond(atomIds[i], atomIds[j]);
}
}
},
updateBonds: async (graph: JSONCifLigandGraph, atomIds: number[], props: JSONCifLigandGraphBondProps) => {
// TODO: iterate on the all-pairs behavior
// e.g. only add bonds if there is no path connecting them,
// or by a distance threshold, ...
for (let i = 0; i < atomIds.length; ++i) {
for (let j = i + 1; j < atomIds.length; ++j) {
graph.addOrUpdateBond(atomIds[i], atomIds[j], props);
}
}
},
attachRgroup: async (graph: JSONCifLigandGraph, atomId: number, name: RGroupName) => {
await attachRGroup(graph, name, atomId);
}
};
export type GeometryEditFn = (param: number) => JSONCifLigandGraph;
export const GeometryEdits = {
twist: (graph: JSONCifLigandGraph, atomIds: number[]): GeometryEditFn => {
if (atomIds.length !== 2) {
throw new Error('Twist requires exactly two atoms.');
}
const { left, right } = splitGraph(graph, atomIds[0], atomIds[1]);
const active = left.length <= right.length ? left : right;
const a = left.length <= right.length ? atomIds[0] : atomIds[1];
const b = left.length <= right.length ? atomIds[1] : atomIds[0];
const pivot = graph.getAtomCoords(a);
const axis = Vec3.sub(Vec3(), pivot, graph.getAtomCoords(b));
Vec3.normalize(axis, axis);
const basePositions = active.map(a => graph.getAtomCoords(a));
const xform = Quat();
const p = Vec3();
return (angle: number) => {
Quat.setAxisAngle(xform, axis, angle);
for (let i = 0; i < active.length; ++i) {
Vec3.copy(p, basePositions[i]);
Vec3.sub(p, p, pivot);
Vec3.transformQuat(p, p, xform);
Vec3.add(p, p, pivot);
graph.modifyAtom(active[i], {
Cartn_x: p[0],
Cartn_y: p[1],
Cartn_z: p[2]
});
}
return graph;
};
},
stretch: (graph: JSONCifLigandGraph, atomIds: number[]): GeometryEditFn => {
if (atomIds.length !== 2) {
throw new Error('Stretch requires exactly two atoms.');
}
const { left, right } = splitGraph(graph, atomIds[0], atomIds[1]);
const a = graph.getAtomCoords(atomIds[0]);
const b = graph.getAtomCoords(atomIds[1]);
const center = Vec3.add(Vec3(), b, a);
Vec3.scale(center, center, 0.5);
const baseDelta = Vec3.sub(Vec3(), a, center);
const baseLeft = left.map(a => graph.getAtomCoords(a));
const baseRight = right.map(a => graph.getAtomCoords(a));
const p = Vec3();
const delta = Vec3();
return (factor: number) => {
Vec3.scale(delta, baseDelta, factor);
for (let i = 0; i < left.length; ++i) {
Vec3.copy(p, baseLeft[i]);
Vec3.add(p, p, delta);
graph.modifyAtom(left[i], {
Cartn_x: p[0],
Cartn_y: p[1],
Cartn_z: p[2]
});
}
for (let i = 0; i < right.length; ++i) {
Vec3.copy(p, baseRight[i]);
Vec3.sub(p, p, delta);
graph.modifyAtom(right[i], {
Cartn_x: p[0],
Cartn_y: p[1],
Cartn_z: p[2]
});
}
return graph;
};
},
};
function approximateAddAtomDirection(graph: JSONCifLigandGraph, parent: JSONCifLigandGraphAtom) {
let deltas: Vec3[] = [];
const bonds = graph.bondByKey.get(parent.key);
if (!bonds?.length) return Vec3.create(1, 0, 0);
const c = graph.getAtomCoords(parent);
for (const b of bonds) {
const delta = Vec3.sub(Vec3(), graph.getAtomCoords(b.atom_2), c);
deltas.push(delta);
}
if (deltas.length === 1) {
const ret = Vec3.negate(Vec3(), deltas[0]);
Vec3.normalize(ret, ret);
return ret;
}
if (deltas.length === 2) {
const ret = Vec3.add(Vec3(), deltas[0], deltas[1]);
Vec3.normalize(ret, ret);
Vec3.negate(ret, ret);
return ret;
}
// Take the first three deltas and cross-product them
deltas = deltas.slice(0, 3);
const crossProducts: Vec3[] = [];
for (let i = 0; i < deltas.length; ++i) {
for (let j = i + 1; j < deltas.length; ++j) {
const cross = Vec3.cross(Vec3(), deltas[i], deltas[j]);
Vec3.normalize(cross, cross);
crossProducts.push(cross);
}
}
for (let i = 1; i < crossProducts.length; ++i) {
Vec3.matchDirection(crossProducts[i], crossProducts[i], crossProducts[0]);
}
const avg = Vec3.create(0, 0, 0);
for (const cp of crossProducts) {
Vec3.add(avg, avg, cp);
}
Vec3.normalize(avg, avg);
return avg;
}
function getAtomDepths(graph: JSONCifLigandGraph, atomId: number) {
return graph.traverse(atomId, 'bfs', new Map<string, number>(), (a, depths, pred) => {
depths.set(a.key, pred ? depths.get(pred.atom_1.key)! + 1 : 0);
});
}
function splitGraph(graph: JSONCifLigandGraph, leftId: number, rightId: number) {
const xs = getAtomDepths(graph, leftId);
const ys = getAtomDepths(graph, rightId);
const l: JSONCifLigandGraphAtom[] = [];
const r: JSONCifLigandGraphAtom[] = [];
for (const a of graph.atoms) {
if (xs.has(a.key) && ys.has(a.key)) {
if (xs.get(a.key)! < ys.get(a.key)!) l.push(a);
else r.push(a);
} else if (xs.has(a.key)) {
l.push(a);
} else if (ys.has(a.key)) {
r.push(a);
}
}
return { left: l, right: r };
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
export const ExampleMol = `2244
-OEChem-04072009073D
21 21 0 0 0 0 0 0 0999 V2000
1.2333 0.5540 0.7792 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.6952 -2.7148 -0.7502 O 0 0 0 0 0 0 0 0 0 0 0 0
0.7958 -2.1843 0.8685 O 0 0 0 0 0 0 0 0 0 0 0 0
1.7813 0.8105 -1.4821 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.0857 0.6088 0.4403 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.7927 -0.5515 0.1244 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.7288 1.8464 0.4133 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.1426 -0.4741 -0.2184 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.0787 1.9238 0.0706 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.7855 0.7636 -0.2453 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.1409 -1.8536 0.1477 C 0 0 0 0 0 0 0 0 0 0 0 0
2.1094 0.6715 -0.3113 C 0 0 0 0 0 0 0 0 0 0 0 0
3.5305 0.5996 0.1635 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.1851 2.7545 0.6593 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.7247 -1.3605 -0.4564 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.5797 2.8872 0.0506 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.8374 0.8238 -0.5090 H 0 0 0 0 0 0 0 0 0 0 0 0
3.7290 1.4184 0.8593 H 0 0 0 0 0 0 0 0 0 0 0 0
4.2045 0.6969 -0.6924 H 0 0 0 0 0 0 0 0 0 0 0 0
3.7105 -0.3659 0.6426 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.2555 -3.5916 -0.7337 H 0 0 0 0 0 0 0 0 0 0 0 0
1 5 1 0 0 0 0
1 12 1 0 0 0 0
2 11 1 0 0 0 0
2 21 1 0 0 0 0
3 11 2 0 0 0 0
4 12 2 0 0 0 0
5 6 1 0 0 0 0
5 7 2 0 0 0 0
6 8 2 0 0 0 0
6 11 1 0 0 0 0
7 9 1 0 0 0 0
7 14 1 0 0 0 0
8 10 1 0 0 0 0
8 15 1 0 0 0 0
9 10 2 0 0 0 0
9 16 1 0 0 0 0
10 17 1 0 0 0 0
12 13 1 0 0 0 0
13 18 1 0 0 0 0
13 19 1 0 0 0 0
13 20 1 0 0 0 0
M END`;

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* Ligand Editor Example</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}
#app {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
}
.editor-controls button {
padding: 0 8px;
background-color: transparent;
color: black;
border: 1px solid rgb(206, 200, 186);
border-radius: 0;
cursor: pointer;
height: 24px;
}
.editor-controls input[type="text"] {
padding: 0 8px;
border: 1px solid rgb(206, 200, 186);
border-radius: 0;
height: 24px;
}
.editor-controls textarea {
padding: 4px 8px;
border: 1px solid rgb(206, 200, 186);
border-radius: 0;
}
.editor-controls button:hover, .editor-controls input:hover, .editor-controls textarea:hover {
background-color: rgba(206, 200, 186, 0.1);
}
.msp-selection-viewport-controls {
display: none;
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
</head>
<body>
<div id="app"></div>
</div>
<script type="text/javascript" src="./index.js"></script>
<script>
initLigandEditorExample('app');
</script>
</body>
</html>

View File

@@ -0,0 +1,433 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { useState } from 'react';
import { createRoot } from 'react-dom/client';
import { BehaviorSubject, Subscription, throttleTime } from 'rxjs';
import { JSONCifLigandGraph, JSONCifLigandGraphBondProps } from '../../extensions/json-cif/ligand-graph';
import { JSONCifDataBlock, JSONCifFile } from '../../extensions/json-cif/model';
import { ParseJSONCifFileData } from '../../extensions/json-cif/transformers';
import { MolViewSpec } from '../../extensions/mvs/behavior';
import { StructureElement, StructureProperties } from '../../mol-model/structure';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { ModelFromTrajectory, StructureFromModel, TrajectoryFromMmCif } from '../../mol-plugin-state/transforms/model';
import { StructureRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { Plugin } from '../../mol-plugin-ui/plugin';
import '../../mol-plugin-ui/skin/light.scss';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginSpec } from '../../mol-plugin/spec';
import { StateObjectSelector } from '../../mol-state';
import { download } from '../../mol-util/download';
import { GeometryEditFn, GeometryEdits, TopologyEdits } from './edits';
import { ExampleMol } from './example-data';
import './index.html';
import { jsonCifToMolfile } from './molfile';
import { RGroupName } from './r-groups';
import { SingleTaskQueue } from './utils';
import { molfileToJSONCif } from '../../extensions/json-cif/utils';
async function init(target: HTMLElement | string, molfile: string = ExampleMol) {
const root = typeof target === 'string' ? document.getElementById(target)! : target;
const plugin = await createViewer(root);
const model = new EditorModel(plugin);
createRoot(root).render(<AppUI model={model} />);
loadMolfile(model, molfile);
return model;
}
(window as any).initLigandEditorExample = init;
async function createViewer(root: HTMLElement) {
const spec = DefaultPluginUISpec();
const plugin = new PluginUIContext({
...spec,
layout: {
initial: {
isExpanded: false,
showControls: false
}
},
components: {
remoteState: 'none',
},
behaviors: [
...spec.behaviors,
PluginSpec.Behavior(MolViewSpec)
],
config: [
[PluginConfig.Viewport.ShowAnimation, false],
[PluginConfig.Viewport.ShowSelectionMode, false],
[PluginConfig.Viewport.ShowExpand, false],
[PluginConfig.Viewport.ShowControls, false],
]
});
await plugin.init();
plugin.managers.interactivity.setProps({ granularity: 'element' });
plugin.selectionMode = true;
return plugin;
}
async function loadMolfile(model: EditorModel, molfile: string) {
const { plugin } = model;
await plugin.clear();
const file = await molfileToJSONCif(molfile);
const update = plugin.build();
const data = update.toRoot()
.apply(ParseJSONCifFileData, { data: file.jsoncif });
data
.apply(TrajectoryFromMmCif)
.apply(ModelFromTrajectory)
.apply(StructureFromModel, { type: { name: 'model', params: {} } })
.apply(StructureRepresentation3D, {
type: { name: 'ball-and-stick', params: {} },
colorTheme: {
name: 'element-symbol',
params: { carbonColor: { name: 'element-symbol', params: {} } }
}
});
await update.commit();
model.setDataSelector(data.selector);
}
class EditorModel {
private dataSelector: StateObjectSelector | undefined = undefined;
state = {
element: new BehaviorSubject<string>('C'),
history: new BehaviorSubject<JSONCifFile[]>([]),
molfile: new BehaviorSubject<string>(''),
};
get data() {
return this.dataSelector?.cell?.transform?.params?.data as JSONCifFile | undefined;
}
get history() {
return this.state.history.value;
}
createGraph() {
return new JSONCifLigandGraph(this.data?.dataBlocks[0]!);
}
setDataSelector(selector: StateObjectSelector) {
this.dataSelector = selector;
this.updateMolFile();
}
updateMolFile() {
if (!this.data) return this.state.molfile.next('');
try {
const molfile = jsonCifToMolfile(this.data?.dataBlocks[0], {
comment: 'Generated by Mol* Ligand Editor'
});
this.state.molfile.next(molfile);
} catch (e) {
console.error('Failed to convert to molfile');
console.error(e);
this.state.molfile.next(`Error: ${e}`);
}
}
async update(data: JSONCifDataBlock, pushHistory = true, historyData?: JSONCifFile) {
if (!this.data) return;
const updated: JSONCifFile = {
...this.data!,
dataBlocks: [data],
};
if (pushHistory) {
this.state.history.next([...this.history, historyData ?? this.data!]);
}
const update = this.plugin.build();
update.to(this.dataSelector!).update({ data: updated });
await update.commit();
this.updateMolFile();
}
undo = async () => {
if (!this.dataSelector) return;
if (this.history.length === 0) return;
const data = this.history[this.history.length - 1];
this.state.history.next(this.history.slice(0, this.history.length - 1));
const update = this.plugin.build();
update.to(this.dataSelector).update({ data });
await update.commit();
this.updateMolFile();
};
private getEditableStructures() {
if (!this.dataSelector?.isOk) return new Set();
const structures = this.plugin.state.data.selectQ(q => q
.byRef(this.dataSelector?.ref!)
.subtree()
.filter(c => PluginStateObject.Molecule.Structure.is(c.obj))
);
return new Set(structures.map(s => s.obj?.data));
}
private getSelectedAtomIds() {
if (!this.data) return [];
const structures = this.getEditableStructures();
if (structures.size === 0) return [];
const { selection } = this.plugin.managers.structure;
const ids: number[] = [];
selection.entries.forEach(e => {
if (!structures.has(e.selection.structure)) return;
StructureElement.Loci.forEachLocation(e.selection, (l) => {
ids.push(StructureProperties.atom.id(l));
});
});
return ids;
}
async editGraphTopology<Args extends any[], T>(fn: (graph: JSONCifLigandGraph, ...args: Args) => Promise<T>, ...args: Args) {
try {
const graph = this.createGraph();
const result = await fn(graph, ...args);
const data = graph.getData().block;
await this.update(data);
this.plugin.managers.interactivity.lociSelects.deselectAll();
return result;
} catch (e) {
console.error('Failed to edit graph');
console.error(e);
this.notify(`${e}`, 5000);
}
}
private notify(message: string, timeoutMs = 2500) {
PluginCommands.Toast.Show(this.plugin, { key: '<edit>', title: 'Edit', message, timeoutMs });
}
setElement = async () => {
const symbol = this.state.element.value.trim();
if (!symbol) return this.notify('No element symbol provided');
const ids = this.getSelectedAtomIds();
if (!ids.length) return this.notify('No atoms selected');
await this.editGraphTopology(TopologyEdits.setElement, ids, symbol);
};
addElement = async () => {
const symbol = this.state.element.value.trim();
if (!symbol) return this.notify('No element symbol provided');
const ids = this.getSelectedAtomIds();
if (ids.length !== 1) return this.notify('Select a single atom to add a new atom to');
await this.editGraphTopology(TopologyEdits.addElement, ids[0], symbol);
};
removeAtoms = async () => {
const ids = this.getSelectedAtomIds();
if (!ids.length) return this.notify('No atoms selected');
await this.editGraphTopology(TopologyEdits.removeAtoms, ids);
};
removeBonds = async () => {
const ids = this.getSelectedAtomIds();
if (!ids.length) return this.notify('No atoms selected');
await this.editGraphTopology(TopologyEdits.removeBonds, ids);
};
updateBonds = async (props: JSONCifLigandGraphBondProps) => {
const ids = this.getSelectedAtomIds();
if (!ids.length) return this.notify('No atoms selected');
await this.editGraphTopology(TopologyEdits.updateBonds, ids, props);
};
attachRgroup = async (name: RGroupName) => {
const ids = this.getSelectedAtomIds();
if (ids.length !== 1) return this.notify('Select a single hydrogen atom to attach an R-group to');
await this.editGraphTopology(TopologyEdits.attachRgroup, ids[0], name);
};
private geometryEditInitialData: JSONCifFile | undefined = undefined;
private geometryEditValues = new BehaviorSubject<[value: number, finish: boolean]>([0, false]);
private currentGeometryEdit: GeometryEditFn | undefined = undefined;
private currentGeomeryEditSub: Subscription | undefined = undefined;
private geometryEditQueue = new SingleTaskQueue();
private applyGeometryEdit = ([param, finish]: [param: number, finish: boolean]) => {
if (!this.currentGeometryEdit) return;
const graph = this.currentGeometryEdit(param);
const data = graph.getData().block;
const initialData = this.geometryEditInitialData;
if (finish) {
this.currentGeometryEdit = undefined;
this.currentGeomeryEditSub?.unsubscribe();
this.currentGeomeryEditSub = undefined;
this.geometryEditInitialData = undefined;
}
this.geometryEditQueue.run(() => this.update(data, finish, initialData));
};
beginGeometryEdit<Args extends any[], T>(fn: (graph: JSONCifLigandGraph, ...args: Args) => GeometryEditFn, initial: number, ...args: Args) {
try {
this.geometryEditValues.next([initial, false]);
const graph = this.createGraph();
this.geometryEditInitialData = this.data!;
this.currentGeometryEdit = fn(graph, ...args);
this.currentGeomeryEditSub = this.geometryEditValues
.pipe(throttleTime(1000 / 60, undefined, { leading: true, trailing: true }))
.subscribe(this.applyGeometryEdit);
} catch (e) {
console.error('Failed to edit graph');
console.error(e);
this.notify(`${e}`, 5000);
}
}
setGeometryEditValue(param: number, finish = false) {
this.geometryEditValues.next([param, finish]);
}
twist = () => {
this.beginGeometryEdit(GeometryEdits.twist, 0, this.getSelectedAtomIds());
};
stretch = () => {
this.beginGeometryEdit(GeometryEdits.stretch, 0, this.getSelectedAtomIds());
};
constructor(public plugin: PluginUIContext) { }
}
function AppUI({ model }: { model: EditorModel }) {
return <div style={{ display: 'flex', flexDirection: 'row', height: '100%', width: '100%' }}>
<div style={{ flexGrow: 1, display: 'block', position: 'relative' }}>
<Plugin plugin={model.plugin} />
</div>
<div style={{ flexShrink: 0, minWidth: 500, width: 400, display: 'flex', flexDirection: 'column', gap: '5px' }}>
<ControlsUI model={model} />
</div>
</div>;
}
function ControlsUI({ model }: { model: EditorModel }) {
return <div style={{ display: 'flex', flexDirection: 'column', gap: '5px', padding: 8, overflow: 'hidden', overflowY: 'auto' }} className='editor-controls'>
<div>
<UndoButton model={model} />
</div>
<b>Atoms</b>
<div style={{ display: 'flex', gap: '5px' }}>
<button onClick={model.removeAtoms}>Remove</button>
<div>
<ElementEditUI model={model} />
<button onClick={model.setElement} style={{ borderLeft: 'none' }}>Set Element</button>
<button onClick={model.addElement} style={{ borderLeft: 'none' }}>Add Element</button>
</div>
</div>
<b>Bonds</b>
<div style={{ display: 'flex', gap: '5px' }}>
<button onClick={model.removeBonds}>Remove</button>
<button onClick={() => model.updateBonds({ value_order: 'sing', type_id: 'covale' })}>-</button>
<button onClick={() => model.updateBonds({ value_order: 'doub', type_id: 'covale' })}>=</button>
<button onClick={() => model.updateBonds({ value_order: 'trip', type_id: 'covale' })}></button>
</div>
<b>R-groups</b>
<div style={{ display: 'flex', gap: '5px' }}>
<button onClick={() => model.attachRgroup('CH3')}>-CH<sub>3</sub></button>
</div>
<b>Geometry</b>
<TwistUI model={model} />
<StretchUI model={model} />
<b>Molfile</b>
<MolFileUI model={model} />
</div>;
}
function MolFileUI({ model }: { model: EditorModel }) {
const molfile = useBehavior(model.state.molfile);
return <>
<textarea value={molfile} readOnly style={{ width: '100%', height: 200, fontFamily: 'monospace', fontSize: '10px' }} />
<div style={{ display: 'flex', gap: '5px' }}>
<button onClick={() => navigator.clipboard.writeText(molfile)}>Copy</button>
<button onClick={() => download(new Blob([molfile], { type: 'text/plain' }), `edited-molecule-${Date.now()}.mol`)}>Save</button>
</div>
</>;
}
function UndoButton({ model }: { model: EditorModel }) {
const history = useBehavior(model.state.history);
return <button onClick={model.undo} disabled={history.length === 0}>Undo [{history.length}]</button>;
}
function ElementEditUI({ model }: { model: EditorModel }) {
const element = useBehavior(model.state.element);
return <input type="text" value={element} style={{ width: 50 }} onChange={e => model.state.element.next(e.target.value)} />;
}
const GeometryLabelWidth = 60;
function TwistUI({ model }: { model: EditorModel }) {
const [value, setValue] = useState(0);
return <div style={{ display: 'flex', alignItems: 'center', gap: 8 }} >
<i style={{ width: GeometryLabelWidth }}>Twist</i> <input
type='range' min={-60} max={60} step={1} value={value}
onMouseDown={model.twist}
onMouseUp={(e) => {
requestAnimationFrame(() => {
model.setGeometryEditValue(Math.PI * value / 60, true);
setValue(0);
});
}}
onChange={e => {
const value = +e.target.value;
setValue(value);
model.setGeometryEditValue(Math.PI * value / 60);
}}
/>
</div>;
}
function StretchUI({ model }: { model: EditorModel }) {
const [value, setValue] = useState(0);
return <div style={{ display: 'flex', alignItems: 'center', gap: 8 }} >
<i style={{ width: GeometryLabelWidth }}>Stretch</i> <input
type='range' min={-60} max={60} step={1} value={value}
onMouseDown={model.stretch}
onMouseUp={(e) => {
requestAnimationFrame(() => {
model.setGeometryEditValue(0.5 * value / 60, true);
setValue(0);
});
}}
onChange={e => {
const value = +e.target.value;
setValue(value);
model.setGeometryEditValue(0.5 * value / 60);
}}
/>
</div>;
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { getJSONCifCategory, JSONCifDataBlock } from '../../extensions/json-cif/model';
import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
import { MolstarBondSiteSchema, MolstarBondSiteTypeId, MolstarBondSiteValueOrder } from '../../mol-model/structure/export/categories/molstar_bond_site';
function padLeft(v: any, n = 3) {
let s = `${v}`;
while (s.length < n) s = ' ' + s;
return s;
}
function padRight(v: any, n = 3) {
let s = `${v}`;
while (s.length < n) s = s + ' ';
return s;
}
function mapMolChage(v: number) {
switch (v) {
case 3: return 1;
case 2: return 2;
case 1: return 3;
case -1: return 5;
case -2: return 6;
case -3: return 7;
default: return 0;
}
}
function mapMolBondOrder(order: MolstarBondSiteValueOrder, type: MolstarBondSiteTypeId) {
if (type !== 'covale') return 8;
switch (order) {
case 'sing': return 1;
case 'doub': return 2;
case 'trip': return 3;
case 'arom': return 4;
default: return 8;
}
}
export function jsonCifToMolfile(data: JSONCifDataBlock, options?: { name?: string, comment?: string }) {
// The method works in the sense that Mol* can re-open the file.
// For production use, this will likely need more testing and tweaks (e.g., support for M CHG property).
if (data.categories.atom_site === undefined || data.categories.molstar_bond_site === undefined) {
throw new Error('The data block must contain atom_site and molstar_bond_site categories.');
}
const { atom_site: _atoms, molstar_bond_site: _bonds } = data.categories;
const atoms = getJSONCifCategory<mmCIF_Schema['atom_site']>(data, 'atom_site')!;
const bonds = getJSONCifCategory<MolstarBondSiteSchema['molstar_bond_site']>(data, 'molstar_bond_site')!;
const lines = [
`${options?.name ?? 'mol'}`,
' Molstar 3D',
options?.comment ?? '',
`${padLeft(atoms.rows.length)}${padLeft(bonds.rows.length)} 0 0 0 0 0 0 0 0 V2000`,
];
const atomIdToIndex = new Map<number, number>();
for (let i = 0; i < atoms.rows.length; ++i) {
const a = atoms.rows[i];
const { id, Cartn_x, Cartn_y, Cartn_z, type_symbol, pdbx_formal_charge } = a;
atomIdToIndex.set(id, i + 1);
const fields = [
padLeft(Cartn_x.toFixed(4), 10),
padLeft(Cartn_y.toFixed(4), 10),
padLeft(Cartn_z.toFixed(4), 10),
' ',
padRight(type_symbol, 2),
' 0',
padLeft(mapMolChage(pdbx_formal_charge), 3),
' 0 0 0 0 0 0 0 0 0 0',
];
lines.push(fields.join(''));
}
for (const b of bonds.rows) {
const { atom_id_1, atom_id_2, value_order, type_id } = b;
const fields = [
padLeft(atomIdToIndex.get(atom_id_1)!, 3),
padLeft(atomIdToIndex.get(atom_id_2)!, 3),
padLeft(mapMolBondOrder(value_order, type_id), 3),
' 0 0 0 0',
];
lines.push(fields.join(''));
}
lines.push('M END');
return lines.join('\n');
}

View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { JSONCifLigandGraph, JSONCifLigandGraphAtom } from '../../extensions/json-cif/ligand-graph';
import { molfileToJSONCif } from '../../extensions/json-cif/utils';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
export type RGroupName = keyof typeof RGroups;
export async function attachRGroup(pGraph: JSONCifLigandGraph, rgroupName: RGroupName, pAtomOrId: number | JSONCifLigandGraphAtom) {
const pAtom = pGraph.getAtom(pAtomOrId);
if (pAtom?.row?.type_symbol !== 'H') {
throw new Error('R-group attachment point must be a hydrogen atom.');
}
const { molfile, jsoncif: rgroupData } = await molfileToJSONCif(RGroups[rgroupName]);
const attachIdx = molfile.attachmentPoints?.[0].atomIdx;
if (typeof attachIdx !== 'number') {
throw new Error('R-group attachment point not specified.');
}
// Compute and apply rGroup transformation
const pBonds = pGraph.getBonds(pAtom);
if (pBonds.length !== 1) {
throw new Error('R-group attachment point must have exactly 1 bond.');
}
const pDir = pGraph.getBondDirection(pBonds[0]);
const pPivot = pBonds[0].atom_2;
Vec3.negate(pDir, pDir);
Vec3.normalize(pDir, pDir);
const rGraph = new JSONCifLigandGraph(rgroupData.dataBlocks[0]);
const rAtom = rGraph.getAtomAtIndex(attachIdx - 1);
if (rAtom.row?.type_symbol !== 'R#') {
throw new Error('R-group attachment point is not a R# atom.');
}
const rCoords = rGraph.getAtomCoords(rAtom);
const rBonds = rGraph.getBonds(rAtom);
if (rBonds.length !== 1) {
throw new Error('R-group R# atom must have exactly 1 bond.');
}
const rPivot = rGraph.getAtom(rBonds[0].atom_2);
const rDir = rGraph.getBondDirection(rBonds[0]);
Vec3.normalize(rDir, rDir);
const rotation = Vec3.makeRotation(Mat4(), rDir, pDir);
const translation = Mat4.fromTranslation(Mat4(), Vec3.sub(Vec3(), pGraph.getAtomCoords(pPivot), rCoords));
const C = Mat4.fromTranslation(Mat4(), Vec3.negate(Vec3(), rCoords));
const CT = Mat4.fromTranslation(Mat4(), rCoords);
const T0 = Mat4.mul3(Mat4(), CT, rotation, C);
const T = Mat4.mul(Mat4(), translation, T0);
rGraph.transformCoords(T);
// Merge the two graphs
pGraph.removeAtom(pAtom);
rGraph.removeAtom(rAtom);
const newAtomMap = new Map<string, JSONCifLigandGraphAtom>();
// Add atoms
for (const a of rGraph.atoms) {
const newAtom = pGraph.addAtom(a.row);
newAtomMap.set(a.key, newAtom);
if (a === rPivot) {
pGraph.addOrUpdateBond(pPivot, newAtom, rBonds[0].props);
}
}
// Add bonds
for (const a of rGraph.atoms) {
if (a === rAtom) continue;
const bonds = rGraph.getBonds(a);
const atom1 = newAtomMap.get(a.key)!;
for (const b of bonds) {
if (b.atom_2 === rAtom) continue;
const atom2 = newAtomMap.get(b.atom_2.key)!;
pGraph.addOrUpdateBond(atom1, atom2, b.props);
}
}
return pGraph;
}
// Assumes the "attachment point (M APO)" points to a hydrogen atom that gets removed
// when the R-group is attached.
const RGroups = {
CH3: `CH3
-OEChem-05072507373D
5 4 0 0 0 0 0 0 0999 V2000
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.5541 0.7996 0.4965 R# 0 0 0 0 0 0 0 0 0 0 0 0
0.6833 -0.8134 -0.2536 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.7782 -0.3735 0.6692 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.4593 0.3874 -0.9121 H 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0 0 0 0
1 3 1 0 0 0 0
1 4 1 0 0 0 0
1 5 1 0 0 0 0
M APO 1 2 1
M END`
};

View File

@@ -0,0 +1,12 @@
# Ligand Editor Example
Basic small molecule editing features utilizing the `json-cif` format/extension.
This application is (at least currently) not meant to be a production-ready molecule editor.
To run development build locally from the root `molstar` directory (after `npm install`):
```bash
npm run dev -- -e ligand-editor
```
and navigate to `build/examples/ligand-editor` in the hosted server linked in the script output.

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
export class SingleTaskQueue {
private queue: (() => Promise<void>)[] = [];
run(fn: () => Promise<void>) {
if (this.queue.length < 2) {
this.queue.push(fn);
} else {
this.queue[this.queue.length - 1] = fn;
}
if (this.queue.length === 1) {
this.next();
}
}
private async next() {
while (this.queue.length > 0) {
try {
const fn = this.queue[0];
await fn();
} catch (e) {
console.error('Error in SingleTaskQueue execution:', e);
} finally {
this.queue.shift();
}
}
}
}

View File

@@ -14,7 +14,7 @@ import { PluginCommands } from '../../mol-plugin/commands';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import './index.html';
require('mol-plugin-ui/skin/light.scss');
import '../../mol-plugin-ui/skin/light.scss';
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
@@ -36,6 +36,7 @@ const Canvas3DPresets = {
blurDepthBias: 0.5,
resolutionScale: 1,
color: Color(0x000000),
transparentThreshold: 0.4,
}
},
outline: {

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,124 @@
<!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>Molecular Stories</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#viewer {
position: absolute;
left: 0;
top: 0;
right: 34%;
bottom: 0;
}
#controls {
position: absolute;
left: 66%;
top: 0;
right: 0;
bottom: 0;
padding: 16px;
padding-bottom: 20px;
border: 1px solid #ccc;
border-left: none;
background: #F6F5F3;
z-index: -2;
display: flex;
flex-direction: column;
gap: 16px;
}
#links {
position: absolute;
bottom: 4px;
right: 8px;
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 0.6rem;
z-index: -1;
color: #666;
}
#links a {
color: #666;
text-decoration: none;
}
@media (orientation:portrait) {
#viewer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 40%;
}
#controls {
position: absolute;
left: 0;
top: 60%;
right: 0;
bottom: 0;
border-top: none;
}
.msp-viewport-controls-buttons {
display: none;
}
}
.select-story select {
width: 100%;
display: inline-block;
height: 38px;
padding: 0 8px;
color: #555;
line-height: 38px;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border-radius: 4px;
border: 1px solid #bbb;
cursor: pointer;
box-sizing: border-box;
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
<script type="text/javascript" src="index.js"></script>
</head>
<body>
<div id="viewer">
<mvs-stories-viewer></mvs-stories-viewer>
</div>
<div id="controls">
<div id="select-story" class="select-story"></div>
<mvs-stories-snapshot-markdown style="flex-grow: 1;"></mvs-stories-snapshot-markdown>
</div>
<div id="links">
<a href="#" id="mvs-data">Download MVS State</a> | <a href="https://github.com/molstar/molstar/tree/master/src/examples/mvs-stories" id="mvs-data" target="_blank" rel="noopener noreferrer">Source Code</a>
</div>
<script>
document.getElementById('mvs-data').addEventListener('click', (e) => {
e.preventDefault();
window.downloadStory();
});
setTimeout(() => {
window.initStories();
}, 0);
</script>
<!-- __MOLSTAR_ANALYTICS__ -->
</body>
</html>

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { download } from '../../mol-util/download';
import { BehaviorSubject } from 'rxjs';
import { Stories } from './stories';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { createRoot } from 'react-dom/client';
import { getMVSStoriesContext } from '../../apps/mvs-stories/context';
import '../../apps/mvs-stories/elements';
import './favicon.ico';
import '../../mol-plugin-ui/skin/light.scss';
import '../../apps/mvs-stories/styles.scss';
import './index.html';
function getContext(name?: string) {
return getMVSStoriesContext({ name });
}
type Story = { kind: 'built-in', id: string } | { kind: 'url', url: string, format: 'mvsx' | 'mvsj' } | undefined;
const CurrentStory = new BehaviorSubject<Story>(undefined);
function SelectStoryUI({ subject }: { subject: BehaviorSubject<Story> }) {
const current = useBehavior(subject);
const selectedId = current?.kind === 'built-in' ? current.id : current?.kind === 'url' ? 'url' : '';
return <select onChange={e => {
const value = e.currentTarget.value;
const s = Stories.find(s => s.id === value);
if (!s) return;
subject.next({ kind: 'built-in', id: s.id });
}} value={selectedId}>
{!current && <option value=''>Select a story...</option>}
{Stories.map(s => <option key={s.name} value={s.id}>Story: {s.name}</option>)}
{current?.kind === 'url' && <option disabled>------------------</option>}
{current?.kind === 'url' && <option value='url'>{current.url}</option>}
</select>;
}
function init() {
CurrentStory.subscribe(story => {
if (!story) {
history.replaceState({}, '', '');
} else if (story.kind === 'url') {
history.replaceState({}, '', story ? `?story-url=${encodeURIComponent(story.url)}&data-format=${story.format}` : '');
getContext().dispatch({
kind: 'load-mvs',
format: story.format,
url: story.url,
});
} else if (story.kind === 'built-in') {
history.replaceState({}, '', story ? `?story=${story.id}` : '');
const s = Stories.find(s => s.id === story.id);
if (s) {
getContext().dispatch({
kind: 'load-mvs',
data: s.buildStory(),
});
} else {
console.warn('Story not found:', story.id);
CurrentStory.next({ kind: 'built-in', id: Stories[0].id });
}
}
});
const urlParams = new URLSearchParams(window.location.search);
const storyUrl = urlParams.get('story-url');
const dataFormat = urlParams.get('data-format') as 'mvsx' | 'mvsj' | null;
const storyId = urlParams.get('story');
if (storyUrl) {
CurrentStory.next({ kind: 'url', url: storyUrl, format: dataFormat ?? 'mvsj' });
} else if (storyId) {
CurrentStory.next({ kind: 'built-in', id: storyId });
} else {
CurrentStory.next({ kind: 'built-in', id: Stories[0].id });
}
createRoot(document.getElementById('select-story')!).render(<SelectStoryUI subject={CurrentStory} />);
}
(window as any).downloadStory = () => {
if (CurrentStory.value?.kind !== 'built-in') return;
const id = CurrentStory.value.id;
const story = Stories.find(s => s.id === id);
if (!story) return;
const data = JSON.stringify(story.buildStory(), null, 2);
download(new Blob([data], { type: 'application/json' }), `${id}-story.mvsj`);
};
(window as any).initStories = init;
(window as any).CurrentStory = CurrentStory;

View File

@@ -0,0 +1,23 @@
# MolViewSpec Stories Example
This example illustrates using the `mvs-stories` app to tell molecular stories built with MolViewSpec.
See the [mvs-stories](../../apps/mvs-stories) app for more info about how to use this app separately.
### Usage
- Clone Mol* GitHub repo and build it.
```bash
git clone https://github.com/molstar/molstar.git
cd molstar
npm install
npm build
```
- See [index.html](./index.html) for example usage.
- For interactive development build (for production use `npm run build`) of the example that immediately reflects changes use:
```bash
npm run dev -- -e mvs-stories
```

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { buildStory as kinase } from './kinase';
import { buildStory as tbp } from './tbp';
export const Stories = [
{ id: 'kinase', name: 'BCR-ABL: A Kinase Out of Control', buildStory: kinase },
{ id: 'tata', name: 'TATA-Binding Protein and its Role in Transcription Initiation ', buildStory: tbp },
] as const;

View File

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

View File

@@ -0,0 +1,541 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
*/
import { MVSData_States } from '../../../extensions/mvs/mvs-data';
import { createMVSBuilder, Structure as MVSStructure, Root } from '../../../extensions/mvs/tree/mvs/mvs-builder';
import { MVSNodeParams } from '../../../extensions/mvs/tree/mvs/mvs-tree';
import {
ColorT,
ComponentExpressionT,
isPrimitiveComponentExpressions,
PrimitivePositionT
} from '../../../extensions/mvs/tree/mvs/param-types';
import { Mat3, Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { decodeColor } from '../../../extensions/mvs/helpers/utils';
const Colors = {
'1vok': '#4577B2' as ColorT,
'1cdw': '#BC536D' as ColorT,
'1cdw-2': '#c5a3af' as ColorT,
'1vtl': '#B9E3A0' as ColorT,
'7enc': '#0072B2' as ColorT,
'7enc-2': '#D55E00' as ColorT,
'7enc-3': '#009E73' as ColorT,
'7enc-4': '#56B4E9' as ColorT,
};
// Obtained using https://www.rcsb.org/alignment
const Superpositions = {
'1cdw': [-0.4665815186, 0.6063873444, -0.6438913535, 0, -0.581544075, -0.7588303199, -0.2932286385, 0, -0.6664144171, 0.2376361381, 0.7066971703, 0, 135.0863694935, 105.5007997009, 153.6890178993, 1] as unknown as Mat4,
'1vtl': [-0.4769460004, 0.7214347188, -0.5020502557, 0, -0.297882932, 0.4047204695, 0.8645617968, 0, 0.8269149119, 0.5619014932, 0.0218732801, 0, 65.6043682658, -3.7328402905, -16.8650755387, 1] as unknown as Mat4,
'7enc': [0.8975055044, -0.4316347566, -0.0904174009, 0, 0.247274877, 0.3227849997, 0.9136000105, 0, -0.3651561375, -0.8423189899, 0.3964337454, 0, -189.7572972798, 304.0841220076, -411.5005782853, 1] as unknown as Mat4,
};
const Steps = [
{
header: 'TATA-Binding Protein',
key: 'intro',
description: `
### TATA-Binding Protein Tells RNA Polymerase Where To Get Started on a Gene
Specialized DNA sequences next to genes, called promoters, define the proper start site and direction for transcription.
Promoters vary in sequence and location from organism to organism.
In eukaryotic cells, a complex promoter system that uses dozens of different proteins ensures that the proper RNA polymerase is targeted to each gene.
The TATA-binding protein (TBP) is the central element of this system, participating in transcription by all three RNA polymerases.
The crystal structure of TBP from *Arabidopsis thaliana* in its apo form is shown ([PDB ID 1VOK](${wwPDBLink('1vok')})).
The structure reveals a highly symmetric DNA-binding fold composed of two topologically identical domains derived from the two direct repeats in the phylogenetically conserved sequence.
The domains are related by an approximate 2-fold axis, and consist of a five-stranded, antiparallel beta sheet and two alpha helices.
It is thought that an ancient gene duplication created this protein by combining two copies of the same gene.
The intramolecular symmetry generates a saddle-shaped structure dominated by a curved antiparallel beta sheet, which forms the saddle's concave face and interacts with the DNA.
The convex face of the saddle would interact with other proteins during transcription initiation.
`,
state: (): Root => {
const builder = createMVSBuilder();
const _1vok = structure(builder, '1vok');
const _1vok_comp = _1vok.component({ selector: { label_asym_id: 'A' } });
_1vok_comp.representation({ type: 'cartoon' })
.color({
custom: {
molstar_color_theme_name: 'sequence-id',
molstar_color_theme_params: { carbonColor: { name: 'sequence-id', params: {} } },
}
});
_1vok.primitives().label({
position: { label_asym_id: 'A', label_seq_id: 88, label_atom_id: 'OD2' },
text: 'TATA-Binding Protein',
label_size: 5,
label_color: Colors['1vok']
});
return builder;
},
camera: {
position: [155.18, 118.49, 49.18],
target: [74.81, 66.8, 30.7],
up: [-0.55, 0.83, 0.1],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'TBP: Highly Conserved in Eukaryotes [1/2]',
key: 'highly-conserved-1',
description: `
### TATA-Binding Protein Is Highly Conserved in Eukaryotes
TBP has a phylogenetically conserved, 180 amino-acid carboxy-terminal domain (ranging from 38 to 93% identity among eukaryotes and archaebacteria), containing two structural repeats flanking a highly basic segment known as the basic repeat.
The C-terminal or core portion of the protein binds the TATA consensus sequence with high affinity, interacting with the minor groove and promoting DNA bending.
Structural superposition of TBP bound to DNA in human ([PDB ID 1CDW](${wwPDBLink('1cdw')})) and *A. thaliana* ([PDB ID 1VTL](${wwPDBLink('1vtl')})) shows that their sequences are 83% identical and the RMSD between the structures is 0.43 Å.
`,
state: () => {
const builder = createMVSBuilder();
const _1cdw = structure(builder, '1cdw');
select(_1cdw, { color: Colors['1cdw'], selector: 'protein' });
select(_1cdw, { color: Colors['1cdw'], selector: 'nucleic', opacity: 0.5 })[0].label({ text: 'DNA' });
// uses a range to 'adjust' font size of label
label(_1cdw, { selector: { label_asym_id: 'C', beg_label_seq_id: 160, end_label_seq_id: 177 }, text: 'TBP (H. sapiens)' });
const _1vtl = structure(builder, '1vtl');
select(_1vtl, { color: Colors['1vtl'], selector: { label_asym_id: 'E' } });
select(_1vtl, { color: Colors['1vtl'], selector: { label_asym_id: 'A' }, opacity: 0.5 });
select(_1vtl, { color: Colors['1vtl'], selector: { label_asym_id: 'B' }, opacity: 0.5 });
// uses a range to 'adjust' font size of label
label(_1vtl, { selector: { label_asym_id: 'E', beg_label_seq_id: 75, end_label_seq_id: 92 }, text: 'TBP (A. thaliana)' });
return builder;
},
camera: {
position: [122.15, 104.37, 72.23],
target: [77.48, 59.61, 30.36],
up: [-0.73, 0.68, 0.06],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'TBP: Minor Groove [2/2]',
key: 'highly-conserved-2',
description: `
### TATA-Binding Protein Is Highly Conserved in Eukaryotes
Eukaryotic protein-coding genes transcribed by RNA polymerase II (pol II) have a characteristic sequence of nucleotides, termed the TATA box, in front of the start site of transcription.
The typical sequence is something like T-A-T-A-a/t-A-a/t, where a/t refers to positions that can be either A or T.
TBP recognizes this TATA sequence and binds to it, creating a landmark that directs pol II to the transcription start site.
The structures of *A. thaliana* and human core TBP-TATA element co-crystal structures demonstrate a common induced-fit mechanism of protein-DNA recognition involving subtle conformation changes in the protein and an unprecedented DNA distortion.
`,
state: () => {
const builder = createMVSBuilder();
const _1cdw = structure(builder, '1cdw');
select(_1cdw, { color: Colors['1cdw'], selector: 'protein' });
select(_1cdw, { color: Colors['1cdw'], selector: 'nucleic', opacity: 0.5 });
// select(_1cdw, { color: Colors['1cdw'], selector: { label_asym_id: 'A', beg_label_seq_id: 6, end_label_seq_id: 11 } })[0].label({ text: 'TATA box' });
label(_1cdw, { selector: { label_asym_id: 'A', label_seq_id: 5 }, text: 'T' });
label(_1cdw, { selector: { label_asym_id: 'A', label_seq_id: 6 }, text: 'A' });
label(_1cdw, { selector: { label_asym_id: 'A', label_seq_id: 7 }, text: 'T' });
label(_1cdw, { selector: { label_asym_id: 'A', label_seq_id: 8 }, text: 'A' });
label(_1cdw, { selector: { label_asym_id: 'A', label_seq_id: 9 }, text: 'A' });
label(_1cdw, { selector: { label_asym_id: 'A', label_seq_id: 10 }, text: 'A' });
const _1vtl = structure(builder, '1vtl');
select(_1vtl, { color: Colors['1vtl'], selector: { label_asym_id: 'E' } });
select(_1vtl, { color: Colors['1vtl'], selector: { label_asym_id: 'A' }, opacity: 0.5 });
select(_1vtl, { color: Colors['1vtl'], selector: { label_asym_id: 'B' }, opacity: 0.5 });
return builder;
},
camera: {
position: [108.48, 92.12, 4.1],
target: [80.16, 56.15, 28.96],
up: [-0.71, 0.68, 0.17],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'TBP: Binding to TATA Box [1/5]',
key: 'tata-box-overview',
description: `
### TATA-Binding Protein Binds to the Minor Groove of the TATA Box
When the first structures of TBP-DNA complexes were determined, researchers discovered that TBP is not gentle when it binds to DNA.
Instead, it grabs the TATA sequence, bends and unwinds it to open up the minor groove, and kinks it sharply in two places (*e.g.*, [PDB ID 1VTL](${wwPDBLink('1vtl')})) and ([PDB ID 1CDW](${wwPDBLink('1cdw')})).
Interactions with the minor groove can be divided into different classes as seen in PDB ([PDB ID 1CDW](${wwPDBLink('1cdw')})).
The combination of interactions allows TATA-binding protein to recognize the proper DNA sequence.
`,
state: () => {
const builder = createMVSBuilder();
const _1cdw = structure(builder, '1cdw');
select(_1cdw, { color: Colors['1cdw'], selector: { label_asym_id: 'C' } });
select(_1cdw, { color: Colors['1cdw-2'], selector: { label_asym_id: 'A' } });
select(_1cdw, { color: Colors['1cdw-2'], selector: { label_asym_id: 'B' } });
return builder;
},
camera: {
position: [122.15, 104.37, 72.23],
target: [77.48, 59.61, 30.36],
up: [-0.73, 0.68, 0.06],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'TBP: Arginine [2/5]',
key: 'tata-box-1',
description: `
### Arginine Residues
A string of arginine amino acids interact with the phosphate groups of the DNA and glues the protein to the DNA: Arg192, Arg199, Arg204, and Arg290.
`,
state: () => {
const builder = createMVSBuilder();
const _1cdw = structure(builder, '1cdw');
select(_1cdw, { color: Colors['1cdw'], selector: { label_asym_id: 'C' } });
select(_1cdw, { color: Colors['1cdw-2'], selector: { label_asym_id: 'A' } });
select(_1cdw, { color: Colors['1cdw-2'], selector: { label_asym_id: 'B' } });
label(_1cdw, { selector: { label_asym_id: 'C', label_seq_id: 38 }, text: 'Arg192' });
drawInteractions(_1cdw, [
['H-bond', { label_asym_id: 'C', label_seq_id: 38, label_atom_id: 'NH2' }, { label_asym_id: 'B', label_seq_id: 7, label_atom_id: 'OP1' }],
['H-bond', { label_asym_id: 'C', label_seq_id: 38, label_atom_id: 'NH2' }, { label_asym_id: 'B', label_seq_id: 6, label_atom_id: `O3'` }],
['H-bond', { label_asym_id: 'C', label_seq_id: 38, label_atom_id: 'NH1' }, { label_asym_id: 'B', label_seq_id: 7, label_atom_id: 'OP1' }],
]);
label(_1cdw, { selector: { label_asym_id: 'C', label_seq_id: 45 }, text: 'Arg199' });
drawInteractions(_1cdw, [
['H-bond', { label_asym_id: 'C', label_seq_id: 45, label_atom_id: 'NE' }, { label_asym_id: 'B', label_seq_id: 8, label_atom_id: 'OP1' }],
['H-bond', { label_asym_id: 'C', label_seq_id: 45, label_atom_id: 'NH2' }, { label_asym_id: 'B', label_seq_id: 8, label_atom_id: 'OP1' }],
]);
label(_1cdw, { selector: { label_asym_id: 'C', label_seq_id: 136 }, text: 'Arg290' });
drawInteractions(_1cdw, [
['H-bond', { label_asym_id: 'C', label_seq_id: 136, label_atom_id: 'NH1' }, { label_asym_id: 'A', label_seq_id: 8, label_atom_id: 'OP1' }],
]);
label(_1cdw, { selector: { label_asym_id: 'C', label_seq_id: 50 }, text: 'Arg204' });
drawInteractions(_1cdw, [
['H-bond', { label_asym_id: 'B', label_seq_id: 9, label_atom_id: 'OP1' }, { label_asym_id: 'C', label_seq_id: 50, label_atom_id: 'NH1' }],
['H-bond', { label_asym_id: 'B', label_seq_id: 9, label_atom_id: 'OP1' }, { label_asym_id: 'C', label_seq_id: 50, label_atom_id: 'NH2' }],
]);
return builder;
},
camera: {
position: [113.87, 71.89, 26.29],
target: [77.29, 61.61, 18.73],
up: [-0.28, 0.96, 0.04],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'TBP: Phenylalanine [3/5]',
key: 'tata-box-2',
description: `
### Phenylalanine-Induced Kinks
At either end of the TATA element there are two pairs of phenylalanine side chains partially inserted between adjacent base pairs, producing the two dramatic kinks (Phe193, Phe210, Phe284, and Phe301). Between the two kinks the DNA is partially unwound.
`,
state: () => {
const builder = createMVSBuilder();
const _1cdw = structure(builder, '1cdw');
select(_1cdw, { color: Colors['1cdw'], selector: { label_asym_id: 'C' } });
select(_1cdw, { color: Colors['1cdw-2'], selector: { label_asym_id: 'A' } });
select(_1cdw, { color: Colors['1cdw-2'], selector: { label_asym_id: 'B' } });
bindingSite(_1cdw, [
[{ label_asym_id: 'C', label_seq_id: 39 }, 'Phe193'],
[{ label_asym_id: 'C', label_seq_id: 56 }, 'Phe210'],
[{ label_asym_id: 'C', label_seq_id: 130 }, 'Phe284'],
[{ label_asym_id: 'C', label_seq_id: 147 }, 'Phe301'],
], { color: Colors['1cdw'] });
return builder;
},
camera: {
position: [111.01, 69.92, -3.14],
target: [85.52, 57.53, 19.15],
up: [-0.51, 0.85, -0.11],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'TBP: H-Bonds in Minor Groove [4/5]',
key: 'tata-box-3',
description: `
### Hydrogen Bonds in the Minor Groove
Polar side chains make minor groove hydrogen bonds with acceptors of base pairs centred about the approximate 2-fold symmetry axis (Asn163, Asn253, and Thr309).
`,
state: () => {
const builder = createMVSBuilder();
const _1cdw = structure(builder, '1cdw');
select(_1cdw, { color: Colors['1cdw'], selector: { label_asym_id: 'C' } });
select(_1cdw, { color: Colors['1cdw-2'], selector: { label_asym_id: 'A' } });
select(_1cdw, { color: Colors['1cdw-2'], selector: { label_asym_id: 'B' } });
// Asn: 9, 99, 155
label(_1cdw, { selector: { label_asym_id: 'C', label_seq_id: 9 }, text: 'Asn163' });
label(_1cdw, { selector: { label_asym_id: 'C', label_seq_id: 155 }, text: 'Thr309' });
drawInteractions(_1cdw, [
['H-bond', { label_asym_id: 'C', label_seq_id: 9, label_atom_id: 'ND2' }, { label_asym_id: 'B', label_seq_id: 8, label_atom_id: 'O2' }],
['H-bond', { label_asym_id: 'B', label_seq_id: 9, label_atom_id: 'O2' }, { label_asym_id: 'C', label_seq_id: 9, label_atom_id: 'ND2' }],
['H-bond', { label_asym_id: 'C', label_seq_id: 155, label_atom_id: 'OG1' }, { label_asym_id: 'A', label_seq_id: 8, label_atom_id: 'N3' }],
]);
bindingSite(_1cdw, [
[{ label_asym_id: 'C', label_seq_id: 99 }, 'Asn253'],
], { color: 'gray', label_color: Colors['1cdw'] });
return builder;
},
camera: {
position: [69.71, 56.65, 17.62],
target: [75.68, 63.48, 32.29],
up: [-0.9, 0.39, 0.18],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'TBP: Non-Polar Interactions [5/5]',
key: 'tata-box-4',
description: `
### Hydrophobic and van der Waals Interactions
Several residues projecting from the concave surface of TBP make hydrophobic or van der Waals side-chain/base contacts (<4 Å between non-hydrogen atoms) with the TATA element.
`,
state: () => {
const builder = createMVSBuilder();
const _1cdw = structure(builder, '1cdw');
select(_1cdw, { color: Colors['1cdw'], selector: { label_asym_id: 'C' } });
select(_1cdw, { color: Colors['1cdw-2'], selector: { label_asym_id: 'A' } });
select(_1cdw, { color: Colors['1cdw-2'], selector: { label_asym_id: 'B' } });
surface(_1cdw, {
selector: { label_asym_id: 'A' },
carbon_color: Colors['1cdw-2'],
});
surface(_1cdw, {
selector: { label_asym_id: 'B' },
carbon_color: Colors['1cdw-2'],
});
return builder;
},
camera: {
position: [122.15, 104.37, 72.23],
target: [77.48, 59.61, 30.36],
up: [-0.73, 0.68, 0.06],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'TBP and Transcription Pre-Initiation Complex',
key: 'pre-init-complex',
description: `
### TATA-Binding Protein and the Transcription Pre-Initiation Complex
The structure shown here, from [PDB ID 7ENC](${wwPDBLink('7enc')}), is that of a pol II “pre-initiation complex” (PIC) poised to start transcription.
This complex includes eukaryotic Mediator and pol II, along with a collection of general transcription factors that perform the necessary tasks of recognizing the transcription start site of genes, separating the two strands of the DNA double helix, and facilitating transcription initiation by RNA polymerase II.
TBP starts the process of assembling the PIC and works with TBP-associated factors (TAFs) as part of a large multi-component transcription factor, TFIID, that belongs to the collection of general transcription factors that cradle pol II with in the PIC.
After TBP binds to the promoter, it recruits additional transcription factors.
TFIIA and TFIIB interact with surrounding regions of the DNA and, along with TFIIF, assist with positioning of RNA polymerase II at the transcription start site.
TFIIE, TFIIH, and other TFIID subunits bring additional functionality to the complex.
In particular, part of TFIIH is a translocase that separates the two strands of DNA in preparation for transcription, and the CAK module of TFIIH adds phosphate groups to the long tail of RNA polymerase II, sending the signal that it is time to get started with mRNA synthesis.
TBP is a critical player that gets the transcription process started and is central to the pre-initiation complex.
Lying at the very heart of this complex macromolecular machine, it is highly conserved among eukaryotes and *archaea*.
`,
state: () => {
const builder = createMVSBuilder();
const _7enc = structure(builder, '7enc');
select(_7enc, { color: Colors['7enc'], selector: { label_asym_id: 'CB' } })[0].label({ text: 'TBP' });
select(_7enc, { color: Colors['7enc-2'], selector: { label_asym_id: 'GB' } });
select(_7enc, { color: Colors['7enc-2'], selector: { label_asym_id: 'HB' } });
select(_7enc, { color: Colors['7enc-3'], opacity: 0.5 });
_7enc.primitives()
.label({ position: { label_entity_id: '57' }, text: 'pol II', label_size: 20, label_color: Colors['7enc'] })
.label({ position: { label_entity_id: '53' }, text: 'DNA', label_size: 20, label_color: Colors['7enc-2'] })
.label({ position: { label_entity_id: '21' }, text: 'mediator', label_size: 20, label_color: Colors['7enc-3'] })
.label({ position: { label_entity_id: '42' }, text: 'TBP-associated factors (TAFs)', label_size: 20, label_color: Colors['7enc-4'] })
.label({ position: [100, 150, 0], text: 'PIC', label_size: 30, label_color: Colors['1vok'] })
;
return builder;
},
camera: {
position: [122.15, 104.37, 72.23],
target: [77.48, 59.61, 30.36],
up: [-0.73, 0.68, 0.06],
} satisfies MVSNodeParams<'camera'>,
}, {
header: 'The End',
key: 'end',
description: `
### The End
That's all folks! We hope you enjoyed this interactive journey through the structural biology of the TATA-binding protein.
The next time you look at a macromolecular structure, remember: each atom tells a story, and each discovery shapes the future of medicine.
Read more in the relevant publications on PDB IDs [1VOK](https://doi.org/10.1038/nsb0994-621), [1CDW](https://doi.org/10.1073/pnas.93.10.4862), [1VTL](https://doi.org/10.1038/365520a0), and [7ENC](https://doi.org/10.1126/science.abg0635) as well as the Molecule of the Month articles on the [TATA-binding protein](https://pdb101.rcsb.org/motm/67) and [Mediator](https://pdb101.rcsb.org/motm/289).
`,
state: (): Root => {
return Steps[Steps.length - 2].state();
},
camera: {
position: [461.95, 218.66, 140.67],
target: [87.56, -22.14, 54.59],
up: [-0.55, 0.83, 0.1],
} satisfies MVSNodeParams<'camera'>,
}
];
type Interaction = [label: string, res1: PrimitivePositionT, res2: PrimitivePositionT, options?: { skipResidue?: boolean }]
function drawInteractions(structure: MVSStructure, interactions: Interaction[]) {
const primitives = structure.primitives();
const interactingResidues: ComponentExpressionT[] = [];
const addedResidues = new Set<string>();
function drawResidue(a: PrimitivePositionT) {
const expressions = isPrimitiveComponentExpressions(a) ? a.expressions! : [a as ComponentExpressionT];
for (const _e of expressions) {
const e = { ..._e };
delete e.auth_atom_id;
delete e.label_atom_id;
const key = JSON.stringify(e);
if (addedResidues.has(key)) continue;
interactingResidues.push(e);
addedResidues.add(key);
}
}
for (const [tooltip, a, b, options] of interactions) {
primitives.tube({ start: a, end: b, color: '#4289B5', tooltip, radius: 0.1, dash_length: 0.1 });
if (options?.skipResidue) continue;
drawResidue(a);
drawResidue(b);
}
if (interactingResidues.length === 0) return;
structure
.component({ selector: interactingResidues })
.representation({ type: 'ball_and_stick' })
.color({
custom: {
molstar_color_theme_name: 'element-symbol',
molstar_color_theme_params: { carbonColor: { name: 'element-symbol', params: {} } },
}
});
}
function transform(structure: MVSStructure, id: keyof typeof Superpositions) {
const rotation = Mat3.fromMat4(Mat3.zero(), Superpositions[id]);
const translation = Mat4.getTranslation(Vec3.zero(), Superpositions[id]) as any;
return structure.transform({ rotation, translation });
}
function wwPDBLink(id: string) {
return `https://doi.org/10.2210/pdb${id.toLowerCase()}/pdb`;
}
function structure(builder: Root, id: string): MVSStructure {
let ret = builder
.download({ url: pdbUrl(id) })
.parse({ format: 'bcif' })
.modelStructure();
if (id in Superpositions) {
ret = transform(ret, id as any);
}
return ret;
}
function select(structure: MVSStructure, { color, opacity = 1.0, selector = 'polymer' }: { color: ColorT, opacity?: number, selector?: Partial<MVSNodeParams<'component'>>['selector'] }) {
const component = structure.component({ selector });
const representation = component.representation({ type: 'cartoon' });
representation.color({ color }).opacity({ opacity });
return [component, representation] as const;
}
function label(structure: MVSStructure, { selector, text }: { selector: Partial<MVSNodeParams<'component'>>['selector'], text: string }) {
structure.component({ selector })
.label({ text });
}
function surface(structure: MVSStructure, options: {
selector: ComponentExpressionT | ComponentExpressionT[],
carbon_color?: ColorT,
}) {
const comp = structure.component({ selector: options.selector });
const coloring = {
custom: {
molstar_color_theme_name: 'element-symbol',
molstar_color_theme_params: { carbonColor: options?.carbon_color ? { name: 'uniform', params: { value: decodeColor(options?.carbon_color) } } : { name: 'element-symbol', params: { } } }
}
};
comp.representation({ type: 'surface' }).color(coloring).opacity({ opacity: 0.33 });
return comp;
}
function bindingSite(structure: MVSStructure, residues: [selector: ComponentExpressionT, label: string][], options: {
color?: ColorT,
label_size?: number,
label_color?: ColorT,
}) {
const color: ColorT = options.color ?? '#5B53A4';
const coloring = {
custom: {
molstar_color_theme_name: 'element-symbol',
molstar_color_theme_params: { carbonColor: { name: 'uniform', params: { value: decodeColor(color) } } }
}
};
structure.component({ selector: residues.map(r => r[0]) }).representation({ type: 'ball_and_stick' }).color(coloring);
const primitives = structure.primitives();
for (const [selector, label] of residues) {
primitives.label({
position: selector,
text: label,
label_color: options?.label_color ?? color,
label_size: options?.label_size ?? 1.5
});
}
}
function pdbUrl(id: string) {
return `https://www.ebi.ac.uk/pdbe/entry-files/download/${id.toLowerCase()}.bcif`;
}
export function buildStory(): MVSData_States {
const snapshots = Steps.map((s, i) => {
const builder = s.state();
if (s.camera) builder.camera(s.camera);
const description = i > 0 ? `${s.description}\n\n[Go to start](#intro)` : s.description;
return builder.getSnapshot({
title: s.header,
key: s.key,
description,
description_format: 'markdown',
linger_duration_ms: 5000,
transition_duration_ms: 1500,
});
});
return {
kind: 'multiple',
snapshots,
metadata: {
title: 'The Structural Story of TATA-Binding Protein and its Role in Transcription Initiation',
version: '1.0',
timestamp: new Date().toISOString(),
}
};
}

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