1use ogeom_algo::{Built, History, make_natural_face};
11use ogeom_core::{OgeomResult, Tolerances, ogeom_bail};
12use ogeom_geom::{Curve, Curve3d as _, Reversible as _, SurfaceGeometry, TrimmedCurve};
13use ogeom_topo::{EdgeRepr, Model, Shape, ShapeType};
14
15pub fn make_filling(
28 model: &mut Model,
29 edges: &[Shape; 4],
30 samples: usize,
31 tolerance: f64,
32 tol: Tolerances,
33) -> OgeomResult<Built> {
34 let mut curves: Vec<Curve> = Vec::with_capacity(4);
35 for edge in edges {
36 if model.kind_of(edge)? != ShapeType::Edge {
37 ogeom_bail!(Construction, "a filling is bounded by edges");
38 }
39 let Some(data) = model.node(edge).and_then(|n| n.data().as_edge()) else {
40 ogeom_bail!(Construction, "edge holds no edge data");
41 };
42 let Some(EdgeRepr::Curve3d { curve, range, .. }) = data.curve3d() else {
43 ogeom_bail!(Construction, "a filling edge needs a 3D curve");
44 };
45 let Some(geometry) = model.geometry().curve(*curve) else {
46 ogeom_bail!(Construction, "edge refers to a curve not in this model");
47 };
48 curves.push(Curve::Trimmed(Box::new(TrimmedCurve::new(
49 geometry.clone(),
50 range.0,
51 range.1,
52 tol,
53 )?)));
54 }
55
56 let slack = tol.confusion() * 1e3;
59 let start = curves[0].start(tol)?;
60 let mut cursor = curves[0].end(tol)?;
61 for curve in curves.iter_mut().skip(1) {
62 if curve.start(tol)?.distance(cursor) > slack {
63 if curve.end(tol)?.distance(cursor) > slack {
64 ogeom_bail!(
65 Construction,
66 "the edges do not chain into a loop; a gap of {} remains",
67 curve
68 .start(tol)?
69 .distance(cursor)
70 .min(curve.end(tol)?.distance(cursor))
71 );
72 }
73 *curve = curve.reversed();
74 }
75 cursor = curve.end(tol)?;
76 }
77 if cursor.distance(start) > slack {
78 ogeom_bail!(
79 Construction,
80 "the loop does not close; a gap of {} remains",
81 cursor.distance(start)
82 );
83 }
84
85 let bottom = curves[0].clone();
88 let right = curves[1].clone();
89 let top = curves[2].reversed();
90 let left = curves[3].reversed();
91 let fitted =
92 ogeom_geom::fit::fill_boundary(&bottom, &top, &left, &right, samples, tolerance, tol)?;
93 if !fitted.met {
94 ogeom_bail!(
95 NotDone,
96 "the filling reached {} against a target of {tolerance}",
97 fitted.error
98 );
99 }
100
101 let built = make_natural_face(model, SurfaceGeometry::BSpline(fitted.curve))?;
102 let mut history = History::new();
103 for edge in edges {
104 history.modify(edge, built.shape.clone());
105 }
106 Ok(Built::new(built.shape, history))
107}