feat: fillet fit option reduces a radius too large for its edges (#388)
* feat(kernel): typed TooLarge refusal and fillet_max search Blend refusals caused only by the size (larger than the neighbouring faces, taller than the walls, longer than the next edge, larger than a corner radius) now return KernelError::TooLarge, displayed as before. fillet_max finds the largest radius below a request at which the fillet of the edges works, to three significant digits, by building candidates; it is an explicit call (up to 16 fillets), not run on every refusal. A corner edge that doesn't run square to the edge is now reported as such instead of as a blend larger than the neighbouring faces. Refs #293 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44 * feat: fillet fit option reduces a radius too large for its edges solid.fillet takes fit: exact (the default, as before: a radius too large for the geometry around the edges is refused) or reduce: the radius is reduced to the largest that works for all the picked edges (kernel fillet_max, three significant digits, every candidate built). The feature stores the requested radius, so it refits on every evaluation; when it reduces, the feature's warning says so and the command returns radius_applied. Pattern copies of the fillet use the same rule. The file format writes fit only when it is reduce. The Fillet dialog gets a Radius Fit row (Exact / Reduce to Fit, constant radius) and, when the preview reduced the radius, an Applied Radius line; editing the feature keeps the choice. One radius for all edges for now; per-edge radii and taper follow. Refs #293 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44 --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
This commit is contained in:
14 files changed
+316
-23
No files matched your search
@@ -323,6 +323,39 @@ fn fillet_consuming_a_side_face_makes_a_full_round() {
|
||||
assert_eq!(measure(&b).unwrap().volume, 840.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fillet_max_finds_the_largest_radius_that_works() {
|
||||
let too_large = |b: &Body, edges: &[Vec3], r: f64| matches!(fillet(b, edges, r), Err(KernelError::TooLarge(_)));
|
||||
// Two rounds on a 6 mm face: up to 3 (a full round), although after the first round at
|
||||
// 3.5 the next edge alone would take only 2.5.
|
||||
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)];
|
||||
assert!(too_large(&b, &mids, 3.5));
|
||||
let (r, body) = fillet_max(&b, &mids, 3.5).unwrap();
|
||||
assert_eq!(r, 3.0);
|
||||
assert!(rel(measure(&body).unwrap().volume, 840.0 - (4.0 - PI) * 90.0) < 2e-4);
|
||||
// A 2 mm disc's top edge: up to 2 (the wall used up).
|
||||
let disc = cylinder(Vec3::ZERO, Vec3::Z, 13.0, 2.0).unwrap();
|
||||
let top = [Vec3::new(0.0, 13.0, 2.0), Vec3::new(0.0, -13.0, 2.0)];
|
||||
assert!(too_large(&disc, &top, 2.5));
|
||||
assert_eq!(fillet_max(&disc, &top, 2.5).map(|x| x.0), Some(2.0));
|
||||
// Walls of different heights: at the shortest one's height only that wall would be used up,
|
||||
// which is refused, so the largest radius is just below it (to the search's 3 digits).
|
||||
let notched = boolean(
|
||||
&box_solid(Vec3::ZERO, Vec3::new(40.0, 30.0, 10.0)).unwrap(),
|
||||
&box_solid(Vec3::new(35.0, -1.0, -1.0), Vec3::new(41.0, 5.0, 3.0)).unwrap(),
|
||||
BoolOp::Cut,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let outline = [Vec3::new(20.0, 0.0, 10.0), Vec3::new(40.0, 15.0, 10.0), Vec3::new(20.0, 30.0, 10.0), Vec3::new(0.0, 15.0, 10.0)];
|
||||
assert!(too_large(¬ched, &outline, 8.0));
|
||||
let (r, _) = fillet_max(¬ched, &outline, 8.0).unwrap();
|
||||
assert!((r - 6.999).abs() < 1e-9, "{r}");
|
||||
// No edge there: no radius.
|
||||
assert!(fillet_max(&b, &[Vec3::new(500.0, 0.0, 5.0)], 1.0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tapered_extrudes() {
|
||||
// 40 x 40 square, 30 up, 10° outward: a frustum.
|
||||
|
||||
Reference in new issue
Block a user