Two L's extruded up and down from one 2 x 2 grid now join into one solid (the planar rebuild gives each fan of faces around a pinched vertex its own vertex copy), and a pad whose end lies half past the edge of the face it stands on fuses exactly: contact faces are imprinted with each other's outline and the common faces glued away, still held to the retry guard. Face splitting handles a line crossing an edge twice; glue matches differently parametrised curves and keeps several pieces apart. Robustness seed 16 now fuses with the exact volume. Fixes #679, fixes #674. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
17e42e7b94
commit
add4a18961
8 files changed
+465
-34
No files matched your search
@@ -153,3 +153,36 @@ fn ribs_flush_with_a_window_change_nothing_away_from_them() {
|
||||
let bounds = s.model.state().body("Lid").unwrap().body.tessellate(0.01).unwrap().bounds();
|
||||
assert!((bounds.max.z - 14.1).abs() < 1e-9, "{}", bounds.max.z);
|
||||
}
|
||||
|
||||
/// #679: one sketch of a 2 × 2 grid of 10 mm squares; three cells extruded up 10 mm (XO/XX) and
|
||||
/// three down 10 mm, joined (XX/OX). The two L's meet back to back over two whole cells, along
|
||||
/// an edge and at the centre: one solid of 6 cells × 10 mm × 100 mm².
|
||||
#[test]
|
||||
fn ls_extruded_up_and_down_from_one_grid_join() {
|
||||
let mut s = Session::default();
|
||||
let run = |s: &mut Session, id: &str, p: Value| s.execute(id, &p).unwrap_or_else(|e| panic!("{id}: {e}"));
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "Grid"}));
|
||||
for (p0, p1) in [([-10, -10], [0, 0]), ([0, -10], [10, 0]), ([-10, 0], [0, 10]), ([0, 0], [10, 10])] {
|
||||
run(&mut s, "sketch.rectangle.two_point", json!({"p0": p0, "p1": p1}));
|
||||
}
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
let cells = |pts: &[[f64; 2]]| Value::Array(pts.iter().map(|p| json!({"point": p})).collect());
|
||||
run(
|
||||
&mut s,
|
||||
"solid.extrude",
|
||||
json!({"sketch": "Grid", "profiles": cells(&[[-5., 5.], [-5., -5.], [5., -5.]]), "distance": 10, "body_name": "Logo"}),
|
||||
);
|
||||
let reply = run(
|
||||
&mut s,
|
||||
"solid.extrude",
|
||||
json!({"sketch": "Grid", "profiles": cells(&[[-5., 5.], [5., 5.], [5., -5.]]), "distance": "10 mm", "direction": "negative", "operation": "join"}),
|
||||
);
|
||||
assert!(reply["warning"].as_str().is_none_or(|w| !w.contains("tool displacement")), "{reply}");
|
||||
let st = s.model.state();
|
||||
assert_eq!(st.bodies.len(), 1);
|
||||
let body = &st.body("Logo").unwrap().body;
|
||||
assert_eq!(body.shell_count(), 1);
|
||||
assert!(body.validity().is_empty(), "{:?}", body.validity());
|
||||
let v = volume(&s, "Logo");
|
||||
assert!((v - 6000.0).abs() < 1e-6, "{v}");
|
||||
}
|
||||
@@ -202,8 +202,8 @@ fn fillet_into_an_earlier_round() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Clean seeds, and the original seed 5 edge contact and seed 16 partial face contact, now
|
||||
/// rejected without distortion.
|
||||
/// Clean seeds (seed 16: a partial face contact, fused), and the original seed 5 edge contact,
|
||||
/// now rejected without distortion.
|
||||
#[test]
|
||||
fn fixed_seeds_run_clean() {
|
||||
for seed in FIXED_SEEDS {
|
||||
@@ -228,12 +228,10 @@ fn fixed_seeds_run_clean() {
|
||||
continue;
|
||||
}
|
||||
if *seed == 16 {
|
||||
// Seed 16 patterns a short cylinder around a block: one copy's cap lies on a side of
|
||||
// the block but half of it runs past the block's bottom edge. That contact can't be
|
||||
// fused yet (#674). Before #577 the join came back as two shells touching over the
|
||||
// half disc (#396); with #577 alone, the cap was pushed into the block over its whole
|
||||
// area, which added 72 mm³ past the edge (45290.6 mm³ for 45218.4). The retry guard (#644) refuses that,
|
||||
// so the pattern is refused and the part stays as it was.
|
||||
// Seed 16 patterns a short cylinder around a block: one copy's end lies on a side of
|
||||
// the block with half of it past the block's bottom edge (#674). The end and the side
|
||||
// are split along each other's outline and the half disc they share goes: the block,
|
||||
// half of the first cylinder (the other half is inside the block) and three whole ones.
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.box", json!({"length": 25.0, "width": 47.5, "height": 37.5}));
|
||||
run(
|
||||
@@ -241,13 +239,13 @@ fn fixed_seeds_run_clean() {
|
||||
"solid.cylinder",
|
||||
json!({"axis": [1.0, 0.0, 0.0], "base": [0.0, 7.5, 0.0], "height": 2.5, "operation": "join", "radius": 5.0}),
|
||||
);
|
||||
let before = total_volume(&s);
|
||||
// The block, and the half of the cylinder outside it.
|
||||
assert!((before - (25.0 * 47.5 * 37.5 + PI * 25.0 * 2.5 / 2.0)).abs() < 0.05, "{before}");
|
||||
let error = s.execute("solid.pattern.circular", &json!({"axis": "Z", "count": 4, "features": ["Cylinder1"]})).unwrap_err();
|
||||
assert!(error.to_string().contains("touch along faces that could not be fused"), "{error}");
|
||||
assert!((total_volume(&s) - before).abs() < 1e-6);
|
||||
run(&mut s, "solid.pattern.circular", json!({"axis": "Z", "count": 4, "features": ["Cylinder1"]}));
|
||||
// (Within the measured round sides' mesh error, about 0.03 mm³ a cylinder.)
|
||||
let want = 25.0 * 47.5 * 37.5 + 3.5 * PI * 25.0 * 2.5;
|
||||
assert!((total_volume(&s) - want).abs() < 0.2, "{} vs {want}", total_volume(&s));
|
||||
assert!(s.model.state().bodies.iter().all(|b| b.body.validity().is_empty()));
|
||||
let r = fuzz::run(*seed, 8);
|
||||
assert!(!r.outcome.is_bug(), "seed {seed}: {:?}\n{}", r.outcome, r.script());
|
||||
continue;
|
||||
}
|
||||
let r = fuzz::run(*seed, 8);
|
||||
|
||||
Reference in new issue
Block a user