fix: fuse a loft standing on a face instead of returning touching shells (#577)
Fixes #396. A union that would come back as shells touching over an area is rejected and the face-in-face case is fused exactly; line and point contact stay as before.
This commit is contained in:
1 parent
bb60a95289
commit
24cff445ad
5 files changed
+245
-2
No files matched your search
@@ -156,6 +156,9 @@ fn contradicted(
|
||||
(a_in_b && under_surface(ma, ia, ib, tol, false, None)) || (b_in_a && !a_in_b && under_surface(mb, ib, ia, tol, false, None))
|
||||
}
|
||||
|
||||
/// Sampled overlap fraction below which two bodies share no material.
|
||||
const NONE: f64 = 0.003;
|
||||
|
||||
/// Booleans that need no intersection: one body clear of the other, or inside it. These are
|
||||
/// common with coincident curved faces (a hole drilled where one already is, a body joined
|
||||
/// with itself), which the intersection can't handle. `counts`: the operands' faces and edges.
|
||||
@@ -178,7 +181,6 @@ fn trivial(
|
||||
}
|
||||
let (ia, ib) = (ma.inside_index(), mb.inside_index());
|
||||
let (b_in_a, a_in_b) = overlap(ma, mb, &ia, &ib)?;
|
||||
const NONE: f64 = 0.003;
|
||||
const ALL: f64 = 0.997;
|
||||
let disjoint = b_in_a < NONE && a_in_b < NONE;
|
||||
// The samples only say that nearly all of one body is clear of, or inside, the other. A
|
||||
@@ -404,6 +406,19 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result<Option<Body>> {
|
||||
let slack = 2e-3 * (va + vb) + 1e-9;
|
||||
// Volume bounds, then membership against the operands (catches misclassified pieces), on
|
||||
// one mesh of the result. Returns (plausible, volume).
|
||||
// Shells that touch were never fused: coincident back-to-back faces leave the intersection
|
||||
// nothing to cut, so it returns both operands side by side (#396). Volume and point
|
||||
// sampling cannot see that; the shell grouping can. Only contact over an area counts
|
||||
// (bodies tangent along a line stay two lumps), and operands already like that, saved
|
||||
// before this was caught, stay joinable.
|
||||
let unfused = |r: &Body| {
|
||||
op == BoolOp::Union
|
||||
&& r.shell_count() > 1
|
||||
&& r.union_lumps().is_err()
|
||||
&& a.union_lumps().is_ok()
|
||||
&& b.union_lumps().is_ok()
|
||||
&& crate::coplanar::faces_touch(a, b)
|
||||
};
|
||||
let check = |r: &Body| -> (bool, f64) {
|
||||
let Ok(mr) = r.tessellate_with(tol_m, true) else { return (false, f64::NAN) };
|
||||
let v = mr.measure().volume;
|
||||
@@ -416,6 +431,9 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result<Option<Body>> {
|
||||
if !bounds {
|
||||
return (false, v);
|
||||
}
|
||||
if unfused(r) {
|
||||
return (false, v);
|
||||
}
|
||||
let consistent = match &meshes {
|
||||
(Some(ma), Some(mb)) => mismatch(ma, mb, &mr, op).is_none_or(|f| f < 0.004),
|
||||
_ => true,
|
||||
@@ -502,6 +520,18 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result<Option<Body>> {
|
||||
Some(Ok(None)) | None => {}
|
||||
}
|
||||
}
|
||||
// A face standing wholly inside a coincident face of the other body, back to back, and no
|
||||
// other contact (#396): exact, by topology alone.
|
||||
if op == BoolOp::Union
|
||||
&& a.solid.boundaries().len() == 1
|
||||
&& b.solid.boundaries().len() == 1
|
||||
&& let Some(Ok(body)) = crate::coplanar::inset(a, b)
|
||||
&& let (Some(ma), Some(mb)) = &meshes
|
||||
&& overlap(ma, mb, &ma.inside_index(), &mb.inside_index()).is_some_and(|(b_in_a, a_in_b)| b_in_a < NONE && a_in_b < NONE)
|
||||
&& check(&body).0
|
||||
{
|
||||
return Ok(Some(body));
|
||||
}
|
||||
// Bores on a common cylinder (a pad's bore around the hole it is joined over).
|
||||
if op == BoolOp::Union && !BORES.with(|c| c.get()) && crate::coplanar::has_coincident_cylinders(a, b) {
|
||||
BORES.with(|c| c.set(true));
|
||||
@@ -548,7 +578,11 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result<Option<Body>> {
|
||||
}
|
||||
return Ok(Some(body));
|
||||
}
|
||||
last = format!("implausible result volume {v:.4}");
|
||||
last = if unfused(&body) {
|
||||
"the bodies touch along faces that could not be fused".into()
|
||||
} else {
|
||||
format!("implausible result volume {v:.4}")
|
||||
};
|
||||
}
|
||||
Err(e) => last = e.to_string(),
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user