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