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:
1 parent
08d7037176
commit
43ddde4a5d
11 files changed
+347
-76
No files matched your search
@@ -48,13 +48,64 @@ pub struct FaceProv {
|
||||
pub change: Change,
|
||||
}
|
||||
|
||||
/// One side of a meeting: the origins of the face on that side of the edge. One origin, or
|
||||
/// several where that face was made from several (a merged face) and the edge lies along it
|
||||
/// as a whole. Never empty; sorted, without repeats, so equal sides compare equal.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Side(Vec<FaceOrigin>);
|
||||
|
||||
impl Side {
|
||||
/// The side made of `origins` (in any order, repeats allowed); `None` when there are none.
|
||||
pub fn new(origins: impl IntoIterator<Item = FaceOrigin>) -> Option<Side> {
|
||||
let side = Side::canonical(origins.into_iter().collect());
|
||||
(!side.0.is_empty()).then_some(side)
|
||||
}
|
||||
|
||||
/// Its origins, sorted.
|
||||
pub fn origins(&self) -> &[FaceOrigin] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// The side's one origin, when it has exactly one.
|
||||
pub fn single(&self) -> Option<FaceOrigin> {
|
||||
match self.0.as_slice() {
|
||||
[o] => Some(*o),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The same side with each origin mapped by `f` (failing where `f` does).
|
||||
fn try_map(&self, f: impl Fn(FaceOrigin) -> Result<FaceOrigin>) -> Result<Side> {
|
||||
Ok(Side::canonical(self.0.iter().map(|o| f(*o)).collect::<Result<_>>()?))
|
||||
}
|
||||
|
||||
/// The same side with each origin's operand renumbered by `f`.
|
||||
fn map_operand(&self, f: impl Fn(usize) -> usize) -> Side {
|
||||
Side::canonical(self.0.iter().map(|o| o.map_operand(&f)).collect())
|
||||
}
|
||||
|
||||
/// `origins` sorted, without repeats.
|
||||
fn canonical(mut origins: Vec<FaceOrigin>) -> Side {
|
||||
origins.sort_unstable();
|
||||
origins.dedup();
|
||||
Side(origins)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FaceOrigin> for Side {
|
||||
fn from(origin: FaceOrigin) -> Side {
|
||||
Side(vec![origin])
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a result edge came from.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum EdgeOrigin {
|
||||
/// (Part of) edge `edge` of input `operand`.
|
||||
Edge { operand: usize, edge: usize },
|
||||
/// Where two faces meet that met nowhere before (a boolean's intersection curve).
|
||||
Meet { a: FaceOrigin, b: FaceOrigin },
|
||||
/// Where two faces meet that met nowhere before (a boolean's intersection curve): the
|
||||
/// origins of the face on each side, `a <= b` (made with [`EdgeOrigin::meet`]).
|
||||
Meet { a: Side, b: Side },
|
||||
}
|
||||
|
||||
/// A result edge: the result faces it separates and what it came from (empty when it is new
|
||||
@@ -91,11 +142,18 @@ impl FaceOrigin {
|
||||
}
|
||||
|
||||
impl EdgeOrigin {
|
||||
/// Where the faces with origins `a` and `b` (sides, or single origins) meet, in canonical
|
||||
/// order (the lesser side first).
|
||||
pub fn meet(a: impl Into<Side>, b: impl Into<Side>) -> EdgeOrigin {
|
||||
let (a, b) = (a.into(), b.into());
|
||||
if a <= b { EdgeOrigin::Meet { a, b } } else { EdgeOrigin::Meet { a: b, b: a } }
|
||||
}
|
||||
|
||||
/// The same origin with its operands renumbered by `f`.
|
||||
fn map_operand(self, f: impl Fn(usize) -> usize) -> EdgeOrigin {
|
||||
fn map_operand(&self, f: impl Fn(usize) -> usize) -> EdgeOrigin {
|
||||
match self {
|
||||
EdgeOrigin::Edge { operand, edge } => EdgeOrigin::Edge { operand: f(operand), edge },
|
||||
EdgeOrigin::Meet { a, b } => EdgeOrigin::Meet { a: a.map_operand(&f), b: b.map_operand(&f) },
|
||||
EdgeOrigin::Edge { operand, edge } => EdgeOrigin::Edge { operand: f(*operand), edge: *edge },
|
||||
EdgeOrigin::Meet { a, b } => EdgeOrigin::meet(a.map_operand(&f), b.map_operand(&f)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,20 +201,24 @@ impl History {
|
||||
}
|
||||
let ok = e.origins.iter().all(|o| match o {
|
||||
EdgeOrigin::Edge { operand, edge } => counts.get(*operand).is_some_and(|c| *edge < c.1),
|
||||
EdgeOrigin::Meet { a, b } => face_ok(a) && face_ok(b),
|
||||
EdgeOrigin::Meet { a, b } => a.origins().iter().chain(b.origins()).all(face_ok),
|
||||
});
|
||||
if !ok {
|
||||
return Err(failed("an edge origin lies outside its operand"));
|
||||
}
|
||||
if e.origins.iter().any(|o| matches!(o, EdgeOrigin::Meet { a, b } if a > b)) {
|
||||
return Err(failed("a meeting's sides are out of order"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compose with the history of the operation that made operand `k`: origins on operand `k`
|
||||
/// become that operation's origins, its operands (none, for a body made from a sketch)
|
||||
/// taking `k`'s place in the operand list. Fails, without changing `self`, where a lineage
|
||||
/// cannot be expressed (an offset of a face that operation generated, a blend of an edge
|
||||
/// it generated, an edge between merged faces).
|
||||
/// taking `k`'s place in the operand list; a meeting's side becomes the origins of the faces
|
||||
/// it names (several where that operation merged faces). Fails, without changing `self`,
|
||||
/// where a lineage cannot be expressed (an offset of a face that operation generated or
|
||||
/// merged, a blend of an edge it generated, a meeting with a face it made from nothing).
|
||||
pub fn substitute(&mut self, k: usize, inner: &History) -> Result<()> {
|
||||
if k >= self.operands {
|
||||
return Err(failed("no such operand"));
|
||||
@@ -209,26 +271,30 @@ impl History {
|
||||
};
|
||||
faces.push(FaceProv { origins, change });
|
||||
}
|
||||
// One side of a meeting: a single face of the composed operation.
|
||||
let side = |o: FaceOrigin| -> Result<FaceOrigin> {
|
||||
match map_face(o)?.0.as_slice() {
|
||||
[x] => Ok(*x),
|
||||
_ => Err(failed("an edge meets a face the inner operation merged")),
|
||||
// Split where an origin lies under several composed faces (pieces the inner operation
|
||||
// split and this one joined back, or dropped all but one of, are not).
|
||||
crate::record::mark_shared_split(&mut faces);
|
||||
// One side of a meeting: the origins of what its faces were made from.
|
||||
let side = |s: &Side| -> Result<Side> {
|
||||
let mut origins = Vec::new();
|
||||
for o in s.origins() {
|
||||
origins.extend(map_face(*o)?.0);
|
||||
}
|
||||
Side::new(origins).ok_or_else(|| failed("an edge meets a face the inner operation made from nothing"))
|
||||
};
|
||||
let mut edges = Vec::with_capacity(self.edges.len());
|
||||
for e in &self.edges {
|
||||
let mut origins = BTreeSet::new();
|
||||
for o in &e.origins {
|
||||
match *o {
|
||||
EdgeOrigin::Edge { operand, edge } if operand == k => {
|
||||
origins.extend(inner_edge(edge)?.origins.iter().map(|x| x.map_operand(lifted)))
|
||||
match o {
|
||||
EdgeOrigin::Edge { operand, edge } if *operand == k => {
|
||||
origins.extend(inner_edge(*edge)?.origins.iter().map(|x| x.map_operand(lifted)))
|
||||
}
|
||||
EdgeOrigin::Edge { .. } => {
|
||||
origins.insert(o.map_operand(outer));
|
||||
}
|
||||
EdgeOrigin::Meet { a, b } => {
|
||||
origins.insert(EdgeOrigin::Meet { a: side(a)?, b: side(b)? });
|
||||
origins.insert(EdgeOrigin::meet(side(a)?, side(b)?));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,9 +334,9 @@ impl History {
|
||||
let origins: BTreeSet<EdgeOrigin> = e
|
||||
.origins
|
||||
.iter()
|
||||
.map(|o| match *o {
|
||||
EdgeOrigin::Edge { operand, edge } => Ok(EdgeOrigin::Edge { operand: to(operand)?, edge }),
|
||||
EdgeOrigin::Meet { a, b } => Ok(EdgeOrigin::Meet { a: face(a)?, b: face(b)? }),
|
||||
.map(|o| match o {
|
||||
EdgeOrigin::Edge { operand, edge } => Ok(EdgeOrigin::Edge { operand: to(*operand)?, edge: *edge }),
|
||||
EdgeOrigin::Meet { a, b } => Ok(EdgeOrigin::meet(a.try_map(face)?, b.try_map(face)?)),
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
edges.push(EdgeProv { faces: e.faces.clone(), origins: origins.into_iter().collect() });
|
||||
|
||||
Reference in new issue
Block a user