A sketch on an extrude's cap keeps its face when the extrude is made thin (#1532)

Thin extrude caps are certified by the curves the wall follows, so a sketch attached to a cap survives switching between solid and thin (#1519).

Co-Authored-By: Philipp Bischoff <[email protected]>
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-10-15 18:55:17 -04:00
1 parent b400958c3e
commit 7160ff7beb
4 files changed
+149 -2

No files matched your search

+29
View File
@@ -148,6 +148,35 @@ pub(crate) fn extrude_cap_bindings(sketch: &SolvedSketch, bodies: &[solidcraft_k
Some(bindings)
}
/// Cap signatures for a thin extrude (#1519). A thin wall's caps are bounded by offset curves
/// the sketch doesn't have, so [`extrude_cap_bindings`] can never certify them. A wall built
/// from one region is signed by the curves it follows instead: the cap of this extrude whose
/// wall runs along them. That is the signature its solid caps had when the wall follows the
/// whole profile (a rectangle's four lines), so a sketch attached to a solid cap keeps its face
/// when the extrude is made thin, and the other way round. Several regions (walls apart, or one
/// meeting itself) are left uncertified: they keep their history names.
pub(crate) fn thin_extrude_cap_bindings(curves: &[String], regions: usize, bodies: &[solidcraft_kernel::Body]) -> CapBindings {
use solidcraft_kernel::FaceSource;
let mut outer = curves.to_vec();
outer.sort();
outer.dedup();
let signed = regions == 1 && bodies.len() == 1 && valid_signature(&outer, &[]);
bodies
.iter()
.map(|body| {
let sources: Vec<_> = body.face_sources().into_iter().flatten().collect();
let ends =
|high: bool| sources.iter().filter(|source| matches!(source, Some(FaceSource::ExtrudeCap { high: h, .. }) if *h == high)).count();
// One cap at each end, or none of them is certified.
let unique = ends(true) <= 1 && ends(false) <= 1;
sources
.iter()
.map(|source| (signed && unique && matches!(source, Some(FaceSource::ExtrudeCap { .. }))).then(|| (outer.clone(), Vec::new())))
.collect()
})
.collect()
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum FaceCertificate {
+4 -1
View File
@@ -2938,7 +2938,10 @@ fn feature_tools_with_constructor(
// certified as a new body's are (see `Body::with_preserved_union_planar_sources`).
if matches!(operation, Operation::NewBody | Operation::Join) && extent.taper.is_none() {
let bodies: Vec<Body> = tools.iter().map(|t| t.body.clone()).collect();
let cap_bindings = crate::config::extrude_cap_bindings(&ss, &bodies, 10_000_000);
let cap_bindings = match thin {
Some(thin) => Some(crate::config::thin_extrude_cap_bindings(&thin.curves, regions.len(), &bodies)),
None => crate::config::extrude_cap_bindings(&ss, &bodies, 10_000_000),
};
let source_curves = cap_bindings.as_ref().map(|_| crate::config::ExactSketchCurves::new(&ss));
tools = tools
.into_iter()