From 35ac9569dca3cc66b887677750ab08464f86b685 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Thu, 8 Oct 2026 14:19:29 +0000 Subject: [PATCH] Holes as deep as the material: a drilled hole whose drill point would start exactly on the exit face is cut through (QA: depth = plate thickness failed with coincident faces on curved bodies) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL --- crates/doc/src/eval.rs | 8 +++++++- crates/engine/src/tests.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/crates/doc/src/eval.rs b/crates/doc/src/eval.rs index 792e3f83..1d17c249 100644 --- a/crates/doc/src/eval.rs +++ b/crates/doc/src/eval.rs @@ -1189,7 +1189,13 @@ fn hole_tools( HoleKind::Simple => tools.push(cyl(r, bottom, top)?), HoleKind::Drilled { tip_angle } => { let half = val(vals, tip_angle, Kind::Angle)? / 2.0; - if depth.is_some() && half > 1e-3 && half < std::f64::consts::FRAC_PI_2 { + // A hole exactly as deep as the material: the point is in the air past the exit + // face, which would lie on the drill point's shoulder (a through hole, as drilled). + let at = |h: f64| *position + dir * (-h); + let eps = size * 1e-6; + let solid_at = |h: f64| st.bodies.iter().any(|b| b.body.contains(at(h))); + let exits_at_shoulder = solid_at(bottom + eps) && !solid_at(bottom - eps); + if depth.is_some() && half > 1e-3 && half < std::f64::consts::FRAC_PI_2 && !exits_at_shoulder { // One revolved tool (cylinder with a drill point). let tip = r / half.tan(); let up = -dir; diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index f72f7d96..cae0f978 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1558,3 +1558,30 @@ fn lip_after_boss() { } } } + +/// A drilled hole as deep as the plate: the drill point breaks through the bottom. +#[test] +fn hole_as_deep_as_the_plate() { + let mut s = Session::default(); + run(&mut s, "PrimitiveBox", json!({"length": 50, "width": 25, "height": 10})); + let v0 = volume(&mut s); + run(&mut s, "FusionHoleCommand", json!({"position": [25, 12.5, 10], "diameter": 6, "depth": 10, "type": "drilled"})); + let v = volume(&mut s); + assert!(v < v0 - PI * 9.0 * 9.0 && v > v0 - PI * 9.0 * 10.0 - 1.0, "{v0} {v}"); + // Other sizes and places, the defaults, and a tapped hole. + for (i, p) in [ + json!({"position": [10, 10, 10], "diameter": 5, "depth": 10, "type": "drilled"}), + json!({"position": [40, 15, 10], "diameter": 5, "depth": 10}), + json!({"position": [12, 6, 10], "diameter": 8.5, "depth": 10}), + json!({"position": [30, 7, 10], "thread": "M6", "depth": 10}), + json!({"position": [20, 18, 10], "diameter": 4, "depth": 10, "tip_angle": 90}), + ] + .into_iter() + .enumerate() + { + let before = volume(&mut s); + let r = s.execute("FusionHoleCommand", &p); + assert!(r.is_ok(), "{i} {p}: {r:?}"); + assert!(volume(&mut s) < before - 1.0, "{i} {p}"); + } +}