An extrude cut or join only extends its tool past an end that lies on a target face, and by less than half the way to the next face level, so a tool can no longer cut through a thin floor beyond an end that touches no face. Un-ignores the #545 and #544 regression tests. Fixes #545. Co-authored-by: jonaswre <[email protected]>
This commit is contained in:
1 parent
5ad8b3966f
commit
4a86ad5a7c
2 files changed
+30
-7
No files matched your search
+30
-5
@@ -1494,7 +1494,10 @@ fn grow_profile_for_coplanar(st: &ModelState, r: &Region2, op: Operation, target
|
||||
|
||||
/// Booleans struggle with coincident faces (a cut starting on the face it cuts, a boss
|
||||
/// starting on the face it joins). Extending the tool past such an end changes nothing when the
|
||||
/// space beyond is empty (cut) or already material (join), so do exactly that.
|
||||
/// space beyond is empty (cut) or already material (join), so do exactly that. Only an end
|
||||
/// that lies on a target face parallel to it moves, and by less than half the way to the next
|
||||
/// such face level: the samples can miss material beyond the end (a thin floor between a recess
|
||||
/// above and a ring pocket below), which a longer tool would cut through.
|
||||
fn extend_for_coplanar(st: &ModelState, plane: &Plane, r: &Region2, lo: f64, hi: f64, op: Operation, targets: &[String]) -> (f64, f64) {
|
||||
if !matches!(op, Operation::Cut | Operation::Join) || st.bodies.is_empty() {
|
||||
return (lo, hi);
|
||||
@@ -1509,18 +1512,40 @@ fn extend_for_coplanar(st: &ModelState, plane: &Plane, r: &Region2, lo: f64, hi:
|
||||
let samples = region_samples(r);
|
||||
let n = plane.normal();
|
||||
let inside_any = |p: Vec3| meshes.iter().any(|m| m.contains(p));
|
||||
let ok_beyond = |h: f64, dir: f64| {
|
||||
// Heights of the targets' faces parallel to the profile plane.
|
||||
let tol = (size * 1e-7).max(1e-9);
|
||||
let levels: Vec<f64> = meshes
|
||||
.iter()
|
||||
.flat_map(|m| {
|
||||
m.triangles.iter().filter_map(|t| {
|
||||
let [a, b, c] = m.tri(t)?;
|
||||
let normal = (b - a).cross(c - a).normalized()?;
|
||||
(normal.dot(n).abs() > 1.0 - 1e-9).then(|| (a - plane.origin).dot(n))
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
// How far the end at `h` may move in `dir`: zero unless a face lies on it, then short of
|
||||
// half the gap to the next face level beyond it.
|
||||
let room = |h: f64, dir: f64| {
|
||||
if !levels.iter().any(|z| (z - h).abs() < tol) {
|
||||
return 0.0;
|
||||
}
|
||||
let gap = levels.iter().map(|z| (z - h) * dir).filter(|g| *g > tol).fold(f64::INFINITY, f64::min);
|
||||
delta.min(gap * 0.5)
|
||||
};
|
||||
let ok_beyond = |h: f64, dir: f64, step: f64| {
|
||||
samples.iter().all(|s| {
|
||||
let base = plane.to_world(*s);
|
||||
[0.5, 1.0].iter().all(|k| {
|
||||
let p = base + n * (h + dir * delta * k);
|
||||
let p = base + n * (h + dir * step * k);
|
||||
let inside = inside_any(p);
|
||||
if op == Operation::Cut { !inside } else { inside }
|
||||
})
|
||||
})
|
||||
};
|
||||
let lo2 = if ok_beyond(lo, -1.0) { lo - delta } else { lo };
|
||||
let hi2 = if ok_beyond(hi, 1.0) { hi + delta } else { hi };
|
||||
let (down, up) = (room(lo, -1.0), room(hi, 1.0));
|
||||
let lo2 = if down > tol && ok_beyond(lo, -1.0, down) { lo - down } else { lo };
|
||||
let hi2 = if up > tol && ok_beyond(hi, 1.0, up) { hi + up } else { hi };
|
||||
(lo2, hi2)
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,6 @@ fn struts_ending_on_a_bore_wall_join_without_a_panic() {
|
||||
/// yet the cut was refused (#544). It removes π · 1² · 10 mm³ (within 5 %: the measure tessellates
|
||||
/// the small bore) and adds the bore's wall and floor to the bar's six faces.
|
||||
#[test]
|
||||
#[ignore = "fails until #544 is fixed"]
|
||||
fn blind_hole_into_a_long_d_profile_bar_stops_short_of_the_round_front() {
|
||||
let script: Value = serde_json::from_str(include_str!("../../../examples/regressions/blind_hole_in_long_d_profile.json")).unwrap();
|
||||
let steps = script.get("commands").and_then(Value::as_array).unwrap();
|
||||
@@ -111,7 +110,6 @@ fn blind_hole_into_a_long_d_profile_bar_stops_short_of_the_round_front() {
|
||||
/// A ring pocket cut from below a recess leaves a 0.5 mm floor joining the outer wall to the inner
|
||||
/// block: one connected part, yet the cut left two bodies and lost volume (#545).
|
||||
#[test]
|
||||
#[ignore = "fails until #545 is fixed"]
|
||||
fn ring_cut_below_a_recess_keeps_the_part_in_one_body() {
|
||||
let s = replay(include_str!("../../../examples/regressions/ring_cut_below_recess_thin_floor.json"));
|
||||
let bodies: Vec<(String, f64)> = s.model.state().bodies.iter().map(|b| (b.name.clone(), volume(&s, &b.name))).collect();
|
||||
|
||||
Reference in new issue
Block a user