1use super::*;
2
3#[wasm_bindgen]
4pub struct WasmBrepSolid {
5 pub(crate) solid: BrepSolid,
6}
7
8#[wasm_bindgen]
9impl WasmBrepSolid {
10 #[wasm_bindgen(constructor)]
11 pub fn new(solid_json: &str) -> Result<WasmBrepSolid, JsValue> {
12 let solid: BrepSolid = json_in(solid_json)?;
13 let solid = validate_imported_solid(solid, "WasmBrepSolid")?;
14 Ok(Self { solid })
15 }
16
17 pub fn from_buffer(data: &[f64], names_json: &str) -> Result<WasmBrepSolid, JsValue> {
20 let solid = decode_solid_buffer(data, names_json)?;
21 let solid = validate_imported_solid(solid, "WasmBrepSolid")?;
22 Ok(Self { solid })
23 }
24
25 pub fn to_buffer(&self) -> Result<WasmSolidBuffer, JsValue> {
26 solid_buffer(&self.solid, "{}".into())
27 }
28
29 pub fn to_json(&self) -> Result<String, JsValue> {
30 json_out(&self.solid)
31 }
32
33 pub fn validate_json(&self) -> Result<String, JsValue> {
34 json_out(&self.solid.validate())
35 }
36
37 pub fn mass_properties_json(&self) -> Result<String, JsValue> {
38 let properties = solid_mass_properties(&self.solid).map_err(javascript_error)?;
39 json_out(&properties)
40 }
41
42 pub fn full_mass_properties_json(&self) -> Result<String, JsValue> {
43 let properties = solid_mass_properties_full(&self.solid).map_err(javascript_error)?;
44 json_out(&properties)
45 }
46
47 pub fn full_mass_properties_with_density_json(&self, density: f64) -> Result<String, JsValue> {
52 let properties = solid_mass_properties_full(&self.solid).map_err(javascript_error)?;
53 json_out(&properties.with_density(density))
54 }
55
56 pub fn tessellate_json(
57 &self,
58 slabs_per_span_u: usize,
59 steps_per_span_v: usize,
60 ) -> Result<String, JsValue> {
61 let mesh = tessellate_brep(
62 &self.solid,
63 TessellationOptions {
64 slabs_per_span_u: slabs_per_span_u.max(1),
65 steps_per_span_v: steps_per_span_v.max(1),
66 },
67 )
68 .map_err(javascript_error)?;
69 json_out(&mesh)
70 }
71
72 pub fn transform_json(
73 &self,
74 matrix: &[f64],
75 reverse_orientation: bool,
76 ) -> Result<String, JsValue> {
77 let matrix: [f64; 16] = matrix
78 .try_into()
79 .map_err(|_| javascript_error("transform matrix must contain 16 values".into()))?;
80 let transform = AffineTransform::new(matrix).map_err(javascript_error)?;
81 let solid = transform_brep(&self.solid, transform, reverse_orientation)
82 .map_err(javascript_error)?;
83 json_out(&solid)
84 }
85
86 pub fn transform_buffer(
87 &self,
88 matrix: &[f64],
89 reverse_orientation: bool,
90 ) -> Result<WasmSolidBuffer, JsValue> {
91 let matrix: [f64; 16] = matrix
92 .try_into()
93 .map_err(|_| javascript_error("transform matrix must contain 16 values".into()))?;
94 let transform = AffineTransform::new(matrix).map_err(javascript_error)?;
95 let solid = transform_brep(&self.solid, transform, reverse_orientation)
96 .map_err(javascript_error)?;
97 solid_buffer(&solid, "{}".into())
98 }
99
100 pub fn boolean_buffer(
101 &self,
102 other: &WasmBrepSolid,
103 operation: &str,
104 tolerance: f64,
105 merge_coplanar_faces: bool,
106 ) -> Result<WasmSolidBuffer, JsValue> {
107 let operation = match operation {
108 "union" => BooleanOperation::Union,
109 "intersect" => BooleanOperation::Intersect,
110 "subtract" => BooleanOperation::Subtract,
111 _ => return Err(javascript_error("unknown boolean operation".into())),
112 };
113 let solid = boolean_operation(
114 &self.solid,
115 &other.solid,
116 operation,
117 &BooleanOptions {
118 tolerance,
119 merge_coplanar_faces,
120 ..BooleanOptions::default()
121 },
122 )
123 .map_err(javascript_refusal)?;
124 solid_buffer(&solid, "{}".into())
125 }
126
127 pub fn offset_shell_buffer(
128 &self,
129 opening_face_ids: &[f64],
130 distance: f64,
131 ) -> Result<WasmSolidBuffer, JsValue> {
132 crate::panic_hook::set_once();
133 let face_ids = opening_face_ids
134 .iter()
135 .map(|id| {
136 if !id.is_finite() || *id < 0.0 || id.fract() != 0.0 {
137 Err(javascript_error("opening face IDs must be integers".into()))
138 } else {
139 Ok(*id as u64)
140 }
141 })
142 .collect::<Result<Vec<_>, _>>()?;
143 let result = offset_shell(&self.solid, &face_ids, distance).map_err(javascript_refusal)?;
144 let metadata = serde_json::json!({ "face_images": result.face_images });
145 solid_buffer(&result.solid, metadata.to_string())
146 }
147
148 pub fn tessellate_watertight_buffers(
149 &self,
150 chord_tolerance: f64,
151 ) -> Result<WasmMeshBuffers, JsValue> {
152 let mesh =
153 tessellate_brep_watertight(&self.solid, chord_tolerance).map_err(javascript_error)?;
154 Ok(WasmMeshBuffers {
155 positions: mesh.positions,
156 normals: mesh.normals,
157 indices: mesh.indices,
158 face_ids: mesh.face_ids,
159 })
160 }
161
162 pub fn tessellate_watertight_face_stride_buffers(
170 &self,
171 chord_tolerance: f64,
172 stride: usize,
173 offset: usize,
174 ) -> Result<WasmMeshBuffers, JsValue> {
175 let mesh =
176 tessellate_brep_watertight_face_stride(&self.solid, chord_tolerance, stride, offset)
177 .map_err(javascript_error)?;
178 Ok(WasmMeshBuffers {
179 positions: mesh.positions,
180 normals: mesh.normals,
181 indices: mesh.indices,
182 face_ids: mesh.face_ids,
183 })
184 }
185
186 pub fn sample_edges_buffer(&self, chord_tolerance: f64) -> Result<Vec<f64>, JsValue> {
190 sample_edges_encoded(&self.solid, chord_tolerance).map_err(javascript_error)
191 }
192
193 pub fn tessellate_watertight_face_stride_with_samples_buffers(
196 &self,
197 chord_tolerance: f64,
198 stride: usize,
199 offset: usize,
200 samples: &[f64],
201 ) -> Result<WasmMeshBuffers, JsValue> {
202 let mesh = tessellate_brep_watertight_face_stride_with_samples(
203 &self.solid,
204 chord_tolerance,
205 stride,
206 offset,
207 samples,
208 )
209 .map_err(javascript_error)?;
210 Ok(WasmMeshBuffers {
211 positions: mesh.positions,
212 normals: mesh.normals,
213 indices: mesh.indices,
214 face_ids: mesh.face_ids,
215 })
216 }
217
218 pub fn tessellate_buffers(
219 &self,
220 slabs_per_span_u: usize,
221 steps_per_span_v: usize,
222 ) -> Result<WasmMeshBuffers, JsValue> {
223 let mesh = tessellate_brep(
224 &self.solid,
225 TessellationOptions {
226 slabs_per_span_u: slabs_per_span_u.max(1),
227 steps_per_span_v: steps_per_span_v.max(1),
228 },
229 )
230 .map_err(javascript_error)?;
231 Ok(WasmMeshBuffers {
232 positions: mesh.positions,
233 normals: mesh.normals,
234 indices: mesh.indices,
235 face_ids: mesh.face_ids,
236 })
237 }
238
239 pub fn boolean_json(
240 &self,
241 other: &WasmBrepSolid,
242 operation: &str,
243 tolerance: f64,
244 merge_coplanar_faces: bool,
245 ) -> Result<String, JsValue> {
246 let operation = match operation {
247 "union" => BooleanOperation::Union,
248 "intersect" => BooleanOperation::Intersect,
249 "subtract" => BooleanOperation::Subtract,
250 _ => return Err(javascript_error("unknown boolean operation".into())),
251 };
252 let solid = boolean_operation(
253 &self.solid,
254 &other.solid,
255 operation,
256 &BooleanOptions {
257 tolerance,
258 merge_coplanar_faces,
259 ..BooleanOptions::default()
260 },
261 )
262 .map_err(javascript_refusal)?;
263 json_out(&solid)
264 }
265
266 pub fn offset_shell_json(
267 &self,
268 opening_face_ids: &[f64],
269 distance: f64,
270 ) -> Result<String, JsValue> {
271 crate::panic_hook::set_once();
272 let face_ids = opening_face_ids
273 .iter()
274 .map(|id| {
275 if !id.is_finite() || *id < 0.0 || id.fract() != 0.0 {
276 Err(javascript_error("opening face IDs must be integers".into()))
277 } else {
278 Ok(*id as u64)
279 }
280 })
281 .collect::<Result<Vec<_>, _>>()?;
282 let result = offset_shell(&self.solid, &face_ids, distance).map_err(javascript_refusal)?;
283 json_out(&result)
284 }
285
286 pub fn delete_face_and_heal_buffer(&self, face_id: f64) -> Result<WasmSolidBuffer, JsValue> {
291 crate::panic_hook::set_once();
292 if !face_id.is_finite() || face_id < 0.0 || face_id.fract() != 0.0 {
293 return Err(javascript_error(
294 "delete_face_and_heal: face id must be a non-negative integer".into(),
295 ));
296 }
297 let solid = delete_face_and_heal(&self.solid, face_id as u64).map_err(javascript_refusal)?;
298 solid_buffer(&solid, "{}".into())
299 }
300
301 pub fn delete_face_and_heal_json(&self, face_id: f64) -> Result<String, JsValue> {
302 crate::panic_hook::set_once();
303 if !face_id.is_finite() || face_id < 0.0 || face_id.fract() != 0.0 {
304 return Err(javascript_error(
305 "delete_face_and_heal: face id must be a non-negative integer".into(),
306 ));
307 }
308 let solid = delete_face_and_heal(&self.solid, face_id as u64).map_err(javascript_refusal)?;
309 json_out(&solid)
310 }
311
312 pub fn move_faces_json(
317 &self,
318 face_ids: &[f64],
319 dx: f64,
320 dy: f64,
321 dz: f64,
322 ) -> Result<String, JsValue> {
323 crate::panic_hook::set_once();
324 let face_ids = face_ids
325 .iter()
326 .map(|id| {
327 if !id.is_finite() || *id < 0.0 || id.fract() != 0.0 {
328 Err(javascript_error(
329 "move_faces: face ids must be non-negative integers".into(),
330 ))
331 } else {
332 Ok(*id as u64)
333 }
334 })
335 .collect::<Result<Vec<_>, _>>()?;
336 let solid =
337 move_faces(&self.solid, &face_ids, Vec3::new(dx, dy, dz)).map_err(javascript_refusal)?;
338 json_out(&solid)
339 }
340
341 pub fn export_step(&self, name: &str, unit: &str, timestamp: &str) -> Result<String, JsValue> {
342 export_step(std::slice::from_ref(&self.solid), name, unit, timestamp)
343 .map_err(javascript_error)
344 }
345
346 pub fn sew_json(&self, tolerance: f64) -> Result<String, JsValue> {
351 crate::panic_hook::set_once();
352 let tolerance = if tolerance.is_finite() && tolerance > 0.0 {
353 tolerance
354 } else {
355 1e-4
356 };
357 let (solid, report) = sew_solid(&self.solid, tolerance).map_err(javascript_refusal)?;
358 json_out(&serde_json::json!({ "solid": solid, "report": report }))
359 }
360}
361
362#[wasm_bindgen]
363impl WasmNurbsSurface {
364 #[wasm_bindgen(constructor)]
365 pub fn new(surface_json: &str) -> Result<WasmNurbsSurface, JsValue> {
366 let serialized: NurbsSurface = json_in(surface_json)?;
367 let surface = NurbsSurface::new(
368 serialized.degree_u,
369 serialized.degree_v,
370 serialized.knots_u,
371 serialized.knots_v,
372 serialized.control_points,
373 )
374 .map_err(javascript_error)?;
375 Ok(Self { surface })
376 }
377
378 pub fn evaluate_many(&self, parameters_uv: &[f64]) -> Result<Vec<f64>, JsValue> {
379 if parameters_uv.len() % 2 != 0 {
380 return Err(javascript_error(
381 "surface parameter buffer must contain u,v pairs".into(),
382 ));
383 }
384 let mut points = Vec::with_capacity(parameters_uv.len() / 2 * 3);
385 for pair in parameters_uv.chunks_exact(2) {
386 let point = self
387 .surface
388 .evaluate(pair[0], pair[1])
389 .map_err(javascript_error)?;
390 points.extend([point.x, point.y, point.z]);
391 }
392 Ok(points)
393 }
394
395 pub fn normals_many(&self, parameters_uv: &[f64]) -> Result<Vec<f64>, JsValue> {
396 if parameters_uv.len() % 2 != 0 {
397 return Err(javascript_error(
398 "surface parameter buffer must contain u,v pairs".into(),
399 ));
400 }
401 let mut normals = Vec::with_capacity(parameters_uv.len() / 2 * 3);
402 for pair in parameters_uv.chunks_exact(2) {
403 let normal = self
404 .surface
405 .normal(pair[0], pair[1])
406 .map_err(javascript_error)?;
407 normals.extend([normal.x, normal.y, normal.z]);
408 }
409 Ok(normals)
410 }
411
412 pub fn derivatives_flat(
413 &self,
414 u: f64,
415 v: f64,
416 derivative_count: usize,
417 ) -> Result<Vec<f64>, JsValue> {
418 let derivatives = self
419 .surface
420 .derivatives(u, v, derivative_count)
421 .map_err(javascript_error)?;
422 let side = derivative_count + 1;
423 let mut flat = vec![0.0; side * side * 3];
424 for k in 0..side {
425 for l in 0..side {
426 let offset = (k * side + l) * 3;
427 flat[offset] = derivatives[k][l].x;
428 flat[offset + 1] = derivatives[k][l].y;
429 flat[offset + 2] = derivatives[k][l].z;
430 }
431 }
432 Ok(flat)
433 }
434}
435
436#[wasm_bindgen]
437impl WasmNurbsCurve {
438 #[wasm_bindgen(constructor)]
439 pub fn new(curve_json: &str) -> Result<WasmNurbsCurve, JsValue> {
440 let serialized: NurbsCurve = json_in(curve_json)?;
441 let curve = NurbsCurve::new(
442 serialized.degree,
443 serialized.knots,
444 serialized.control_points,
445 )
446 .map_err(javascript_error)?;
447 Ok(Self { curve })
448 }
449
450 pub fn evaluate_many(&self, parameters: &[f64]) -> Result<Vec<f64>, JsValue> {
451 let mut points = Vec::with_capacity(parameters.len() * 3);
452 for parameter in parameters {
453 let point = self.curve.evaluate(*parameter).map_err(javascript_error)?;
454 points.extend([point.x, point.y, point.z]);
455 }
456 Ok(points)
457 }
458
459 pub fn derivatives_flat(
460 &self,
461 parameter: f64,
462 derivative_count: usize,
463 ) -> Result<Vec<f64>, JsValue> {
464 let derivatives = self
465 .curve
466 .derivatives(parameter, derivative_count)
467 .map_err(javascript_error)?;
468 let mut result = Vec::with_capacity(derivatives.len() * 3);
469 for derivative in derivatives {
470 result.extend([derivative.x, derivative.y, derivative.z]);
471 }
472 Ok(result)
473 }
474}