1#![allow(clippy::missing_errors_doc, clippy::too_many_lines)]
4
5use std::f64::consts::PI;
6
7use wasm_bindgen::prelude::*;
8
9use brepkit_math::vec::{Point3, Vec3};
10use brepkit_topology::edge::EdgeCurve;
11use brepkit_topology::face::{Face, FaceSurface};
12
13use crate::error::{WasmError, validate_finite};
14use crate::handles::{
15 edge_id_to_u32, face_id_to_u32, shell_id_to_u32, solid_id_to_u32, vertex_id_to_u32,
16 wire_id_to_u32,
17};
18use brepkit_geometry::convert::{DetectedCurveKind, detect_curve_kind, detect_surface_kind};
19
20use crate::helpers::sample_full_period_curve;
21use crate::kernel::BrepKernel;
22
23#[wasm_bindgen]
24impl BrepKernel {
25 #[wasm_bindgen(js_name = "getSolidFaces")]
35 pub fn get_solid_faces(&self, solid: u32) -> Result<Vec<u32>, JsError> {
36 let solid_id = self.resolve_solid(solid)?;
37 let faces = brepkit_topology::explorer::solid_faces(&self.topo, solid_id)?;
38 #[allow(clippy::cast_possible_truncation)]
39 Ok(faces.iter().map(|f| f.index() as u32).collect())
40 }
41
42 #[wasm_bindgen(js_name = "getSolidEdges")]
50 pub fn get_solid_edges(&self, solid: u32) -> Result<Vec<u32>, JsError> {
51 let solid_id = self.resolve_solid(solid)?;
52 let edges = brepkit_topology::explorer::solid_edges(&self.topo, solid_id)?;
53 #[allow(clippy::cast_possible_truncation)]
54 Ok(edges.iter().map(|e| e.index() as u32).collect())
55 }
56
57 #[wasm_bindgen(js_name = "getSolidVertices")]
65 pub fn get_solid_vertices(&self, solid: u32) -> Result<Vec<u32>, JsError> {
66 let solid_id = self.resolve_solid(solid)?;
67 let verts = brepkit_topology::explorer::solid_vertices(&self.topo, solid_id)?;
68 #[allow(clippy::cast_possible_truncation)]
69 Ok(verts.iter().map(|v| v.index() as u32).collect())
70 }
71
72 #[wasm_bindgen(js_name = "getSolidShells")]
82 pub fn get_solid_shells(&self, solid: u32) -> Result<Vec<u32>, JsError> {
83 let solid_id = self.resolve_solid(solid)?;
84 let solid_data = self.topo.solid(solid_id)?;
85 let mut shells = Vec::with_capacity(1 + solid_data.inner_shells().len());
86 shells.push(shell_id_to_u32(solid_data.outer_shell()));
87 shells.extend(
88 solid_data
89 .inner_shells()
90 .iter()
91 .map(|s| shell_id_to_u32(*s)),
92 );
93 Ok(shells)
94 }
95
96 #[wasm_bindgen(js_name = "getEdgeVertices")]
104 pub fn get_edge_vertices(&self, edge: u32) -> Result<Vec<f64>, JsError> {
105 let edge_id = self.resolve_edge(edge)?;
106 let edge_data = self.topo.edge(edge_id)?;
107 let start = self.topo.vertex(edge_data.start())?.point();
108 let end = self.topo.vertex(edge_data.end())?.point();
109 Ok(vec![
110 start.x(),
111 start.y(),
112 start.z(),
113 end.x(),
114 end.y(),
115 end.z(),
116 ])
117 }
118
119 #[wasm_bindgen(js_name = "getEdgeVertexHandles")]
127 pub fn get_edge_vertex_handles(&self, edge: u32) -> Result<Vec<u32>, JsError> {
128 let edge_id = self.resolve_edge(edge)?;
129 let edge_data = self.topo.edge(edge_id)?;
130 Ok(vec![
131 vertex_id_to_u32(edge_data.start()),
132 vertex_id_to_u32(edge_data.end()),
133 ])
134 }
135
136 #[wasm_bindgen(js_name = "getVertexPosition")]
144 pub fn get_vertex_position(&self, vertex: u32) -> Result<Vec<f64>, JsError> {
145 let vertex_id = self.resolve_vertex(vertex)?;
146 let point = self.topo.vertex(vertex_id)?.point();
147 Ok(vec![point.x(), point.y(), point.z()])
148 }
149
150 #[wasm_bindgen(js_name = "toBREP")]
159 pub fn to_brep(&self, solid: u32) -> Result<JsValue, JsError> {
160 let solid_id = self.resolve_solid(solid)?;
161 let step_str = brepkit_io::step::writer::write_step(&self.topo, &[solid_id])
162 .map_err(|e| JsError::new(&e.to_string()))?;
163 Ok(step_str.into())
164 }
165
166 #[wasm_bindgen(js_name = "toBrepJson")]
172 #[allow(clippy::too_many_lines)]
173 pub fn to_brep_json(&self, solid: u32) -> Result<JsValue, JsError> {
174 let solid_id = self.resolve_solid(solid)?;
175 let faces = brepkit_topology::explorer::solid_faces(&self.topo, solid_id)?;
176 let edges = brepkit_topology::explorer::solid_edges(&self.topo, solid_id)?;
177 let verts = brepkit_topology::explorer::solid_vertices(&self.topo, solid_id)?;
178
179 let vert_json: Vec<serde_json::Value> = verts
180 .iter()
181 .map(|&vid| -> Result<serde_json::Value, JsError> {
182 let v = self.topo.vertex(vid)?;
183 let p = v.point();
184 Ok(serde_json::json!({
185 "id": vertex_id_to_u32(vid),
186 "position": [p.x(), p.y(), p.z()],
187 }))
188 })
189 .collect::<Result<_, _>>()?;
190
191 let edge_json: Vec<serde_json::Value> = edges
192 .iter()
193 .map(|&eid| -> Result<serde_json::Value, JsError> {
194 let e = self.topo.edge(eid)?;
195 let curve_type = match e.curve() {
196 EdgeCurve::Line => "line",
197 EdgeCurve::Circle(_) => "circle",
198 EdgeCurve::Ellipse(_) => "ellipse",
199 EdgeCurve::NurbsCurve(_) => "nurbs",
200 };
201 let curve_params = match e.curve() {
202 EdgeCurve::Line => serde_json::json!(null),
203 EdgeCurve::Circle(c) => serde_json::json!({
204 "center": [c.center().x(), c.center().y(), c.center().z()],
205 "axis": [c.normal().x(), c.normal().y(), c.normal().z()],
206 "xAxis": [c.u_axis().x(), c.u_axis().y(), c.u_axis().z()],
207 "radius": c.radius(),
208 }),
209 EdgeCurve::Ellipse(el) => serde_json::json!({
210 "center": [el.center().x(), el.center().y(), el.center().z()],
211 "axis": [el.normal().x(), el.normal().y(), el.normal().z()],
212 "majorAxis": [el.u_axis().x(), el.u_axis().y(), el.u_axis().z()],
213 "majorRadius": el.semi_major(),
214 "minorRadius": el.semi_minor(),
215 }),
216 EdgeCurve::NurbsCurve(n) => serde_json::json!({
217 "degree": n.degree(),
218 "controlPoints": n.control_points().iter()
219 .map(|p| [p.x(), p.y(), p.z()])
220 .collect::<Vec<_>>(),
221 "weights": n.weights().to_vec(),
222 "knots": n.knots().to_vec(),
223 }),
224 };
225 Ok(serde_json::json!({
226 "id": edge_id_to_u32(eid),
227 "curveType": curve_type,
228 "curveParams": curve_params,
229 "startVertex": vertex_id_to_u32(e.start()),
230 "endVertex": vertex_id_to_u32(e.end()),
231 }))
232 })
233 .collect::<Result<_, _>>()?;
234
235 let face_json: Vec<serde_json::Value> = faces
236 .iter()
237 .map(|&fid| -> Result<serde_json::Value, JsError> {
238 let f = self.topo.face(fid)?;
239 let surface_type = match f.surface() {
240 brepkit_topology::face::FaceSurface::Plane { .. } => "plane",
241 brepkit_topology::face::FaceSurface::Nurbs(_) => "nurbs",
242 brepkit_topology::face::FaceSurface::Cylinder(_) => "cylinder",
243 brepkit_topology::face::FaceSurface::Cone(_) => "cone",
244 brepkit_topology::face::FaceSurface::Sphere(_) => "sphere",
245 brepkit_topology::face::FaceSurface::Torus(_) => "torus",
246 };
247 let surface_params = match f.surface() {
248 FaceSurface::Plane { normal, d } => serde_json::json!({
249 "normal": [normal.x(), normal.y(), normal.z()],
250 "d": d,
251 }),
252 FaceSurface::Cylinder(c) => serde_json::json!({
253 "origin": [c.origin().x(), c.origin().y(), c.origin().z()],
254 "axis": [c.axis().x(), c.axis().y(), c.axis().z()],
255 "refDir": [c.x_axis().x(), c.x_axis().y(), c.x_axis().z()],
256 "radius": c.radius(),
257 }),
258 FaceSurface::Cone(c) => serde_json::json!({
259 "apex": [c.apex().x(), c.apex().y(), c.apex().z()],
260 "axis": [c.axis().x(), c.axis().y(), c.axis().z()],
261 "refDir": [c.x_axis().x(), c.x_axis().y(), c.x_axis().z()],
262 "halfAngle": c.half_angle(),
263 }),
264 FaceSurface::Sphere(s) => serde_json::json!({
265 "center": [s.center().x(), s.center().y(), s.center().z()],
266 "axis": [s.z_axis().x(), s.z_axis().y(), s.z_axis().z()],
267 "radius": s.radius(),
268 }),
269 FaceSurface::Torus(t) => serde_json::json!({
270 "center": [t.center().x(), t.center().y(), t.center().z()],
271 "axis": [t.z_axis().x(), t.z_axis().y(), t.z_axis().z()],
272 "majorRadius": t.major_radius(),
273 "minorRadius": t.minor_radius(),
274 }),
275 FaceSurface::Nurbs(n) => {
276 let cps: Vec<Vec<serde_json::Value>> = n
277 .control_points()
278 .iter()
279 .map(|row| {
280 row.iter()
281 .map(|p| serde_json::json!([p.x(), p.y(), p.z()]))
282 .collect()
283 })
284 .collect();
285 serde_json::json!({
286 "degreeU": n.degree_u(),
287 "degreeV": n.degree_v(),
288 "controlPoints": cps,
289 "weights": n.weights(),
290 "knotsU": n.knots_u(),
291 "knotsV": n.knots_v(),
292 })
293 }
294 };
295 let outer_wire = self.topo.wire(f.outer_wire())?;
296 let outer_edges: Vec<u32> = outer_wire
297 .edges()
298 .iter()
299 .map(|e| edge_id_to_u32(e.edge()))
300 .collect();
301 let outer_edge_orientations: Vec<bool> = outer_wire
302 .edges()
303 .iter()
304 .map(brepkit_topology::wire::OrientedEdge::is_forward)
305 .collect();
306 let inner_wires: Vec<serde_json::Value> = f
307 .inner_wires()
308 .iter()
309 .filter_map(|&wid| {
310 self.topo.wire(wid).ok().map(|w| {
311 let edges: Vec<u32> =
312 w.edges().iter().map(|e| edge_id_to_u32(e.edge())).collect();
313 let orientations: Vec<bool> = w
314 .edges()
315 .iter()
316 .map(brepkit_topology::wire::OrientedEdge::is_forward)
317 .collect();
318 serde_json::json!({
319 "edges": edges,
320 "orientations": orientations,
321 })
322 })
323 })
324 .collect();
325 Ok(serde_json::json!({
326 "id": face_id_to_u32(fid),
327 "surfaceType": surface_type,
328 "surfaceParams": surface_params,
329 "reversed": f.is_reversed(),
330 "outerWireEdges": outer_edges,
331 "outerWireOrientations": outer_edge_orientations,
332 "innerWires": inner_wires,
333 }))
334 })
335 .collect::<Result<_, _>>()?;
336
337 Ok(serde_json::to_string(&serde_json::json!({
338 "type": "solid",
339 "solidId": solid_id_to_u32(solid_id),
340 "vertices": vert_json,
341 "edges": edge_json,
342 "faces": face_json,
343 }))
344 .map_err(|e| JsError::new(&e.to_string()))?
345 .into())
346 }
347
348 #[wasm_bindgen(js_name = "fromBREP")]
361 #[allow(clippy::wrong_self_convention)]
362 pub fn from_brep(&mut self, data: &str) -> Result<u32, JsError> {
363 let trimmed = data.trim_start();
364 if trimmed.starts_with('{') {
365 Ok(self.from_brep_impl(data)?)
367 } else {
368 let solids = brepkit_io::step::reader::read_step(data, self.topo_mut())
370 .map_err(|e| JsError::new(&e.to_string()))?;
371 let first = solids
372 .first()
373 .ok_or_else(|| JsError::new("fromBREP: STEP data produced no solids"))?;
374 #[allow(clippy::cast_possible_truncation)]
375 Ok(first.index() as u32)
376 }
377 }
378
379 #[wasm_bindgen(js_name = "getFaceNormal")]
387 pub fn get_face_normal(&self, face: u32) -> Result<Vec<f64>, JsError> {
388 let face_id = self.resolve_face(face)?;
389 let face_data = self.topo.face(face_id)?;
390 match face_data.surface() {
391 brepkit_topology::face::FaceSurface::Plane { normal, .. } => {
392 Ok(vec![normal.x(), normal.y(), normal.z()])
393 }
394 _ => Err(WasmError::InvalidInput {
395 reason: "getFaceNormal only works on planar faces".into(),
396 }
397 .into()),
398 }
399 }
400
401 #[wasm_bindgen(js_name = "getEntityCounts")]
407 pub fn get_entity_counts(&self, solid: u32) -> Result<Vec<u32>, JsError> {
408 let solid_id = self.resolve_solid(solid)?;
409 let (f, e, v) = brepkit_topology::explorer::solid_entity_counts(&self.topo, solid_id)?;
410 #[allow(clippy::cast_possible_truncation)]
411 Ok(vec![f as u32, e as u32, v as u32])
412 }
413
414 #[wasm_bindgen(js_name = "getFaceEdges")]
420 pub fn get_face_edges(&self, face: u32) -> Result<Vec<u32>, JsError> {
421 let face_id = self.resolve_face(face)?;
422 let edges = brepkit_topology::explorer::face_edges(&self.topo, face_id)?;
423 #[allow(clippy::cast_possible_truncation)]
424 Ok(edges.iter().map(|e| e.index() as u32).collect())
425 }
426
427 #[wasm_bindgen(js_name = "getFaceVertices")]
431 pub fn get_face_vertices(&self, face: u32) -> Result<Vec<u32>, JsError> {
432 let face_id = self.resolve_face(face)?;
433 let verts = brepkit_topology::explorer::face_vertices(&self.topo, face_id)?;
434 #[allow(clippy::cast_possible_truncation)]
435 Ok(verts.iter().map(|v| v.index() as u32).collect())
436 }
437
438 #[wasm_bindgen(js_name = "getFaceOuterWire")]
442 pub fn get_face_outer_wire(&self, face: u32) -> Result<u32, JsError> {
443 let face_id = self.resolve_face(face)?;
444 let face_data = self.topo.face(face_id)?;
445 Ok(wire_id_to_u32(face_data.outer_wire()))
446 }
447
448 #[wasm_bindgen(js_name = "getFaceWires")]
453 pub fn get_face_wires(&self, face: u32) -> Result<Vec<u32>, JsError> {
454 let face_id = self.resolve_face(face)?;
455 let face_data = self.topo.face(face_id)?;
456 let mut wires = vec![wire_id_to_u32(face_data.outer_wire())];
457 for &iw in face_data.inner_wires() {
458 wires.push(wire_id_to_u32(iw));
459 }
460 Ok(wires)
461 }
462
463 #[wasm_bindgen(js_name = "getSurfaceType")]
472 pub fn get_surface_type(&self, face: u32) -> Result<String, JsError> {
473 let face_id = self.resolve_face(face)?;
474 let face_data = self.topo.face(face_id)?;
475 Ok(match face_data.surface() {
476 FaceSurface::Plane { .. } => "plane",
477 FaceSurface::Nurbs(ns) => detect_surface_kind(ns).as_str(),
478 FaceSurface::Cylinder(_) => "cylinder",
479 FaceSurface::Cone(_) => "cone",
480 FaceSurface::Sphere(_) => "sphere",
481 FaceSurface::Torus(_) => "torus",
482 }
483 .into())
484 }
485
486 #[wasm_bindgen(js_name = "getEdgeCurveType")]
494 pub fn get_edge_curve_type(&self, edge: u32) -> Result<String, JsError> {
495 let edge_id = self.resolve_edge(edge)?;
496 let edge_data = self.topo.edge(edge_id)?;
497 Ok(match edge_data.curve() {
498 EdgeCurve::Line => "LINE",
499 EdgeCurve::NurbsCurve(nc) => match detect_curve_kind(nc) {
500 DetectedCurveKind::Line => "LINE",
501 DetectedCurveKind::Circle => "CIRCLE",
502 DetectedCurveKind::BSpline => "BSPLINE_CURVE",
503 },
504 EdgeCurve::Circle(_) => "CIRCLE",
505 EdgeCurve::Ellipse(_) => "ELLIPSE",
506 }
507 .into())
508 }
509
510 #[wasm_bindgen(js_name = "getEdgeCurveParameters")]
516 pub fn get_edge_curve_parameters(&self, edge: u32) -> Result<Vec<f64>, JsError> {
517 let edge_id = self.resolve_edge(edge)?;
518 let edge_data = self.topo.edge(edge_id)?;
519 match edge_data.curve() {
520 EdgeCurve::Line => {
521 let start = self.topo.vertex(edge_data.start())?.point();
522 let end = self.topo.vertex(edge_data.end())?.point();
523 let len = (end - start).length();
524 Ok(vec![0.0, len])
525 }
526 EdgeCurve::NurbsCurve(curve) => {
527 let (u_start, u_end) = curve.domain();
528 Ok(vec![u_start, u_end])
529 }
530 EdgeCurve::Circle(_) | EdgeCurve::Ellipse(_) => Ok(vec![0.0, std::f64::consts::TAU]),
531 }
532 }
533
534 #[wasm_bindgen(js_name = "evaluateEdgeCurve")]
538 pub fn evaluate_edge_curve(&self, edge: u32, t: f64) -> Result<Vec<f64>, JsError> {
539 validate_finite(t, "t")?;
540 let edge_id = self.resolve_edge(edge)?;
541 let edge_data = self.topo.edge(edge_id)?;
542 let point = match edge_data.curve() {
543 EdgeCurve::Line => {
544 let start = self.topo.vertex(edge_data.start())?.point();
545 let end = self.topo.vertex(edge_data.end())?.point();
546 let len = (end - start).length();
547 if len < 1e-15 {
548 start
549 } else {
550 let frac = t / len;
551 let dir = end - start;
552 Point3::new(
553 start.x() + dir.x() * frac,
554 start.y() + dir.y() * frac,
555 start.z() + dir.z() * frac,
556 )
557 }
558 }
559 EdgeCurve::NurbsCurve(curve) => curve.evaluate(t),
560 EdgeCurve::Circle(circle) => circle.evaluate(t),
561 EdgeCurve::Ellipse(ellipse) => ellipse.evaluate(t),
562 };
563 Ok(vec![point.x(), point.y(), point.z()])
564 }
565
566 #[wasm_bindgen(js_name = "evaluateEdgeCurveD1")]
570 pub fn evaluate_edge_curve_d1(&self, edge: u32, t: f64) -> Result<Vec<f64>, JsError> {
571 validate_finite(t, "t")?;
572 let edge_id = self.resolve_edge(edge)?;
573 let edge_data = self.topo.edge(edge_id)?;
574 match edge_data.curve() {
575 EdgeCurve::Line => {
576 let start = self.topo.vertex(edge_data.start())?.point();
577 let end = self.topo.vertex(edge_data.end())?.point();
578 let dir = end - start;
579 let len = dir.length();
580 let frac = if len < 1e-15 { 0.0 } else { t / len };
581 let point = Point3::new(
582 start.x() + dir.x() * frac,
583 start.y() + dir.y() * frac,
584 start.z() + dir.z() * frac,
585 );
586 let tangent = if len < 1e-15 {
587 Vec3::new(1.0, 0.0, 0.0)
588 } else {
589 Vec3::new(dir.x() / len, dir.y() / len, dir.z() / len)
590 };
591 Ok(vec![
592 point.x(),
593 point.y(),
594 point.z(),
595 tangent.x(),
596 tangent.y(),
597 tangent.z(),
598 ])
599 }
600 EdgeCurve::NurbsCurve(curve) => {
601 let point = curve.evaluate(t);
602 let derivs = curve.derivatives(t, 1);
603 let tangent = if derivs.len() > 1 {
604 derivs[1]
605 } else {
606 Vec3::new(1.0, 0.0, 0.0)
607 };
608 Ok(vec![
609 point.x(),
610 point.y(),
611 point.z(),
612 tangent.x(),
613 tangent.y(),
614 tangent.z(),
615 ])
616 }
617 EdgeCurve::Circle(circle) => {
618 let point = circle.evaluate(t);
619 let tangent = circle.tangent(t);
620 Ok(vec![
621 point.x(),
622 point.y(),
623 point.z(),
624 tangent.x(),
625 tangent.y(),
626 tangent.z(),
627 ])
628 }
629 EdgeCurve::Ellipse(ellipse) => {
630 let point = ellipse.evaluate(t);
631 let tangent = ellipse.tangent(t);
632 Ok(vec![
633 point.x(),
634 point.y(),
635 point.z(),
636 tangent.x(),
637 tangent.y(),
638 tangent.z(),
639 ])
640 }
641 }
642 }
643
644 #[wasm_bindgen(js_name = "measureCurvatureAtEdge")]
649 pub fn measure_curvature_at_edge(&self, edge: u32, t: f64) -> Result<Vec<f64>, JsError> {
650 validate_finite(t, "t")?;
651 let edge_id = self.resolve_edge(edge)?;
652 let edge_data = self.topo.edge(edge_id)?;
653 match edge_data.curve() {
654 EdgeCurve::Line => {
655 let start = self.topo.vertex(edge_data.start())?.point();
656 let end = self.topo.vertex(edge_data.end())?.point();
657 let dir = end - start;
658 let len = dir.length();
659 let tangent = if len < 1e-15 {
660 Vec3::new(1.0, 0.0, 0.0)
661 } else {
662 Vec3::new(dir.x() / len, dir.y() / len, dir.z() / len)
663 };
664 Ok(vec![
665 0.0,
666 tangent.x(),
667 tangent.y(),
668 tangent.z(),
669 0.0,
670 0.0,
671 0.0,
672 ])
673 }
674 EdgeCurve::NurbsCurve(curve) => {
675 let curvature = curve.curvature(t).unwrap_or(0.0);
676 let derivs = curve.derivatives(t, 2);
677 let tangent = if derivs.len() > 1 {
678 derivs[1].normalize().unwrap_or(Vec3::new(1.0, 0.0, 0.0))
679 } else {
680 Vec3::new(1.0, 0.0, 0.0)
681 };
682 let normal = if derivs.len() > 2 {
683 let d1 = derivs[1];
684 let d2 = derivs[2];
685 let cross = d1.cross(d2);
686 let binormal = cross.normalize().unwrap_or(Vec3::new(0.0, 0.0, 1.0));
687 binormal
688 .cross(tangent)
689 .normalize()
690 .unwrap_or(Vec3::new(0.0, 1.0, 0.0))
691 } else {
692 Vec3::new(0.0, 1.0, 0.0)
693 };
694 Ok(vec![
695 curvature,
696 tangent.x(),
697 tangent.y(),
698 tangent.z(),
699 normal.x(),
700 normal.y(),
701 normal.z(),
702 ])
703 }
704 EdgeCurve::Circle(circle) => {
705 let curvature = 1.0 / circle.radius();
706 let tangent = circle
707 .tangent(t)
708 .normalize()
709 .unwrap_or(Vec3::new(1.0, 0.0, 0.0));
710 let point = circle.evaluate(t);
711 let to_center = Vec3::new(
712 circle.center().x() - point.x(),
713 circle.center().y() - point.y(),
714 circle.center().z() - point.z(),
715 );
716 let normal = to_center.normalize().unwrap_or(Vec3::new(0.0, 1.0, 0.0));
717 Ok(vec![
718 curvature,
719 tangent.x(),
720 tangent.y(),
721 tangent.z(),
722 normal.x(),
723 normal.y(),
724 normal.z(),
725 ])
726 }
727 EdgeCurve::Ellipse(ellipse) => {
728 let point = ellipse.evaluate(t);
729 let tangent = ellipse
730 .tangent(t)
731 .normalize()
732 .unwrap_or(Vec3::new(1.0, 0.0, 0.0));
733 let dt = 1e-6;
735 let p0 = ellipse.evaluate(t - dt);
736 let p1 = ellipse.evaluate(t + dt);
737 let d1 = p1 - p0;
738 let d2 = (p1 - point) - (point - p0);
739 let speed = d1.length() / (2.0 * dt);
740 let curvature = if speed > 1e-15 {
741 d1.cross(d2).length() / ((2.0 * dt) * speed * speed * speed)
742 } else {
743 0.0
744 };
745 let normal = Vec3::new(
746 ellipse.center().x() - point.x(),
747 ellipse.center().y() - point.y(),
748 ellipse.center().z() - point.z(),
749 )
750 .normalize()
751 .unwrap_or(Vec3::new(0.0, 1.0, 0.0));
752 Ok(vec![
753 curvature,
754 tangent.x(),
755 tangent.y(),
756 tangent.z(),
757 normal.x(),
758 normal.y(),
759 normal.z(),
760 ])
761 }
762 }
763 }
764
765 #[wasm_bindgen(js_name = "evaluateSurfaceNormal")]
769 pub fn evaluate_surface_normal(&self, face: u32, u: f64, v: f64) -> Result<Vec<f64>, JsError> {
770 let face_id = self.resolve_face(face)?;
771 let face_data = self.topo.face(face_id)?;
772 match face_data.surface() {
773 FaceSurface::Plane { normal, .. } => Ok(vec![normal.x(), normal.y(), normal.z()]),
774 FaceSurface::Nurbs(surface) => {
775 let derivs = surface.derivatives(u, v, 1);
776 let du = if derivs.len() > 1 && !derivs[1].is_empty() {
777 derivs[1][0]
778 } else {
779 Vec3::new(1.0, 0.0, 0.0)
780 };
781 let dv = if !derivs.is_empty() && derivs[0].len() > 1 {
782 derivs[0][1]
783 } else {
784 Vec3::new(0.0, 1.0, 0.0)
785 };
786 let n = du.cross(dv);
787 match n.normalize() {
788 Ok(normal) => Ok(vec![normal.x(), normal.y(), normal.z()]),
789 Err(_) => Ok(vec![0.0, 0.0, 1.0]),
790 }
791 }
792 FaceSurface::Cylinder(cyl) => {
793 let n = cyl.normal(u, v);
794 Ok(vec![n.x(), n.y(), n.z()])
795 }
796 FaceSurface::Cone(cone) => {
797 let n = cone.normal(u, v);
798 Ok(vec![n.x(), n.y(), n.z()])
799 }
800 FaceSurface::Sphere(sph) => {
801 let n = sph.normal(u, v);
802 Ok(vec![n.x(), n.y(), n.z()])
803 }
804 FaceSurface::Torus(tor) => {
805 let n = tor.normal(u, v);
806 Ok(vec![n.x(), n.y(), n.z()])
807 }
808 }
809 }
810
811 #[wasm_bindgen(js_name = "evaluateSurface")]
815 pub fn evaluate_surface(&self, face: u32, u: f64, v: f64) -> Result<Vec<f64>, JsError> {
816 let face_id = self.resolve_face(face)?;
817 let face_data = self.topo.face(face_id)?;
818 let point = match face_data.surface() {
819 FaceSurface::Plane { normal, d } => {
820 let up = if normal.x().abs() < 0.9 {
823 Vec3::new(1.0, 0.0, 0.0)
824 } else {
825 Vec3::new(0.0, 1.0, 0.0)
826 };
827 let x_axis = normal.cross(up);
828 let y_axis = normal.cross(x_axis);
829 Point3::new(
830 normal.x() * d + x_axis.x() * u + y_axis.x() * v,
831 normal.y() * d + x_axis.y() * u + y_axis.y() * v,
832 normal.z() * d + x_axis.z() * u + y_axis.z() * v,
833 )
834 }
835 FaceSurface::Nurbs(surface) => surface.evaluate(u, v),
836 FaceSurface::Cylinder(cyl) => cyl.evaluate(u, v),
837 FaceSurface::Cone(cone) => cone.evaluate(u, v),
838 FaceSurface::Sphere(sph) => sph.evaluate(u, v),
839 FaceSurface::Torus(tor) => tor.evaluate(u, v),
840 };
841 Ok(vec![point.x(), point.y(), point.z()])
842 }
843
844 #[wasm_bindgen(js_name = "measureCurvatureAtSurface")]
849 #[allow(clippy::too_many_lines)]
850 pub fn measure_curvature_at_surface(
851 &self,
852 face: u32,
853 u: f64,
854 v: f64,
855 ) -> Result<Vec<f64>, JsError> {
856 let face_id = self.resolve_face(face)?;
857 let face_data = self.topo.face(face_id)?;
858 match face_data.surface() {
859 FaceSurface::Plane { .. } => Ok(vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]),
860 FaceSurface::Nurbs(surface) => {
861 let derivs = surface.derivatives(u, v, 2);
862 let su = if derivs.len() > 1 && !derivs[1].is_empty() {
864 derivs[1][0]
865 } else {
866 return Ok(vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]);
867 };
868 let sv = if !derivs.is_empty() && derivs[0].len() > 1 {
869 derivs[0][1]
870 } else {
871 return Ok(vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]);
872 };
873 let suu = if derivs.len() > 2 && !derivs[2].is_empty() {
874 derivs[2][0]
875 } else {
876 Vec3::new(0.0, 0.0, 0.0)
877 };
878 let suv = if derivs.len() > 1 && derivs[1].len() > 1 {
879 derivs[1][1]
880 } else {
881 Vec3::new(0.0, 0.0, 0.0)
882 };
883 let svv = if !derivs.is_empty() && derivs[0].len() > 2 {
884 derivs[0][2]
885 } else {
886 Vec3::new(0.0, 0.0, 0.0)
887 };
888
889 let normal = su.cross(sv);
890 let normal = match normal.normalize() {
891 Ok(n) => n,
892 Err(_) => return Ok(vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]),
893 };
894
895 let ee = su.dot(su);
897 let ff = su.dot(sv);
898 let gg = sv.dot(sv);
899
900 let ll = suu.dot(normal);
902 let mm = suv.dot(normal);
903 let nn = svv.dot(normal);
904
905 let denom = ee * gg - ff * ff;
907 if denom.abs() < 1e-30 {
908 return Ok(vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]);
909 }
910 let h = 0.5 * (ee * nn - 2.0 * ff * mm + gg * ll) / denom; let k = (ll * nn - mm * mm) / denom; let disc = (h * h - k).max(0.0).sqrt();
913 let k1 = h + disc;
914 let k2 = h - disc;
915
916 let su_norm = su.normalize().unwrap_or(Vec3::new(1.0, 0.0, 0.0));
918 let sv_norm = sv.normalize().unwrap_or(Vec3::new(0.0, 1.0, 0.0));
919
920 Ok(vec![
921 k1,
922 k2,
923 su_norm.x(),
924 su_norm.y(),
925 su_norm.z(),
926 sv_norm.x(),
927 sv_norm.y(),
928 sv_norm.z(),
929 ])
930 }
931 FaceSurface::Cylinder(cyl) => {
932 let r = cyl.radius();
933 let axis = cyl.axis().normalize().unwrap_or(Vec3::new(0.0, 0.0, 1.0));
934 let point = cyl.evaluate(u, v);
935 let to_axis = Vec3::new(
936 cyl.origin().x() - point.x()
937 + axis.x()
938 * axis.dot(Vec3::new(
939 point.x() - cyl.origin().x(),
940 point.y() - cyl.origin().y(),
941 point.z() - cyl.origin().z(),
942 )),
943 cyl.origin().y() - point.y()
944 + axis.y()
945 * axis.dot(Vec3::new(
946 point.x() - cyl.origin().x(),
947 point.y() - cyl.origin().y(),
948 point.z() - cyl.origin().z(),
949 )),
950 cyl.origin().z() - point.z()
951 + axis.z()
952 * axis.dot(Vec3::new(
953 point.x() - cyl.origin().x(),
954 point.y() - cyl.origin().y(),
955 point.z() - cyl.origin().z(),
956 )),
957 );
958 let radial = to_axis.normalize().unwrap_or(Vec3::new(1.0, 0.0, 0.0));
959 Ok(vec![
960 1.0 / r,
961 0.0,
962 radial.x(),
963 radial.y(),
964 radial.z(),
965 axis.x(),
966 axis.y(),
967 axis.z(),
968 ])
969 }
970 FaceSurface::Sphere(sph) => {
971 let r = sph.radius();
972 let point = sph.evaluate(u, v);
973 let radial = Vec3::new(
974 point.x() - sph.center().x(),
975 point.y() - sph.center().y(),
976 point.z() - sph.center().z(),
977 )
978 .normalize()
979 .unwrap_or(Vec3::new(0.0, 0.0, 1.0));
980 let d1 = Vec3::new(-radial.y(), radial.x(), 0.0)
982 .normalize()
983 .unwrap_or(Vec3::new(1.0, 0.0, 0.0));
984 let d2 = radial.cross(d1);
985 Ok(vec![
986 1.0 / r,
987 1.0 / r,
988 d1.x(),
989 d1.y(),
990 d1.z(),
991 d2.x(),
992 d2.y(),
993 d2.z(),
994 ])
995 }
996 FaceSurface::Cone(cone) => {
997 let half_angle = cone.half_angle();
998 let v_pos = v.abs().max(1e-10);
999 let local_r = v_pos * half_angle.sin();
1000 let k_parallel = if local_r > 1e-15 {
1001 half_angle.cos() / local_r
1002 } else {
1003 0.0
1004 };
1005 let axis = cone.axis().normalize().unwrap_or(Vec3::new(0.0, 0.0, 1.0));
1006 Ok(vec![
1007 0.0,
1008 k_parallel,
1009 axis.x(),
1010 axis.y(),
1011 axis.z(),
1012 1.0,
1013 0.0,
1014 0.0,
1015 ])
1016 }
1017 FaceSurface::Torus(torus) => {
1018 let r_major = torus.major_radius();
1019 let r_minor = torus.minor_radius();
1020 let k1 = 1.0 / r_minor;
1021 let k2 = u.cos() / (r_major + r_minor * u.cos());
1022 Ok(vec![k1, k2, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0])
1023 }
1024 }
1025 }
1026
1027 #[wasm_bindgen(js_name = "tessellateEdge")]
1034 pub fn tessellate_edge(&self, edge: u32, num_points: u32) -> Result<Vec<f64>, JsError> {
1035 let edge_id = self.resolve_edge(edge)?;
1036 let edge_data = self.topo.edge(edge_id)?;
1037
1038 match edge_data.curve() {
1039 EdgeCurve::Line => {
1040 let start = self.topo.vertex(edge_data.start())?.point();
1041 let end = self.topo.vertex(edge_data.end())?.point();
1042 Ok(vec![
1043 start.x(),
1044 start.y(),
1045 start.z(),
1046 end.x(),
1047 end.y(),
1048 end.z(),
1049 ])
1050 }
1051 EdgeCurve::NurbsCurve(curve) => {
1052 let (u0, u1) = curve.domain();
1053 let n = std::cmp::max(2, num_points as usize);
1054 let mut result = Vec::with_capacity(n * 3);
1055 for i in 0..n {
1056 #[allow(clippy::cast_precision_loss)]
1057 let t = u0 + (u1 - u0) * (i as f64) / ((n - 1) as f64);
1058 let p = curve.evaluate(t);
1059 result.push(p.x());
1060 result.push(p.y());
1061 result.push(p.z());
1062 }
1063 Ok(result)
1064 }
1065 EdgeCurve::Circle(circle) => {
1066 let n = std::cmp::max(2, num_points as usize);
1067 Ok(sample_full_period_curve(n, |t| circle.evaluate(t)))
1068 }
1069 EdgeCurve::Ellipse(ellipse) => {
1070 let n = std::cmp::max(2, num_points as usize);
1071 Ok(sample_full_period_curve(n, |t| ellipse.evaluate(t)))
1072 }
1073 }
1074 }
1075
1076 #[wasm_bindgen(js_name = "isEdgeForwardInWire")]
1080 pub fn is_edge_forward_in_wire(&self, edge: u32, wire: u32) -> Result<bool, JsError> {
1081 let edge_id = self.resolve_edge(edge)?;
1082 let wire_id = self.resolve_wire(wire)?;
1083 let wire_data = self.topo.wire(wire_id)?;
1084
1085 for oe in wire_data.edges() {
1086 if oe.edge() == edge_id {
1087 return Ok(oe.is_forward());
1088 }
1089 }
1090
1091 Err(WasmError::InvalidInput {
1092 reason: "edge not found in wire".into(),
1093 }
1094 .into())
1095 }
1096
1097 #[wasm_bindgen(js_name = "getSurfaceDomain")]
1101 pub fn get_surface_domain(&self, face: u32) -> Result<Vec<f64>, JsError> {
1102 let face_id = self.resolve_face(face)?;
1103 let face_data = self.topo.face(face_id)?;
1104 match face_data.surface() {
1105 FaceSurface::Plane { .. } => Ok(vec![-1e6, 1e6, -1e6, 1e6]),
1106 FaceSurface::Nurbs(surface) => {
1107 let (u0, u1) = surface.domain_u();
1108 let (v0, v1) = surface.domain_v();
1109 Ok(vec![u0, u1, v0, v1])
1110 }
1111 FaceSurface::Cylinder(cyl) => {
1112 let v_range = brepkit_check::properties::axial_v_range(
1113 &self.topo,
1114 face_id,
1115 cyl.origin(),
1116 cyl.axis(),
1117 )?;
1118 Ok(vec![0.0, 2.0 * PI, v_range.0, v_range.1])
1119 }
1120 FaceSurface::Cone(cone) => {
1121 let v_range = brepkit_check::properties::axial_v_range(
1122 &self.topo,
1123 face_id,
1124 cone.apex(),
1125 cone.axis(),
1126 )?;
1127 Ok(vec![0.0, 2.0 * PI, v_range.0, v_range.1])
1128 }
1129 FaceSurface::Sphere(_) => Ok(vec![0.0, 2.0 * PI, -PI / 2.0, PI / 2.0]),
1130 FaceSurface::Torus(_) => Ok(vec![0.0, 2.0 * PI, 0.0, 2.0 * PI]),
1131 }
1132 }
1133
1134 #[wasm_bindgen(js_name = "projectPointOnSurface")]
1138 pub fn project_point_on_surface(
1139 &self,
1140 face: u32,
1141 px: f64,
1142 py: f64,
1143 pz: f64,
1144 ) -> Result<Vec<f64>, JsError> {
1145 let face_id = self.resolve_face(face)?;
1146 let face_data = self.topo.face(face_id)?;
1147 let target = Point3::new(px, py, pz);
1148
1149 match face_data.surface() {
1150 FaceSurface::Plane { normal, d } => {
1151 let dist_to_plane = normal.x() * px + normal.y() * py + normal.z() * pz - d;
1153 let proj = Point3::new(
1154 px - dist_to_plane * normal.x(),
1155 py - dist_to_plane * normal.y(),
1156 pz - dist_to_plane * normal.z(),
1157 );
1158 let dist = (proj - target).length();
1159 Ok(vec![proj.x(), proj.y(), proj.x(), proj.y(), proj.z(), dist])
1161 }
1162 FaceSurface::Nurbs(surface) => {
1163 let (u0, u1) = surface.domain_u();
1165 let (v0, v1) = surface.domain_v();
1166 let mut best_u = f64::midpoint(u0, u1);
1167 let mut best_v = f64::midpoint(v0, v1);
1168 let mut best_dist = f64::MAX;
1169
1170 let n_grid = 8;
1172 for iu in 0..=n_grid {
1173 for iv in 0..=n_grid {
1174 #[allow(clippy::cast_precision_loss)]
1175 let u = u0 + (u1 - u0) * (iu as f64) / (n_grid as f64);
1176 #[allow(clippy::cast_precision_loss)]
1177 let v = v0 + (v1 - v0) * (iv as f64) / (n_grid as f64);
1178 let p = surface.evaluate(u, v);
1179 let d = (p - target).length();
1180 if d < best_dist {
1181 best_dist = d;
1182 best_u = u;
1183 best_v = v;
1184 }
1185 }
1186 }
1187
1188 for _ in 0..5 {
1190 let p = surface.evaluate(best_u, best_v);
1191 let derivs = surface.derivatives(best_u, best_v, 1);
1192 if derivs.len() < 2 || derivs[0].len() < 2 || derivs[1].is_empty() {
1193 break;
1194 }
1195 let du = derivs[1][0]; let dv = derivs[0][1]; let diff = p - target;
1198
1199 let j00 = du.dot(du);
1201 let j01 = du.dot(dv);
1202 let j10 = j01;
1203 let j11 = dv.dot(dv);
1204 let r0 = diff.x() * du.x() + diff.y() * du.y() + diff.z() * du.z();
1205 let r1 = diff.x() * dv.x() + diff.y() * dv.y() + diff.z() * dv.z();
1206
1207 let det = j00 * j11 - j01 * j10;
1208 if det.abs() < 1e-20 {
1209 break;
1210 }
1211 let delta_u = -(j11 * r0 - j01 * r1) / det;
1212 let delta_v = -(-j10 * r0 + j00 * r1) / det;
1213
1214 best_u = (best_u + delta_u).clamp(u0, u1);
1215 best_v = (best_v + delta_v).clamp(v0, v1);
1216 }
1217
1218 let proj = surface.evaluate(best_u, best_v);
1219 let dist = (proj - target).length();
1220 Ok(vec![best_u, best_v, proj.x(), proj.y(), proj.z(), dist])
1221 }
1222 _ => {
1223 let mut best_u = 0.0;
1225 let mut best_v = 0.0;
1226 let mut best_dist = f64::MAX;
1227 let n_grid = 16;
1228 for iu in 0..=n_grid {
1229 for iv in 0..=n_grid {
1230 #[allow(clippy::cast_precision_loss)]
1231 let u = 2.0 * PI * (iu as f64) / (n_grid as f64);
1232 #[allow(clippy::cast_precision_loss)]
1233 let v = -PI + 2.0 * PI * (iv as f64) / (n_grid as f64);
1234 let p = match face_data.surface() {
1235 FaceSurface::Cylinder(cyl) => cyl.evaluate(u, v),
1236 FaceSurface::Cone(cone) => cone.evaluate(u, v),
1237 FaceSurface::Sphere(sph) => sph.evaluate(u, v),
1238 FaceSurface::Torus(tor) => tor.evaluate(u, v),
1239 _ => continue,
1240 };
1241 let d = (p - target).length();
1242 if d < best_dist {
1243 best_dist = d;
1244 best_u = u;
1245 best_v = v;
1246 }
1247 }
1248 }
1249 let proj = match face_data.surface() {
1250 FaceSurface::Cylinder(cyl) => cyl.evaluate(best_u, best_v),
1251 FaceSurface::Cone(cone) => cone.evaluate(best_u, best_v),
1252 FaceSurface::Sphere(sph) => sph.evaluate(best_u, best_v),
1253 FaceSurface::Torus(tor) => tor.evaluate(best_u, best_v),
1254 _ => target,
1255 };
1256 Ok(vec![
1257 best_u,
1258 best_v,
1259 proj.x(),
1260 proj.y(),
1261 proj.z(),
1262 best_dist,
1263 ])
1264 }
1265 }
1266 }
1267
1268 #[wasm_bindgen(js_name = "addHolesToFace")]
1273 #[allow(clippy::needless_pass_by_value)]
1274 pub fn add_holes_to_face(
1275 &mut self,
1276 face: u32,
1277 hole_wire_handles: Vec<u32>,
1278 ) -> Result<u32, JsError> {
1279 let face_id = self.resolve_face(face)?;
1280 let face_data = self.topo.face(face_id)?;
1281 let outer_wire = face_data.outer_wire();
1282 let surface = face_data.surface().clone();
1283 let mut inner_wires: Vec<brepkit_topology::wire::WireId> = face_data.inner_wires().to_vec();
1284
1285 for &wh in &hole_wire_handles {
1286 let wid = self.resolve_wire(wh)?;
1287 inner_wires.push(wid);
1288 }
1289
1290 let new_face = Face::new(outer_wire, inner_wires, surface);
1291 let fid = self.topo_mut().add_face(new_face);
1292 Ok(face_id_to_u32(fid))
1293 }
1294
1295 #[wasm_bindgen(js_name = "getEdgeNurbsData")]
1300 pub fn get_edge_nurbs_data(&self, edge: u32) -> Result<JsValue, JsError> {
1301 let edge_id = self.resolve_edge(edge)?;
1302 let edge_data = self.topo.edge(edge_id)?;
1303 match edge_data.curve() {
1304 EdgeCurve::Line | EdgeCurve::Circle(_) | EdgeCurve::Ellipse(_) => Ok(JsValue::NULL),
1305 EdgeCurve::NurbsCurve(curve) => {
1306 let cp_flat: Vec<f64> = curve
1307 .control_points()
1308 .iter()
1309 .flat_map(|p| [p.x(), p.y(), p.z()])
1310 .collect();
1311 let data = serde_json::json!({
1312 "degree": curve.degree(),
1313 "knots": curve.knots(),
1314 "controlPoints": cp_flat,
1315 "weights": curve.weights(),
1316 });
1317 Ok(JsValue::from_str(&data.to_string()))
1318 }
1319 }
1320 }
1321
1322 #[wasm_bindgen(js_name = "edgeToFaceMap")]
1326 pub fn edge_to_face_map(&self, solid: u32) -> Result<String, JsError> {
1327 let solid_id = self.resolve_solid(solid)?;
1328 let map = brepkit_topology::explorer::edge_to_face_map(&self.topo, solid_id)?;
1329 let json_map: std::collections::HashMap<String, Vec<u32>> = map
1330 .into_iter()
1331 .map(|(edge_idx, face_ids)| {
1332 let fids: Vec<u32> = face_ids.iter().map(|f| face_id_to_u32(*f)).collect();
1333 (edge_idx.to_string(), fids)
1334 })
1335 .collect();
1336 Ok(serde_json::json!(json_map).to_string())
1337 }
1338
1339 #[wasm_bindgen(js_name = "sharedEdges")]
1343 pub fn shared_edges(&self, face_a: u32, face_b: u32) -> Result<Vec<u32>, JsError> {
1344 let fa = self.resolve_face(face_a)?;
1345 let fb = self.resolve_face(face_b)?;
1346 let edges = brepkit_topology::explorer::shared_edges(&self.topo, fa, fb)?;
1347 Ok(edges.iter().map(|e| edge_id_to_u32(*e)).collect())
1348 }
1349
1350 #[wasm_bindgen(js_name = "adjacentFaces")]
1354 pub fn adjacent_faces(&self, solid: u32, face: u32) -> Result<Vec<u32>, JsError> {
1355 let solid_id = self.resolve_solid(solid)?;
1356 let face_id = self.resolve_face(face)?;
1357 let map = brepkit_topology::explorer::edge_to_face_map(&self.topo, solid_id)?;
1358 let adj = brepkit_topology::explorer::adjacent_faces(&self.topo, face_id, &map)?;
1359 Ok(adj.iter().map(|f| face_id_to_u32(*f)).collect())
1360 }
1361
1362 #[wasm_bindgen(js_name = "faceWires")]
1366 pub fn face_wires(&self, face: u32) -> Result<Vec<u32>, JsError> {
1367 let face_id = self.resolve_face(face)?;
1368 let wires = brepkit_topology::explorer::face_wires(&self.topo, face_id)?;
1369 Ok(wires.iter().map(|w| wire_id_to_u32(*w)).collect())
1370 }
1371
1372 #[wasm_bindgen(js_name = "getCompoundSolids")]
1380 pub fn get_compound_solids(&self, compound: u32) -> Result<Vec<u32>, JsError> {
1381 let compound_id = self.resolve_compound(compound)?;
1382 let compound_data = self.topo.compound(compound_id)?;
1383 Ok(compound_data
1384 .solids()
1385 .iter()
1386 .map(|s| solid_id_to_u32(*s))
1387 .collect())
1388 }
1389
1390 #[wasm_bindgen(js_name = "getShellFaces")]
1398 pub fn get_shell_faces(&self, shell: u32) -> Result<Vec<u32>, JsError> {
1399 let shell_id = self.resolve_shell(shell)?;
1400 let shell_data = self.topo.shell(shell_id)?;
1401 Ok(shell_data
1402 .faces()
1403 .iter()
1404 .map(|f| face_id_to_u32(*f))
1405 .collect())
1406 }
1407
1408 #[wasm_bindgen(js_name = "getWireEdges")]
1416 pub fn get_wire_edges(&self, wire: u32) -> Result<Vec<u32>, JsError> {
1417 let wire_id = self.resolve_wire(wire)?;
1418 let wire_data = self.topo.wire(wire_id)?;
1419 Ok(wire_data
1420 .edges()
1421 .iter()
1422 .map(|oe| edge_id_to_u32(oe.edge()))
1423 .collect())
1424 }
1425
1426 #[wasm_bindgen(js_name = "isWireClosed")]
1428 pub fn is_wire_closed(&self, wire: u32) -> Result<bool, JsError> {
1429 let wire_id = self.resolve_wire(wire)?;
1430 let wire_data = self.topo.wire(wire_id)?;
1431 Ok(wire_data.is_closed())
1432 }
1433
1434 #[wasm_bindgen(js_name = "wireLength")]
1436 pub fn wire_length(&self, wire: u32) -> Result<f64, JsError> {
1437 let wire_id = self.resolve_wire(wire)?;
1438 let wire_data = self.topo.wire(wire_id)?;
1439 let mut total = 0.0;
1440 for oe in wire_data.edges() {
1441 total += brepkit_operations::measure::edge_length(&self.topo, oe.edge())?;
1442 }
1443 Ok(total)
1444 }
1445
1446 #[wasm_bindgen(js_name = "getAnalyticSurfaceParams")]
1450 pub fn get_analytic_surface_params(&self, face: u32) -> Result<String, JsError> {
1451 let face_id = self.resolve_face(face)?;
1452 let face_data = self.topo.face(face_id)?;
1453 let json = match face_data.surface() {
1454 FaceSurface::Plane { normal, d } => serde_json::json!({
1455 "type": "plane",
1456 "normal": [normal.x(), normal.y(), normal.z()],
1457 "d": d,
1458 }),
1459 FaceSurface::Nurbs(_) => serde_json::json!({
1460 "type": "nurbs",
1461 }),
1462 FaceSurface::Cylinder(cyl) => serde_json::json!({
1463 "type": "cylinder",
1464 "origin": [cyl.origin().x(), cyl.origin().y(), cyl.origin().z()],
1465 "axis": [cyl.axis().x(), cyl.axis().y(), cyl.axis().z()],
1466 "radius": cyl.radius(),
1467 }),
1468 FaceSurface::Cone(cone) => serde_json::json!({
1469 "type": "cone",
1470 "apex": [cone.apex().x(), cone.apex().y(), cone.apex().z()],
1471 "axis": [cone.axis().x(), cone.axis().y(), cone.axis().z()],
1472 "halfAngle": cone.half_angle(),
1473 }),
1474 FaceSurface::Sphere(sph) => serde_json::json!({
1475 "type": "sphere",
1476 "center": [sph.center().x(), sph.center().y(), sph.center().z()],
1477 "radius": sph.radius(),
1478 }),
1479 FaceSurface::Torus(tor) => serde_json::json!({
1480 "type": "torus",
1481 "center": [tor.center().x(), tor.center().y(), tor.center().z()],
1482 "majorRadius": tor.major_radius(),
1483 "minorRadius": tor.minor_radius(),
1484 }),
1485 };
1486 Ok(json.to_string())
1487 }
1488}
1489
1490#[cfg(test)]
1491mod tests;