feat(kernel): creation builders report exact face history

Extrude, revolve (partial and full turn), tapered extrude, loft (incl. to a
point), sweep, path sweep, coil, primitives, lip, emboss and thicken attach
the exact history of the bodies they build: each face's profile segment, cap
or operand face, and each edge's operand edge, recorded by topology identity
inside the building call. Nothing is sampled; a body whose faces do not follow
the expected construction carries no history. Face sources are unchanged.

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 cc4955a8da
commit 8c81bc60d0
11 files changed
+1088 -71

No files matched your search

+25 -1
View File
@@ -7,6 +7,7 @@ use solvecraft_geom::Vec3;
use truck_modeling::{self as mt, builder};
use crate::body::{Body, Solid, from_p3, p3, v3};
use crate::history::{Change, FaceOrigin, FaceProv};
use crate::{KernelError, Result, guard};
fn fail(m: &str) -> KernelError {
@@ -175,6 +176,15 @@ pub fn raise_band(body: &Body, at: Vec3, width: f64, height: f64, outside: bool)
Some(m) => builder::circle_arc(a, b, p3(m)),
});
}
// The loop's edges and the band's edge beside each, by the loop edge's index in the body.
let edge_index = crate::record::edge_index(body);
let mut wall_edge: std::collections::HashMap<mt::EdgeID, usize> = std::collections::HashMap::new();
for (e, ce) in along.edge_iter().zip(&cedges) {
if let Some(i) = edge_index.get(&e.id()) {
wall_edge.insert(e.id(), *i);
wall_edge.insert(ce.id(), *i);
}
}
let c: mt::Wire = cedges.into();
// The rest of the face: its other loop, and the band's edge as a loop of its own.
// (The band's edge runs the face's way round: a hole when the band follows the inner
@@ -190,6 +200,20 @@ pub fn raise_band(body: &Body, at: Vec3, width: f64, height: f64, outside: bool)
let flipped = |sh: &mt::Shell, yes: bool| -> mt::Shell {
if yes { sh.face_iter().map(|x| x.inverse()).collect::<Vec<_>>().into() } else { sh.clone() }
};
// History (one operand, the body), face by face as gathered below: the other faces
// kept, the face trimmed to what the band leaves, each wall made along the loop
// edge it rises from (the band's inner wall along the edge it runs beside), and the
// band's top an offset of the face. (Flipping a face keeps its edges.)
let history: Option<Vec<FaceProv>> = (0..faces.len())
.filter(|i| *i != fi)
.map(|i| Some(crate::record::from_face(0, i, Change::Kept)))
.chain([Some(crate::record::from_face(0, fi, Change::Modified))])
.chain(wall_along.face_iter().chain(wall_c.face_iter()).map(|w| {
let edge = w.absolute_boundaries().iter().flatten().find_map(|e| wall_edge.get(&e.id()))?;
Some(crate::record::generated(vec![FaceOrigin::Edge { operand: 0, edge: *edge }]))
}))
.chain([Some(crate::record::generated(vec![FaceOrigin::Offset { operand: 0, face: fi }]))])
.collect();
let mut last = String::new();
for flip in 0..8u32 {
let (wa, wc) = (flipped(&wall_along, flip & 1 == 1), flipped(&wall_c, flip & 2 == 2));
@@ -211,7 +235,7 @@ pub fn raise_band(body: &Body, at: Vec3, width: f64, height: f64, outside: bool)
Ok(s) => {
let b = Body::new(s)?;
if b.validity().is_empty() {
return Ok(b);
return crate::record::attach_if(b, &[body], history.clone());
}
last = b.validity().join("; ");
}