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