1use ogeom_algo::{
11 Built, Containment, History, classify_on_face, make_edge_between, make_vertex, shape_bounds,
12};
13use ogeom_core::{OgeomResult, Tolerances};
14use ogeom_geom::{Curve, Curve3d as _, PlaneSurface, SurfaceGeometry, Transformable as _};
15use ogeom_intersect::{CurveCurveOptions, IntersectOptions, SurfaceIntersection};
16use ogeom_math::{Plane, Point};
17use ogeom_mesh::Deflection;
18use ogeom_topo::{EdgeRepr, Filter, Model, Shape, ShapeType, explore, explore_unique};
19
20pub fn section_face(
35 model: &mut Model,
36 shape: &Shape,
37 plane: &Plane,
38 tol: Tolerances,
39) -> OgeomResult<Built> {
40 let faces = explore(model, shape, Filter::OfType(ShapeType::Face))?;
41 if faces.is_empty() {
42 ogeom_core::ogeom_bail!(Construction, "a section by a plane needs faces to cut");
43 }
44 let bounds = shape_bounds(model, shape, tol)?;
47 let (Some(low), Some(high)) = (bounds.low(), bounds.high()) else {
48 return empty(model, shape);
49 };
50 let reach = low.distance(high).max(tol.confusion() * 1e3);
51 let centre = plane.frame().to_local(low.midpoint(high));
52 let cutter: SurfaceGeometry = PlaneSurface::over(
53 *plane,
54 (centre.x - reach, centre.x + reach),
55 (centre.y - reach, centre.y + reach),
56 )?
57 .into();
58
59 let mut edges: Vec<Shape> = Vec::new();
60 let mut vertices: Vec<(Point, Shape)> = Vec::new();
61 let mut seen_boundary: Vec<ogeom_topo::TShapeId> = Vec::new();
62 for face in &faces {
63 let corners = shape_bounds(model, face, tol)?.corners();
67 let side = |c: &Point| plane.signed_distance_to(*c);
68 if !corners.is_empty()
69 && (corners.iter().all(|c| side(c) > tol.confusion())
70 || corners.iter().all(|c| side(c) < -tol.confusion()))
71 {
72 continue;
73 }
74 let Some(surface_id) = model
75 .node(face)
76 .and_then(|n| n.data().as_face())
77 .map(|d| d.surface)
78 else {
79 continue;
80 };
81 let Some(surface) = model.geometry().surface(surface_id) else {
82 continue;
83 };
84 let placed = surface.transformed(&face.transform(model.datums())?, tol)?;
85 let found = ogeom_intersect::intersect_surfaces(
86 &placed,
87 &cutter,
88 IntersectOptions {
89 tolerance: tol.confusion() * 0.5,
90 ..IntersectOptions::default()
91 },
92 tol,
93 )?;
94 let curves = match found {
95 SurfaceIntersection::Along(curves) => curves,
96 SurfaceIntersection::Same => {
98 for edge in explore_unique(model, face, ShapeType::Edge)? {
99 if !seen_boundary.contains(&edge.node()) {
100 seen_boundary.push(edge.node());
101 edges.push(edge);
102 }
103 }
104 continue;
105 }
106 SurfaceIntersection::Apart | SurfaceIntersection::Touching(_) => continue,
107 };
108 let boundary = boundary_curves(model, face, tol)?;
109 for section in curves {
110 for (lo, hi) in on_face(model, face, §ion.curve, &boundary, tol)? {
111 let from = section.curve.point_at(lo, tol)?;
112 let to = section.curve.point_at(hi, tol)?;
113 let v0 = vertex(model, &mut vertices, from, tol)?;
114 let v1 = vertex(model, &mut vertices, to, tol)?;
115 edges.push(
116 make_edge_between(model, section.curve.clone(), (lo, hi), &v0, &v1, tol)?.shape,
117 );
118 }
119 }
120 }
121 let result = model.add_compound(&edges)?;
122 let mut history = History::new();
123 history.modify(shape, result.clone());
124 Ok(Built::new(result, history))
125}
126
127fn empty(model: &mut Model, shape: &Shape) -> OgeomResult<Built> {
128 let result = model.add_compound(&[])?;
129 let mut history = History::new();
130 history.modify(shape, result.clone());
131 Ok(Built::new(result, history))
132}
133
134pub(crate) fn vertex(
137 model: &mut Model,
138 vertices: &mut Vec<(Point, Shape)>,
139 at: Point,
140 tol: Tolerances,
141) -> OgeomResult<Shape> {
142 if let Some((p, v)) = vertices
143 .iter()
144 .find(|(p, _)| p.distance(at) <= tol.confusion() * 10.0)
145 {
146 let gap = p.distance(at) * 1.01;
149 if gap > tol.confusion()
150 && let Ok(reach) = ogeom_core::Tolerance::new(gap)
151 {
152 model.widen(v, reach)?;
153 }
154 return Ok(v.clone());
155 }
156 let v = make_vertex(model, at).shape;
157 vertices.push((at, v.clone()));
158 Ok(v)
159}
160
161fn boundary_curves(model: &Model, face: &Shape, tol: Tolerances) -> OgeomResult<Vec<Curve>> {
163 let mut out = Vec::new();
164 for edge in explore_unique(model, face, ShapeType::Edge)? {
165 let Some(data) = model.node(&edge).and_then(|n| n.data().as_edge()) else {
166 continue;
167 };
168 let Some(EdgeRepr::Curve3d { curve, range, .. }) = data.curve3d() else {
169 continue;
170 };
171 let Some(geometry) = model.geometry().curve(*curve) else {
172 continue;
173 };
174 let placed = geometry
175 .clone()
176 .transformed(&edge.transform(model.datums())?, tol)?;
177 if let Ok(trimmed) = ogeom_geom::TrimmedCurve::new(placed, range.0, range.1, tol) {
178 out.push(trimmed.into());
179 }
180 }
181 Ok(out)
182}
183
184fn on_face(
188 model: &Model,
189 face: &Shape,
190 curve: &Curve,
191 boundary: &[Curve],
192 tol: Tolerances,
193) -> OgeomResult<Vec<(f64, f64)>> {
194 let (lo, hi) = curve.domain();
195 let mut cuts = vec![lo, hi];
196 let options = CurveCurveOptions {
197 gap: tol.confusion() * 10.0,
198 ..CurveCurveOptions::default()
199 };
200 for edge in boundary {
201 let found = ogeom_intersect::intersect_curves(curve, edge, options, tol)?;
202 cuts.extend(found.crossings.iter().map(|c| c.on_a));
203 for overlap in &found.overlaps {
204 cuts.push(overlap.on_a.0);
205 cuts.push(overlap.on_a.1);
206 }
207 }
208 cuts.retain(|t| *t >= lo && *t <= hi);
209 cuts.sort_by(f64::total_cmp);
210 cuts.dedup_by(|a, b| (*a - *b).abs() <= tol.parametric());
211 let mut kept: Vec<(f64, f64)> = Vec::new();
212 for pair in cuts.windows(2) {
213 let (a, b) = (pair[0], pair[1]);
214 if b - a <= tol.parametric() {
215 continue;
216 }
217 let middle = curve.point_at(f64::midpoint(a, b), tol)?;
218 if classify_on_face(model, face, middle, Deflection::default(), tol)? == Containment::In {
219 match kept.last_mut() {
221 Some((_, end)) if (*end - a).abs() <= tol.parametric() => *end = b,
222 _ => kept.push((a, b)),
223 }
224 }
225 }
226 Ok(kept)
227}