From 64188ce43537304abd37bd55834fbfdfd2a188a9 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Thu, 29 Oct 2026 22:10:42 -0400 Subject: [PATCH] Planar regions: a hole goes to the material that holds it, read closer than the hole lies Reading a planar sheet's faces back as regions, each hole went to the smallest outline whose Loop2::contains held its middle: even-odd against the outline's polyline within 1e-3 mm. A 5e-5 mm square cut 1.7e-4 mm inside a radius-10 circle lies outside the polyline's chords there, so no outline held it and the cut failed ("face 0 has a hole outside its material"). An outline now holds a point by its polyline within a quarter of the point's distance from it (its nearest point on the exact segments), which no chord can pass the point by. Fixes kernel prism::planar_tests::tiny_tool_between_circle_and_display_chord_is_not_lost. Co-Authored-By: Claude Opus 5.5 --- crates/kernel/src/prism.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/kernel/src/prism.rs b/crates/kernel/src/prism.rs index 05323d84cb..0b420e9693 100644 --- a/crates/kernel/src/prism.rs +++ b/crates/kernel/src/prism.rs @@ -260,7 +260,7 @@ mod sheets { let mid = hole.segs.first().map(|s| s.point_at(0.5)).ok_or_else(|| failed("face", format!("face {f} has an empty loop")))?; let owner = out[first..] .iter_mut() - .filter(|r| r.outer.contains(mid)) + .filter(|r| holds(&r.outer, mid)) .min_by(|a, b| a.outer.signed_area().total_cmp(&b.outer.signed_area())) .ok_or_else(|| failed("face", format!("face {f} has a hole outside its material")))?; owner.holes.push(hole); @@ -269,6 +269,15 @@ mod sheets { Ok(Regions { regions: out, touching }) } + /// Whether the loop `outer` holds `p`: even-odd against its polyline within a quarter of + /// `p`'s distance from it, so the polyline's chords cannot pass `p` by (a loop's own + /// [`Loop2::contains`] reads it within 1e-3 mm, and a hole closer than that to an arc of the + /// outline was taken for one outside it, a chord cutting it off). + fn holds(outer: &Loop2, p: Vec2) -> bool { + let d = outer.segs.iter().map(|s| s.point_at(s.closest_param(p)).dist(p)).fold(f64::INFINITY, f64::min); + outer.polyline((d * 0.25).clamp(1e-9, 1e-3)).contains(p) + } + /// The natural normal of a planar surface record. fn natural_normal(s: &Surface) -> Option { let v = |p: [f64; 3]| Vec3::new(p[0], p[1], p[2]);