fix: a lenient parameter boundary found across a seam is not used; record seed 81 (#1003)

#970's lenient retry of a face's parameter boundary searches the whole surface as a last resort; on robustness seed 20 it found an edge's points across a cylinder's seam, half a turn apart, and the Cut failed. A lenient boundary whose points jump half the parameter range or more is refused, and the face is left undivided as before (#970's elbows step at most a fifth of the range). Seed 81's volume moved by 0.1 mm3 with #952's hole direction; recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-14 04:56:39 -04:00
1 parent cf9d7772f1
commit dfbf7dd6a8
3 files changed
+25 -2

No files matched your search

+10
View File
@@ -549,3 +549,13 @@ fn blend_corners_where_edges_meet_on_a_curved_wall() {
let removed = base - planned_volume(&s);
assert!((removed - 67.05).abs() < 0.15, "three at the corner: removed {removed}, expected 67.05");
}
/// Seed 20 cuts a patterned cylinder's holes through a filleted plate. The lenient retry of a
/// face's parameter boundary (#970) searched the whole cylinder for a point and found it across
/// the seam, half a turn away; the face was divided wrongly and the cut failed. Such a boundary
/// is refused now, and the cut builds as before #970.
#[test]
fn a_boundary_found_across_a_seam_is_not_used() {
let r = fuzz::run(20, 8);
assert_eq!(r.outcome.kind, "ok", "seed 20: {:?}\n{}", r.outcome, r.script());
}
+3 -2
View File
@@ -82,7 +82,7 @@
"78": {"area":2510.733756,"bodies":1,"edges":36,"error":"offset: a corner whose faces don't meet the same way after t","faces":14,"outcome":"failed","rejected":0,"shells":1,"steps":["revolved section","cut cylinder","cut box","shell"],"unsupported":0,"volume":3707.416641},
"79": {"area":13585.252616,"bodies":1,"edges":27,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","join cylinder"],"unsupported":0,"volume":104810.763072},
"80": {"area":8652.533342,"bodies":1,"edges":20,"error":null,"faces":9,"outcome":"ok","rejected":0,"shells":1,"steps":["box","fillet","fillet"],"unsupported":0,"volume":37387.449469},
"81": {"area":4001.811796,"bodies":2,"edges":75,"error":null,"faces":29,"outcome":"ok","rejected":1,"shells":2,"steps":["revolved section","cut sphere","circular pattern","rectangular pattern","hole","join cylinder","join box","cut sphere"],"unsupported":2,"volume":13139.562065},
"81": {"area":4001.847983,"bodies":2,"edges":75,"error":null,"faces":29,"outcome":"ok","rejected":1,"shells":2,"steps":["revolved section","cut sphere","circular pattern","rectangular pattern","hole","join cylinder","join box","cut sphere"],"unsupported":2,"volume":13139.658749},
"82": {"area":3195.487113,"bodies":1,"edges":36,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","hole","circular pattern","cut box"],"unsupported":0,"volume":5902.162954},
"83": {"area":5125.0,"bodies":1,"edges":28,"error":"offset: a corner whose faces don't meet the same way after t","faces":12,"outcome":"failed","rejected":0,"shells":1,"steps":["box","join box","rectangular pattern","shell"],"unsupported":0,"volume":19250.0},
"84": {"area":4906.192237,"bodies":1,"edges":67,"error":null,"faces":25,"outcome":"ok","rejected":2,"shells":1,"steps":["extruded profile","fillet","hole","hole","cut cylinder","cut box","fillet"],"unsupported":0,"volume":14219.865875},
@@ -111,6 +111,7 @@
{"reason":"a hole placed on a face without a direction drills into that face (it took the direction from any face whose plane passed through the point, the last one found, such as a side of a patterned box standing on the point's plane): the holes on these seeds now go straight in (#950)","seeds":[7,21,85]},
{"reason":"the last-resort tool nudge is tried the other way when it fails or leaves sliver faces the operands didn't have: seed 43's join builds and the design runs to the end, 31 and 78 come out with fewer faces (#827, #897)","seeds":[31,43,78]},
{"reason":"main's fillet changes since the last record (#926, #949) move seeds 33 and 91: a fillet step builds differently or is picked differently","seeds":[33,91]},
{"reason":"hole tangent inside a stepped boss: touching sews split zero-thickness curved walls, so a pattern of holes spaced by their diameter (touching) in a stepped body cuts exactly instead of by a tool nudge: 42 builds more faces (the walls split where the holes touch), 51 picks other edges afterwards","seeds":[42,51]}
{"reason":"hole tangent inside a stepped boss: touching sews split zero-thickness curved walls, so a pattern of holes spaced by their diameter (touching) in a stepped body cuts exactly instead of by a tool nudge: 42 builds more faces (the walls split where the holes touch), 51 picks other edges afterwards","seeds":[42,51]},
{"reason":"#952: a hole placed on a face drills into the material; seed 81's hole moves its volume by 0.1 mm\u00b3 (area by 0.04 mm\u00b2)","seeds":[81]}
]
}
@@ -71,6 +71,18 @@ where
vec.append(&mut add?);
Some(vec)
})?;
// SolidCraft: a lenient boundary whose points jump half the parameter range or more has been
// found across a periodic surface's seam by the whole-surface search (on a cylinder the
// points of one edge landed at both ends of its angle): it would divide the face wrongly, so
// the face is left undivided as before.
if lenient {
let (u, v) = surface.try_range_tuple();
let half = |r: Option<(f64, f64)>| r.map_or(f64::INFINITY, |(a, b)| (b - a).abs() / 2.0);
let (hu, hv) = (half(u), half(v));
if vec.windows(2).any(|w| (w[1].x - w[0].x).abs() >= hu || (w[1].y - w[0].y).abs() >= hv) {
return None;
}
}
Some(PolylineCurve(vec))
}