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.

Files changed (34) hide show
  1. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/Cargo.lock +42 -19
  2. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/Cargo.toml +3 -3
  3. cosmol_viewer-0.1.3.dev2/PKG-INFO +179 -0
  4. cosmol_viewer-0.1.3.dev2/README.md +170 -0
  5. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/lib.rs +118 -5
  6. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/scene.rs +3 -3
  7. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/canvas.rs +2 -1
  8. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shapes/sphere.rs +0 -5
  9. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/python/Cargo.toml +1 -0
  10. cosmol_viewer-0.1.3.dev2/crates/python/README.md +170 -0
  11. cosmol_viewer-0.1.3.dev2/crates/python/cosmol_viewer.pyi +315 -0
  12. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/python/src/lib.rs +48 -167
  13. cosmol_viewer-0.1.3.dev2/crates/python/src/shapes.rs +146 -0
  14. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/wasm/Cargo.toml +3 -2
  15. cosmol_viewer-0.1.3.dev2/crates/wasm/src/lib.rs +424 -0
  16. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/pyproject.toml +2 -1
  17. cosmol_viewer-0.1.2.dev5/PKG-INFO +0 -70
  18. cosmol_viewer-0.1.2.dev5/crates/python/README.md +0 -62
  19. cosmol_viewer-0.1.2.dev5/crates/python/src/shapes.rs +0 -287
  20. cosmol_viewer-0.1.2.dev5/crates/wasm/src/lib.rs +0 -257
  21. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/Cargo.toml +0 -0
  22. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/parser/mod.rs +0 -0
  23. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/parser/sdf.rs +0 -0
  24. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/bg_fragment.glsl +0 -0
  25. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/bg_vertex.glsl +0 -0
  26. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/fragment.glsl +0 -0
  27. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/mod.rs +0 -0
  28. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shader/vertex.glsl +0 -0
  29. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shapes/mod.rs +0 -0
  30. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shapes/molecules.rs +0 -0
  31. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/shapes/stick.rs +0 -0
  32. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/core/src/utils.rs +0 -0
  33. {cosmol_viewer-0.1.2.dev5 → cosmol_viewer-0.1.3.dev2}/crates/python/build.rs +0 -0
  34. {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.2-nightly.5"
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.2-nightly.5"
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.2-nightly.5"
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.77"
1749
+ version = "0.3.81"
1728
1750
  source = "registry+https://github.com/rust-lang/crates.io-index"
1729
- checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
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.100"
3504
+ version = "0.2.104"
3483
3505
  source = "registry+https://github.com/rust-lang/crates.io-index"
3484
- checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
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.100"
3517
+ version = "0.2.104"
3495
3518
  source = "registry+https://github.com/rust-lang/crates.io-index"
3496
- checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
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.50"
3531
+ version = "0.4.54"
3509
3532
  source = "registry+https://github.com/rust-lang/crates.io-index"
3510
- checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
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.100"
3544
+ version = "0.2.104"
3522
3545
  source = "registry+https://github.com/rust-lang/crates.io-index"
3523
- checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
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.100"
3554
+ version = "0.2.104"
3532
3555
  source = "registry+https://github.com/rust-lang/crates.io-index"
3533
- checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
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.100"
3567
+ version = "0.2.104"
3545
3568
  source = "registry+https://github.com/rust-lang/crates.io-index"
3546
- checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
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.77"
3685
+ version = "0.3.81"
3663
3686
  source = "registry+https://github.com/rust-lang/crates.io-index"
3664
- checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
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.2-nightly.5"
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.2-nightly.5", path = "cosmol_viewer"}
17
- cosmol_viewer_core = { version = "0.1.2-nightly.5", path = "crates/core" }
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 = 3000;
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
  }