test: failing regression test for #263: sheet.fold accepts a fixed point that is not on the sheet (#278)
* test: reproduce #263 (sheet.fold accepts a fixed point that is not on the sheet) * fix(sheet): refuse a fold's fixed point that lies off the sheet's material (#263) A fixed point inside a cut-out or outside the base outline (in the panel's thickness) silently picked a side. Refuse it with an error that names the point; side-face picks and points on other panels keep working. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT --------- Co-authored-by: bherbruck <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2 files changed
+42
-1
No files matched your search
+23
-1
@@ -1355,7 +1355,29 @@ impl SheetBody {
|
||||
}
|
||||
(None, Some(p)) => {
|
||||
let q = apply_point(&inv, p);
|
||||
Vec2::new(q.x, q.y).dot(n) < l0
|
||||
let q2 = Vec2::new(q.x, q.y);
|
||||
// A point in the panel's slab must be on material: inside the outline and
|
||||
// outside every cut-out (a side face of either counts as material).
|
||||
let near = |poly: &[Vec2]| {
|
||||
let m = poly.len();
|
||||
(0..m).any(|i| match (poly.get(i), poly.get((i + 1) % m)) {
|
||||
(Some(&a), Some(&b)) => {
|
||||
let d = b - a;
|
||||
let t = if d.len2() > 1e-18 { ((q2 - a).dot(d) / d.len2()).clamp(0.0, 1.0) } else { 0.0 };
|
||||
(a + d * t).dist(q2) < 1e-3
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
};
|
||||
let in_slab = q.z > -1e-4 && q.z < self.t + 1e-4;
|
||||
let in_hole = self.holes.iter().any(|h| h.contains(q2) && !near(&h.outer.ccw().polyline(1e-3)));
|
||||
if in_slab && ((!point_in(&pts, q2) && !near(&pts)) || in_hole) {
|
||||
return Err(DocError::Invalid(format!(
|
||||
"the fixed point ({:.3}, {:.3}, {:.3}) is not on the sheet: pick a point on the face of the side that stays",
|
||||
p.x, p.y, p.z
|
||||
)));
|
||||
}
|
||||
q2.dot(n) < l0
|
||||
}
|
||||
(None, None) => side_area(n) <= side_area(n * -1.0),
|
||||
};
|
||||
|
||||
@@ -1568,6 +1568,25 @@ fn outline_blank(s: &mut Session, outline: Value) {
|
||||
run(s, "sheet.flange", json!({"sketch": "Blank"}));
|
||||
}
|
||||
|
||||
/// `fixed` inside a cut-out touches no material: the fold must refuse it or say which side it kept (#263).
|
||||
#[test]
|
||||
fn fold_fixed_point_off_the_sheet_is_not_silently_ignored() {
|
||||
let mut s = Session::default();
|
||||
outline_blank(&mut s, json!([[0, 0], [100, 0], [100, 60], [0, 60]]));
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY", "offset": "2", "name": "Window"}));
|
||||
run(&mut s, "sketch.rectangle.two_point", json!({"p0": [10, 15], "p1": [40, 45]}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "Window", "distance": "4", "direction": "negative", "operation": "cut"}));
|
||||
match s.execute("sheet.fold", &json!({"points": [[80, 0, 0.8], [80, 60, 0.8]], "fixed": [25, 30, 0.8]})) {
|
||||
Err(e) => assert!(e.to_string().contains("fixed"), "{e}"),
|
||||
Ok(r) => {
|
||||
let warned =
|
||||
["warning", "warnings"].iter().any(|k| r.get(*k).is_some_and(|w| !w.is_null() && w != &json!([]) && w.to_string().contains("fixed")));
|
||||
assert!(warned, "fixed [25, 30] is inside the window, yet the fold succeeded silently: {r}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A tab whose fold line sits exactly at its root: the bend zone reaches the blank outline beside
|
||||
/// the tab. Every edge is straight, so the error must not blame a slanted or curved edge (#262).
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user