fix(kernel): fillet that uses up a side face makes a full round (#317)

A constant-radius fillet whose setback equals the width of a neighbouring
face was refused ("the blend is larger than the neighbouring faces"), so
r = 3 on the vertical edges of a 6 mm wide box failed while 2.999 worked.
The corner edge is now consumed: the rail ends at its far vertex and a side
face used up at both ends is removed, its remaining edge becoming the rail.
A face used up at one end only, and radii past the limit, are still refused.

Refs #199


Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iraklis KourtisandClaude Opus 5.5 authored and GitHub committed 2026-10-12 11:44:32 -04:00
1 parent a3ff816176
commit a9e4830ded
3 files changed
+109 -17

No files matched your search

+33
View File
@@ -181,6 +181,39 @@ fn fillets_on_several_edges_and_angles() {
assert!(fillet(&u, &[Vec3::new(15.0, 10.0, 20.0)], 1.0).is_err());
}
#[test]
fn fillet_consuming_a_side_face_makes_a_full_round() {
// 14 x 6 x 10: r = 3 on the four vertical edges uses up both 6 mm end faces (#199).
let b = box_solid(Vec3::new(14.0, 0.0, 0.0), Vec3::new(28.0, 6.0, 10.0)).unwrap();
let mids = [Vec3::new(14.0, 0.0, 5.0), Vec3::new(28.0, 0.0, 5.0), Vec3::new(14.0, 6.0, 5.0), Vec3::new(28.0, 6.0, 5.0)];
let full = 840.0 - (4.0 - PI) * 9.0 * 10.0;
for order in [mids.to_vec(), mids.iter().rev().copied().collect()] {
let f = fillet(&b, &order, 3.0).unwrap();
let m = measure(&f).unwrap();
assert!(rel(m.volume, full) < 2e-4, "{} vs {full}", m.volume);
// Top, bottom, the two long sides and four quarter rounds; the end faces are gone.
assert_eq!(m.faces, 8, "{m:?}");
assert_eq!(f.lumps().unwrap().len(), 1);
assert!(f.validity().is_empty(), "{:?}", f.validity());
}
// One end only: two rounds meeting on one end face.
let f = fillet(&b, &mids[1..4].iter().step_by(2).copied().collect::<Vec<_>>(), 3.0).unwrap();
assert!(rel(measure(&f).unwrap().volume, 840.0 - (4.0 - PI) * 9.0 * 5.0) < 2e-4);
assert!(f.validity().is_empty());
// A 6 x 6 post rounded on all four edges with r = 3 is a cylinder.
let post = box_solid(Vec3::ZERO, Vec3::new(6.0, 6.0, 10.0)).unwrap();
let corners = [Vec3::new(0.0, 0.0, 5.0), Vec3::new(6.0, 0.0, 5.0), Vec3::new(6.0, 6.0, 5.0), Vec3::new(0.0, 6.0, 5.0)];
let c = fillet(&post, &corners, 3.0).unwrap();
assert!(rel(measure(&c).unwrap().volume, PI * 9.0 * 10.0) < 2e-4, "{}", measure(&c).unwrap().volume);
assert!(c.validity().is_empty());
// Past the limit there is no face left to consume: still refused, the body unchanged.
for r in [3.0 + 1e-3, 3.5] {
let e = fillet(&b, &mids, r).unwrap_err();
assert!(e.to_string().contains("larger than the neighbouring faces"), "{r}: {e}");
}
assert_eq!(measure(&b).unwrap().volume, 840.0);
}
#[test]
fn tapered_extrudes() {
// 40 x 40 square, 30 up, 10° outward: a frustum.