kernel: a body SMLib walks some loops of the other way keeps its order

smlib_body_ordered keeps SMLib's face and edge order for history only where SMLib's export
numbers the body as its record does, and compared each loop edge for edge in the same order
and direction. SMLib walks a face whose surface it turned (a hole's wall, a concave cylinder)
the other way round and may start the loop elsewhere, so a plate with a hole lost its history
through every SMLib boolean and shell although its faces and edges are numbered alike. A loop
now matches when it walks the same edges in the same cyclic order, or in the opposite order
with every edge reversed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT
This commit is contained in:
jyviko
2026-10-21 08:13:26 -04:00
parent 0f3d6f177e
commit 82865e2832
+20 -3
View File
@@ -240,9 +240,7 @@ fn same_numbering(a: &solidcraft_geom::brep::Brep, b: &solidcraft_geom::brep::Br
let loops_alike = a.faces.iter().zip(&b.faces).all(|(fa, fb)| {
fa.loops.len() == fb.loops.len()
&& fa.loops.iter().zip(&fb.loops).all(|pair| match pair {
(Loop::Edges { edges: x }, Loop::Edges { edges: y }) => {
x.len() == y.len() && x.iter().zip(y).all(|(u, w)| u.edge == w.edge && u.forward == w.forward)
}
(Loop::Edges { edges: x }, Loop::Edges { edges: y }) => same_cycle(x, y),
(Loop::Vertex { vertex: x }, Loop::Vertex { vertex: y }) => x == y,
_ => false,
})
@@ -255,6 +253,25 @@ fn same_numbering(a: &solidcraft_geom::brep::Brep, b: &solidcraft_geom::brep::Br
loops_alike && ends_alike && at_same_place
}
/// Whether two loops walk the same edges: in the same order from some edge on, or in the
/// opposite order with each edge the other way round. SMLib walks a face whose surface it turned
/// (a hole's wall) the other way, starting where it likes; its faces and edges are numbered
/// alike all the same.
#[cfg(feature = "smlib")]
fn same_cycle(x: &[solidcraft_geom::brep::OrientedEdge], y: &[solidcraft_geom::brep::OrientedEdge]) -> bool {
let n = x.len();
if n != y.len() {
return false;
}
let Some(first) = x.first() else { return true };
let at = |k: usize| y.get(k % n);
(0..n).filter(|k| at(*k).is_some_and(|e| e.edge == first.edge)).any(|k| {
let ahead = x.iter().enumerate().all(|(i, u)| at(k + i).is_some_and(|w| w.edge == u.edge && w.forward == u.forward));
let back = x.iter().enumerate().all(|(i, u)| at(k + n - i).is_some_and(|w| w.edge == u.edge && w.forward != u.forward));
ahead || back
})
}
fn validate_topology(saved: &SavedBody) -> Result<()> {
saved.solid.validate().map_err(invalid)?;
let (faces, edges) = (saved.solid.faces.len(), saved.solid.edges.len());