mesh_shell/shell/
generate.rs1use tracing::{debug, info};
6
7use mesh_repair::{compute_vertex_normals, Mesh};
8
9use super::rim::generate_rim;
10
11#[derive(Debug, Clone)]
13pub struct ShellParams {
14 pub wall_thickness_mm: f64,
16 pub min_thickness_mm: f64,
18}
19
20impl Default for ShellParams {
21 fn default() -> Self {
22 Self {
23 wall_thickness_mm: 2.5,
24 min_thickness_mm: 1.5,
25 }
26 }
27}
28
29#[derive(Debug)]
31pub struct ShellResult {
32 pub inner_vertex_count: usize,
34 pub outer_vertex_count: usize,
36 pub rim_face_count: usize,
38 pub total_face_count: usize,
40 pub boundary_size: usize,
42}
43
44pub fn generate_shell(inner_shell: &Mesh, params: &ShellParams) -> (Mesh, ShellResult) {
56 info!("Generating shell with thickness={:.2}mm", params.wall_thickness_mm);
57
58 let n = inner_shell.vertices.len();
59 let mut shell = Mesh::new();
60
61 let mut inner_with_normals = inner_shell.clone();
63 compute_vertex_normals(&mut inner_with_normals);
64
65 for vertex in &inner_with_normals.vertices {
67 shell.vertices.push(vertex.clone());
69 }
70
71 for vertex in &inner_with_normals.vertices {
72 let normal = vertex.normal.unwrap_or_else(|| nalgebra::Vector3::new(0.0, 0.0, 1.0));
74 let outer_pos = vertex.position + normal * params.wall_thickness_mm;
75
76 let mut outer_vertex = vertex.clone();
77 outer_vertex.position = outer_pos;
78 outer_vertex.normal = Some(normal);
80
81 shell.vertices.push(outer_vertex);
82 }
83
84 debug!("Generated {} inner + {} outer vertices", n, n);
85
86 for face in &inner_shell.faces {
88 shell.faces.push([face[0], face[2], face[1]]);
90 }
91
92 for face in &inner_shell.faces {
94 let n32 = n as u32;
95 shell.faces.push([face[0] + n32, face[1] + n32, face[2] + n32]);
96 }
97
98 let inner_face_count = inner_shell.faces.len();
99 debug!("Added {} inner + {} outer faces", inner_face_count, inner_face_count);
100
101 let (rim_faces, boundary_size) = generate_rim(&inner_with_normals, n);
103
104 let rim_face_count = rim_faces.len();
105 for face in rim_faces {
106 shell.faces.push(face);
107 }
108
109 info!(
110 "Shell generation complete: {} vertices, {} faces",
111 shell.vertices.len(),
112 shell.faces.len()
113 );
114
115 let result = ShellResult {
116 inner_vertex_count: n,
117 outer_vertex_count: n,
118 rim_face_count,
119 total_face_count: shell.faces.len(),
120 boundary_size,
121 };
122
123 (shell, result)
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use mesh_repair::Vertex;
130
131 fn create_open_box() -> Mesh {
132 let mut mesh = Mesh::new();
134
135 mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
137 mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
138 mesh.vertices.push(Vertex::from_coords(10.0, 10.0, 0.0));
139 mesh.vertices.push(Vertex::from_coords(0.0, 10.0, 0.0));
140 mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 10.0));
142 mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 10.0));
143 mesh.vertices.push(Vertex::from_coords(10.0, 10.0, 10.0));
144 mesh.vertices.push(Vertex::from_coords(0.0, 10.0, 10.0));
145
146 mesh.faces.push([0, 2, 1]);
148 mesh.faces.push([0, 3, 2]);
149 mesh.faces.push([0, 1, 5]);
151 mesh.faces.push([0, 5, 4]);
152 mesh.faces.push([2, 3, 7]);
154 mesh.faces.push([2, 7, 6]);
155 mesh.faces.push([0, 4, 7]);
157 mesh.faces.push([0, 7, 3]);
158 mesh.faces.push([1, 2, 6]);
160 mesh.faces.push([1, 6, 5]);
161 mesh
164 }
165
166 #[test]
167 fn test_shell_params_default() {
168 let params = ShellParams::default();
169 assert_eq!(params.wall_thickness_mm, 2.5);
170 assert_eq!(params.min_thickness_mm, 1.5);
171 }
172
173 #[test]
174 fn test_generate_shell_doubles_vertices() {
175 let inner = create_open_box();
176 let params = ShellParams::default();
177
178 let (shell, result) = generate_shell(&inner, ¶ms);
179
180 assert_eq!(shell.vertices.len(), inner.vertices.len() * 2);
182 assert_eq!(result.inner_vertex_count, inner.vertices.len());
183 assert_eq!(result.outer_vertex_count, inner.vertices.len());
184 }
185
186 #[test]
187 fn test_shell_has_more_faces() {
188 let inner = create_open_box();
189 let params = ShellParams::default();
190
191 let (shell, result) = generate_shell(&inner, ¶ms);
192
193 assert!(shell.faces.len() > inner.faces.len() * 2);
195 assert!(result.rim_face_count > 0);
196 }
197}