cosmol-viewer 0.1.2.dev5__tar.gz → 0.1.3.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cosmol-viewer might be problematic. Click here for more details.
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/Cargo.lock +42 -19
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/Cargo.toml +3 -3
- cosmol_viewer-0.1.3.dev2/PKG-INFO +179 -0
- cosmol_viewer-0.1.3.dev2/README.md +170 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/lib.rs +118 -5
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/scene.rs +3 -3
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/canvas.rs +2 -1
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shapes/sphere.rs +0 -5
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/python/Cargo.toml +1 -0
- cosmol_viewer-0.1.3.dev2/crates/python/README.md +170 -0
- cosmol_viewer-0.1.3.dev2/crates/python/cosmol_viewer.pyi +315 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/python/src/lib.rs +48 -167
- cosmol_viewer-0.1.3.dev2/crates/python/src/shapes.rs +146 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/wasm/Cargo.toml +3 -2
- cosmol_viewer-0.1.3.dev2/crates/wasm/src/lib.rs +424 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/pyproject.toml +2 -1
- cosmol_viewer-0.1.2.dev5/PKG-INFO +0 -70
- cosmol_viewer-0.1.2.dev5/crates/python/README.md +0 -62
- cosmol_viewer-0.1.2.dev5/crates/python/src/shapes.rs +0 -287
- cosmol_viewer-0.1.2.dev5/crates/wasm/src/lib.rs +0 -257
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/Cargo.toml +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/parser/mod.rs +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/parser/sdf.rs +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/bg_fragment.glsl +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/bg_vertex.glsl +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/fragment.glsl +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/mod.rs +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/vertex.glsl +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shapes/mod.rs +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shapes/molecules.rs +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shapes/stick.rs +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/utils.rs +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/python/build.rs +0 -0
- {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/python/src/parser.rs +0 -0
|
@@ -727,7 +727,7 @@ dependencies = [
|
|
|
727
727
|
|
|
728
728
|
[[package]]
|
|
729
729
|
name = "cosmol_viewer"
|
|
730
|
-
version = "0.1.
|
|
730
|
+
version = "0.1.3-nightly.2"
|
|
731
731
|
dependencies = [
|
|
732
732
|
"bytemuck",
|
|
733
733
|
"cosmol_viewer_core",
|
|
@@ -740,7 +740,7 @@ dependencies = [
|
|
|
740
740
|
|
|
741
741
|
[[package]]
|
|
742
742
|
name = "cosmol_viewer_core"
|
|
743
|
-
version = "0.1.
|
|
743
|
+
version = "0.1.3-nightly.2"
|
|
744
744
|
dependencies = [
|
|
745
745
|
"bytemuck",
|
|
746
746
|
"eframe",
|
|
@@ -770,11 +770,12 @@ dependencies = [
|
|
|
770
770
|
|
|
771
771
|
[[package]]
|
|
772
772
|
name = "cosmol_viewer_wasm"
|
|
773
|
-
version = "0.1.
|
|
773
|
+
version = "0.1.3-nightly.2"
|
|
774
774
|
dependencies = [
|
|
775
775
|
"base64",
|
|
776
776
|
"cosmol_viewer_core",
|
|
777
777
|
"eframe",
|
|
778
|
+
"gloo-timers",
|
|
778
779
|
"log",
|
|
779
780
|
"pyo3",
|
|
780
781
|
"serde",
|
|
@@ -1255,6 +1256,15 @@ dependencies = [
|
|
|
1255
1256
|
"percent-encoding",
|
|
1256
1257
|
]
|
|
1257
1258
|
|
|
1259
|
+
[[package]]
|
|
1260
|
+
name = "futures-channel"
|
|
1261
|
+
version = "0.3.31"
|
|
1262
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1263
|
+
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
|
1264
|
+
dependencies = [
|
|
1265
|
+
"futures-core",
|
|
1266
|
+
]
|
|
1267
|
+
|
|
1258
1268
|
[[package]]
|
|
1259
1269
|
name = "futures-core"
|
|
1260
1270
|
version = "0.3.31"
|
|
@@ -1374,6 +1384,18 @@ dependencies = [
|
|
|
1374
1384
|
"serde",
|
|
1375
1385
|
]
|
|
1376
1386
|
|
|
1387
|
+
[[package]]
|
|
1388
|
+
name = "gloo-timers"
|
|
1389
|
+
version = "0.3.0"
|
|
1390
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1391
|
+
checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
|
|
1392
|
+
dependencies = [
|
|
1393
|
+
"futures-channel",
|
|
1394
|
+
"futures-core",
|
|
1395
|
+
"js-sys",
|
|
1396
|
+
"wasm-bindgen",
|
|
1397
|
+
]
|
|
1398
|
+
|
|
1377
1399
|
[[package]]
|
|
1378
1400
|
name = "glow"
|
|
1379
1401
|
version = "0.16.0"
|
|
@@ -1724,9 +1746,9 @@ checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
|
|
|
1724
1746
|
|
|
1725
1747
|
[[package]]
|
|
1726
1748
|
name = "js-sys"
|
|
1727
|
-
version = "0.3.
|
|
1749
|
+
version = "0.3.81"
|
|
1728
1750
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1729
|
-
checksum = "
|
|
1751
|
+
checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
|
|
1730
1752
|
dependencies = [
|
|
1731
1753
|
"once_cell",
|
|
1732
1754
|
"wasm-bindgen",
|
|
@@ -3479,21 +3501,22 @@ dependencies = [
|
|
|
3479
3501
|
|
|
3480
3502
|
[[package]]
|
|
3481
3503
|
name = "wasm-bindgen"
|
|
3482
|
-
version = "0.2.
|
|
3504
|
+
version = "0.2.104"
|
|
3483
3505
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3484
|
-
checksum = "
|
|
3506
|
+
checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
|
|
3485
3507
|
dependencies = [
|
|
3486
3508
|
"cfg-if",
|
|
3487
3509
|
"once_cell",
|
|
3488
3510
|
"rustversion",
|
|
3489
3511
|
"wasm-bindgen-macro",
|
|
3512
|
+
"wasm-bindgen-shared",
|
|
3490
3513
|
]
|
|
3491
3514
|
|
|
3492
3515
|
[[package]]
|
|
3493
3516
|
name = "wasm-bindgen-backend"
|
|
3494
|
-
version = "0.2.
|
|
3517
|
+
version = "0.2.104"
|
|
3495
3518
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3496
|
-
checksum = "
|
|
3519
|
+
checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
|
|
3497
3520
|
dependencies = [
|
|
3498
3521
|
"bumpalo",
|
|
3499
3522
|
"log",
|
|
@@ -3505,9 +3528,9 @@ dependencies = [
|
|
|
3505
3528
|
|
|
3506
3529
|
[[package]]
|
|
3507
3530
|
name = "wasm-bindgen-futures"
|
|
3508
|
-
version = "0.4.
|
|
3531
|
+
version = "0.4.54"
|
|
3509
3532
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3510
|
-
checksum = "
|
|
3533
|
+
checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c"
|
|
3511
3534
|
dependencies = [
|
|
3512
3535
|
"cfg-if",
|
|
3513
3536
|
"js-sys",
|
|
@@ -3518,9 +3541,9 @@ dependencies = [
|
|
|
3518
3541
|
|
|
3519
3542
|
[[package]]
|
|
3520
3543
|
name = "wasm-bindgen-macro"
|
|
3521
|
-
version = "0.2.
|
|
3544
|
+
version = "0.2.104"
|
|
3522
3545
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3523
|
-
checksum = "
|
|
3546
|
+
checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
|
|
3524
3547
|
dependencies = [
|
|
3525
3548
|
"quote",
|
|
3526
3549
|
"wasm-bindgen-macro-support",
|
|
@@ -3528,9 +3551,9 @@ dependencies = [
|
|
|
3528
3551
|
|
|
3529
3552
|
[[package]]
|
|
3530
3553
|
name = "wasm-bindgen-macro-support"
|
|
3531
|
-
version = "0.2.
|
|
3554
|
+
version = "0.2.104"
|
|
3532
3555
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3533
|
-
checksum = "
|
|
3556
|
+
checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
|
|
3534
3557
|
dependencies = [
|
|
3535
3558
|
"proc-macro2",
|
|
3536
3559
|
"quote",
|
|
@@ -3541,9 +3564,9 @@ dependencies = [
|
|
|
3541
3564
|
|
|
3542
3565
|
[[package]]
|
|
3543
3566
|
name = "wasm-bindgen-shared"
|
|
3544
|
-
version = "0.2.
|
|
3567
|
+
version = "0.2.104"
|
|
3545
3568
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3546
|
-
checksum = "
|
|
3569
|
+
checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
|
|
3547
3570
|
dependencies = [
|
|
3548
3571
|
"unicode-ident",
|
|
3549
3572
|
]
|
|
@@ -3659,9 +3682,9 @@ dependencies = [
|
|
|
3659
3682
|
|
|
3660
3683
|
[[package]]
|
|
3661
3684
|
name = "web-sys"
|
|
3662
|
-
version = "0.3.
|
|
3685
|
+
version = "0.3.81"
|
|
3663
3686
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3664
|
-
checksum = "
|
|
3687
|
+
checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120"
|
|
3665
3688
|
dependencies = [
|
|
3666
3689
|
"js-sys",
|
|
3667
3690
|
"wasm-bindgen",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[workspace.package]
|
|
2
2
|
edition = "2024"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3-nightly.2"
|
|
4
4
|
authors = ["9028 wjt@cosmol.org"]
|
|
5
5
|
repository = "https://github.com/COSMol-repl/COSMol-viewer"
|
|
6
6
|
homepage = "https://github.com/COSMol-repl/COSMol-viewer"
|
|
@@ -13,8 +13,8 @@ resolver = "2"
|
|
|
13
13
|
members = ["crates/python"]
|
|
14
14
|
|
|
15
15
|
[workspace.dependencies]
|
|
16
|
-
cosmol_viewer = { version = "0.1.
|
|
17
|
-
cosmol_viewer_core = { version = "0.1.
|
|
16
|
+
cosmol_viewer = { version = "0.1.3-nightly.2", path = "cosmol_viewer"}
|
|
17
|
+
cosmol_viewer_core = { version = "0.1.3-nightly.2", path = "crates/core" }
|
|
18
18
|
|
|
19
19
|
eframe = { version = "0.32.0", features = ["wayland","x11"] }
|
|
20
20
|
pyo3 = { version = "0.25.1", features = ["extension-module", "abi3-py37"] }
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cosmol-viewer
|
|
3
|
+
Version: 0.1.3.dev2
|
|
4
|
+
Summary: Molecular visualization tools
|
|
5
|
+
Author-email: 95028 <wjt@cosmol.org>
|
|
6
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
7
|
+
Project-URL: Repository, https://github.com/COSMol-repl/COSMol-viewer
|
|
8
|
+
|
|
9
|
+
# COSMol-viewer
|
|
10
|
+
|
|
11
|
+
<div align="center">
|
|
12
|
+
<a href="https://crates.io/crates/cosmol_viewer">
|
|
13
|
+
<img src="https://img.shields.io/crates/v/cosmol_viewer.svg" alt="crates.io Latest Release"/>
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://pypi.org/project/cosmol_viewer/">
|
|
16
|
+
<img src="https://img.shields.io/pypi/v/cosmol_viewer.svg" alt="PyPi Latest Release"/>
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://cosmol-repl.github.io/COSMol-viewer">
|
|
19
|
+
<img src="https://img.shields.io/badge/docs-latest-blue.svg" alt="Documentation Status"/>
|
|
20
|
+
</a>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
**COSMol-viewer** is a high-performance molecular visualization library, written in **Rust** and powered by **WebGPU**, designed for seamless integration into **Python** workflows.
|
|
24
|
+
|
|
25
|
+
- ⚡ **High-speed rendering** — GPU-accelerated performance at native speed
|
|
26
|
+
- 🧬 **Flexible input** — Load structures from `.sdf`, `.pdb`, or dynamically generated coordinates
|
|
27
|
+
- 📓 **Notebook-ready** — Fully compatible with Jupyter and Google Colab, ideal for teaching, research, and interactive demos
|
|
28
|
+
- 🔁 **Dynamic visualization** — Update molecular structures on-the-fly or play smooth preloaded animations
|
|
29
|
+
- 🎨 **Customizable** — Fine-grained control of rendering styles, camera, and scene parameters
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
pip install cosmol-viewer
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from cosmol_viewer import Scene, Viewer, parse_sdf, Molecules
|
|
45
|
+
|
|
46
|
+
# === Step 1: Load and render a molecule ===
|
|
47
|
+
with open("molecule.sdf", "r") as f:
|
|
48
|
+
sdf = f.read()
|
|
49
|
+
mol = Molecules(parse_sdf(sdf)).centered()
|
|
50
|
+
|
|
51
|
+
scene = Scene()
|
|
52
|
+
scene.scale(0.1)
|
|
53
|
+
scene.add_shape(mol, "mol")
|
|
54
|
+
|
|
55
|
+
viewer = Viewer.render(scene, width=600, height=400) # Launch viewer
|
|
56
|
+
|
|
57
|
+
print("Press Any Key to exit...", end='', flush=True)
|
|
58
|
+
_ = input() # Keep the viewer open until you decide to close
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Animation Modes
|
|
64
|
+
|
|
65
|
+
COSMol-viewer supports **two complementary animation workflows**, depending on whether you prefer **real-time updates** or **preloaded playback**.
|
|
66
|
+
|
|
67
|
+
### 1. Real-Time Updates (Frame-by-Frame Streaming)
|
|
68
|
+
|
|
69
|
+
Update the molecule directly inside an existing scene:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import time
|
|
73
|
+
from cosmol_viewer import Scene, Viewer, parse_sdf, Molecules
|
|
74
|
+
|
|
75
|
+
scene = Scene()
|
|
76
|
+
scene.scale(0.1)
|
|
77
|
+
|
|
78
|
+
# Initial load
|
|
79
|
+
with open("frames/frame_1.sdf", "r") as f:
|
|
80
|
+
sdf = f.read()
|
|
81
|
+
mol = Molecules(parse_sdf(sdf)).centered()
|
|
82
|
+
scene.add_shape(mol, "mol")
|
|
83
|
+
|
|
84
|
+
viewer = Viewer.render(scene, width=600, height=400)
|
|
85
|
+
|
|
86
|
+
# Update in real time
|
|
87
|
+
for i in range(2, 10):
|
|
88
|
+
with open(f"frames/frame_{i}.sdf", "r") as f:
|
|
89
|
+
sdf = f.read()
|
|
90
|
+
updated_mol = Molecules(parse_sdf(sdf)).centered()
|
|
91
|
+
|
|
92
|
+
scene.update_shape("mol", updated_mol)
|
|
93
|
+
viewer.update(scene)
|
|
94
|
+
|
|
95
|
+
time.sleep(0.033) # ~30 FPS
|
|
96
|
+
|
|
97
|
+
print("Press Any Key to exit...", end='', flush=True)
|
|
98
|
+
_ = input()
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Use cases:**
|
|
102
|
+
- Visualizing the *progress* of a simulation step-by-step
|
|
103
|
+
- Interactive experiments or streaming scenarios where frames are not known in advance
|
|
104
|
+
|
|
105
|
+
**Trade-offs:**
|
|
106
|
+
- ✅ Low memory usage — no need to preload frames
|
|
107
|
+
- ⚠️ Playback smoothness depends on computation / I/O speed → may stutter if frame generation is slow
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### 2. Preloaded Playback (One-Shot Animation) (Start from 0.1.3)
|
|
112
|
+
|
|
113
|
+
Load all frames into memory first, then play them back smoothly:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from cosmol_viewer import Scene, Viewer, parse_sdf, Molecules
|
|
117
|
+
|
|
118
|
+
frames = []
|
|
119
|
+
interval = 0.033 # ~30 FPS
|
|
120
|
+
|
|
121
|
+
# Preload all frames
|
|
122
|
+
for i in range(1, 10):
|
|
123
|
+
with open(f"frames/frame_{i}.sdf", "r") as f:
|
|
124
|
+
sdf = f.read()
|
|
125
|
+
mol = Molecules(parse_sdf(sdf)).centered()
|
|
126
|
+
|
|
127
|
+
scene = Scene()
|
|
128
|
+
scene.scale(0.1)
|
|
129
|
+
scene.add_shape(mol, "mol")
|
|
130
|
+
frames.append(scene)
|
|
131
|
+
|
|
132
|
+
# Playback once
|
|
133
|
+
Viewer.play(frames, interval=interval, loops=1, width=600, height=400)
|
|
134
|
+
|
|
135
|
+
print("Press Any Key to exit...", end='', flush=True)
|
|
136
|
+
_ = input()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Use cases:**
|
|
140
|
+
- Smooth, stable playback for presentations or teaching
|
|
141
|
+
- Demonstrating precomputed trajectories (e.g., molecular dynamics snapshots)
|
|
142
|
+
|
|
143
|
+
**Trade-offs:**
|
|
144
|
+
- ✅ Very smooth playback, independent of computation speed
|
|
145
|
+
- ⚠️ Requires preloading all frames → higher memory usage
|
|
146
|
+
- ⚠️ Longer initial load time for large trajectories
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Choosing the Right Mode
|
|
151
|
+
|
|
152
|
+
- ✅ Use **real-time updates** if your frames are generated on-the-fly or memory is limited
|
|
153
|
+
- ✅ Use **preloaded playback** if you want guaranteed smooth animations and can preload your trajectory
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Exiting the Viewer
|
|
158
|
+
|
|
159
|
+
> **Important:** The viewer is bound to the Python process.
|
|
160
|
+
> When your script finishes, the rendering window will close automatically.
|
|
161
|
+
|
|
162
|
+
To keep the visualization alive until you are ready to exit, always add:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
print("Press Any Key to exit...", end='', flush=True)
|
|
166
|
+
_ = input()
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
This ensures:
|
|
170
|
+
- The window stays open for inspection
|
|
171
|
+
- The user decides when to end visualization
|
|
172
|
+
- Prevents premature termination at the end of the script
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Documentation
|
|
177
|
+
|
|
178
|
+
For API reference and advanced usage, please see the [latest documentation](https://cosmol-repl.github.io/COSMol-viewer).
|
|
179
|
+
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# COSMol-viewer
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
<a href="https://crates.io/crates/cosmol_viewer">
|
|
5
|
+
<img src="https://img.shields.io/crates/v/cosmol_viewer.svg" alt="crates.io Latest Release"/>
|
|
6
|
+
</a>
|
|
7
|
+
<a href="https://pypi.org/project/cosmol_viewer/">
|
|
8
|
+
<img src="https://img.shields.io/pypi/v/cosmol_viewer.svg" alt="PyPi Latest Release"/>
|
|
9
|
+
</a>
|
|
10
|
+
<a href="https://cosmol-repl.github.io/COSMol-viewer">
|
|
11
|
+
<img src="https://img.shields.io/badge/docs-latest-blue.svg" alt="Documentation Status"/>
|
|
12
|
+
</a>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
**COSMol-viewer** is a high-performance molecular visualization library, written in **Rust** and powered by **WebGPU**, designed for seamless integration into **Python** workflows.
|
|
16
|
+
|
|
17
|
+
- ⚡ **High-speed rendering** — GPU-accelerated performance at native speed
|
|
18
|
+
- 🧬 **Flexible input** — Load structures from `.sdf`, `.pdb`, or dynamically generated coordinates
|
|
19
|
+
- 📓 **Notebook-ready** — Fully compatible with Jupyter and Google Colab, ideal for teaching, research, and interactive demos
|
|
20
|
+
- 🔁 **Dynamic visualization** — Update molecular structures on-the-fly or play smooth preloaded animations
|
|
21
|
+
- 🎨 **Customizable** — Fine-grained control of rendering styles, camera, and scene parameters
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pip install cosmol-viewer
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from cosmol_viewer import Scene, Viewer, parse_sdf, Molecules
|
|
37
|
+
|
|
38
|
+
# === Step 1: Load and render a molecule ===
|
|
39
|
+
with open("molecule.sdf", "r") as f:
|
|
40
|
+
sdf = f.read()
|
|
41
|
+
mol = Molecules(parse_sdf(sdf)).centered()
|
|
42
|
+
|
|
43
|
+
scene = Scene()
|
|
44
|
+
scene.scale(0.1)
|
|
45
|
+
scene.add_shape(mol, "mol")
|
|
46
|
+
|
|
47
|
+
viewer = Viewer.render(scene, width=600, height=400) # Launch viewer
|
|
48
|
+
|
|
49
|
+
print("Press Any Key to exit...", end='', flush=True)
|
|
50
|
+
_ = input() # Keep the viewer open until you decide to close
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Animation Modes
|
|
56
|
+
|
|
57
|
+
COSMol-viewer supports **two complementary animation workflows**, depending on whether you prefer **real-time updates** or **preloaded playback**.
|
|
58
|
+
|
|
59
|
+
### 1. Real-Time Updates (Frame-by-Frame Streaming)
|
|
60
|
+
|
|
61
|
+
Update the molecule directly inside an existing scene:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import time
|
|
65
|
+
from cosmol_viewer import Scene, Viewer, parse_sdf, Molecules
|
|
66
|
+
|
|
67
|
+
scene = Scene()
|
|
68
|
+
scene.scale(0.1)
|
|
69
|
+
|
|
70
|
+
# Initial load
|
|
71
|
+
with open("frames/frame_1.sdf", "r") as f:
|
|
72
|
+
sdf = f.read()
|
|
73
|
+
mol = Molecules(parse_sdf(sdf)).centered()
|
|
74
|
+
scene.add_shape(mol, "mol")
|
|
75
|
+
|
|
76
|
+
viewer = Viewer.render(scene, width=600, height=400)
|
|
77
|
+
|
|
78
|
+
# Update in real time
|
|
79
|
+
for i in range(2, 10):
|
|
80
|
+
with open(f"frames/frame_{i}.sdf", "r") as f:
|
|
81
|
+
sdf = f.read()
|
|
82
|
+
updated_mol = Molecules(parse_sdf(sdf)).centered()
|
|
83
|
+
|
|
84
|
+
scene.update_shape("mol", updated_mol)
|
|
85
|
+
viewer.update(scene)
|
|
86
|
+
|
|
87
|
+
time.sleep(0.033) # ~30 FPS
|
|
88
|
+
|
|
89
|
+
print("Press Any Key to exit...", end='', flush=True)
|
|
90
|
+
_ = input()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Use cases:**
|
|
94
|
+
- Visualizing the *progress* of a simulation step-by-step
|
|
95
|
+
- Interactive experiments or streaming scenarios where frames are not known in advance
|
|
96
|
+
|
|
97
|
+
**Trade-offs:**
|
|
98
|
+
- ✅ Low memory usage — no need to preload frames
|
|
99
|
+
- ⚠️ Playback smoothness depends on computation / I/O speed → may stutter if frame generation is slow
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
### 2. Preloaded Playback (One-Shot Animation) (Start from 0.1.3)
|
|
104
|
+
|
|
105
|
+
Load all frames into memory first, then play them back smoothly:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from cosmol_viewer import Scene, Viewer, parse_sdf, Molecules
|
|
109
|
+
|
|
110
|
+
frames = []
|
|
111
|
+
interval = 0.033 # ~30 FPS
|
|
112
|
+
|
|
113
|
+
# Preload all frames
|
|
114
|
+
for i in range(1, 10):
|
|
115
|
+
with open(f"frames/frame_{i}.sdf", "r") as f:
|
|
116
|
+
sdf = f.read()
|
|
117
|
+
mol = Molecules(parse_sdf(sdf)).centered()
|
|
118
|
+
|
|
119
|
+
scene = Scene()
|
|
120
|
+
scene.scale(0.1)
|
|
121
|
+
scene.add_shape(mol, "mol")
|
|
122
|
+
frames.append(scene)
|
|
123
|
+
|
|
124
|
+
# Playback once
|
|
125
|
+
Viewer.play(frames, interval=interval, loops=1, width=600, height=400)
|
|
126
|
+
|
|
127
|
+
print("Press Any Key to exit...", end='', flush=True)
|
|
128
|
+
_ = input()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Use cases:**
|
|
132
|
+
- Smooth, stable playback for presentations or teaching
|
|
133
|
+
- Demonstrating precomputed trajectories (e.g., molecular dynamics snapshots)
|
|
134
|
+
|
|
135
|
+
**Trade-offs:**
|
|
136
|
+
- ✅ Very smooth playback, independent of computation speed
|
|
137
|
+
- ⚠️ Requires preloading all frames → higher memory usage
|
|
138
|
+
- ⚠️ Longer initial load time for large trajectories
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Choosing the Right Mode
|
|
143
|
+
|
|
144
|
+
- ✅ Use **real-time updates** if your frames are generated on-the-fly or memory is limited
|
|
145
|
+
- ✅ Use **preloaded playback** if you want guaranteed smooth animations and can preload your trajectory
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Exiting the Viewer
|
|
150
|
+
|
|
151
|
+
> **Important:** The viewer is bound to the Python process.
|
|
152
|
+
> When your script finishes, the rendering window will close automatically.
|
|
153
|
+
|
|
154
|
+
To keep the visualization alive until you are ready to exit, always add:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
print("Press Any Key to exit...", end='', flush=True)
|
|
158
|
+
_ = input()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This ensures:
|
|
162
|
+
- The window stays open for inspection
|
|
163
|
+
- The user decides when to end visualization
|
|
164
|
+
- Prevents premature termination at the end of the script
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Documentation
|
|
169
|
+
|
|
170
|
+
For API reference and advanced usage, please see the [latest documentation](https://cosmol-repl.github.io/COSMol-viewer).
|
|
@@ -38,6 +38,23 @@ pub struct App {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
impl App {
|
|
41
|
+
pub fn play(
|
|
42
|
+
cc: &eframe::CreationContext<'_>,
|
|
43
|
+
frames: Vec<Scene>,
|
|
44
|
+
interval: f32,
|
|
45
|
+
loop_: bool,
|
|
46
|
+
) -> Self {
|
|
47
|
+
let gl = cc.gl.clone();
|
|
48
|
+
let canvas = Canvas::new(gl.as_ref().unwrap().clone(), frames[0].clone()).unwrap();
|
|
49
|
+
App {
|
|
50
|
+
gl,
|
|
51
|
+
canvas,
|
|
52
|
+
ctx: cc.egui_ctx.clone(),
|
|
53
|
+
screenshot_requested: false,
|
|
54
|
+
screenshot_result: None,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
pub fn new(cc: &eframe::CreationContext<'_>, scene: Scene) -> Self {
|
|
42
59
|
let gl = cc.gl.clone();
|
|
43
60
|
let canvas = Canvas::new(gl.as_ref().unwrap().clone(), scene).unwrap();
|
|
@@ -130,7 +147,7 @@ pub struct NativeGuiViewer {
|
|
|
130
147
|
}
|
|
131
148
|
|
|
132
149
|
impl NativeGuiViewer {
|
|
133
|
-
pub fn render(scene: &Scene) -> Self {
|
|
150
|
+
pub fn render(scene: &Scene, width: f32, height: f32) -> Self {
|
|
134
151
|
use std::{
|
|
135
152
|
sync::{Arc, Mutex},
|
|
136
153
|
thread,
|
|
@@ -142,7 +159,7 @@ impl NativeGuiViewer {
|
|
|
142
159
|
egui::{Vec2, ViewportBuilder},
|
|
143
160
|
};
|
|
144
161
|
|
|
145
|
-
let viewport_size = scene.viewport.unwrap_or([800, 500]);
|
|
162
|
+
// let viewport_size = scene.viewport.unwrap_or([800, 500]);
|
|
146
163
|
|
|
147
164
|
let app: Arc<Mutex<Option<App>>> = Arc::new(Mutex::new(None));
|
|
148
165
|
let app_clone = Arc::clone(&app);
|
|
@@ -173,9 +190,9 @@ impl NativeGuiViewer {
|
|
|
173
190
|
}));
|
|
174
191
|
|
|
175
192
|
let native_options = NativeOptions {
|
|
176
|
-
viewport: ViewportBuilder::default()
|
|
177
|
-
.with_inner_size(Vec2::new(viewport_size[0] as f32, viewport_size[1] as f32)),
|
|
193
|
+
viewport: ViewportBuilder::default().with_inner_size(Vec2::new(width, height)),
|
|
178
194
|
depth_buffer: 24,
|
|
195
|
+
multisampling: 4,
|
|
179
196
|
event_loop_builder,
|
|
180
197
|
..Default::default()
|
|
181
198
|
};
|
|
@@ -193,7 +210,7 @@ impl NativeGuiViewer {
|
|
|
193
210
|
});
|
|
194
211
|
|
|
195
212
|
// 等待 App 初始化完成
|
|
196
|
-
let timeout_ms =
|
|
213
|
+
let timeout_ms = 30000;
|
|
197
214
|
let mut waited = 0;
|
|
198
215
|
|
|
199
216
|
loop {
|
|
@@ -244,4 +261,100 @@ impl NativeGuiViewer {
|
|
|
244
261
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
245
262
|
}
|
|
246
263
|
}
|
|
264
|
+
|
|
265
|
+
pub fn play(frames: Vec<Scene>, interval: f32, loops: i64, width: f32, height: f32) {
|
|
266
|
+
use std::{
|
|
267
|
+
sync::{Arc, Mutex},
|
|
268
|
+
thread,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
272
|
+
use eframe::{
|
|
273
|
+
NativeOptions,
|
|
274
|
+
egui::{Vec2, ViewportBuilder},
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
let app: Arc<Mutex<Option<App>>> = Arc::new(Mutex::new(None));
|
|
278
|
+
let app_clone = Arc::clone(&app);
|
|
279
|
+
|
|
280
|
+
let scene_init = frames[0].clone();
|
|
281
|
+
|
|
282
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
283
|
+
thread::spawn(move || {
|
|
284
|
+
use std::process;
|
|
285
|
+
|
|
286
|
+
use eframe::{EventLoopBuilderHook, run_native};
|
|
287
|
+
let event_loop_builder: Option<EventLoopBuilderHook> =
|
|
288
|
+
Some(Box::new(|event_loop_builder| {
|
|
289
|
+
#[cfg(target_family = "windows")]
|
|
290
|
+
{
|
|
291
|
+
use egui_winit::winit::platform::windows::EventLoopBuilderExtWindows;
|
|
292
|
+
event_loop_builder.with_any_thread(true);
|
|
293
|
+
}
|
|
294
|
+
#[cfg(feature = "wayland")]
|
|
295
|
+
{
|
|
296
|
+
use egui_winit::winit::platform::wayland::EventLoopBuilderExtWayland;
|
|
297
|
+
event_loop_builder.with_any_thread(true);
|
|
298
|
+
}
|
|
299
|
+
#[cfg(feature = "x11")]
|
|
300
|
+
{
|
|
301
|
+
use egui_winit::winit::platform::x11::EventLoopBuilderExtX11;
|
|
302
|
+
event_loop_builder.with_any_thread(true);
|
|
303
|
+
}
|
|
304
|
+
}));
|
|
305
|
+
|
|
306
|
+
let native_options = NativeOptions {
|
|
307
|
+
viewport: ViewportBuilder::default().with_inner_size(Vec2::new(width, height)),
|
|
308
|
+
depth_buffer: 24,
|
|
309
|
+
event_loop_builder,
|
|
310
|
+
..Default::default()
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
let _ = run_native(
|
|
314
|
+
"cosmol_viewer",
|
|
315
|
+
native_options,
|
|
316
|
+
Box::new(move |cc| {
|
|
317
|
+
let mut guard = app_clone.lock().unwrap();
|
|
318
|
+
*guard = Some(App::new(cc, scene_init));
|
|
319
|
+
Ok(Box::new(AppWrapper(app_clone.clone())))
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
322
|
+
process::exit(0);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// 等待 App 初始化完成
|
|
326
|
+
let timeout_ms = 30000;
|
|
327
|
+
let mut waited = 0;
|
|
328
|
+
|
|
329
|
+
loop {
|
|
330
|
+
if app.lock().unwrap().is_some() {
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
if waited > timeout_ms {
|
|
334
|
+
panic!("Fail to initialize App");
|
|
335
|
+
}
|
|
336
|
+
thread::sleep(Duration::from_millis(10));
|
|
337
|
+
waited += 10;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
let mut count = 0;
|
|
341
|
+
loop {
|
|
342
|
+
if loops >= 0 && count >= loops {
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
count += 1;
|
|
346
|
+
for frame in &frames {
|
|
347
|
+
{
|
|
348
|
+
let mut guard = app.lock().unwrap();
|
|
349
|
+
if let Some(app) = &mut *guard {
|
|
350
|
+
app.update_scene(frame.clone());
|
|
351
|
+
app.ctx.request_repaint();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
thread::sleep(Duration::from_secs_f32(interval));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Self { app }
|
|
359
|
+
}
|
|
247
360
|
}
|