From 5732b62ec3f2e5abad646c8dde4d2fc01732b9bd Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Fri, 30 Oct 2026 08:42:30 -0400 Subject: [PATCH] Tapered extrude: the end check of a profile with holes takes the section a swallowed line leaves A tapered extrusion of a region with holes checks, at its two ends, that each hole's section stays inside the outer loop's and clear of the others. Those sections were made with the start section's rule, which refuses a line the offset swallows ("draft extrusion that starts before its profile where the taper closes a side"). At the far end such a line is ordinary: a short line between two concave corners whose wall closes on the way up. The letter g of "tres long" (8 mm, joined 2 mm with a 3 degree taper) has one, and the whole text was refused with a message about the start that does not apply. The end check now takes the section as it is there (offset_loop's swallowed lines left out, their neighbours meeting where they were); whether the closing wall is built stays the draft's to say. The g builds at 3 and 15 degrees. Co-Authored-By: Claude Opus 5.5 --- crates/kernel/src/build.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/kernel/src/build.rs b/crates/kernel/src/build.rs index 8dfbb96c41..b87478d027 100644 --- a/crates/kernel/src/build.rs +++ b/crates/kernel/src/build.rs @@ -827,12 +827,23 @@ fn smlib_tapered(plane: &Plane, region: &Region2, from: f64, to: f64, foot: f64, return Err(KernelError::Invalid("a tapered extrusion needs a taper".into())); }; let holes: Vec = joined.holes.iter().map(|h| section(h, from, -taper)).collect::>()?; + // A loop's section `at` as it is there: a line the offset swallows is gone, its neighbours + // meeting where it was (its wall closes on the way; whether that wall is built is the + // draft's to say). + let shape = |l: &NumberedLoop, at: f64, taper: f64| -> Result { + let ccw = l.oriented(false); + if at == 0.0 || taper == 0.0 { + return Ok(ccw); + } + let segs: Vec = offset_loop(&ccw.lp, at * taper.tan())?.into_iter().flatten().collect(); + Ok(NumberedLoop { lp: Loop2 { segs }, from: ccw.from }) + }; // The sections where the walls are farthest moved, the two ends: each hole stays inside the // outer loop and clear of the other holes there (the walls are planes, cones and their like, // which move monotonically in between). for at in [from, to] { - let o = section(&joined.outer, at, taper)?; - let hs: Vec = joined.holes.iter().map(|h| section(h, at, -taper)).collect::>()?; + let o = shape(&joined.outer, at, taper)?; + let hs: Vec = joined.holes.iter().map(|h| shape(h, at, -taper)).collect::>()?; let pts = |l: &NumberedLoop| l.lp.segs.iter().flat_map(|s| s.polyline(1e-3)).collect::>(); // Apart: no point of either inside the other. let apart = |a: &NumberedLoop, b: &NumberedLoop| !pts(a).iter().any(|p| b.lp.contains(*p)) && !pts(b).iter().any(|p| a.lp.contains(*p));