hole: keep a countersink's tool within its rim above the face

The countersink's cone started 1% of the drilled body's size above the
entry face, widening on to rc + top tan(half) there, so above the face
it cut beyond the countersink's rim: a wall 0.15 mm from the rim was
notched (#5041, 12 faces instead of 10). The tool is now one revolved
profile: the cone from its rim on the face to just inside the bore, and
above the face the rim's own cylinder, as the bore's cylinder runs on
above it. The cone stays the profile's first segment (csink:side:0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-26 05:46:39 -04:00
parent 610173e1fe
commit 3fb263d004
2 changed files with 42 additions and 8 deletions
+16 -8
View File
@@ -4300,8 +4300,8 @@ fn hole_tools(
}
/// The scale a hole's tools are sized by: the largest body the hole's axis meets (#2904), so a
/// large body elsewhere in the model changes neither the tool's reach above the face (and so
/// the countersink's outline) nor its depth. A hole whose axis meets no body is sized by the
/// large body elsewhere in the model changes neither the tool's reach above the face nor its
/// depth. A hole whose axis meets no body is sized by the
/// largest body of the model, as before.
fn hole_axis_size(st: &ModelState, position: &Vec3, dir: Vec3) -> f64 {
let along = |b: &ModelBody| {
@@ -4417,13 +4417,21 @@ fn hole_tools_and_prisms(
if !(rc > r && half > 1e-3 && half < std::f64::consts::FRAC_PI_2) {
return Err(DocError::Invalid("the countersink must be wider than the hole".into()));
}
// Cone from above the face to just inside the hole radius, then the hole.
let r_top = rc + top * half.tan();
// One revolved tool: the cone from its rim on the face to just inside the hole
// radius, and above the face the rim's cylinder, as the bore's own cylinder runs on
// above it. The cone widening on above the face would cut what stands beside the rim
// (#5041). The cone is the profile's first segment (`csink:side:0`).
let r_end = r * 0.9;
let len = (r_top - r_end) / half.tan();
let top_plane = plane.offset(top);
let csink = kernel::extrude_tapered(&top_plane, &disc(r_top), len, -1.0, -half, kernel::DraftCorner::default())?;
tools.push((crate::lineage::part_tool(csink, "csink"), None));
let len = (rc - r_end) / half.tan();
let up = -dir;
let rp = Plane::new(*position, up.any_perp(), up).ok_or_else(|| DocError::Invalid("hole axis".into()))?;
let pts = [Vec2::new(rc, 0.0), Vec2::new(r_end, -len), Vec2::new(0.0, -len), Vec2::new(0.0, top), Vec2::new(rc, top)];
let region = Region2 { outer: solidcraft_geom::Loop2::polygon(&pts), holes: vec![] };
tools.extend(
kernel::revolve(&rp, &[region], Vec2::ZERO, Vec2::Y, std::f64::consts::TAU)?
.into_iter()
.map(|tool| (crate::lineage::part_tool(tool, "csink"), None)),
);
tools.extend(bore(tip_angle.as_ref())?);
}
}
@@ -133,3 +133,29 @@ fn a_far_body_does_not_change_a_countersunk_hole() {
assert_eq!(small_changed, large_changed, "bodies changed");
assert_eq!(large_changed, json!(["lid"]), "only the lid is drilled");
}
/// #5041: a countersunk Ø6.4 hole (Ø13.7, 90°) 0.15 mm from a wall standing on the plate cuts
/// nothing above the entry face: the countersink's rim (y 29.85) stays clear of the wall (y 30).
/// 10 faces: bottom, the two L-shaped ends, front and back, the plate top, the wall's front and
/// top, the cone and the bore.
#[test]
fn a_countersink_beside_a_wall_stays_below_the_face() {
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 40, "width": 40, "height": 8, "corner": [0, 0, 0]}));
run(&mut s, "solid.box", json!({"length": 40, "width": 10, "height": 30, "corner": [0, 30, 8], "operation": "join"}));
run(
&mut s,
"solid.hole",
json!({"position": [20, 23, 8], "direction": [0, 0, -1], "diameter": 6.4, "type": "countersink", "cs_diameter": 13.7, "cs_angle": 90}),
);
let st = s.model.state();
let [body] = st.bodies.as_slice() else { panic!("one body, got {}", st.bodies.len()) };
assert_eq!(body.body.face_count(), 10, "the wall is not notched");
// In the wall's foot, 0.4 mm above the face: still solid.
assert!(solid_at(&s, Vec3::new(20.0, 30.2, 8.4)), "the wall's foot is cut");
let (rr, r, h) = (6.85_f64, 3.2_f64, 3.65_f64);
let removed = PI * h / 3.0 * (rr * rr + rr * r + r * r) + PI * r * r * (8.0 - h);
let want = 24800.0 - removed;
let got = volume(&mut s);
assert!((got - want).abs() < 1e-3, "volume {got}, want {want}");
}