feat(kernel): an edge's meeting sides are sets of origins (merged faces)

EdgeOrigin::Meet now names each side as a Side: the sorted, de-duplicated
origins of the face there. substitute maps a side to the union of the inner
faces' origins, so a meeting with a face an inner operation merged keeps its
lineage; meeting() derives sides from whole faces. Fillets after box cuts
that leave coplanar pieces now report exact history. Prism lineage meets a
cap of several origins and resolves wall junctions away from shared
vertices by the operand's one edge between the walls. bore_union's recut
reports history only for a one-face bore (pieces of a two-arc bore's recut
wall were given both arcs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
This commit is contained in:
Iraklis KourtisandClaude Opus 5.5 authored and bherbruck committed 2026-10-12 11:52:53 -04:00
1 parent 08d7037176
commit 43ddde4a5d
11 files changed
+347 -76

No files matched your search

+25 -19
View File
@@ -13,7 +13,7 @@ use truck_modeling as mt;
use super::{Index, Section, Tag, dist_to, inside};
use crate::BoolOp;
use crate::body::{Body, edge_order, from_p3};
use crate::history::{EdgeOrigin, FaceOrigin, FaceOrigin::Face, FaceProv};
use crate::history::{EdgeOrigin, FaceOrigin, FaceOrigin::Face, FaceProv, Side};
/// A cap of an operand: a planar face square to the prism direction.
struct Cap {
@@ -188,7 +188,8 @@ impl Context<'_> {
/// The origins of the edge between the wall over a segment tagged `t` and a cap lying on the
/// operand faces `cap`: the operand's edge between the wall and its own cap there (the very
/// edge the segment was read off, when that is one), else where the wall meets the cap.
/// edge the segment was read off, when that is one), else where the wall meets the cap (all of
/// the faces it lies on).
fn cap_edge(&self, t: Tag, cap: &BTreeSet<FaceOrigin>) -> Option<Vec<EdgeOrigin>> {
let (x, w) = (t.operand, t.wall?);
let own: Vec<usize> =
@@ -199,33 +200,32 @@ impl Context<'_> {
return Some(vec![EdgeOrigin::Edge { operand: x, edge: e }]);
}
let along: Vec<usize> = own.iter().flat_map(|c| self.edges_between(x, [w, *c], None)).collect();
match (along.as_slice(), cap.first()) {
([e], _) => Some(vec![EdgeOrigin::Edge { operand: x, edge: *e }]),
([], Some(o)) if cap.len() == 1 => {
let wall = Self::wall(t)?;
Some(vec![EdgeOrigin::Meet { a: wall.min(*o), b: wall.max(*o) }])
}
match along.as_slice() {
[e] => Some(vec![EdgeOrigin::Edge { operand: x, edge: *e }]),
[] => Some(vec![EdgeOrigin::meet(Self::wall(t)?, Side::new(cap.iter().copied())?)]),
_ => None,
}
}
/// The origins of the edge between the walls over two segments where the first (tagged
/// `t1`) runs into the second (`t2`): the operand's edge there between its walls (at the
/// operand vertex the segments meet at), where two operands' walls meet, or none within one
/// wall (a segment split there).
/// operand vertex the segments meet at, else the one edge between them), where two
/// operands' walls meet, or none within one wall (a segment split there).
fn junction(&self, t1: Tag, t2: Tag) -> Option<Vec<EdgeOrigin>> {
let (p, q) = (Self::wall(t1)?, Self::wall(t2)?);
if t1.operand != t2.operand {
return Some(vec![EdgeOrigin::Meet { a: p.min(q), b: p.max(q) }]);
return Some(vec![EdgeOrigin::meet(p, q)]);
}
let (x, w1, w2) = (t1.operand, t1.wall?, t2.wall?);
match (t1.end, t2.start) {
(Some(v), Some(u)) if v == u => match self.edges_between(x, [w1, w2], Some(v)).as_slice() {
[e] => Some(vec![EdgeOrigin::Edge { operand: x, edge: *e }]),
_ => None,
},
// At that vertex: the operand's edge there (a seam, within one wall), else two edges
// of one wall's cap outline running on through it.
(Some(v), Some(u)) if v == u => self.one_edge(x, [w1, w2], Some(v)).or_else(|| (w1 == w2).then(Vec::new)),
_ if w1 == w2 => Some(Vec::new()),
_ => None,
// Elsewhere (a segment split off there, or read off another cap): only the operand's
// walls are around the edge, so the operand has its edge between them there, and
// that is the one edge between them where there is one.
_ => self.one_edge(x, [w1, w2], None),
}
}
@@ -238,10 +238,16 @@ impl Context<'_> {
return Some(Vec::new());
}
if t1.operand != t2.operand {
return Some(vec![EdgeOrigin::Meet { a: p.min(q), b: p.max(q) }]);
return Some(vec![EdgeOrigin::meet(p, q)]);
}
match self.edges_between(t1.operand, [t1.wall?, t2.wall?], None).as_slice() {
[e] => Some(vec![EdgeOrigin::Edge { operand: t1.operand, edge: *e }]),
self.one_edge(t1.operand, [t1.wall?, t2.wall?], None)
}
/// The operand's edge between the faces `faces` (touching vertex `at` when given), when it
/// has exactly one.
fn one_edge(&self, operand: usize, faces: [usize; 2], at: Option<usize>) -> Option<Vec<EdgeOrigin>> {
match self.edges_between(operand, faces, at).as_slice() {
[edge] => Some(vec![EdgeOrigin::Edge { operand, edge: *edge }]),
_ => None,
}
}