mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 11:05:56 +00:00
kernel: coplanar faces joined by SMLib's merge_faces, the record read from the body
planar_regularize now calls the fork's smlib_merge_faces as it is (SmBrep::RemoveTopologicalEdgesAndVertices with the edges under the kept points staying): every edge the lineage does not choose is kept by a point at its middle, so SMLib joins the faces across the chosen ones only. A body with more kept edges than one call takes stays as it is, as the other budgets leave it. splitface, planar_sources and planar_regularize read the body's record in place (Body::record) and a face's use from Body::face_forward; the two-lump test body is Body::from_record of the two records side by side. (not built: base in progress) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
//! Exact topological removal of construction-induced planar subdivisions.
|
||||
//! Shared edge identity and complete construction lineage select the edges; SMLib joins the
|
||||
//! faces either side of them where they are topological (`solidcraft_smlib::Brep::merge_faces`).
|
||||
//! faces either side of them where they are topological (`solidcraft_smlib::Brep::merge_faces`,
|
||||
//! every other edge kept by a point at its middle).
|
||||
use super::Body;
|
||||
use crate::{KernelError, Result, guard};
|
||||
use solidcraft_geom::Vec3;
|
||||
@@ -14,6 +15,12 @@ fn edge_points(e: &brep::Edge) -> Vec<Vec3> {
|
||||
(0..=4).filter_map(|k| c.point(t0 + (t1 - t0) * k as f64 / 4.0)).collect()
|
||||
}
|
||||
|
||||
/// The point halfway along a record edge's interval.
|
||||
fn edge_middle(e: &brep::Edge) -> Option<Vec3> {
|
||||
let [t0, t1] = e.interval;
|
||||
CurveEval::new(&e.curve).ok()?.point(0.5 * (t0 + t1))
|
||||
}
|
||||
|
||||
fn failed(e: impl std::fmt::Display) -> KernelError {
|
||||
KernelError::Failed(format!("SMLib planar regularization: {e}"))
|
||||
}
|
||||
@@ -79,7 +86,7 @@ impl Body {
|
||||
.filter(|p| actual.is_some_and(|actual| super::planar_sources::same_support(p, &actual)))
|
||||
})
|
||||
.collect();
|
||||
let record = crate::native::brep_record(&self)?;
|
||||
let record = self.record();
|
||||
let mut shell_of = vec![usize::MAX; record.faces.len()];
|
||||
for (k, shell) in record.shells.iter().enumerate() {
|
||||
if let brep::Shell::Faces { faces, .. } = shell {
|
||||
@@ -127,15 +134,23 @@ impl Body {
|
||||
if eligible.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
// SMLib removes the topological edges and vertices but those under the points kept:
|
||||
// every edge not chosen (at its middle) stays.
|
||||
let keep: Vec<[f64; 3]> = record
|
||||
.edges
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(k, _)| eligible.binary_search(k).is_err())
|
||||
.map(|(_, e)| edge_middle(e).map(|p| [p.x, p.y, p.z]))
|
||||
.collect::<Option<_>>()
|
||||
.ok_or_else(|| failed("an edge of the body has no middle point"))?;
|
||||
if keep.len() > solidcraft_smlib::MAX_POINTS {
|
||||
return Ok(None);
|
||||
}
|
||||
let (sb, ob) = crate::native::smlib_body_ordered(&self)?;
|
||||
let ob = ob.ok_or_else(|| failed("SMLib numbers the body's edges unlike its record"))?;
|
||||
let at: Vec<usize> = eligible
|
||||
.iter()
|
||||
.map(|e| if ob.edges.is_empty() { Some(*e) } else { ob.edges.iter().position(|c| c == e) })
|
||||
.collect::<Option<_>>()
|
||||
.ok_or_else(|| failed("an edge is not in SMLib's body"))?;
|
||||
let (r, removed) = sb.tagged(0).map_err(failed)?.merge_faces(&at).map_err(failed)?;
|
||||
if removed == 0 {
|
||||
let r = sb.tagged(0).map_err(failed)?.merge_faces(&keep, (self.size() * 1e-6).max(1e-9)).map_err(failed)?;
|
||||
if r.counts().map_err(failed)?.first() == Some(&self.face_count()) {
|
||||
return Ok(None);
|
||||
}
|
||||
let check = r.check(solidcraft_smlib::CheckLevel::All).map_err(failed)?;
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::sync::Arc;
|
||||
/// The oriented plane each face of `body` lies on (in face order), from its record: the
|
||||
/// surface's plane, its normal turned with the face; none for a face on any other surface.
|
||||
pub(super) fn actual_planes(body: &Body) -> Result<Vec<Option<Plane>>> {
|
||||
let record = crate::native::brep_record(body)?;
|
||||
let record = body.record();
|
||||
let mut forward = vec![true; record.faces.len()];
|
||||
for shell in &record.shells {
|
||||
if let brep::Shell::Faces { faces, .. } = shell {
|
||||
@@ -362,7 +362,7 @@ mod tests {
|
||||
|
||||
/// The corner points of face `i` of `b`, from its record.
|
||||
fn face_points(b: &Body, i: usize) -> Vec<Vec3> {
|
||||
let record = crate::native::brep_record(b).unwrap();
|
||||
let record = b.record();
|
||||
let mut out = Vec::new();
|
||||
for l in &record.faces[i].loops {
|
||||
match l {
|
||||
@@ -385,7 +385,7 @@ mod tests {
|
||||
|
||||
/// The edges of face `i` of `b` as their end points, from its record.
|
||||
fn face_edges(b: &Body, i: usize) -> Vec<(Vec3, Vec3)> {
|
||||
let record = crate::native::brep_record(b).unwrap();
|
||||
let record = b.record();
|
||||
let at = |v: u32| {
|
||||
let p = record.vertices[v as usize].point;
|
||||
Vec3::new(p[0], p[1], p[2])
|
||||
@@ -404,7 +404,7 @@ mod tests {
|
||||
/// beyond `a` along x), each face's source that of the face of its own operand on the same
|
||||
/// plane (none from `b` when `b_sources` is false).
|
||||
fn side_by_side(a: &Body, b: &Body, b_sources: bool) -> Body {
|
||||
let (ra, rb) = (crate::native::brep_record(a).unwrap(), crate::native::brep_record(b).unwrap());
|
||||
let (ra, rb) = (a.record(), b.record());
|
||||
let mut r = ra.clone();
|
||||
let (nf, ne, nv, ns) = (r.faces.len() as u32, r.edges.len() as u32, r.vertices.len() as u32, r.shells.len() as u32);
|
||||
for mut face in rb.faces.clone() {
|
||||
@@ -433,7 +433,7 @@ mod tests {
|
||||
region.shells.iter_mut().for_each(|s| *s += ns);
|
||||
r.regions.push(region);
|
||||
}
|
||||
let mut combined = crate::native::body_from_smlib(&solidcraft_smlib::Brep::from_record(&r, true).unwrap()).unwrap();
|
||||
let mut combined = Body::from_record(r).unwrap();
|
||||
let planes = |x: &Body| actual_planes(x).unwrap();
|
||||
let (pa, pb, pc) = (planes(a), planes(b), planes(&combined));
|
||||
let a_right = a.outline_bounds().max.x;
|
||||
|
||||
@@ -151,16 +151,9 @@ fn cutters(tool: &SplitTool, reach: &Aabb3) -> Result<Vec<Vec<Patch>>> {
|
||||
/// where the surface is), facing out of the body.
|
||||
fn face_patch(body: &Body, face: usize) -> Result<Patch> {
|
||||
body.require_brep("a split tool")?;
|
||||
let record = crate::native::brep_record(body)?;
|
||||
let record = body.record();
|
||||
let f = record.faces.get(face).ok_or_else(|| invalid(format!("the tool body has no face {face}")))?;
|
||||
let forward = record
|
||||
.shells
|
||||
.iter()
|
||||
.find_map(|s| match s {
|
||||
brep::Shell::Faces { faces, .. } => faces.iter().find(|o| o.face as usize == face).map(|o| o.forward),
|
||||
_ => None,
|
||||
})
|
||||
.ok_or_else(|| invalid(format!("face {face} of the tool body lies in no shell")))?;
|
||||
let forward = body.face_forward(face);
|
||||
// The surface's own parameter box, unbounded where it is.
|
||||
let dom = SurfaceEval::new(&f.surface).map_err(|e| invalid(format!("the split tool's surface: {e}")))?.domain();
|
||||
let range = |k: usize| dom.range[k].map_or(ALL, |(a, b)| [a, b]);
|
||||
@@ -440,7 +433,7 @@ pub(crate) fn face_sides(body: &Body, tool: &SplitTool) -> Result<Vec<f64>> {
|
||||
/// and how many there are. Faces are in one region when they meet at an edge the tool does not
|
||||
/// run along; an edge along the tool (a split line) parts them.
|
||||
pub(crate) fn regions(split: &Body, tool: &SplitTool) -> Result<(Vec<usize>, usize)> {
|
||||
let record = crate::native::brep_record(split)?;
|
||||
let record = split.record();
|
||||
let fields: Vec<ToolSide> = cutters(tool, &reach_of(split))?
|
||||
.iter()
|
||||
.map(|g| g.iter().map(Measured::new).collect::<Result<Vec<_>>>().map(ToolSide))
|
||||
@@ -590,7 +583,7 @@ fn split(body: &Body, faces: &[usize], tool: &SplitTool) -> Result<(Body, Vec<us
|
||||
/// The edges of `split` that no edge of the body was before (its split lines), as polylines
|
||||
/// through their exact points.
|
||||
fn new_lines(split: &Body, h: &History) -> Result<Vec<Vec<Vec3>>> {
|
||||
let record = crate::native::brep_record(split)?;
|
||||
let record = split.record();
|
||||
let mut out = Vec::new();
|
||||
for (e, prov) in record.edges.iter().zip(&h.edges) {
|
||||
if prov.origins.iter().any(|o| matches!(o, EdgeOrigin::Edge { .. })) {
|
||||
@@ -814,7 +807,7 @@ mod tests {
|
||||
let quarter = std::f64::consts::PI * 36.0 / 4.0;
|
||||
// (Face areas come from meshes.)
|
||||
assert!(rel(tops[0], quarter) < 1e-3 && rel(tops[1], 200.0 - quarter) < 1e-3, "{tops:?}");
|
||||
let record = crate::native::brep_record(&s).unwrap();
|
||||
let record = s.record();
|
||||
assert!(record.edges.iter().any(|e| matches!(e.curve, brep::Curve::Circle { .. } | brep::Curve::RationalSpline { .. })));
|
||||
// A listed face the tool misses is named: the plane z = 10 crosses the sides, not the top.
|
||||
let mid = Plane { origin: Vec3::new(0.0, 0.0, 10.0), ..Plane::XY };
|
||||
|
||||
Reference in New Issue
Block a user