diff --git a/crates/engine/src/fuzz.rs b/crates/engine/src/fuzz.rs index c1f51c5f33..2219c5ed6a 100644 --- a/crates/engine/src/fuzz.rs +++ b/crates/engine/src/fuzz.rs @@ -246,7 +246,9 @@ fn next_step(r: &mut Rng, s: &Session, last_feature: &Option) -> Option< let edges = body.body.edges(tol).ok()?; let n = 1 + r.below(3); let picks: Vec<[f64; 3]> = (0..n).filter_map(|_| edges.get(r.below(edges.len()))).map(|e| [e.mid.x, e.mid.y, e.mid.z]).collect(); - step("chamfer", Expect::Shrink, vec![("solid.chamfer", json!({"edges": picks, "distance": r.pick(&[0.5, 1.0, 2.0])?}))]) + // Any, as for fillets: the edges are picked at random, and a chamfer on a concave edge + // fills the corner, so it adds material. + step("chamfer", Expect::Any, vec![("solid.chamfer", json!({"edges": picks, "distance": r.pick(&[0.5, 1.0, 2.0])?}))]) } 8 => { let faces: Vec<_> = body.body.faces(tol).ok()?.into_iter().filter(|f| f.plane_normal.is_some()).collect(); diff --git a/crates/engine/src/tests/robustness.rs b/crates/engine/src/tests/robustness.rs index 217e1cc4bd..7e71d95cb4 100644 --- a/crates/engine/src/tests/robustness.rs +++ b/crates/engine/src/tests/robustness.rs @@ -575,3 +575,30 @@ fn seed_33_display_plan_and_measure_agree() { assert!((planned - measured).abs() < 2e-5 * measured, "{}: planned {planned}, measured {measured}", b.name); } } + +/// A chamfer on a concave edge fills the corner, so it adds material, as a fillet there does. The +/// fuzzer expected every chamfer to shrink the body and reported these as a wrong volume (about +/// 1 % of the designs of a 6,000-seed run; seed 7100465: a box, a shell, a chamfer of an inside +/// edge). On a shelled 20 mm box, a 1 mm chamfer of an inside bottom edge adds its 16 mm long +/// half-square prism, and one of an outside bottom edge takes 20 mm of it away. +#[test] +fn chamfer_of_a_concave_edge_adds_material() { + let shelled = || { + vec![ + json!({"command": "solid.box", "params": {"corner": [0, 0, 0], "length": 20, "width": 20, "height": 20}}), + json!({"command": "solid.shell", "params": {"faces": [[10, 10, 20]], "thickness": 2}}), + ] + }; + let base = total_volume(&script(Value::Array(shelled()))); + let chamfered = |edge: [f64; 3]| { + let mut steps = shelled(); + steps.push(json!({"command": "solid.chamfer", "params": {"edges": [edge], "distance": 1}})); + total_volume(&script(Value::Array(steps))) + }; + let inside = chamfered([10.0, 2.0, 2.0]); + let outside = chamfered([10.0, 0.0, 0.0]); + assert!((inside - (base + 16.0 * 0.5)).abs() < 1e-6, "inside edge: {base} -> {inside}"); + assert!((outside - (base - 20.0 * 0.5)).abs() < 1e-6, "outside edge: {base} -> {outside}"); + let r = fuzz::run(7100465, 8); + assert!(r.outcome.kind != "volume", "seed 7100465: {:?}\n{}", r.outcome, r.script()); +}