mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
Union: when the slab past pushed-apart coplanar faces can't be cut off, trim it by editing faces (#2801)
This commit is contained in:
@@ -945,3 +945,31 @@ fn combine_of_a_piece_a_cut_used_to_split_off_takes_the_body_it_is_part_of() {
|
||||
assert!(r.is_err(), "{target} {operation} {tools}: {r:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A triangular prism and a thin slab sharing their bottom plane, the prism's edge in that plane
|
||||
/// lying in the slab's end face, the slab's corners rounded nearly to half its width (so its flat
|
||||
/// end is narrower than the prism).
|
||||
#[test]
|
||||
fn prism_edge_in_a_round_cornered_slab_end_face_joins() {
|
||||
for r in [4.5, 4.9] {
|
||||
let mut s = Session::default();
|
||||
let mut run = |c: &str, p: Value| s.execute(c, &p).unwrap();
|
||||
run("sketch.create", json!({"plane": {"origin": [-45.6, 0, 0], "x_dir": [0, 1, 0], "y_dir": [0, 0, 1]}}));
|
||||
run("sketch.line", json!({"points": [[-63, 0], [-3, 0], [-63, 60]], "closed": true}));
|
||||
run("sketch.finish", json!({}));
|
||||
run("solid.extrude", json!({"distance": 1.2, "operation": "new", "body_name": "Prism"}));
|
||||
run("sketch.create", json!({"plane": "XY"}));
|
||||
run("sketch.rectangle.rounded", json!({"p0": [-50, -67], "p1": [-40, -3], "radius": r}));
|
||||
run("sketch.finish", json!({}));
|
||||
run("solid.extrude", json!({"distance": 0.4, "operation": "new", "body_name": "Slab"}));
|
||||
let (prism, slab) = (volume(&s, "Prism"), volume(&s, "Slab"));
|
||||
s.execute("solid.combine", &json!({"target": "Prism", "tools": ["Slab"], "operation": "join"})).unwrap();
|
||||
let join = s.model.results.last().unwrap();
|
||||
assert_eq!(join.error.as_deref(), None, "r {r}");
|
||||
// The prism inside the slab: 1.2 wide, 0.4 high and 60 long, less the 0.4 x 0.4 triangle the
|
||||
// sloped face leaves empty at the slab's end.
|
||||
let shared = 1.2 * 0.4 * 60.0 - 1.2 * 0.4 * 0.4 / 2.0;
|
||||
let got = volume(&s, "Prism");
|
||||
assert!((got - (prism + slab - shared)).abs() < 1e-3 * got, "r {r}: {got} vs {}", prism + slab - shared);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -905,12 +905,19 @@ fn apart_with(a: &Body, b: &Body, op: BoolOp, grow: bool) -> Option<Result<Optio
|
||||
} else if op == BoolOp::Union {
|
||||
for (n, d) in &same {
|
||||
let slab = slab_beyond(*n, *d, d1.max(d2) * 3.0, size * 4.0)?;
|
||||
match crate::ops::boolean(&r, &slab, BoolOp::Cut)? {
|
||||
Some(x) => {
|
||||
match crate::ops::boolean(&r, &slab, BoolOp::Cut) {
|
||||
Ok(Some(x)) => {
|
||||
h = h.and_then(|h| trimmed(&x, h, &slab, [a, b], *n, *d, tol));
|
||||
r = x;
|
||||
}
|
||||
None => return Ok(None),
|
||||
Ok(None) => return Ok(None),
|
||||
// An edge of the union can lie in the plane, where a pushed side crosses a side
|
||||
// of the other body (a prism's edge in the plane lying in a slab's end face):
|
||||
// the cut along it fails, and the same trim is made by editing the faces.
|
||||
Err(e) => {
|
||||
r = trim_exact(&r, *n, *d).map_err(|_| e)?;
|
||||
h = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"67": {"area":7940.064,"bodies":1,"edges":84,"error":null,"faces":35,"outcome":"ok","rejected":1,"shells":2,"steps":["extruded profile","cut cylinder","hole","join cylinder","join box","hole","chamfer","rectangular pattern"],"unsupported":1,"volume":27606.450855},
|
||||
"68": {"area":7338.675126,"bodies":1,"edges":36,"error":null,"faces":14,"outcome":"ok","rejected":2,"shells":1,"steps":["extruded profile","hole","hole","rectangular pattern","rectangular pattern","fillet","circular pattern"],"unsupported":0,"volume":29780.741772},
|
||||
"69": {"area":6451.122767,"bodies":1,"edges":60,"error":"offset: a corner whose faces don't meet the same way after t","faces":27,"outcome":"failed","rejected":0,"shells":3,"steps":["revolved section","join box","shell","shell"],"unsupported":0,"volume":4761.477515},
|
||||
"70": {"area":3682.669157,"bodies":1,"edges":24,"error":"lumps: shell boundaries intersect or touch; cavity ownership","faces":12,"outcome":"failed","rejected":0,"shells":2,"steps":["extruded profile","join box","hole"],"unsupported":1,"volume":9155.027162},
|
||||
"70": {"area":5638.09657,"bodies":1,"edges":48,"error":"lumps: shell boundaries intersect or touch; cavity ownership","faces":23,"outcome":"failed","rejected":0,"shells":3,"steps":["extruded profile","join box","shell","hole"],"unsupported":0,"volume":4120.897175},
|
||||
"71": {"area":3980.574721,"bodies":1,"edges":52,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut box","cut cylinder","hole","join cylinder"],"unsupported":0,"volume":15749.927814},
|
||||
"72": {"area":5770.270336,"bodies":1,"edges":44,"error":null,"faces":20,"outcome":"ok","rejected":1,"shells":2,"steps":["box","join cylinder","hole","join box","join box","cut box","cut cylinder","cut cylinder"],"unsupported":0,"volume":16081.801492},
|
||||
"73": {"area":6868.162117,"bodies":1,"edges":40,"error":"offset: a corner whose faces don't meet the same way after t","faces":18,"outcome":"failed","rejected":0,"shells":1,"steps":["extruded profile","join box","shell"],"unsupported":1,"volume":36461.939381},
|
||||
@@ -142,6 +142,7 @@
|
||||
{"reason":"#2064 (multi-profile extrusion) merges coplanar faces the boolean used to leave split. Seed 67 has one face/two edges fewer from its first cut on, at the same volume at every step; seed 94's last box cut leaves one face/two edges more, same volume and area. Seed 13's second cylinder cut leaves 35 faces/84 edges instead of 37/88 at the same volume, so its random fillet (edges picked by index) rounds different edges: 2688.99 instead of 2675.18 mm3 after the fillet; the hole after it removes the same 278.2 mm3. Every step passes the validity and volume-direction checks. Pull request CI ran only every 8th seed, so main was the first run to see these.","seeds":[13,67,94]},
|
||||
{"reason":"#2247: a cut no longer splits the separate pieces of a body into bodies of their own, so these seeds keep one body (bodies N -> 1). The generator looks at the model, so after the first such cut some seeds build different steps: seed 33 joins and patterns a box instead of chamfering (volume 13858 -> 24911 mm3), seed 18 now builds (its hole pattern failed with no result), and seed 54 picks a shell that fails the same way on main before #2247 (offset corner). Every step passes the validity and volume-direction checks.","seeds":[7,9,12,16,18,22,27,31,33,45,50,54,67,72,81,93]},
|
||||
{"reason":"Reseam rotationally symmetric drill cutters after failed cuts (#2387): seed 86 now drills its hole, so its following pattern, shell and sphere cut complete with the fuzz validity and material checks.","seeds":[86]},
|
||||
{"reason":"Rebase #2387 onto #2247: patterned pieces stay in one body. Seed 86 drills its hole, then reaches the same offset-corner shell refusal as main instead of shelling separate bodies. Completed steps still pass validity and volume-direction checks.","seeds":[86]}
|
||||
{"reason":"Rebase #2387 onto #2247: patterned pieces stay in one body. Seed 86 drills its hole, then reaches the same offset-corner shell refusal as main instead of shelling separate bodies. Completed steps still pass validity and volume-direction checks.","seeds":[86]},
|
||||
{"reason":"A join whose coincident faces were pushed apart now trims the slab beyond the plane by editing faces when the boolean cut of it fails (a prism whose edge in the plane lies in the end face of a slab with rounded corners). Seed 70: the shell of a body in two lumps, refused before, now builds; its volume matches an independent offset of the profile (4120.90 vs 4120.67 mm3, the gap is the unshelled profile's own tessellation).","seeds":[70]}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user