1#![allow(clippy::missing_errors_doc)]
4
5use wasm_bindgen::prelude::*;
6
7use brepkit_operations::transform::transform_solid;
8
9use crate::error::{validate_finite, validate_positive};
10use crate::handles::solid_id_to_u32;
11use crate::kernel::BrepKernel;
12
13#[wasm_bindgen]
14impl BrepKernel {
15 #[wasm_bindgen(js_name = "makeBox")]
23 pub fn make_box_solid(&mut self, dx: f64, dy: f64, dz: f64) -> Result<u32, JsError> {
24 validate_positive(dx, "dx")?;
25 validate_positive(dy, "dy")?;
26 validate_positive(dz, "dz")?;
27 let solid_id = brepkit_operations::primitives::make_box(self.topo_mut(), dx, dy, dz)?;
28 Ok(solid_id_to_u32(solid_id))
29 }
30
31 #[wasm_bindgen(js_name = "makeCylinder")]
39 pub fn make_cylinder_solid(&mut self, radius: f64, height: f64) -> Result<u32, JsError> {
40 validate_positive(radius, "radius")?;
41 validate_positive(height, "height")?;
42 let solid_id =
43 brepkit_operations::primitives::make_cylinder(self.topo_mut(), radius, height)?;
44 Ok(solid_id_to_u32(solid_id))
45 }
46
47 #[wasm_bindgen(js_name = "makeSphere")]
55 pub fn make_sphere_solid(&mut self, radius: f64, segments: u32) -> Result<u32, JsError> {
56 validate_positive(radius, "radius")?;
57 let solid_id = brepkit_operations::primitives::make_sphere(
58 self.topo_mut(),
59 radius,
60 segments as usize,
61 )?;
62 Ok(solid_id_to_u32(solid_id))
63 }
64
65 #[wasm_bindgen(js_name = "makeCone")]
73 pub fn make_cone_solid(
74 &mut self,
75 bottom_radius: f64,
76 top_radius: f64,
77 height: f64,
78 ) -> Result<u32, JsError> {
79 validate_finite(bottom_radius, "bottom_radius")?;
80 validate_finite(top_radius, "top_radius")?;
81 validate_positive(height, "height")?;
82 let solid_id = brepkit_operations::primitives::make_cone(
83 self.topo_mut(),
84 bottom_radius,
85 top_radius,
86 height,
87 )?;
88 Ok(solid_id_to_u32(solid_id))
89 }
90
91 #[wasm_bindgen(js_name = "makeTorus")]
99 pub fn make_torus_solid(
100 &mut self,
101 major_radius: f64,
102 minor_radius: f64,
103 segments: u32,
104 ) -> Result<u32, JsError> {
105 validate_positive(major_radius, "major_radius")?;
106 validate_positive(minor_radius, "minor_radius")?;
107 let solid_id = brepkit_operations::primitives::make_torus(
108 self.topo_mut(),
109 major_radius,
110 minor_radius,
111 segments as usize,
112 )?;
113 Ok(solid_id_to_u32(solid_id))
114 }
115
116 #[wasm_bindgen(js_name = "makeEllipsoid")]
124 pub fn make_ellipsoid_solid(&mut self, rx: f64, ry: f64, rz: f64) -> Result<u32, JsError> {
125 validate_positive(rx, "rx")?;
126 validate_positive(ry, "ry")?;
127 validate_positive(rz, "rz")?;
128 let solid_id = brepkit_operations::primitives::make_sphere(self.topo_mut(), 1.0, 16)?;
130 let mat = brepkit_math::mat::Mat4::scale(rx, ry, rz);
131 transform_solid(self.topo_mut(), solid_id, &mat)?;
132 Ok(solid_id_to_u32(solid_id))
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 #![allow(clippy::unwrap_used, clippy::expect_used)]
139
140 use crate::kernel::BrepKernel;
141
142 fn batch_has_ok(result: &str, idx: usize) -> bool {
144 let parsed: serde_json::Value = serde_json::from_str(result).unwrap();
145 parsed[idx]["ok"].is_number()
146 }
147
148 fn batch_has_error(result: &str, idx: usize) -> bool {
149 let parsed: serde_json::Value = serde_json::from_str(result).unwrap();
150 parsed[idx]["error"].is_string()
151 }
152
153 #[test]
156 fn make_box_happy_path() {
157 let mut k = BrepKernel::new();
158 let r = k
159 .execute_batch(r#"[{"op": "makeBox", "args": {"width": 1, "height": 2, "depth": 3}}]"#);
160 assert!(batch_has_ok(&r, 0));
161 }
162
163 #[test]
164 fn make_box_negative_width() {
165 let mut k = BrepKernel::new();
166 let r = k.execute_batch(
167 r#"[{"op": "makeBox", "args": {"width": -1, "height": 2, "depth": 3}}]"#,
168 );
169 assert!(batch_has_error(&r, 0));
170 }
171
172 #[test]
173 fn make_box_zero_height() {
174 let mut k = BrepKernel::new();
175 let r = k
176 .execute_batch(r#"[{"op": "makeBox", "args": {"width": 1, "height": 0, "depth": 3}}]"#);
177 assert!(batch_has_error(&r, 0));
178 }
179
180 #[test]
181 fn make_box_volume() {
182 let mut k = BrepKernel::new();
183 let r = k.execute_batch(
184 r#"[
185 {"op": "makeBox", "args": {"width": 2, "height": 3, "depth": 4}},
186 {"op": "volume", "args": {"solid": 0}}
187 ]"#,
188 );
189 let parsed: serde_json::Value = serde_json::from_str(&r).unwrap();
190 let vol = parsed[1]["ok"].as_f64().unwrap();
191 assert!((vol - 24.0).abs() < 0.1, "expected ~24.0, got {vol}");
192 }
193
194 #[test]
197 fn make_cylinder_happy_path() {
198 let mut k = BrepKernel::new();
199 let r = k.execute_batch(r#"[{"op": "makeCylinder", "args": {"radius": 1, "height": 2}}]"#);
200 assert!(batch_has_ok(&r, 0));
201 }
202
203 #[test]
204 fn make_cylinder_negative_radius() {
205 let mut k = BrepKernel::new();
206 let r = k.execute_batch(r#"[{"op": "makeCylinder", "args": {"radius": -1, "height": 2}}]"#);
207 assert!(batch_has_error(&r, 0));
208 }
209
210 #[test]
211 fn make_cylinder_zero_height() {
212 let mut k = BrepKernel::new();
213 let r = k.execute_batch(r#"[{"op": "makeCylinder", "args": {"radius": 1, "height": 0}}]"#);
214 assert!(batch_has_error(&r, 0));
215 }
216
217 #[test]
220 fn make_sphere_happy_path() {
221 let mut k = BrepKernel::new();
222 let r = k.execute_batch(r#"[{"op": "makeSphere", "args": {"radius": 1}}]"#);
223 assert!(batch_has_ok(&r, 0));
224 }
225
226 #[test]
227 fn make_sphere_negative_radius() {
228 let mut k = BrepKernel::new();
229 let r = k.execute_batch(r#"[{"op": "makeSphere", "args": {"radius": -1}}]"#);
230 assert!(batch_has_error(&r, 0));
231 }
232
233 #[test]
236 fn make_cone_happy_path_full_cone() {
237 let mut k = BrepKernel::new();
238 let r = k.execute_batch(
239 r#"[{"op": "makeCone", "args": {"bottomRadius": 2, "topRadius": 0, "height": 3}}]"#,
240 );
241 assert!(batch_has_ok(&r, 0));
242 }
243
244 #[test]
245 fn make_cone_happy_path_frustum() {
246 let mut k = BrepKernel::new();
247 let r = k.execute_batch(
248 r#"[{"op": "makeCone", "args": {"bottomRadius": 2, "topRadius": 1, "height": 3}}]"#,
249 );
250 assert!(batch_has_ok(&r, 0));
251 }
252
253 #[test]
254 fn make_cone_both_radii_zero() {
255 let mut k = BrepKernel::new();
256 let r = k.execute_batch(
257 r#"[{"op": "makeCone", "args": {"bottomRadius": 0, "topRadius": 0, "height": 3}}]"#,
258 );
259 assert!(batch_has_error(&r, 0));
260 }
261
262 #[test]
263 fn make_cone_negative_radius() {
264 let mut k = BrepKernel::new();
265 let r = k.execute_batch(
266 r#"[{"op": "makeCone", "args": {"bottomRadius": -1, "topRadius": 1, "height": 3}}]"#,
267 );
268 assert!(batch_has_error(&r, 0));
269 }
270
271 #[test]
272 fn make_cone_zero_height() {
273 let mut k = BrepKernel::new();
274 let r = k.execute_batch(
275 r#"[{"op": "makeCone", "args": {"bottomRadius": 1, "topRadius": 0.5, "height": 0}}]"#,
276 );
277 assert!(batch_has_error(&r, 0));
278 }
279
280 #[test]
283 fn make_torus_happy_path() {
284 let mut k = BrepKernel::new();
285 let r = k.execute_batch(
286 r#"[{"op": "makeTorus", "args": {"majorRadius": 3, "minorRadius": 1}}]"#,
287 );
288 assert!(batch_has_ok(&r, 0));
289 }
290
291 #[test]
292 fn make_torus_minor_equals_major() {
293 let mut k = BrepKernel::new();
294 let r = k.execute_batch(
295 r#"[{"op": "makeTorus", "args": {"majorRadius": 2, "minorRadius": 2}}]"#,
296 );
297 assert!(batch_has_error(&r, 0));
298 }
299
300 #[test]
301 fn make_torus_minor_greater_than_major() {
302 let mut k = BrepKernel::new();
303 let r = k.execute_batch(
304 r#"[{"op": "makeTorus", "args": {"majorRadius": 1, "minorRadius": 2}}]"#,
305 );
306 assert!(batch_has_error(&r, 0));
307 }
308
309 #[test]
310 fn make_torus_zero_major_radius() {
311 let mut k = BrepKernel::new();
312 let r = k.execute_batch(
313 r#"[{"op": "makeTorus", "args": {"majorRadius": 0, "minorRadius": 1}}]"#,
314 );
315 assert!(batch_has_error(&r, 0));
316 }
317
318 #[test]
321 fn make_ellipsoid_happy_path() {
322 let mut k = BrepKernel::new();
323 let r =
324 k.execute_batch(r#"[{"op": "makeEllipsoid", "args": {"rx": 1, "ry": 2, "rz": 3}}]"#);
325 assert!(batch_has_ok(&r, 0));
326 }
327
328 #[test]
329 fn make_ellipsoid_negative_rx() {
330 let mut k = BrepKernel::new();
331 let r =
332 k.execute_batch(r#"[{"op": "makeEllipsoid", "args": {"rx": -1, "ry": 2, "rz": 3}}]"#);
333 assert!(batch_has_error(&r, 0));
334 }
335
336 #[test]
337 fn make_ellipsoid_zero_ry() {
338 let mut k = BrepKernel::new();
339 let r =
340 k.execute_batch(r#"[{"op": "makeEllipsoid", "args": {"rx": 1, "ry": 0, "rz": 3}}]"#);
341 assert!(batch_has_error(&r, 0));
342 }
343
344 #[test]
347 fn multiple_primitives_independent_handles() {
348 let mut k = BrepKernel::new();
349 let r = k.execute_batch(
350 r#"[
351 {"op": "makeBox", "args": {"width": 1, "height": 1, "depth": 1}},
352 {"op": "makeCylinder", "args": {"radius": 0.5, "height": 2}},
353 {"op": "makeSphere", "args": {"radius": 0.5}}
354 ]"#,
355 );
356 let parsed: serde_json::Value = serde_json::from_str(&r).unwrap();
357 let h0 = parsed[0]["ok"].as_u64().unwrap();
358 let h1 = parsed[1]["ok"].as_u64().unwrap();
359 let h2 = parsed[2]["ok"].as_u64().unwrap();
360 assert_ne!(h0, h1);
361 assert_ne!(h1, h2);
362 assert_ne!(h0, h2);
363 }
364
365 #[test]
366 fn all_six_primitives_in_one_kernel() {
367 let mut k = BrepKernel::new();
368 let r = k.execute_batch(
369 r#"[
370 {"op": "makeBox", "args": {"width": 1, "height": 2, "depth": 3}},
371 {"op": "makeCylinder", "args": {"radius": 1, "height": 2}},
372 {"op": "makeSphere", "args": {"radius": 1}},
373 {"op": "makeCone", "args": {"bottomRadius": 1, "topRadius": 0.5, "height": 2}},
374 {"op": "makeTorus", "args": {"majorRadius": 3, "minorRadius": 1}},
375 {"op": "makeEllipsoid", "args": {"rx": 1, "ry": 2, "rz": 3}}
376 ]"#,
377 );
378 let parsed: serde_json::Value = serde_json::from_str(&r).unwrap();
379 for i in 0..6 {
380 assert!(
381 parsed[i]["ok"].is_number(),
382 "primitive {i} failed: {}",
383 parsed[i]
384 );
385 }
386 }
387
388 #[test]
389 fn invalid_handle_returns_error() {
390 let mut k = BrepKernel::new();
391 let r = k.execute_batch(r#"[{"op": "volume", "args": {"solid": 9999}}]"#);
392 assert!(batch_has_error(&r, 0));
393 }
394}