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:
1 parent
cc4955a8da
commit
8c81bc60d0
11 files changed
+1088
-71
No files matched your search
@@ -1,5 +1,6 @@
|
||||
//! Exact radial embossing of orthogonal profiles on complete cylindrical walls.
|
||||
use crate::body::{Body, Solid, from_p3, p3};
|
||||
use crate::history::Change;
|
||||
use crate::offset::{Surf, surf_of};
|
||||
use crate::{KernelError, Result, guard};
|
||||
use solvecraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
|
||||
@@ -255,6 +256,9 @@ fn emboss_impl(body: &Body, anchor: Option<Vec3>, plane: &Plane, regions: &[Regi
|
||||
let map2 = |p: Vec2| Vec2::new(s0 + plane.dir_to_world(p).dot(tangent), z0 + plane.dir_to_world(p).dot(axis));
|
||||
let mut count = 0usize;
|
||||
let mut mapped = Vec::new();
|
||||
// The regions mapped onto the wall as given (their loops' turning may differ from the
|
||||
// sketch's): profile numbering follows them.
|
||||
let mut unoriented = Vec::new();
|
||||
for reg in regions {
|
||||
let mut loops = Vec::new();
|
||||
for lp in std::iter::once(®.outer).chain(®.holes) {
|
||||
@@ -293,6 +297,8 @@ fn emboss_impl(body: &Body, anchor: Option<Vec3>, plane: &Plane, regions: &[Regi
|
||||
let outer = loops.first().ok_or_else(|| fail("profile"))?.ccw();
|
||||
let holes = loops.iter().skip(1).map(|l| l.ccw().reversed()).collect();
|
||||
mapped.push(Region2 { outer, holes });
|
||||
let mut given = loops.into_iter();
|
||||
unoriented.push(Region2 { outer: given.next().ok_or_else(|| fail("profile"))?, holes: given.collect() });
|
||||
}
|
||||
validate_profiles(&mapped, tol)?;
|
||||
if qualify {
|
||||
@@ -334,10 +340,19 @@ fn emboss_impl(body: &Body, anchor: Option<Vec3>, plane: &Plane, regions: &[Regi
|
||||
let [p, q, s, t] = c3;
|
||||
let mat = mt::Matrix4::new(a, b, c, d, e, f, g, h, i, j, l, n, p, q, s, t);
|
||||
let raised = selected.oriented_surface().transformed(mat);
|
||||
// History of the faces added (walls from their profile segments, the raised or sunk
|
||||
// region the profile's end cap), in the order added.
|
||||
let mut added_history = Vec::new();
|
||||
let mut origins = crate::record::loop_origins(&unoriented).into_iter();
|
||||
for reg in &mapped {
|
||||
let mut cap = Vec::new();
|
||||
for (li, lp) in std::iter::once(®.outer).chain(®.holes).enumerate() {
|
||||
let (base, top, walls) = wrapped_loop(lp, r, rr, &at)?;
|
||||
let loop_origins = origins.next().ok_or_else(|| fail("profile numbering"))?;
|
||||
if loop_origins.len() != walls.len() {
|
||||
return Err(fail("profile numbering"));
|
||||
}
|
||||
added_history.extend(loop_origins.iter().map(|o| crate::record::profile(o)));
|
||||
if li == 0 {
|
||||
footprints.push((lp, base.inverse()));
|
||||
} else {
|
||||
@@ -349,6 +364,7 @@ fn emboss_impl(body: &Body, anchor: Option<Vec3>, plane: &Plane, regions: &[Regi
|
||||
added.extend(walls);
|
||||
}
|
||||
added.push(mt::Face::try_new(cap, raised.clone()).map_err(|e| fail(&e.to_string()))?);
|
||||
added_history.push(crate::record::cap(true));
|
||||
}
|
||||
// An island inside a profile hole cuts that hole's original-radius patch,
|
||||
// rather than adding another hole to the already-cut exterior patch.
|
||||
@@ -363,6 +379,13 @@ fn emboss_impl(body: &Body, anchor: Option<Vec3>, plane: &Plane, regions: &[Regi
|
||||
base_patches.get_mut(owner).ok_or_else(|| fail("profile ownership"))?.push(wire);
|
||||
}
|
||||
let mut all: Vec<_> = faces.iter().enumerate().filter(|(i, _)| *i != fi).map(|(_, f)| f.clone()).collect();
|
||||
// History (one operand, the body; profile numbering over `regions`), face by face as
|
||||
// gathered: the other faces kept, the wall's pieces left around the footprints (split
|
||||
// when the profile's holes leave more than one), then the faces added.
|
||||
let pieces = if base_patches.len() > 1 { Change::Split } else { Change::Modified };
|
||||
let mut history: Vec<_> = (0..faces.len()).filter(|i| *i != fi).map(|i| crate::record::from_face(0, i, Change::Kept)).collect();
|
||||
history.extend(std::iter::repeat_n(crate::record::from_face(0, fi, pieces), base_patches.len()));
|
||||
history.extend(added_history);
|
||||
for patch in base_patches {
|
||||
all.push(mt::Face::try_new(patch, selected.oriented_surface()).map_err(|e| fail(&e.to_string()))?);
|
||||
}
|
||||
@@ -372,7 +395,7 @@ fn emboss_impl(body: &Body, anchor: Option<Vec3>, plane: &Plane, regions: &[Regi
|
||||
if !result.validity().is_empty() {
|
||||
return Err(fail("rebuilt cylinder is not a closed manifold"));
|
||||
}
|
||||
Ok(EmbossOutcome::Body(result))
|
||||
Ok(EmbossOutcome::Body(crate::record::attach(result, &[body], history)?))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user