cosmol-viewer 0.1.1.dev3__tar.gz → 0.1.1.dev5__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 (29) hide show
  1. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/Cargo.lock +7 -5
  2. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/Cargo.toml +1 -1
  3. cosmol_viewer-0.1.1.dev5/PKG-INFO +57 -0
  4. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/Cargo.toml +2 -0
  5. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shader/canvas.rs +22 -30
  6. cosmol_viewer-0.1.1.dev5/crates/core/src/shader/fragment.glsl +34 -0
  7. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shapes/molecules.rs +11 -14
  8. cosmol_viewer-0.1.1.dev5/crates/core/src/shapes/sphere.rs +246 -0
  9. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shapes/stick.rs +7 -8
  10. cosmol_viewer-0.1.1.dev5/crates/python/README.md +49 -0
  11. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/python/build.rs +1 -1
  12. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/python/src/lib.rs +2 -4
  13. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/python/src/shapes.rs +1 -1
  14. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/pyproject.toml +4 -1
  15. cosmol_viewer-0.1.1.dev3/PKG-INFO +0 -5
  16. cosmol_viewer-0.1.1.dev3/crates/core/src/shader/fragment.glsl +0 -28
  17. cosmol_viewer-0.1.1.dev3/crates/core/src/shapes/sphere.rs +0 -136
  18. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/lib.rs +0 -0
  19. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/parser/mod.rs +0 -0
  20. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/parser/sdf.rs +0 -0
  21. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/scene.rs +0 -0
  22. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shader/bg_fragment.glsl +0 -0
  23. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shader/bg_vertex.glsl +0 -0
  24. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shader/mod.rs +0 -0
  25. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shader/vertex.glsl +0 -0
  26. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/shapes/mod.rs +0 -0
  27. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/core/src/utils.rs +0 -0
  28. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/python/Cargo.toml +0 -0
  29. {cosmol_viewer-0.1.1.dev3 → cosmol_viewer-0.1.1.dev5}/crates/python/src/parser.rs +0 -0
@@ -699,7 +699,7 @@ dependencies = [
699
699
 
700
700
  [[package]]
701
701
  name = "cosmol_viewer"
702
- version = "0.1.1-nightly.3"
702
+ version = "0.1.1-nightly.5"
703
703
  dependencies = [
704
704
  "bytemuck",
705
705
  "cosmol_viewer_core",
@@ -717,21 +717,23 @@ dependencies = [
717
717
 
718
718
  [[package]]
719
719
  name = "cosmol_viewer_core"
720
- version = "0.1.1-nightly.3"
720
+ version = "0.1.1-nightly.5"
721
721
  dependencies = [
722
722
  "bytemuck",
723
723
  "eframe",
724
724
  "egui_extras",
725
725
  "glam",
726
+ "once_cell",
726
727
  "serde",
727
728
  "serde_json",
729
+ "serde_repr",
728
730
  "wasm-bindgen-futures",
729
731
  "web-sys",
730
732
  ]
731
733
 
732
734
  [[package]]
733
735
  name = "cosmol_viewer_gui"
734
- version = "0.1.1-nightly.3"
736
+ version = "0.1.1-nightly.5"
735
737
  dependencies = [
736
738
  "bytemuck",
737
739
  "cosmol_viewer_core",
@@ -765,7 +767,7 @@ dependencies = [
765
767
 
766
768
  [[package]]
767
769
  name = "cosmol_viewer_wasm"
768
- version = "0.1.1-nightly.3"
770
+ version = "0.1.1-nightly.5"
769
771
  dependencies = [
770
772
  "cosmol_viewer_core",
771
773
  "eframe",
@@ -3160,7 +3162,7 @@ dependencies = [
3160
3162
 
3161
3163
  [[package]]
3162
3164
  name = "test"
3163
- version = "0.1.1-nightly.3"
3165
+ version = "0.1.1-nightly.5"
3164
3166
  dependencies = [
3165
3167
  "cosmol_viewer",
3166
3168
  "cosmol_viewer_core",
@@ -1,6 +1,6 @@
1
1
  [workspace.package]
2
2
  edition = "2024"
3
- version = "0.1.1-nightly.3"
3
+ version = "0.1.1-nightly.5"
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"
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: cosmol-viewer
3
+ Version: 0.1.1.dev5
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.dev5
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
+ ```
@@ -12,4 +12,6 @@ egui_extras.workspace = true
12
12
  serde = { version = "1.0.219" , features = ["derive"] }
13
13
  bytemuck = "1.23.1"
14
14
  web-sys = "0.3.77"
15
+ serde_repr = "0.1"
15
16
  wasm-bindgen-futures = "0.4.50"
17
+ once_cell = "1.21.3"
@@ -39,7 +39,6 @@ impl Canvas {
39
39
  // 正值表示向上滚动,通常是“缩小”,负值是放大
40
40
  if scroll_delta != 0.0 {
41
41
  self.camera_state.scale *= (1.0 + scroll_delta * 0.001).clamp(0.1, 10.0);
42
- println!("scale {:?}", self.camera_state.scale);
43
42
  }
44
43
 
45
44
  self.camera_state = rotate_camera(self.camera_state, response.drag_motion());
@@ -289,15 +288,13 @@ impl Shader {
289
288
  for mesh in scene_data._get_meshes() {
290
289
  self.vertex3d
291
290
  .extend(mesh.vertices.iter().enumerate().map(|(i, pos)| {
292
- let mut j = i;
293
- j = i % 50;
294
291
  Vertex3d {
295
292
  position: *pos,
296
293
  normal: mesh.normals[i],
297
294
  color: mesh
298
295
  .colors
299
296
  .as_ref()
300
- .and_then(|colors| colors.get(j))
297
+ .and_then(|colors| colors.get(i))
301
298
  .unwrap_or(&[1.0, 1.0, 1.0, 1.0])
302
299
  .clone(),
303
300
  }
@@ -311,14 +308,6 @@ impl Shader {
311
308
  self.dirty = true;
312
309
  }
313
310
 
314
- fn destroy(&self, gl: &glow::Context) {
315
- // use glow::HasContext as _;
316
- // unsafe {
317
- // gl.delete_program(self.program);
318
- // gl.delete_vertex_array(self.vertex_array);
319
- // }
320
- }
321
-
322
311
  fn paint(&mut self, gl: &glow::Context, aspect_ratio: f32, camera_state: CameraState) {
323
312
  use glow::HasContext as _;
324
313
 
@@ -336,7 +325,7 @@ impl Shader {
336
325
  let light = Light {
337
326
  position: [2.0, -3.0, 2.0],
338
327
  color: [1.0, 0.9, 0.9],
339
- intensity: 0.7,
328
+ intensity: 1.0,
340
329
  };
341
330
 
342
331
  unsafe {
@@ -362,7 +351,7 @@ impl Shader {
362
351
 
363
352
  // === 绘制场景 ===
364
353
  gl.enable(glow::DEPTH_TEST);
365
- // gl.depth_mask(false); // ✅ 关键:恢复写入深度缓冲区
354
+ gl.depth_mask(true); // ✅ 关键:恢复写入深度缓冲区
366
355
 
367
356
  // gl.enable(glow::BLEND);
368
357
  // gl.blend_func_separate(
@@ -437,7 +426,7 @@ impl Shader {
437
426
  gl.uniform_3_f32_slice(
438
427
  gl.get_uniform_location(self.program, "u_light_color")
439
428
  .as_ref(),
440
- (light.color).as_ref(),
429
+ (light.color.map(|x| x * light.intensity)).as_ref(),
441
430
  );
442
431
 
443
432
  gl.uniform_1_f32(
@@ -497,34 +486,37 @@ impl CameraState {
497
486
 
498
487
  pub fn rotate_camera(mut camera_state: CameraState, drag_motion: Vec2) -> CameraState {
499
488
  let sensitivity = 0.005;
500
- let yaw = drag_motion.x * sensitivity;
501
- let pitch = drag_motion.y * sensitivity;
489
+ let yaw = -drag_motion.x * sensitivity; // 水平拖动 → 绕 up 旋转
490
+ let pitch = -drag_motion.y * sensitivity; // 垂直拖动 → 绕 right 旋转
502
491
 
503
- // 计算右向量
504
- let right = camera_state.direction.cross(camera_state.up).normalize();
492
+ // 当前方向
493
+ let dir = camera_state.direction;
505
494
 
506
- // 计算绕 Y 的旋转(水平拖动)
507
- let yaw_quat = Quat::from_axis_angle(Vec3::Y, yaw);
495
+ // right = 当前方向 × 当前 up
496
+ let right = dir.cross(camera_state.up).normalize();
508
497
 
509
- // 计算绕右轴的旋转(垂直拖动)
510
- let pitch_quat = Quat::from_axis_angle(right, -pitch);
498
+ // 1. pitch:绕当前 right 轴旋转(垂直)
499
+ let pitch_quat = Quat::from_axis_angle(right, pitch);
500
+ let rotated_dir = pitch_quat * dir;
501
+ let rotated_up = pitch_quat * camera_state.up;
511
502
 
512
- // 最终方向 = 原方向应用 pitch,然后再应用 yaw
513
- let new_direction = yaw_quat * pitch_quat * camera_state.direction;
514
- camera_state.direction = new_direction.normalize();
503
+ // 2. yaw:绕当前“视角 up”旋转(水平)
504
+ let yaw_quat = Quat::from_axis_angle(rotated_up, yaw);
505
+ let final_dir = yaw_quat * rotated_dir;
515
506
 
516
- // 更新 up 向量(可选,视需求而定)
517
- camera_state.up = yaw_quat * pitch_quat * camera_state.up;
507
+ camera_state.direction = final_dir.normalize();
508
+ camera_state.up = (yaw_quat * rotated_up).normalize();
518
509
 
519
- return camera_state;
510
+ camera_state
520
511
  }
521
512
 
513
+
522
514
  #[repr(C)]
523
515
  #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Debug, Serialize, Deserialize)]
524
516
  pub struct Vertex3d {
525
517
  pub position: [f32; 3],
526
518
  pub normal: [f32; 3],
527
- pub color: [f32; 4], // 可选颜色属性
519
+ pub color: [f32; 4],
528
520
  }
529
521
 
530
522
  #[repr(C)]
@@ -0,0 +1,34 @@
1
+ precision mediump float;
2
+
3
+ uniform vec3 u_light_pos;
4
+ uniform vec3 u_light_color;
5
+ uniform vec3 u_view_pos;
6
+ uniform float u_light_intensity;
7
+
8
+ in vec3 v_normal;
9
+ in vec4 v_color;
10
+ in vec3 v_frag_pos;
11
+
12
+ out vec4 FragColor;
13
+
14
+ void main() {
15
+ vec3 normal = normalize(v_normal);
16
+ vec3 light_dir = normalize(u_light_pos - v_frag_pos);
17
+ vec3 view_dir = normalize(u_view_pos - v_frag_pos);
18
+
19
+ // === 环境光 ===
20
+ vec3 ambient = 0.7 * u_light_color;
21
+
22
+ // === 漫反射 ===
23
+ float diff = max(dot(normal, light_dir), 0.0);
24
+ vec3 diffuse = 0.5 * diff * u_light_color;
25
+
26
+ // === 高光 ===
27
+ vec3 halfway_dir = normalize(light_dir + view_dir); // Blinn 模型
28
+ float spec = pow(max(dot(normal, halfway_dir), 0.0), 64.0); // shininess 可调
29
+ vec3 specular = 0.3 * spec * u_light_color; // 强度可调
30
+
31
+ // === 最终颜色 ===
32
+ vec3 lighting = (ambient + diffuse) * v_color.rgb + specular;
33
+ FragColor = vec4(lighting * u_light_intensity, v_color.a);
34
+ }
@@ -1,10 +1,9 @@
1
- use glam::Mat3;
2
1
  use serde::{Deserialize, Serialize};
2
+ use serde_repr::{Deserialize_repr, Serialize_repr};
3
3
 
4
4
  use crate::{
5
5
  Shape,
6
6
  parser::sdf::MoleculeData,
7
- scene::Scene,
8
7
  shapes::{sphere::Sphere, stick::Stick},
9
8
  utils::{Interaction, MeshData, VisualShape, VisualStyle},
10
9
  };
@@ -50,7 +49,7 @@ impl AtomType {
50
49
  pub fn color(&self) -> [f32; 3] {
51
50
  match self {
52
51
  AtomType::H => [1.0, 1.0, 1.0], // 白色
53
- AtomType::C => [0.2, 0.2, 0.2], // 深灰
52
+ AtomType::C => [0.3, 0.3, 0.3], // 深灰
54
53
  AtomType::N => [0.0, 0.0, 1.0], // 蓝色
55
54
  AtomType::O => [1.0, 0.0, 0.0], // 红色
56
55
  AtomType::F => [0.0, 0.8, 0.0], // 绿
@@ -80,12 +79,13 @@ impl AtomType {
80
79
  }
81
80
  }
82
81
 
83
- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
82
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
83
+ #[repr(u8)]
84
84
  pub enum BondType {
85
- SINGLE,
86
- DOUBLE,
87
- TRIPLE,
88
- AROMATIC,
85
+ SINGLE = 1,
86
+ DOUBLE = 2,
87
+ TRIPLE = 3,
88
+ AROMATIC = 0,
89
89
  }
90
90
 
91
91
  #[derive(Serialize, Deserialize, Debug, Clone)]
@@ -210,10 +210,7 @@ impl Molecules {
210
210
  let radius = self.atom_types.get(i).unwrap_or(&AtomType::Unknown).radius() * 0.2;
211
211
  let color = self.atom_types.get(i).unwrap_or(&AtomType::Unknown).color();
212
212
 
213
- println!("atom: {}, color: {:?}, radius: {}", i, color, radius);
214
-
215
213
  let mut sphere = Sphere::new(*pos, radius);
216
- sphere.style = self.style; // 继承样式
217
214
  sphere.interaction = self.interaction;
218
215
  sphere = sphere.color(color);
219
216
 
@@ -226,7 +223,7 @@ impl Molecules {
226
223
  for n in mesh.normals {
227
224
  normals.push(n.map(|x| x * scale));
228
225
  }
229
- for c in mesh.colors.unwrap_or_default() {
226
+ for c in mesh.colors.unwrap() {
230
227
  colors.push(c);
231
228
  }
232
229
  for idx in mesh.indices {
@@ -243,8 +240,8 @@ impl Molecules {
243
240
  let pos_b = self.atoms[b as usize];
244
241
 
245
242
  let mut stick = Stick::new(pos_a, pos_b, 0.1); // or radius by bond type
246
- stick.style = self.style;
247
243
  stick.interaction = self.interaction;
244
+ stick = stick.color([0.7, 0.7, 0.7]);
248
245
 
249
246
  let mesh = stick.to_mesh(1.0);
250
247
 
@@ -254,7 +251,7 @@ impl Molecules {
254
251
  for n in mesh.normals {
255
252
  normals.push(n.map(|x| x * scale));
256
253
  }
257
- for c in mesh.colors.unwrap_or_default() {
254
+ for c in mesh.colors.unwrap() {
258
255
  colors.push(c);
259
256
  }
260
257
  for idx in mesh.indices {
@@ -0,0 +1,246 @@
1
+ use serde::{Deserialize, Serialize};
2
+
3
+ use crate::{scene::Scene, utils::{Interaction, MeshData, VisualShape, VisualStyle}, Shape};
4
+
5
+ use once_cell::sync::Lazy;
6
+ use std::collections::HashMap;
7
+ use std::sync::Mutex;
8
+
9
+ #[derive(Clone)]
10
+ struct SphereTemplate {
11
+ vertices: Vec<[f32; 3]>,
12
+ normals: Vec<[f32; 3]>,
13
+ indices: Vec<u32>,
14
+ }
15
+
16
+ static SPHERE_TEMPLATE_CACHE: Lazy<Mutex<HashMap<u32, SphereTemplate>>> = Lazy::new(|| {
17
+ Mutex::new(HashMap::new())
18
+ });
19
+
20
+ fn get_or_generate_template(quality: u32) -> SphereTemplate {
21
+ let mut cache = SPHERE_TEMPLATE_CACHE.lock().unwrap();
22
+
23
+ if let Some(template) = cache.get(&quality) {
24
+ return template.clone(); // 直接返回已有的
25
+ }
26
+
27
+ let lat_segments = 10 * quality;
28
+ let lon_segments = 20 * quality;
29
+
30
+ let mut vertices = Vec::new();
31
+ let mut normals = Vec::new();
32
+ let mut indices = Vec::new();
33
+
34
+ for i in 0..=lat_segments {
35
+ let theta = std::f32::consts::PI * (i as f32) / (lat_segments as f32);
36
+ let sin_theta = theta.sin();
37
+ let cos_theta = theta.cos();
38
+
39
+ for j in 0..=lon_segments {
40
+ let phi = 2.0 * std::f32::consts::PI * (j as f32) / (lon_segments as f32);
41
+ let sin_phi = phi.sin();
42
+ let cos_phi = phi.cos();
43
+
44
+ let nx = sin_theta * cos_phi;
45
+ let ny = cos_theta;
46
+ let nz = sin_theta * sin_phi;
47
+
48
+ vertices.push([nx, ny, nz]); // 单位球
49
+ normals.push([nx, ny, nz]);
50
+ }
51
+ }
52
+
53
+ for i in 0..lat_segments {
54
+ for j in 0..lon_segments {
55
+ let first = i * (lon_segments + 1) + j;
56
+ let second = first + lon_segments + 1;
57
+
58
+ indices.push(first);
59
+ indices.push(first + 1);
60
+ indices.push(second);
61
+
62
+ indices.push(second);
63
+ indices.push(first + 1);
64
+ indices.push(second + 1);
65
+ }
66
+ }
67
+
68
+ let template = SphereTemplate {
69
+ vertices,
70
+ normals,
71
+ indices,
72
+ };
73
+
74
+ cache.insert(quality, template.clone());
75
+
76
+ template
77
+ }
78
+
79
+
80
+ #[derive(Serialize, Deserialize, Debug, Clone, Copy)]
81
+ pub struct Sphere {
82
+ pub center: [f32; 3],
83
+ pub radius: f32,
84
+ pub quality: u32,
85
+
86
+ pub style: VisualStyle,
87
+ pub interaction: Interaction,
88
+ }
89
+
90
+ impl Into<Shape> for Sphere {
91
+ fn into(self) -> Shape {
92
+ Shape::Sphere(self)
93
+ }
94
+ }
95
+
96
+ impl Sphere {
97
+ pub fn new(center: [f32; 3], radius: f32) -> Self {
98
+ Self {
99
+ center,
100
+ radius,
101
+ quality: 2,
102
+ style: VisualStyle {
103
+ opacity: 1.0,
104
+ visible: true,
105
+ ..Default::default()
106
+ },
107
+ interaction: Default::default(),
108
+ }
109
+ }
110
+
111
+ pub fn center(mut self, center: [f32; 3])-> Self {
112
+ self.center = center;
113
+ self
114
+ }
115
+
116
+ pub fn set_radius(mut self, radius: f32)-> Self {
117
+ self.radius = radius;
118
+ self
119
+ }
120
+
121
+ pub fn clickable(mut self, val: bool) -> Self {
122
+ self.interaction.clickable = val;
123
+ self
124
+ }
125
+
126
+ // pub fn to_mesh(&self, scale: f32) -> MeshData {
127
+ // let mut vertices = Vec::new();
128
+ // let mut normals = Vec::new();
129
+ // let mut indices = Vec::new();
130
+ // let mut colors = Vec::new();
131
+
132
+ // let lat_segments = 10 * self.quality;
133
+ // let lon_segments = 20 * self.quality;
134
+
135
+ // let r = self.radius;
136
+ // let [cx, cy, cz] = self.center;
137
+
138
+ // // 基础颜色(带透明度)
139
+ // let base_color = self.style.color.unwrap_or([1.0, 1.0, 1.0]);
140
+ // let alpha = self.style.opacity.clamp(0.0, 1.0);
141
+ // let color_rgba = [base_color[0], base_color[1], base_color[2], alpha];
142
+
143
+ // for i in 0..=lat_segments {
144
+ // let theta = std::f32::consts::PI * (i as f32) / (lat_segments as f32);
145
+ // let sin_theta = theta.sin();
146
+ // let cos_theta = theta.cos();
147
+
148
+ // for j in 0..=lon_segments {
149
+ // let phi = 2.0 * std::f32::consts::PI * (j as f32) / (lon_segments as f32);
150
+ // let sin_phi = phi.sin();
151
+ // let cos_phi = phi.cos();
152
+
153
+ // let nx = sin_theta * cos_phi;
154
+ // let ny = cos_theta;
155
+ // let nz = sin_theta * sin_phi;
156
+
157
+ // let x = cx + r * nx;
158
+ // let y = cy + r * ny;
159
+ // let z = cz + r * nz;
160
+
161
+ // vertices.push([x, y, z].map(|x| x * scale));
162
+ // normals.push([nx, ny, nz].map(|x| x * scale));
163
+ // colors.push(color_rgba); // 每个顶点同样颜色
164
+ // }
165
+ // }
166
+
167
+ // for i in 0..lat_segments {
168
+ // for j in 0..lon_segments {
169
+ // let first = i * (lon_segments + 1) + j;
170
+ // let second = first + lon_segments + 1;
171
+
172
+ // indices.push(first);
173
+ // indices.push(first + 1);
174
+ // indices.push(second);
175
+
176
+ // indices.push(second);
177
+ // indices.push(first + 1);
178
+ // indices.push(second + 1);
179
+ // }
180
+ // }
181
+
182
+ // MeshData {
183
+ // vertices,
184
+ // normals,
185
+ // indices,
186
+ // colors: Some(colors),
187
+ // transform: None,
188
+ // is_wireframe: self.style.wireframe,
189
+ // }
190
+ // }
191
+ pub fn to_mesh(&self, scale: f32) -> MeshData {
192
+ let template = get_or_generate_template(self.quality);
193
+
194
+ let [cx, cy, cz] = self.center;
195
+ let r = self.radius;
196
+
197
+ let transformed_vertices: Vec<[f32; 3]> = template.vertices.iter()
198
+ .map(|v| [
199
+ (v[0] * r + cx) * scale,
200
+ (v[1] * r + cy) * scale,
201
+ (v[2] * r + cz) * scale,
202
+ ])
203
+ .collect();
204
+
205
+ let transformed_normals: Vec<[f32; 3]> = template.normals
206
+ .iter()
207
+ .map(|n| n.map(|x| x * scale)) // 你可以不乘 scale,如果只用于方向
208
+ .collect();
209
+
210
+ let base_color = self.style.color.unwrap_or([1.0, 1.0, 1.0]);
211
+ let alpha = self.style.opacity.clamp(0.0, 1.0);
212
+ let color = [base_color[0], base_color[1], base_color[2], alpha];
213
+
214
+ let colors = vec![color; transformed_vertices.len()];
215
+
216
+ MeshData {
217
+ vertices: transformed_vertices,
218
+ normals: transformed_normals,
219
+ indices: template.indices.clone(),
220
+ colors: Some(colors),
221
+ transform: None,
222
+ is_wireframe: self.style.wireframe,
223
+ }
224
+
225
+ }
226
+ }
227
+
228
+ impl VisualShape for Sphere {
229
+ fn style_mut(&mut self) -> &mut VisualStyle {
230
+ &mut self.style
231
+ }
232
+ }
233
+
234
+ pub trait UpdateSphere {
235
+ fn update_sphere(&mut self, id: &str, f: impl FnOnce(&mut Sphere));
236
+ }
237
+
238
+ impl UpdateSphere for Scene {
239
+ fn update_sphere(&mut self, id: &str, f: impl FnOnce(&mut Sphere)) {
240
+ if let Some(Shape::Sphere(sphere)) = self.named_shapes.get_mut(id) {
241
+ f(sphere);
242
+ } else {
243
+ panic!("Sphere with ID '{}' not found or is not a Sphere", id);
244
+ }
245
+ }
246
+ }
@@ -1,4 +1,3 @@
1
- use glam::Mat3;
2
1
  use serde::{Deserialize, Serialize};
3
2
 
4
3
  use crate::{
@@ -40,17 +39,17 @@ impl Stick {
40
39
  }
41
40
  }
42
41
 
43
- pub fn radius(mut self, radius: f32) -> Self{
42
+ pub fn radius(mut self, radius: f32) -> Self {
44
43
  self.radius = radius;
45
44
  self
46
45
  }
47
46
 
48
- pub fn start(mut self, start: [f32; 3]) -> Self{
47
+ pub fn start(mut self, start: [f32; 3]) -> Self {
49
48
  self.start = start;
50
49
  self
51
50
  }
52
51
 
53
- pub fn end(mut self, end: [f32; 3]) -> Self{
52
+ pub fn end(mut self, end: [f32; 3]) -> Self {
54
53
  self.end = end;
55
54
  self
56
55
  }
@@ -96,13 +95,13 @@ impl Stick {
96
95
 
97
96
  for i in 0..segments {
98
97
  let idx = i * 2;
99
- indices.push(idx);
100
- indices.push(idx + 1);
101
98
  indices.push(idx + 2);
102
-
103
99
  indices.push(idx + 1);
104
- indices.push(idx + 3);
100
+ indices.push(idx);
101
+
105
102
  indices.push(idx + 2);
103
+ indices.push(idx + 3);
104
+ indices.push(idx + 1);
106
105
  }
107
106
 
108
107
  // 对齐旋转:Z -> axis
@@ -0,0 +1,49 @@
1
+ # COSMol-viewer
2
+
3
+ A high-performance molecular visualization library built with Rust and WebGPU, designed for seamless integration into Python workflows.
4
+
5
+ - ⚡ Fast: Native-speed rendering powered by Rust and GPU acceleration
6
+
7
+ - 🧬 Flexible: Load molecules from .sdf, .pdb, and dynamically update 3D structures
8
+
9
+ - 📓 Notebook-friendly: Fully supports Jupyter and Google Colab — ideal for education, research, and live demos
10
+
11
+ - 🔁 Real-time updates: Update molecular coordinates on-the-fly for simulations or animations
12
+
13
+ - 🎨 Customizable: Control styles, camera, and rendering settings programmatically
14
+
15
+ # Installation
16
+
17
+ ```sh
18
+ pip install cosmol-viewer==0.1.1.dev5
19
+ ```
20
+
21
+ # Usage
22
+
23
+ ```python
24
+ from cosmol_viewer import Scene, Viewer, parse_sdf, Molecules
25
+
26
+ # === Step 1: Load and render a molecule ===
27
+ with open("molecule.sdf", "r") as f:
28
+ sdf = f.read()
29
+ mol = Molecules(parse_sdf(sdf)).centered()
30
+
31
+ scene = Scene()
32
+ scene.scale(0.1)
33
+ scene.add_shape(mol, "mol")
34
+
35
+ viewer = Viewer.render(scene) # Launch the viewer
36
+
37
+ # === Step 2: Update the same molecule dynamically ===
38
+ import time
39
+
40
+ for i in range(1, 10): # Simulate multiple frames
41
+ with open(f"frames/frame_{i}.sdf", "r") as f:
42
+ sdf = f.read()
43
+ updated_mol = Molecules(parse_sdf(sdf)).centered()
44
+
45
+ scene.update_shape("mol", updated_mol)
46
+ viewer.update(scene)
47
+
48
+ time.sleep(0.033) # ~30 FPS
49
+ ```
@@ -1,4 +1,4 @@
1
- use std::{env, process::Command};
1
+ // use std::{env, process::Command};
2
2
 
3
3
  fn main() {
4
4
  // let is_ci = env::var("GITHUB_ACTIONS").is_ok();
@@ -18,8 +18,8 @@ pub struct Scene {
18
18
 
19
19
  #[pymethods]
20
20
  impl Scene {
21
- #[staticmethod]
22
- pub fn create_viewer() -> Self {
21
+ #[new]
22
+ pub fn new() -> Self {
23
23
  Self {
24
24
  inner: _Scene::new(),
25
25
  }
@@ -138,8 +138,6 @@ impl Viewer {
138
138
 
139
139
  #[staticmethod]
140
140
  pub fn render(scene: &Scene, py: Python) -> Self {
141
- println!("scene {}", serde_json::to_string(&scene.inner).unwrap());
142
-
143
141
  let env_type = detect_runtime_env(py).unwrap();
144
142
  match env_type {
145
143
  RuntimeEnv::Colab | RuntimeEnv::Jupyter => {
@@ -1,6 +1,6 @@
1
1
  use cosmol_viewer_core::{
2
2
  shapes::{molecules::Molecules, sphere::Sphere, stick::Stick},
3
- utils::{VisualShape, VisualStyle},
3
+ utils::VisualShape,
4
4
  };
5
5
  use pyo3::{PyRefMut, pyclass, pymethods};
6
6
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cosmol-viewer"
3
- version = "0.1.1.dev3"
3
+ version = "0.1.1.dev5"
4
4
  description = "Molecular visualization tools"
5
5
  authors = [{name = "95028", email = "wjt@cosmol.org"}]
6
6
 
@@ -11,3 +11,6 @@ build-backend = "maturin"
11
11
  [tool.maturin]
12
12
  name = "cosmol_viewer"
13
13
  manifest-path = "crates/python/Cargo.toml"
14
+
15
+ [project.urls]
16
+ Repository = "https://github.com/COSMol-repl/COSMol-viewer"
@@ -1,5 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: cosmol-viewer
3
- Version: 0.1.1.dev3
4
- Summary: Molecular visualization tools
5
- Author-email: 95028 <wjt@cosmol.org>
@@ -1,28 +0,0 @@
1
- precision mediump float;
2
-
3
- uniform vec3 u_light_pos;
4
- uniform vec3 u_light_color;
5
- uniform vec3 u_view_pos;
6
- uniform float u_light_intensity;
7
-
8
- in vec3 v_normal;
9
- in vec4 v_color;
10
- in vec3 v_frag_pos;
11
-
12
- out vec4 FragColor;
13
- void main() {
14
- vec3 normal = normalize(v_normal); // 归一化法线
15
- vec3 light_dir = normalize(u_light_pos - v_frag_pos); // 计算从片元到光源的方向
16
- vec3 view_dir = normalize(u_view_pos - v_frag_pos); // 计算从片元到相机的方向
17
- vec3 halfway_dir = normalize(light_dir + view_dir); // halfway 向量用于 Blinn-Phong 高光
18
-
19
- float diff = max(dot(normal, light_dir), 0.0); // 漫反射项(Lambert)
20
- float spec = pow(max(dot(normal, halfway_dir), 0.0), 32.0); // 高光项,32 是 shininess 参数
21
-
22
- vec3 ambient = 0.3 * u_light_color; // 环境光,固定系数0.2
23
- vec3 diffuse = diff * u_light_color; // 漫反射
24
-
25
- vec3 light = (ambient + diffuse) * u_light_intensity; // 叠加光照并乘以强度
26
-
27
- FragColor = vec4(v_color.rgb * light, v_color.a);
28
- }
@@ -1,136 +0,0 @@
1
- use serde::{Deserialize, Serialize};
2
-
3
- use crate::{scene::Scene, utils::{Interaction, MeshData, VisualShape, VisualStyle}, Shape};
4
-
5
- #[derive(Serialize, Deserialize, Debug, Clone, Copy)]
6
- pub struct Sphere {
7
- pub center: [f32; 3],
8
- pub radius: f32,
9
- pub quality: u32,
10
-
11
- pub style: VisualStyle,
12
- pub interaction: Interaction,
13
- }
14
-
15
- impl Into<Shape> for Sphere {
16
- fn into(self) -> Shape {
17
- Shape::Sphere(self)
18
- }
19
- }
20
-
21
- impl Sphere {
22
- pub fn new(center: [f32; 3], radius: f32) -> Self {
23
- Self {
24
- center,
25
- radius,
26
- quality: 2,
27
- style: VisualStyle {
28
- opacity: 1.0,
29
- visible: true,
30
- ..Default::default()
31
- },
32
- interaction: Default::default(),
33
- }
34
- }
35
-
36
- pub fn center(mut self, center: [f32; 3])-> Self {
37
- self.center = center;
38
- self
39
- }
40
-
41
- pub fn set_radius(mut self, radius: f32)-> Self {
42
- self.radius = radius;
43
- self
44
- }
45
-
46
- pub fn clickable(mut self, val: bool) -> Self {
47
- self.interaction.clickable = val;
48
- self
49
- }
50
-
51
- pub fn to_mesh(&self, scale: f32) -> MeshData {
52
- let mut vertices = Vec::new();
53
- let mut normals = Vec::new();
54
- let mut indices = Vec::new();
55
- let mut colors = Vec::new();
56
-
57
- let lat_segments = 10 * self.quality;
58
- let lon_segments = 20 * self.quality;
59
-
60
- let r = self.radius;
61
- let [cx, cy, cz] = self.center;
62
-
63
- // 基础颜色(带透明度)
64
- let base_color = self.style.color.unwrap_or([1.0, 1.0, 1.0]);
65
- let alpha = self.style.opacity.clamp(0.0, 1.0);
66
- let color_rgba = [base_color[0], base_color[1], base_color[2], alpha];
67
-
68
- for i in 0..=lat_segments {
69
- let theta = std::f32::consts::PI * (i as f32) / (lat_segments as f32);
70
- let sin_theta = theta.sin();
71
- let cos_theta = theta.cos();
72
-
73
- for j in 0..=lon_segments {
74
- let phi = 2.0 * std::f32::consts::PI * (j as f32) / (lon_segments as f32);
75
- let sin_phi = phi.sin();
76
- let cos_phi = phi.cos();
77
-
78
- let nx = sin_theta * cos_phi;
79
- let ny = cos_theta;
80
- let nz = sin_theta * sin_phi;
81
-
82
- let x = cx + r * nx;
83
- let y = cy + r * ny;
84
- let z = cz + r * nz;
85
-
86
- vertices.push([x, y, z].map(|x| x * scale));
87
- normals.push([nx, ny, nz].map(|x| x * scale));
88
- colors.push(color_rgba); // 每个顶点同样颜色
89
- }
90
- }
91
-
92
- for i in 0..lat_segments {
93
- for j in 0..lon_segments {
94
- let first = i * (lon_segments + 1) + j;
95
- let second = first + lon_segments + 1;
96
-
97
- indices.push(first);
98
- indices.push(second);
99
- indices.push(first + 1);
100
-
101
- indices.push(second);
102
- indices.push(second + 1);
103
- indices.push(first + 1);
104
- }
105
- }
106
-
107
- MeshData {
108
- vertices,
109
- normals,
110
- indices,
111
- colors: Some(colors),
112
- transform: None,
113
- is_wireframe: self.style.wireframe,
114
- }
115
- }
116
- }
117
-
118
- impl VisualShape for Sphere {
119
- fn style_mut(&mut self) -> &mut VisualStyle {
120
- &mut self.style
121
- }
122
- }
123
-
124
- pub trait UpdateSphere {
125
- fn update_sphere(&mut self, id: &str, f: impl FnOnce(&mut Sphere));
126
- }
127
-
128
- impl UpdateSphere for Scene {
129
- fn update_sphere(&mut self, id: &str, f: impl FnOnce(&mut Sphere)) {
130
- if let Some(Shape::Sphere(sphere)) = self.named_shapes.get_mut(id) {
131
- f(sphere);
132
- } else {
133
- panic!("Sphere with ID '{}' not found or is not a Sphere", id);
134
- }
135
- }
136
- }