cosmol-viewer 0.1.0__tar.gz → 0.1.1__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.0 → cosmol_viewer-0.1.1}/Cargo.lock +143 -12
- cosmol_viewer-0.1.1/Cargo.toml +22 -0
- cosmol_viewer-0.1.1/PKG-INFO +57 -0
- cosmol_viewer-0.1.1/crates/core/Cargo.toml +17 -0
- cosmol_viewer-0.1.1/crates/core/src/lib.rs +72 -0
- cosmol_viewer-0.1.1/crates/core/src/parser/mod.rs +2 -0
- cosmol_viewer-0.1.1/crates/core/src/parser/sdf.rs +254 -0
- cosmol_viewer-0.1.1/crates/core/src/scene.rs +63 -0
- cosmol_viewer-0.1.0/cosmol_viewer_core/src/shader/app.rs → cosmol_viewer-0.1.1/crates/core/src/shader/canvas.rs +127 -86
- cosmol_viewer-0.1.1/crates/core/src/shader/fragment.glsl +34 -0
- cosmol_viewer-0.1.1/crates/core/src/shader/mod.rs +3 -0
- {cosmol_viewer-0.1.0/cosmol_viewer_core → cosmol_viewer-0.1.1/crates/core}/src/shader/vertex.glsl +1 -1
- cosmol_viewer-0.1.1/crates/core/src/shapes/mod.rs +3 -0
- cosmol_viewer-0.1.1/crates/core/src/shapes/molecules.rs +273 -0
- cosmol_viewer-0.1.1/crates/core/src/shapes/sphere.rs +246 -0
- cosmol_viewer-0.1.1/crates/core/src/shapes/stick.rs +152 -0
- cosmol_viewer-0.1.1/crates/core/src/utils.rs +85 -0
- {cosmol_viewer-0.1.0/cosmol_viewer_python → cosmol_viewer-0.1.1/crates/python}/Cargo.toml +6 -3
- cosmol_viewer-0.1.1/crates/python/README.md +49 -0
- cosmol_viewer-0.1.1/crates/python/build.rs +22 -0
- cosmol_viewer-0.1.1/crates/python/src/lib.rs +354 -0
- cosmol_viewer-0.1.1/crates/python/src/parser.rs +24 -0
- cosmol_viewer-0.1.1/crates/python/src/shapes.rs +102 -0
- cosmol_viewer-0.1.1/pyproject.toml +16 -0
- cosmol_viewer-0.1.0/Cargo.toml +0 -15
- cosmol_viewer-0.1.0/PKG-INFO +0 -5
- cosmol_viewer-0.1.0/cosmol_viewer_core/Cargo.toml +0 -11
- cosmol_viewer-0.1.0/cosmol_viewer_core/src/lib.rs +0 -113
- cosmol_viewer-0.1.0/cosmol_viewer_core/src/shader/fragment.glsl +0 -30
- cosmol_viewer-0.1.0/cosmol_viewer_core/src/shader/mod.rs +0 -3
- cosmol_viewer-0.1.0/cosmol_viewer_core/src/utils.rs +0 -167
- cosmol_viewer-0.1.0/cosmol_viewer_python/build.rs +0 -22
- cosmol_viewer-0.1.0/cosmol_viewer_python/src/lib.rs +0 -150
- cosmol_viewer-0.1.0/pyproject.toml +0 -14
- {cosmol_viewer-0.1.0/cosmol_viewer_core → cosmol_viewer-0.1.1/crates/core}/src/shader/bg_fragment.glsl +0 -0
- {cosmol_viewer-0.1.0/cosmol_viewer_core → cosmol_viewer-0.1.1/crates/core}/src/shader/bg_vertex.glsl +0 -0
|
@@ -431,6 +431,15 @@ version = "0.22.1"
|
|
|
431
431
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
432
432
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
|
433
433
|
|
|
434
|
+
[[package]]
|
|
435
|
+
name = "bincode"
|
|
436
|
+
version = "1.3.3"
|
|
437
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
438
|
+
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
|
439
|
+
dependencies = [
|
|
440
|
+
"serde",
|
|
441
|
+
]
|
|
442
|
+
|
|
434
443
|
[[package]]
|
|
435
444
|
name = "bit-set"
|
|
436
445
|
version = "0.8.0"
|
|
@@ -688,40 +697,78 @@ dependencies = [
|
|
|
688
697
|
"libc",
|
|
689
698
|
]
|
|
690
699
|
|
|
700
|
+
[[package]]
|
|
701
|
+
name = "cosmol_viewer"
|
|
702
|
+
version = "0.1.1"
|
|
703
|
+
dependencies = [
|
|
704
|
+
"bytemuck",
|
|
705
|
+
"cosmol_viewer_core",
|
|
706
|
+
"eframe",
|
|
707
|
+
"egui_extras",
|
|
708
|
+
"hex",
|
|
709
|
+
"ipc-channel",
|
|
710
|
+
"serde",
|
|
711
|
+
"serde_json",
|
|
712
|
+
"sha2",
|
|
713
|
+
"wasm-bindgen-futures",
|
|
714
|
+
"web-sys",
|
|
715
|
+
]
|
|
716
|
+
|
|
691
717
|
[[package]]
|
|
692
718
|
name = "cosmol_viewer_core"
|
|
693
|
-
version = "0.1.
|
|
719
|
+
version = "0.1.1"
|
|
694
720
|
dependencies = [
|
|
695
721
|
"bytemuck",
|
|
696
722
|
"eframe",
|
|
697
723
|
"egui_extras",
|
|
698
724
|
"glam",
|
|
725
|
+
"once_cell",
|
|
726
|
+
"serde",
|
|
727
|
+
"serde_json",
|
|
728
|
+
"serde_repr",
|
|
729
|
+
"wasm-bindgen-futures",
|
|
730
|
+
"web-sys",
|
|
731
|
+
]
|
|
732
|
+
|
|
733
|
+
[[package]]
|
|
734
|
+
name = "cosmol_viewer_gui"
|
|
735
|
+
version = "0.1.1"
|
|
736
|
+
dependencies = [
|
|
737
|
+
"bytemuck",
|
|
738
|
+
"cosmol_viewer_core",
|
|
739
|
+
"eframe",
|
|
740
|
+
"egui_extras",
|
|
741
|
+
"ipc-channel",
|
|
699
742
|
"serde",
|
|
743
|
+
"serde_json",
|
|
744
|
+
"wasm-bindgen-futures",
|
|
745
|
+
"web-sys",
|
|
700
746
|
]
|
|
701
747
|
|
|
702
748
|
[[package]]
|
|
703
749
|
name = "cosmol_viewer_python"
|
|
704
|
-
version = "0.
|
|
750
|
+
version = "0.0.0"
|
|
705
751
|
dependencies = [
|
|
706
752
|
"base64 0.22.1",
|
|
707
753
|
"cosmol_viewer_core",
|
|
708
754
|
"eframe",
|
|
709
755
|
"egui_extras",
|
|
710
|
-
"
|
|
756
|
+
"hex",
|
|
757
|
+
"ipc-channel",
|
|
711
758
|
"pyo3",
|
|
712
759
|
"serde_json",
|
|
760
|
+
"sha2",
|
|
713
761
|
"uuid",
|
|
714
762
|
"wasm-bindgen",
|
|
715
763
|
]
|
|
716
764
|
|
|
717
765
|
[[package]]
|
|
718
766
|
name = "cosmol_viewer_wasm"
|
|
719
|
-
version = "0.1.
|
|
767
|
+
version = "0.1.1"
|
|
720
768
|
dependencies = [
|
|
721
769
|
"cosmol_viewer_core",
|
|
722
770
|
"eframe",
|
|
723
771
|
"egui_extras",
|
|
724
|
-
"glam",
|
|
725
772
|
"log",
|
|
726
773
|
"serde_json",
|
|
727
774
|
"wasm-bindgen",
|
|
@@ -747,6 +794,15 @@ dependencies = [
|
|
|
747
794
|
"cfg-if",
|
|
748
795
|
]
|
|
749
796
|
|
|
797
|
+
[[package]]
|
|
798
|
+
name = "crossbeam-channel"
|
|
799
|
+
version = "0.5.15"
|
|
800
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
801
|
+
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
|
802
|
+
dependencies = [
|
|
803
|
+
"crossbeam-utils",
|
|
804
|
+
]
|
|
805
|
+
|
|
750
806
|
[[package]]
|
|
751
807
|
name = "crossbeam-utils"
|
|
752
808
|
version = "0.8.21"
|
|
@@ -1132,6 +1188,12 @@ version = "0.9.0"
|
|
|
1132
1188
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1133
1189
|
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
|
1134
1190
|
|
|
1191
|
+
[[package]]
|
|
1192
|
+
name = "fnv"
|
|
1193
|
+
version = "1.0.7"
|
|
1194
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1195
|
+
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|
1196
|
+
|
|
1135
1197
|
[[package]]
|
|
1136
1198
|
name = "foldhash"
|
|
1137
1199
|
version = "0.1.5"
|
|
@@ -1603,6 +1665,24 @@ version = "2.0.6"
|
|
|
1603
1665
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1604
1666
|
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
|
1605
1667
|
|
|
1668
|
+
[[package]]
|
|
1669
|
+
name = "ipc-channel"
|
|
1670
|
+
version = "0.20.0"
|
|
1671
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1672
|
+
checksum = "5b1c98b70019c830a1fc39cecfe1f60ff99c4122f0a189697c810c90ec545c14"
|
|
1673
|
+
dependencies = [
|
|
1674
|
+
"bincode",
|
|
1675
|
+
"crossbeam-channel",
|
|
1676
|
+
"fnv",
|
|
1677
|
+
"libc",
|
|
1678
|
+
"mio",
|
|
1679
|
+
"rand 0.9.1",
|
|
1680
|
+
"serde",
|
|
1681
|
+
"tempfile",
|
|
1682
|
+
"uuid",
|
|
1683
|
+
"windows",
|
|
1684
|
+
]
|
|
1685
|
+
|
|
1606
1686
|
[[package]]
|
|
1607
1687
|
name = "itoa"
|
|
1608
1688
|
version = "1.0.15"
|
|
@@ -1826,6 +1906,17 @@ dependencies = [
|
|
|
1826
1906
|
"simd-adler32",
|
|
1827
1907
|
]
|
|
1828
1908
|
|
|
1909
|
+
[[package]]
|
|
1910
|
+
name = "mio"
|
|
1911
|
+
version = "1.0.4"
|
|
1912
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1913
|
+
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
|
1914
|
+
dependencies = [
|
|
1915
|
+
"libc",
|
|
1916
|
+
"wasi 0.11.1+wasi-snapshot-preview1",
|
|
1917
|
+
"windows-sys 0.59.0",
|
|
1918
|
+
]
|
|
1919
|
+
|
|
1829
1920
|
[[package]]
|
|
1830
1921
|
name = "naga"
|
|
1831
1922
|
version = "24.0.0"
|
|
@@ -2318,7 +2409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
2318
2409
|
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
|
2319
2410
|
dependencies = [
|
|
2320
2411
|
"phf_shared",
|
|
2321
|
-
"rand",
|
|
2412
|
+
"rand 0.8.5",
|
|
2322
2413
|
]
|
|
2323
2414
|
|
|
2324
2415
|
[[package]]
|
|
@@ -2579,8 +2670,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
2579
2670
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
|
2580
2671
|
dependencies = [
|
|
2581
2672
|
"libc",
|
|
2582
|
-
"rand_chacha",
|
|
2583
|
-
"rand_core",
|
|
2673
|
+
"rand_chacha 0.3.1",
|
|
2674
|
+
"rand_core 0.6.4",
|
|
2675
|
+
]
|
|
2676
|
+
|
|
2677
|
+
[[package]]
|
|
2678
|
+
name = "rand"
|
|
2679
|
+
version = "0.9.1"
|
|
2680
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2681
|
+
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
|
2682
|
+
dependencies = [
|
|
2683
|
+
"rand_chacha 0.9.0",
|
|
2684
|
+
"rand_core 0.9.3",
|
|
2584
2685
|
]
|
|
2585
2686
|
|
|
2586
2687
|
[[package]]
|
|
@@ -2590,7 +2691,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
2590
2691
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
|
2591
2692
|
dependencies = [
|
|
2592
2693
|
"ppv-lite86",
|
|
2593
|
-
"rand_core",
|
|
2694
|
+
"rand_core 0.6.4",
|
|
2695
|
+
]
|
|
2696
|
+
|
|
2697
|
+
[[package]]
|
|
2698
|
+
name = "rand_chacha"
|
|
2699
|
+
version = "0.9.0"
|
|
2700
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2701
|
+
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
|
2702
|
+
dependencies = [
|
|
2703
|
+
"ppv-lite86",
|
|
2704
|
+
"rand_core 0.9.3",
|
|
2594
2705
|
]
|
|
2595
2706
|
|
|
2596
2707
|
[[package]]
|
|
@@ -2602,6 +2713,15 @@ dependencies = [
|
|
|
2602
2713
|
"getrandom 0.2.16",
|
|
2603
2714
|
]
|
|
2604
2715
|
|
|
2716
|
+
[[package]]
|
|
2717
|
+
name = "rand_core"
|
|
2718
|
+
version = "0.9.3"
|
|
2719
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2720
|
+
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
|
2721
|
+
dependencies = [
|
|
2722
|
+
"getrandom 0.3.3",
|
|
2723
|
+
]
|
|
2724
|
+
|
|
2605
2725
|
[[package]]
|
|
2606
2726
|
name = "raw-window-handle"
|
|
2607
2727
|
version = "0.6.2"
|
|
@@ -2805,6 +2925,17 @@ dependencies = [
|
|
|
2805
2925
|
"digest",
|
|
2806
2926
|
]
|
|
2807
2927
|
|
|
2928
|
+
[[package]]
|
|
2929
|
+
name = "sha2"
|
|
2930
|
+
version = "0.10.9"
|
|
2931
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2932
|
+
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
|
2933
|
+
dependencies = [
|
|
2934
|
+
"cfg-if",
|
|
2935
|
+
"cpufeatures",
|
|
2936
|
+
"digest",
|
|
2937
|
+
]
|
|
2938
|
+
|
|
2808
2939
|
[[package]]
|
|
2809
2940
|
name = "shlex"
|
|
2810
2941
|
version = "1.3.0"
|
|
@@ -3027,12 +3158,12 @@ dependencies = [
|
|
|
3027
3158
|
|
|
3028
3159
|
[[package]]
|
|
3029
3160
|
name = "test"
|
|
3030
|
-
version = "0.1.
|
|
3161
|
+
version = "0.1.1"
|
|
3031
3162
|
dependencies = [
|
|
3163
|
+
"cosmol_viewer",
|
|
3032
3164
|
"cosmol_viewer_core",
|
|
3033
3165
|
"eframe",
|
|
3034
3166
|
"egui_extras",
|
|
3035
|
-
"glam",
|
|
3036
3167
|
]
|
|
3037
3168
|
|
|
3038
3169
|
[[package]]
|
|
@@ -4242,7 +4373,7 @@ dependencies = [
|
|
|
4242
4373
|
"hex",
|
|
4243
4374
|
"nix",
|
|
4244
4375
|
"ordered-stream",
|
|
4245
|
-
"rand",
|
|
4376
|
+
"rand 0.8.5",
|
|
4246
4377
|
"serde",
|
|
4247
4378
|
"serde_repr",
|
|
4248
4379
|
"sha1",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[workspace.package]
|
|
2
|
+
edition = "2024"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
authors = ["9028 wjt@cosmol.org"]
|
|
5
|
+
repository = "https://github.com/COSMol-repl/COSMol-viewer"
|
|
6
|
+
homepage = "https://github.com/COSMol-repl/COSMol-viewer"
|
|
7
|
+
keywords = ["molecular", "visualization"]
|
|
8
|
+
|
|
9
|
+
[workspace]
|
|
10
|
+
resolver = "2"
|
|
11
|
+
members = ["crates/python"]
|
|
12
|
+
|
|
13
|
+
[workspace.dependencies]
|
|
14
|
+
cosmol_viewer = {path = "cosmol_viewer"}
|
|
15
|
+
cosmol_viewer_core = { path = "crates/core" }
|
|
16
|
+
eframe = { version = "0.31.1"}
|
|
17
|
+
egui_extras = { version = "0.31.1", features = ["svg"] }
|
|
18
|
+
serde = { version = "1.0.219" , features = ["derive"] }
|
|
19
|
+
serde_json = "1.0.140"
|
|
20
|
+
sha2 = "0.10.9"
|
|
21
|
+
hex = "0.4.3"
|
|
22
|
+
ipc-channel = "0.20.0"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cosmol-viewer
|
|
3
|
+
Version: 0.1.1
|
|
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
|
+
A high-performance molecular visualization library built with Rust and WebGPU, designed for seamless integration into Python workflows.
|
|
12
|
+
|
|
13
|
+
- ⚡ Fast: Native-speed rendering powered by Rust and GPU acceleration
|
|
14
|
+
|
|
15
|
+
- 🧬 Flexible: Load molecules from .sdf, .pdb, and dynamically update 3D structures
|
|
16
|
+
|
|
17
|
+
- 📓 Notebook-friendly: Fully supports Jupyter and Google Colab — ideal for education, research, and live demos
|
|
18
|
+
|
|
19
|
+
- 🔁 Real-time updates: Update molecular coordinates on-the-fly for simulations or animations
|
|
20
|
+
|
|
21
|
+
- 🎨 Customizable: Control styles, camera, and rendering settings programmatically
|
|
22
|
+
|
|
23
|
+
# Installation
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pip install cosmol-viewer==0.1.1
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
# Usage
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from cosmol_viewer import Scene, Viewer, parse_sdf, Molecules
|
|
33
|
+
|
|
34
|
+
# === Step 1: Load and render a molecule ===
|
|
35
|
+
with open("molecule.sdf", "r") as f:
|
|
36
|
+
sdf = f.read()
|
|
37
|
+
mol = Molecules(parse_sdf(sdf)).centered()
|
|
38
|
+
|
|
39
|
+
scene = Scene()
|
|
40
|
+
scene.scale(0.1)
|
|
41
|
+
scene.add_shape(mol, "mol")
|
|
42
|
+
|
|
43
|
+
viewer = Viewer.render(scene) # Launch the viewer
|
|
44
|
+
|
|
45
|
+
# === Step 2: Update the same molecule dynamically ===
|
|
46
|
+
import time
|
|
47
|
+
|
|
48
|
+
for i in range(1, 10): # Simulate multiple frames
|
|
49
|
+
with open(f"frames/frame_{i}.sdf", "r") as f:
|
|
50
|
+
sdf = f.read()
|
|
51
|
+
updated_mol = Molecules(parse_sdf(sdf)).centered()
|
|
52
|
+
|
|
53
|
+
scene.update_shape("mol", updated_mol)
|
|
54
|
+
viewer.update(scene)
|
|
55
|
+
|
|
56
|
+
time.sleep(0.033) # ~30 FPS
|
|
57
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "cosmol_viewer_core"
|
|
3
|
+
version.workspace = true
|
|
4
|
+
edition = "2024"
|
|
5
|
+
publish = false
|
|
6
|
+
|
|
7
|
+
[dependencies]
|
|
8
|
+
eframe.workspace = true
|
|
9
|
+
serde_json.workspace = true
|
|
10
|
+
glam = { version = "0.30.3" , features = ["serde"] }
|
|
11
|
+
egui_extras.workspace = true
|
|
12
|
+
serde.workspace = true
|
|
13
|
+
bytemuck = "1.23.1"
|
|
14
|
+
web-sys = "0.3.77"
|
|
15
|
+
serde_repr = "0.1"
|
|
16
|
+
wasm-bindgen-futures = "0.4.50"
|
|
17
|
+
once_cell = "1.21.3"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
mod shader;
|
|
2
|
+
use std::{
|
|
3
|
+
sync::{Arc, Mutex},
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
pub mod utils;
|
|
7
|
+
pub mod parser;
|
|
8
|
+
pub use eframe::egui;
|
|
9
|
+
|
|
10
|
+
use eframe::egui::{Color32, Stroke};
|
|
11
|
+
|
|
12
|
+
use shader::Canvas;
|
|
13
|
+
|
|
14
|
+
pub use crate::utils::{Shape};
|
|
15
|
+
pub mod shapes;
|
|
16
|
+
use crate::{scene::Scene};
|
|
17
|
+
|
|
18
|
+
pub mod scene;
|
|
19
|
+
|
|
20
|
+
pub struct AppWrapper(pub Arc<Mutex<Option<App>>>);
|
|
21
|
+
|
|
22
|
+
impl eframe::App for AppWrapper {
|
|
23
|
+
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
|
24
|
+
if let Some(app) = &mut *self.0.lock().unwrap() {
|
|
25
|
+
app.update(ctx, frame);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub struct App {
|
|
31
|
+
canvas: Canvas,
|
|
32
|
+
gl: Option<Arc<eframe::glow::Context>>,
|
|
33
|
+
pub ctx: egui::Context,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl App {
|
|
37
|
+
pub fn new(cc: &eframe::CreationContext<'_>, scene: Scene) -> Self {
|
|
38
|
+
let gl = cc.gl.clone();
|
|
39
|
+
let canvas = Canvas::new(gl.as_ref().unwrap().clone(), scene).unwrap();
|
|
40
|
+
App {
|
|
41
|
+
gl,
|
|
42
|
+
canvas,
|
|
43
|
+
ctx: cc.egui_ctx.clone(),
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub fn update_scene(&mut self, scene: Scene) {
|
|
48
|
+
self.canvas.update_scene(scene);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl eframe::App for App {
|
|
53
|
+
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
|
54
|
+
egui_extras::install_image_loaders(ctx);
|
|
55
|
+
egui::CentralPanel::default()
|
|
56
|
+
.frame(
|
|
57
|
+
egui::Frame::default()
|
|
58
|
+
.fill(Color32::from_rgb(48, 48, 48))
|
|
59
|
+
.inner_margin(0.0)
|
|
60
|
+
.outer_margin(0.0)
|
|
61
|
+
.stroke(Stroke::new(0.0, Color32::from_rgb(30, 200, 30))),
|
|
62
|
+
)
|
|
63
|
+
.show(ctx, |ui| {
|
|
64
|
+
ui.set_width(ui.available_width());
|
|
65
|
+
ui.set_height(ui.available_height());
|
|
66
|
+
|
|
67
|
+
self.canvas.custom_painting(ui);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#[derive(Debug, Clone)]
|
|
2
|
+
pub struct Atom {
|
|
3
|
+
pub atom: String,
|
|
4
|
+
pub elem: String,
|
|
5
|
+
pub x: f32,
|
|
6
|
+
pub y: f32,
|
|
7
|
+
pub z: f32,
|
|
8
|
+
pub serial: usize,
|
|
9
|
+
pub index: usize,
|
|
10
|
+
pub hetflag: bool,
|
|
11
|
+
pub bonds: Vec<usize>,
|
|
12
|
+
pub bond_order: Vec<f32>,
|
|
13
|
+
pub properties: std::collections::HashMap<String, String>,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub type Molecule = Vec<Atom>;
|
|
17
|
+
pub type MoleculeData = Vec<Molecule>;
|
|
18
|
+
|
|
19
|
+
#[derive(Default)]
|
|
20
|
+
pub struct ParserOptions {
|
|
21
|
+
pub keep_h: bool,
|
|
22
|
+
pub multimodel: bool,
|
|
23
|
+
pub onemol: bool,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub fn parse_sdf(sdf: &str, options: &ParserOptions) -> MoleculeData {
|
|
27
|
+
let lines: Vec<&str> = sdf.lines().collect();
|
|
28
|
+
if lines.len() > 3 && lines[3].len() > 38 {
|
|
29
|
+
let version = lines[3][34..39].trim();
|
|
30
|
+
match version {
|
|
31
|
+
"V3000" => parse_v3000(lines, options),
|
|
32
|
+
_ => parse_v2000(lines, options),
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
vec![vec![]]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn parse_v2000(mut lines: Vec<&str>, options: &ParserOptions) -> MoleculeData {
|
|
40
|
+
let model_count = count_models(&lines);
|
|
41
|
+
// 多个分子但用户没开启
|
|
42
|
+
if model_count > 0 && !options.multimodel {
|
|
43
|
+
panic!(
|
|
44
|
+
"Found multiple molecules but 'multimodel' is false. Please enable 'multimodel = true' to parse all molecules."
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 用户开启了但其实只有一个
|
|
49
|
+
if model_count == 0 && options.multimodel {
|
|
50
|
+
panic!(
|
|
51
|
+
"Only one molecule found, but 'multimodel = true' was set. Consider setting 'multimodel = false' to avoid confusion."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let mut molecules = vec![vec![]];
|
|
56
|
+
let mut current = 0;
|
|
57
|
+
|
|
58
|
+
while lines.len() >= 4 {
|
|
59
|
+
let header = lines[3];
|
|
60
|
+
let atom_count = header[0..3].trim().parse::<usize>().unwrap_or(0);
|
|
61
|
+
let bond_count = header[3..6].trim().parse::<usize>().unwrap_or(0);
|
|
62
|
+
|
|
63
|
+
if atom_count == 0 || lines.len() < 4 + atom_count + bond_count {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let mut serial_to_index = vec![None; atom_count];
|
|
68
|
+
let mut offset = 4;
|
|
69
|
+
let start = molecules[current].len();
|
|
70
|
+
|
|
71
|
+
for i in 0..atom_count {
|
|
72
|
+
let line = lines[offset + i];
|
|
73
|
+
let elem = line[31..34].trim();
|
|
74
|
+
let elem_cap = capitalize(elem);
|
|
75
|
+
if elem_cap != "H" || options.keep_h {
|
|
76
|
+
let atom = Atom {
|
|
77
|
+
atom: elem_cap.clone(),
|
|
78
|
+
elem: elem_cap,
|
|
79
|
+
x: line[0..10].trim().parse().unwrap_or(0.0),
|
|
80
|
+
y: line[10..20].trim().parse().unwrap_or(0.0),
|
|
81
|
+
z: line[20..30].trim().parse().unwrap_or(0.0),
|
|
82
|
+
serial: start + i,
|
|
83
|
+
index: molecules[current].len(),
|
|
84
|
+
hetflag: true,
|
|
85
|
+
bonds: vec![],
|
|
86
|
+
bond_order: vec![],
|
|
87
|
+
properties: std::collections::HashMap::new(),
|
|
88
|
+
};
|
|
89
|
+
serial_to_index[i] = Some(molecules[current].len());
|
|
90
|
+
molecules[current].push(atom);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
offset += atom_count;
|
|
95
|
+
|
|
96
|
+
for i in 0..bond_count {
|
|
97
|
+
let line = lines[offset + i];
|
|
98
|
+
let from = line[0..3]
|
|
99
|
+
.trim()
|
|
100
|
+
.parse::<usize>()
|
|
101
|
+
.unwrap_or(0)
|
|
102
|
+
.saturating_sub(1);
|
|
103
|
+
let to = line[3..6]
|
|
104
|
+
.trim()
|
|
105
|
+
.parse::<usize>()
|
|
106
|
+
.unwrap_or(0)
|
|
107
|
+
.saturating_sub(1);
|
|
108
|
+
let order = line[6..].trim().parse::<f32>().unwrap_or(1.0);
|
|
109
|
+
if let (Some(f), Some(t)) = (
|
|
110
|
+
serial_to_index.get(from).and_then(|x| *x),
|
|
111
|
+
serial_to_index.get(to).and_then(|x| *x),
|
|
112
|
+
) {
|
|
113
|
+
molecules[current][f].bonds.push(t);
|
|
114
|
+
molecules[current][f].bond_order.push(order);
|
|
115
|
+
molecules[current][t].bonds.push(f);
|
|
116
|
+
molecules[current][t].bond_order.push(order);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let mut next_offset = offset + bond_count;
|
|
121
|
+
if options.multimodel {
|
|
122
|
+
if !options.onemol {
|
|
123
|
+
molecules.push(vec![]);
|
|
124
|
+
current += 1;
|
|
125
|
+
}
|
|
126
|
+
while next_offset < lines.len() && lines[next_offset] != "$$$$" {
|
|
127
|
+
next_offset += 1;
|
|
128
|
+
}
|
|
129
|
+
lines.drain(0..=next_offset);
|
|
130
|
+
} else {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
molecules
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn parse_v3000(mut lines: Vec<&str>, options: &ParserOptions) -> MoleculeData {
|
|
139
|
+
let model_count = count_models(&lines);
|
|
140
|
+
|
|
141
|
+
// 多个分子但用户没开启
|
|
142
|
+
if model_count > 0 && !options.multimodel {
|
|
143
|
+
panic!(
|
|
144
|
+
"Found multiple molecules but 'multimodel' is false. Please enable 'multimodel = true' to parse all molecules."
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 用户开启了但其实只有一个
|
|
149
|
+
if model_count == 0 && options.multimodel {
|
|
150
|
+
panic!(
|
|
151
|
+
"Only one molecule found, but 'multimodel = true' was set. Consider setting 'multimodel = false' to avoid confusion."
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let mut molecules = vec![vec![]];
|
|
156
|
+
let mut current = 0;
|
|
157
|
+
|
|
158
|
+
while lines.len() >= 8 {
|
|
159
|
+
if !lines[4].starts_with("M V30 BEGIN CTAB") || !lines[5].starts_with("M V30 COUNTS") {
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let counts: Vec<_> = lines[5][13..].split_whitespace().collect();
|
|
164
|
+
let atom_count = counts
|
|
165
|
+
.get(0)
|
|
166
|
+
.and_then(|s| s.parse::<usize>().ok())
|
|
167
|
+
.unwrap_or(0);
|
|
168
|
+
let bond_count = counts
|
|
169
|
+
.get(1)
|
|
170
|
+
.and_then(|s| s.parse::<usize>().ok())
|
|
171
|
+
.unwrap_or(0);
|
|
172
|
+
let mut offset = 7;
|
|
173
|
+
|
|
174
|
+
let mut serial_to_index = vec![None; atom_count];
|
|
175
|
+
let start = molecules[current].len();
|
|
176
|
+
|
|
177
|
+
for i in 0..atom_count {
|
|
178
|
+
let line = lines[offset + i];
|
|
179
|
+
let parts: Vec<_> = line[6..].split_whitespace().collect();
|
|
180
|
+
if parts.len() > 4 {
|
|
181
|
+
let elem_cap = capitalize(parts[1]);
|
|
182
|
+
if elem_cap != "H" || options.keep_h {
|
|
183
|
+
let atom = Atom {
|
|
184
|
+
atom: elem_cap.clone(),
|
|
185
|
+
elem: elem_cap,
|
|
186
|
+
x: parts[2].parse().unwrap_or(0.0),
|
|
187
|
+
y: parts[3].parse().unwrap_or(0.0),
|
|
188
|
+
z: parts[4].parse().unwrap_or(0.0),
|
|
189
|
+
serial: start + i,
|
|
190
|
+
index: molecules[current].len(),
|
|
191
|
+
hetflag: true,
|
|
192
|
+
bonds: vec![],
|
|
193
|
+
bond_order: vec![],
|
|
194
|
+
properties: std::collections::HashMap::new(),
|
|
195
|
+
};
|
|
196
|
+
serial_to_index[i] = Some(molecules[current].len());
|
|
197
|
+
molecules[current].push(atom);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
offset += atom_count + 1; // skip "END ATOM"
|
|
203
|
+
offset += 1; // BEGIN BOND
|
|
204
|
+
|
|
205
|
+
for i in 0..bond_count {
|
|
206
|
+
let line = lines[offset + i];
|
|
207
|
+
let parts: Vec<_> = line[6..].split_whitespace().collect();
|
|
208
|
+
if parts.len() > 3 {
|
|
209
|
+
let from = parts[2].parse::<usize>().unwrap_or(0).saturating_sub(1);
|
|
210
|
+
let to = parts[3].parse::<usize>().unwrap_or(0).saturating_sub(1);
|
|
211
|
+
let order = parts[1].parse::<f32>().unwrap_or(1.0);
|
|
212
|
+
if let (Some(f), Some(t)) = (
|
|
213
|
+
serial_to_index.get(from).and_then(|x| *x),
|
|
214
|
+
serial_to_index.get(to).and_then(|x| *x),
|
|
215
|
+
) {
|
|
216
|
+
molecules[current][f].bonds.push(t);
|
|
217
|
+
molecules[current][f].bond_order.push(order);
|
|
218
|
+
molecules[current][t].bonds.push(f);
|
|
219
|
+
molecules[current][t].bond_order.push(order);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let mut next_offset = offset + bond_count;
|
|
225
|
+
if options.multimodel {
|
|
226
|
+
if !options.onemol {
|
|
227
|
+
molecules.push(vec![]);
|
|
228
|
+
current += 1;
|
|
229
|
+
}
|
|
230
|
+
while next_offset < lines.len() && lines[next_offset] != "$$$$" {
|
|
231
|
+
next_offset += 1;
|
|
232
|
+
}
|
|
233
|
+
lines.drain(0..=next_offset);
|
|
234
|
+
} else {
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
molecules
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn capitalize(s: &str) -> String {
|
|
243
|
+
let mut chars = s.chars();
|
|
244
|
+
match chars.next() {
|
|
245
|
+
Some(first) => {
|
|
246
|
+
first.to_ascii_uppercase().to_string() + &chars.as_str().to_ascii_lowercase()
|
|
247
|
+
}
|
|
248
|
+
None => String::new(),
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fn count_models(lines: &[&str]) -> usize {
|
|
253
|
+
lines.iter().filter(|line| line.trim() == "$$$$").count()
|
|
254
|
+
}
|