1#![allow(clippy::missing_errors_doc, clippy::too_many_arguments)]
4
5use std::f64::consts::PI;
6
7use brepkit_math::nurbs::curve::NurbsCurve;
8use brepkit_math::vec::{Point3, Vec3};
9use brepkit_topology::edge::{Edge, EdgeCurve};
10use brepkit_topology::vertex::Vertex;
11use brepkit_topology::wire::{OrientedEdge, Wire};
12use wasm_bindgen::prelude::*;
13
14use crate::error::{WasmError, validate_finite, validate_positive};
15use crate::handles::{
16 edge_id_to_u32, face_id_to_u32, solid_id_to_u32, vertex_id_to_u32, wire_id_to_u32,
17};
18use crate::helpers::{TOL, parse_points};
19use crate::kernel::BrepKernel;
20
21#[wasm_bindgen]
22impl BrepKernel {
23 #[wasm_bindgen(js_name = "makeRectangle")]
34 pub fn make_rectangle(&mut self, width: f64, height: f64) -> Result<u32, JsError> {
35 validate_positive(width, "width")?;
36 validate_positive(height, "height")?;
37
38 let hw = width / 2.0;
39 let hh = height / 2.0;
40
41 let points = [
42 Point3::new(-hw, -hh, 0.0),
43 Point3::new(hw, -hh, 0.0),
44 Point3::new(hw, hh, 0.0),
45 Point3::new(-hw, hh, 0.0),
46 ];
47
48 let face_id = self.make_planar_face(&points)?;
49 Ok(face_id_to_u32(face_id))
50 }
51
52 #[wasm_bindgen(js_name = "makePolygon")]
62 #[allow(clippy::needless_pass_by_value)] pub fn make_polygon(&mut self, coords: Vec<f64>) -> Result<u32, JsError> {
64 if !coords.len().is_multiple_of(3) {
65 return Err(WasmError::InvalidInput {
66 reason: format!(
67 "coordinate array length must be a multiple of 3, got {}",
68 coords.len()
69 ),
70 }
71 .into());
72 }
73 let n = coords.len() / 3;
74 if n < 3 {
75 return Err(WasmError::InvalidInput {
76 reason: format!("polygon requires at least 3 points, got {n}"),
77 }
78 .into());
79 }
80
81 if let Some(pos) = coords.iter().position(|v| !v.is_finite()) {
82 return Err(WasmError::InvalidInput {
83 reason: format!("coordinate at index {pos} is not finite"),
84 }
85 .into());
86 }
87
88 let points: Vec<Point3> = coords
89 .chunks_exact(3)
90 .map(|c| Point3::new(c[0], c[1], c[2]))
91 .collect();
92
93 let face_id = self.make_planar_face(&points)?;
94 Ok(face_id_to_u32(face_id))
95 }
96
97 #[wasm_bindgen(js_name = "makeCircle")]
107 pub fn make_circle(&mut self, radius: f64, segments: u32) -> Result<u32, JsError> {
108 validate_positive(radius, "radius")?;
109 if segments < 3 {
110 return Err(WasmError::InvalidInput {
111 reason: format!("circle requires at least 3 segments, got {segments}"),
112 }
113 .into());
114 }
115
116 let n = segments as usize;
117 let mut points = Vec::with_capacity(n);
118 for i in 0..n {
119 #[allow(clippy::cast_precision_loss)]
120 let angle = 2.0 * PI * (i as f64) / (n as f64);
121 points.push(Point3::new(radius * angle.cos(), radius * angle.sin(), 0.0));
122 }
123
124 let face_id = self.make_planar_face(&points)?;
125 Ok(face_id_to_u32(face_id))
126 }
127
128 #[wasm_bindgen(js_name = "makeVertex")]
134 pub fn make_vertex(&mut self, x: f64, y: f64, z: f64) -> Result<u32, JsError> {
135 validate_finite(x, "x")?;
136 validate_finite(y, "y")?;
137 validate_finite(z, "z")?;
138 let id = self
139 .topo_mut()
140 .add_vertex(Vertex::new(Point3::new(x, y, z), TOL));
141 Ok(vertex_id_to_u32(id))
142 }
143
144 #[wasm_bindgen(js_name = "makeLineEdge")]
148 pub fn make_line_edge(
149 &mut self,
150 x1: f64,
151 y1: f64,
152 z1: f64,
153 x2: f64,
154 y2: f64,
155 z2: f64,
156 ) -> Result<u32, JsError> {
157 let start = Point3::new(x1, y1, z1);
158 let end = Point3::new(x2, y2, z2);
159 let eid = brepkit_topology::builder::make_line_edge(self.topo_mut(), start, end, TOL)?;
160 Ok(edge_id_to_u32(eid))
161 }
162
163 #[wasm_bindgen(js_name = "makeCircleEdge")]
177 pub fn make_circle_edge(
178 &mut self,
179 cx: f64,
180 cy: f64,
181 cz: f64,
182 nx: f64,
183 ny: f64,
184 nz: f64,
185 radius: f64,
186 ) -> Result<u32, JsError> {
187 validate_finite(cx, "cx")?;
188 validate_finite(cy, "cy")?;
189 validate_finite(cz, "cz")?;
190 validate_finite(nx, "nx")?;
191 validate_finite(ny, "ny")?;
192 validate_finite(nz, "nz")?;
193 validate_positive(radius, "radius")?;
194
195 let center = Point3::new(cx, cy, cz);
196 let normal = Vec3::new(nx, ny, nz);
197 normal.normalize().map_err(|e| WasmError::InvalidInput {
198 reason: format!("invalid normal: {e}"),
199 })?;
200 let eid = brepkit_topology::builder::make_circle_edge(
201 self.topo_mut(),
202 center,
203 normal,
204 radius,
205 TOL,
206 )?;
207 Ok(edge_id_to_u32(eid))
208 }
209
210 #[wasm_bindgen(js_name = "makeEllipseEdge")]
224 pub fn make_ellipse_edge(
225 &mut self,
226 cx: f64,
227 cy: f64,
228 cz: f64,
229 nx: f64,
230 ny: f64,
231 nz: f64,
232 semi_major: f64,
233 semi_minor: f64,
234 ) -> Result<u32, JsError> {
235 validate_finite(cx, "cx")?;
236 validate_finite(cy, "cy")?;
237 validate_finite(cz, "cz")?;
238 validate_finite(nx, "nx")?;
239 validate_finite(ny, "ny")?;
240 validate_finite(nz, "nz")?;
241 validate_positive(semi_major, "semi_major")?;
242 validate_positive(semi_minor, "semi_minor")?;
243 if semi_minor > semi_major {
244 return Err(WasmError::InvalidInput {
245 reason: format!(
246 "semi_minor ({semi_minor}) must not exceed semi_major ({semi_major})"
247 ),
248 }
249 .into());
250 }
251
252 let center = Point3::new(cx, cy, cz);
253 let normal = Vec3::new(nx, ny, nz);
254 normal.normalize().map_err(|e| WasmError::InvalidInput {
255 reason: format!("invalid normal: {e}"),
256 })?;
257 let eid = brepkit_topology::builder::make_ellipse_edge(
258 self.topo_mut(),
259 center,
260 normal,
261 semi_major,
262 semi_minor,
263 TOL,
264 )?;
265 Ok(edge_id_to_u32(eid))
266 }
267
268 #[wasm_bindgen(js_name = "makeCircleEdgeWithRef")]
288 pub fn make_circle_edge_with_ref(
289 &mut self,
290 cx: f64,
291 cy: f64,
292 cz: f64,
293 nx: f64,
294 ny: f64,
295 nz: f64,
296 radius: f64,
297 rx: f64,
298 ry: f64,
299 rz: f64,
300 ) -> Result<u32, JsError> {
301 validate_finite(cx, "cx")?;
302 validate_finite(cy, "cy")?;
303 validate_finite(cz, "cz")?;
304 validate_finite(nx, "nx")?;
305 validate_finite(ny, "ny")?;
306 validate_finite(nz, "nz")?;
307 validate_finite(rx, "rx")?;
308 validate_finite(ry, "ry")?;
309 validate_finite(rz, "rz")?;
310 validate_positive(radius, "radius")?;
311
312 let center = Point3::new(cx, cy, cz);
313 let normal = Vec3::new(nx, ny, nz);
314 normal.normalize().map_err(|e| WasmError::InvalidInput {
315 reason: format!("invalid normal: {e}"),
316 })?;
317 let ref_dir = Vec3::new(rx, ry, rz);
318 ref_dir.normalize().map_err(|e| WasmError::InvalidInput {
319 reason: format!("invalid ref_dir: {e}"),
320 })?;
321 let eid = brepkit_topology::builder::make_circle_edge_with_ref(
322 self.topo_mut(),
323 center,
324 normal,
325 radius,
326 ref_dir,
327 TOL,
328 )?;
329 Ok(edge_id_to_u32(eid))
330 }
331
332 #[wasm_bindgen(js_name = "makeEllipseEdgeWithRef")]
355 pub fn make_ellipse_edge_with_ref(
356 &mut self,
357 cx: f64,
358 cy: f64,
359 cz: f64,
360 nx: f64,
361 ny: f64,
362 nz: f64,
363 semi_major: f64,
364 semi_minor: f64,
365 rx: f64,
366 ry: f64,
367 rz: f64,
368 ) -> Result<u32, JsError> {
369 validate_finite(cx, "cx")?;
370 validate_finite(cy, "cy")?;
371 validate_finite(cz, "cz")?;
372 validate_finite(nx, "nx")?;
373 validate_finite(ny, "ny")?;
374 validate_finite(nz, "nz")?;
375 validate_finite(rx, "rx")?;
376 validate_finite(ry, "ry")?;
377 validate_finite(rz, "rz")?;
378 validate_positive(semi_major, "semi_major")?;
379 validate_positive(semi_minor, "semi_minor")?;
380 if semi_minor > semi_major {
381 return Err(WasmError::InvalidInput {
382 reason: format!(
383 "semi_minor ({semi_minor}) must not exceed semi_major ({semi_major})"
384 ),
385 }
386 .into());
387 }
388
389 let center = Point3::new(cx, cy, cz);
390 let normal = Vec3::new(nx, ny, nz);
391 normal.normalize().map_err(|e| WasmError::InvalidInput {
392 reason: format!("invalid normal: {e}"),
393 })?;
394 let ref_dir = Vec3::new(rx, ry, rz);
395 ref_dir.normalize().map_err(|e| WasmError::InvalidInput {
396 reason: format!("invalid ref_dir: {e}"),
397 })?;
398 let eid = brepkit_topology::builder::make_ellipse_edge_with_ref(
399 self.topo_mut(),
400 center,
401 normal,
402 semi_major,
403 semi_minor,
404 ref_dir,
405 TOL,
406 )?;
407 Ok(edge_id_to_u32(eid))
408 }
409
410 #[wasm_bindgen(js_name = "makeCircleArc3d")]
418 pub fn make_circle_arc_3d(
419 &mut self,
420 start_x: f64,
421 start_y: f64,
422 start_z: f64,
423 end_x: f64,
424 end_y: f64,
425 end_z: f64,
426 center_x: f64,
427 center_y: f64,
428 center_z: f64,
429 axis_x: f64,
430 axis_y: f64,
431 axis_z: f64,
432 ) -> Result<u32, JsError> {
433 let start_pt = Point3::new(start_x, start_y, start_z);
434 let end_pt = Point3::new(end_x, end_y, end_z);
435 let center = Point3::new(center_x, center_y, center_z);
436 let axis = Vec3::new(axis_x, axis_y, axis_z);
437
438 let n = axis.normalize().map_err(|e| WasmError::InvalidInput {
439 reason: format!("invalid axis: {e}"),
440 })?;
441
442 let radial = start_pt - center;
444 let radius = radial.length();
445 if radius < 1e-12 {
446 return Err(WasmError::InvalidInput {
447 reason: "start point coincides with center".into(),
448 }
449 .into());
450 }
451 let u_axis = Vec3::new(
452 radial.x() / radius,
453 radial.y() / radius,
454 radial.z() / radius,
455 );
456 let v_axis = n.cross(u_axis);
457
458 let circle = brepkit_math::curves::Circle3D::with_axes(center, n, radius, u_axis, v_axis)
459 .map_err(|e| WasmError::InvalidInput {
460 reason: format!("invalid circle: {e}"),
461 })?;
462
463 let v_start = self.topo_mut().add_vertex(Vertex::new(start_pt, TOL));
464 let v_end = if (start_pt - end_pt).length() < TOL * 100.0 {
465 v_start
466 } else {
467 self.topo_mut().add_vertex(Vertex::new(end_pt, TOL))
468 };
469 let eid = self
470 .topo_mut()
471 .add_edge(Edge::new(v_start, v_end, EdgeCurve::Circle(circle)));
472 Ok(edge_id_to_u32(eid))
473 }
474
475 #[wasm_bindgen(js_name = "makeEllipseArc3d")]
491 #[allow(clippy::too_many_arguments)]
492 pub fn make_ellipse_arc_3d(
493 &mut self,
494 start_x: f64,
495 start_y: f64,
496 start_z: f64,
497 end_x: f64,
498 end_y: f64,
499 end_z: f64,
500 center_x: f64,
501 center_y: f64,
502 center_z: f64,
503 axis_x: f64,
504 axis_y: f64,
505 axis_z: f64,
506 ref_x: f64,
507 ref_y: f64,
508 ref_z: f64,
509 semi_major: f64,
510 semi_minor: f64,
511 ) -> Result<u32, JsError> {
512 for (v, name) in [
513 (start_x, "start_x"),
514 (start_y, "start_y"),
515 (start_z, "start_z"),
516 (end_x, "end_x"),
517 (end_y, "end_y"),
518 (end_z, "end_z"),
519 (center_x, "center_x"),
520 (center_y, "center_y"),
521 (center_z, "center_z"),
522 (axis_x, "axis_x"),
523 (axis_y, "axis_y"),
524 (axis_z, "axis_z"),
525 (ref_x, "ref_x"),
526 (ref_y, "ref_y"),
527 (ref_z, "ref_z"),
528 ] {
529 validate_finite(v, name)?;
530 }
531 validate_positive(semi_major, "semi_major")?;
532 validate_positive(semi_minor, "semi_minor")?;
533
534 let center = Point3::new(center_x, center_y, center_z);
535 let axis = Vec3::new(axis_x, axis_y, axis_z);
536 let ref_dir = Vec3::new(ref_x, ref_y, ref_z);
537 let start_pt = Point3::new(start_x, start_y, start_z);
538 let end_pt = Point3::new(end_x, end_y, end_z);
539
540 let eid = brepkit_topology::builder::make_ellipse_arc(
541 self.topo_mut(),
542 center,
543 axis,
544 semi_major,
545 semi_minor,
546 ref_dir,
547 start_pt,
548 end_pt,
549 TOL,
550 )?;
551 Ok(edge_id_to_u32(eid))
552 }
553
554 #[wasm_bindgen(js_name = "makeNurbsEdge")]
558 #[allow(clippy::needless_pass_by_value)]
559 pub fn make_nurbs_edge(
560 &mut self,
561 start_x: f64,
562 start_y: f64,
563 start_z: f64,
564 end_x: f64,
565 end_y: f64,
566 end_z: f64,
567 degree: u32,
568 knots: Vec<f64>,
569 control_points: Vec<f64>,
570 weights: Vec<f64>,
571 ) -> Result<u32, JsError> {
572 if !control_points.len().is_multiple_of(3) {
573 return Err(WasmError::InvalidInput {
574 reason: format!(
575 "control_points length must be a multiple of 3, got {}",
576 control_points.len()
577 ),
578 }
579 .into());
580 }
581 let cp: Vec<Point3> = control_points
582 .chunks_exact(3)
583 .map(|c| Point3::new(c[0], c[1], c[2]))
584 .collect();
585 let curve = NurbsCurve::new(degree as usize, knots, cp, weights)?;
586
587 let start_pt = Point3::new(start_x, start_y, start_z);
588 let end_pt = Point3::new(end_x, end_y, end_z);
589 let v_start = self.topo_mut().add_vertex(Vertex::new(start_pt, TOL));
590 let v_end = if (start_pt - end_pt).length() < TOL * 100.0 {
593 v_start
594 } else {
595 self.topo_mut().add_vertex(Vertex::new(end_pt, TOL))
596 };
597 let eid = self
598 .topo_mut()
599 .add_edge(Edge::new(v_start, v_end, EdgeCurve::NurbsCurve(curve)));
600 Ok(edge_id_to_u32(eid))
601 }
602
603 #[wasm_bindgen(js_name = "makeTangentArc3d")]
611 pub fn make_tangent_arc_3d(
612 &mut self,
613 start_x: f64,
614 start_y: f64,
615 start_z: f64,
616 tangent_x: f64,
617 tangent_y: f64,
618 tangent_z: f64,
619 end_x: f64,
620 end_y: f64,
621 end_z: f64,
622 ) -> Result<u32, JsError> {
623 Ok(self.make_tangent_arc_3d_impl(
624 start_x, start_y, start_z, tangent_x, tangent_y, tangent_z, end_x, end_y, end_z,
625 )?)
626 }
627 #[wasm_bindgen(js_name = "liftCurve2dToPlane")]
636 #[allow(
637 clippy::too_many_arguments,
638 clippy::too_many_lines,
639 clippy::needless_pass_by_value
640 )]
641 pub fn lift_curve2d_to_plane(
642 &mut self,
643 curve_type: u32,
644 curve_params: Vec<f64>,
645 origin_x: f64,
646 origin_y: f64,
647 origin_z: f64,
648 x_axis_x: f64,
649 x_axis_y: f64,
650 x_axis_z: f64,
651 normal_x: f64,
652 normal_y: f64,
653 normal_z: f64,
654 t_start: f64,
655 t_end: f64,
656 ) -> Result<u32, JsError> {
657 Ok(self.lift_curve2d_to_plane_impl(
658 curve_type,
659 curve_params,
660 origin_x,
661 origin_y,
662 origin_z,
663 x_axis_x,
664 x_axis_y,
665 x_axis_z,
666 normal_x,
667 normal_y,
668 normal_z,
669 t_start,
670 t_end,
671 )?)
672 }
673
674 #[wasm_bindgen(js_name = "makeWire")]
678 #[allow(clippy::needless_pass_by_value)]
679 pub fn make_wire(&mut self, edge_handles: Vec<u32>, closed: bool) -> Result<u32, JsError> {
680 let tol = brepkit_math::tolerance::Tolerance::new();
681
682 let edge_ids: Vec<brepkit_topology::edge::EdgeId> = edge_handles
683 .iter()
684 .map(|&h| self.resolve_edge(h))
685 .collect::<Result<_, WasmError>>()?;
686
687 if edge_ids.len() > 1 {
691 for i in 0..edge_ids.len() {
692 let next = if i + 1 < edge_ids.len() {
693 i + 1
694 } else if closed {
695 0 } else {
697 continue;
698 };
699 if next == i {
700 continue; }
702
703 let end_vid = self.topo.edge(edge_ids[i])?.end();
704 let start_vid = self.topo.edge(edge_ids[next])?.start();
705
706 if end_vid == start_vid {
707 continue; }
709
710 let end_pos = self.topo.vertex(end_vid)?.point();
711 let start_pos = self.topo.vertex(start_vid)?.point();
712
713 let dist = (end_pos - start_pos).length();
714 if dist < tol.linear {
715 self.topo_mut().edge_mut(edge_ids[next])?.set_start(end_vid);
717 }
718 }
719 }
720
721 let oriented: Vec<OrientedEdge> = edge_ids
722 .iter()
723 .map(|&eid| OrientedEdge::new(eid, true))
724 .collect();
725 let wire = Wire::new(oriented, closed)?;
726 let wid = self.topo_mut().add_wire(wire);
727 Ok(wire_id_to_u32(wid))
728 }
729
730 #[wasm_bindgen(js_name = "makeFaceFromWire")]
739 pub fn make_face_from_wire(&mut self, wire: u32) -> Result<u32, JsError> {
740 let wid = self.resolve_wire(wire)?;
741 let fid = brepkit_topology::builder::make_face_from_wire(self.topo_mut(), wid)?;
742 Ok(face_id_to_u32(fid))
743 }
744
745 #[wasm_bindgen(js_name = "makePlanarFaceFromWire")]
753 pub fn make_planar_face_from_wire(&mut self, wire: u32) -> Result<u32, JsError> {
754 let wid = self.resolve_wire(wire)?;
755 let fid = brepkit_topology::builder::make_planar_face_from_wire(self.topo_mut(), wid)?;
756 Ok(face_id_to_u32(fid))
757 }
758
759 #[wasm_bindgen(js_name = "solidFromShell")]
763 pub fn solid_from_shell(&mut self, shell: u32) -> Result<u32, JsError> {
764 let shell_id = self.resolve_shell(shell)?;
765 let solid = brepkit_topology::solid::Solid::new(shell_id, vec![]);
766 let sid = self.topo_mut().add_solid(solid);
767 Ok(solid_id_to_u32(sid))
768 }
769
770 #[wasm_bindgen(js_name = "makeCompound")]
774 #[allow(clippy::needless_pass_by_value)]
775 pub fn make_compound(&mut self, solid_handles: Vec<u32>) -> Result<u32, JsError> {
776 let solid_ids: Vec<brepkit_topology::solid::SolidId> = solid_handles
777 .iter()
778 .map(|&h| self.resolve_solid(h))
779 .collect::<Result<_, _>>()?;
780 let compound = brepkit_topology::compound::Compound::new(solid_ids);
781 #[allow(clippy::cast_possible_truncation)]
782 let cid = self.topo_mut().add_compound(compound);
783 Ok(cid.index() as u32)
784 }
785
786 #[wasm_bindgen(js_name = "convexHull")]
796 #[allow(clippy::needless_pass_by_value)]
797 pub fn convex_hull(&mut self, coords: Vec<f64>) -> Result<u32, JsError> {
798 if !coords.len().is_multiple_of(3) {
799 return Err(WasmError::InvalidInput {
800 reason: format!(
801 "coordinate array length must be a multiple of 3, got {}",
802 coords.len()
803 ),
804 }
805 .into());
806 }
807 let points: Vec<Point3> = coords
808 .chunks_exact(3)
809 .map(|c| Point3::new(c[0], c[1], c[2]))
810 .collect();
811 if points.len() < 4 {
812 return Err(WasmError::InvalidInput {
813 reason: format!(
814 "convex hull requires at least 4 points, got {}",
815 points.len()
816 ),
817 }
818 .into());
819 }
820
821 let solid_id = brepkit_operations::primitives::make_convex_hull(self.topo_mut(), &points)?;
822 Ok(solid_id_to_u32(solid_id))
823 }
824
825 #[wasm_bindgen(js_name = "makePolygonWire")]
829 #[allow(clippy::needless_pass_by_value)]
830 pub fn make_polygon_wire(&mut self, coords: Vec<f64>) -> Result<u32, JsError> {
831 let points = parse_points(&coords)?;
832 if points.len() < 3 {
833 return Err(WasmError::InvalidInput {
834 reason: format!("polygon wire needs at least 3 points, got {}", points.len()),
835 }
836 .into());
837 }
838 let n = points.len();
839 let verts: Vec<_> = points
840 .iter()
841 .map(|p| self.topo_mut().add_vertex(Vertex::new(*p, TOL)))
842 .collect();
843 let edges: Vec<_> = (0..n)
844 .map(|i| {
845 self.topo_mut()
846 .add_edge(Edge::new(verts[i], verts[(i + 1) % n], EdgeCurve::Line))
847 })
848 .collect();
849 let oriented: Vec<_> = edges
850 .iter()
851 .map(|&eid| OrientedEdge::new(eid, true))
852 .collect();
853 let wire = Wire::new(oriented, true)?;
854 let wid = self.topo_mut().add_wire(wire);
855 Ok(wire_id_to_u32(wid))
856 }
857
858 #[wasm_bindgen(js_name = "makeRegularPolygonWire")]
862 pub fn make_regular_polygon_wire(&mut self, radius: f64, n_sides: u32) -> Result<u32, JsError> {
863 validate_positive(radius, "radius")?;
864 if n_sides < 3 {
865 return Err(WasmError::InvalidInput {
866 reason: format!("polygon needs at least 3 sides, got {n_sides}"),
867 }
868 .into());
869 }
870 let wid = brepkit_topology::builder::make_regular_polygon_wire(
871 self.topo_mut(),
872 radius,
873 n_sides as usize,
874 TOL,
875 )?;
876 Ok(wire_id_to_u32(wid))
877 }
878
879 #[wasm_bindgen(js_name = "makeCircleFace")]
883 pub fn make_circle_face(&mut self, radius: f64, segments: u32) -> Result<u32, JsError> {
884 validate_positive(radius, "radius")?;
885 if segments < 3 {
886 return Err(WasmError::InvalidInput {
887 reason: format!("circle face needs at least 3 segments, got {segments}"),
888 }
889 .into());
890 }
891 let fid = brepkit_topology::builder::make_circle_face(
892 self.topo_mut(),
893 radius,
894 segments as usize,
895 TOL,
896 )?;
897 Ok(face_id_to_u32(fid))
898 }
899}
900
901#[cfg(test)]
902mod tests {
903 #![allow(clippy::unwrap_used, clippy::expect_used)]
904
905 use super::*;
906 use brepkit_topology::face::FaceSurface;
907
908 #[test]
911 fn make_rectangle_returns_valid_face() {
912 let mut k = BrepKernel::new();
913 let h = k.make_rectangle(4.0, 2.0).unwrap();
914 let fid = k.resolve_face(h).unwrap();
915 let face = k.topo.face(fid).unwrap();
916 assert!(
917 matches!(face.surface(), FaceSurface::Plane { .. }),
918 "expected a Plane surface"
919 );
920 }
921
922 #[test]
923 fn make_rectangle_zero_width_is_error() {
924 use crate::error::validate_positive;
925 assert!(validate_positive(0.0, "width").is_err());
926 }
927
928 #[test]
929 fn make_rectangle_negative_height_is_error() {
930 use crate::error::validate_positive;
931 assert!(validate_positive(-3.0, "height").is_err());
932 }
933
934 #[test]
935 fn make_rectangle_nan_is_error() {
936 use crate::error::validate_positive;
937 assert!(validate_positive(f64::NAN, "width").is_err());
938 }
939
940 #[test]
943 fn make_polygon_triangle_returns_valid_face() {
944 let mut k = BrepKernel::new();
945 let coords = vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
946 let h = k.make_polygon(coords).unwrap();
947 let fid = k.resolve_face(h).unwrap();
948 let face = k.topo.face(fid).unwrap();
949 assert!(matches!(face.surface(), FaceSurface::Plane { .. }));
950 }
951
952 #[test]
953 fn make_polygon_odd_length_coords_is_error() {
954 assert_ne!(7 % 3, 0, "length 7 should fail the multiple-of-3 check");
956 }
957
958 #[test]
959 fn validate_positive_rejects_zero() {
960 assert!(crate::error::validate_positive(0.0, "x").is_err());
961 }
962
963 #[test]
964 fn validate_finite_rejects_nan() {
965 assert!(crate::error::validate_finite(f64::NAN, "x").is_err());
966 }
967
968 #[test]
971 fn make_vertex_stores_position() {
972 let mut k = BrepKernel::new();
973 let h = k.make_vertex(1.0, 2.0, 3.0).unwrap();
974 let vid = k.resolve_vertex(h).unwrap();
975 let v = k.topo.vertex(vid).unwrap();
976 let p = v.point();
977 assert!((p.x() - 1.0).abs() < 1e-10);
978 assert!((p.y() - 2.0).abs() < 1e-10);
979 assert!((p.z() - 3.0).abs() < 1e-10);
980 }
981
982 #[test]
983 fn validate_finite_rejects_infinity() {
984 assert!(crate::error::validate_finite(f64::INFINITY, "x").is_err());
985 }
986
987 #[test]
990 fn make_line_edge_creates_line_curve() {
991 let mut k = BrepKernel::new();
992 let h = k.make_line_edge(0.0, 0.0, 0.0, 1.0, 0.0, 0.0).unwrap();
993 let eid = k.resolve_edge(h).unwrap();
994 let edge = k.topo.edge(eid).unwrap();
995 assert!(
996 matches!(edge.curve(), EdgeCurve::Line),
997 "expected EdgeCurve::Line"
998 );
999 }
1000
1001 #[test]
1002 fn make_line_edge_endpoints_are_distinct_vertices() {
1003 let mut k = BrepKernel::new();
1004 let h = k.make_line_edge(0.0, 0.0, 0.0, 3.0, 4.0, 0.0).unwrap();
1005 let eid = k.resolve_edge(h).unwrap();
1006 let edge = k.topo.edge(eid).unwrap();
1007 assert_ne!(edge.start(), edge.end(), "start and end should differ");
1008 }
1009
1010 #[test]
1013 fn make_wire_from_three_edges_succeeds() {
1014 let mut k = BrepKernel::new();
1015 let e0 = k.make_line_edge(0.0, 0.0, 0.0, 1.0, 0.0, 0.0).unwrap();
1016 let e1 = k.make_line_edge(1.0, 0.0, 0.0, 1.0, 1.0, 0.0).unwrap();
1017 let e2 = k.make_line_edge(1.0, 1.0, 0.0, 0.0, 0.0, 0.0).unwrap();
1018 let wh = k.make_wire(vec![e0, e1, e2], true).unwrap();
1019 let wid = k.resolve_wire(wh).unwrap();
1020 let wire = k.topo.wire(wid).unwrap();
1021 assert_eq!(wire.edges().len(), 3);
1022 assert!(wire.is_closed());
1023 }
1024
1025 #[test]
1026 fn resolve_edge_invalid_handle_is_error() {
1027 let k = BrepKernel::new();
1028 assert!(k.resolve_edge(999).is_err());
1029 }
1030}