Through-all holes beside walls: a long thin tool keeps its outside out (#629)
Body::new no longer turns a long thin solid (a through-all hole's tool) inside out on the strength of a coarse mesh volume: it checks again on a fine mesh first. 79 more face.hole actions pass in the action matrix; the remaining no-result cuts stay under #559. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
8d58e3b9f9
commit
bb60a95289
4 files changed
+115
-87
No files matched your search
@@ -477,13 +477,26 @@ impl Body {
|
||||
if lo.x.is_finite() { (hi - lo).len() } else { 1.0 }
|
||||
};
|
||||
let tol = (span * 0.01).clamp(1e-3, 1.0);
|
||||
let (v, missing) = guard("orient", || {
|
||||
let shells = mesh_shells(&solid, tol);
|
||||
let volume = shells.iter().map(|s| s.to_polygon().volume()).sum::<f64>();
|
||||
let missing =
|
||||
shells.iter().flat_map(|s| s.face_iter()).filter(|f| f.surface().is_none_or(|m| m.faces().triangle_iter().next().is_none())).count();
|
||||
Ok((volume, missing))
|
||||
})?;
|
||||
let orient = |tol: f64| {
|
||||
guard("orient", || {
|
||||
let shells = mesh_shells(&solid, tol);
|
||||
let volume = shells.iter().map(|s| s.to_polygon().volume()).sum::<f64>();
|
||||
let missing = shells
|
||||
.iter()
|
||||
.flat_map(|s| s.face_iter())
|
||||
.filter(|f| f.surface().is_none_or(|m| m.faces().triangle_iter().next().is_none()))
|
||||
.count();
|
||||
Ok((volume, missing))
|
||||
})
|
||||
};
|
||||
let (mut v, mut missing) = orient(tol)?;
|
||||
// A long thin round part (a through hole's tool: a cylinder whose vertices all lie on
|
||||
// its seam, so they span its length only) meshed that coarsely can come out with a
|
||||
// negative volume when it is outward already: check finely before turning it inside out.
|
||||
let fine = (tol * 0.02).max(1e-4);
|
||||
if v < 0.0 && fine < tol {
|
||||
(v, missing) = orient(fine)?;
|
||||
}
|
||||
if v < 0.0 {
|
||||
solid.not();
|
||||
}
|
||||
|
||||
@@ -303,3 +303,19 @@ fn grouping_lumps_does_not_infer_absent_or_misaligned_sources() {
|
||||
let malformed = body.with_face_sources(Some(std::sync::Arc::new(vec![None])));
|
||||
assert!(malformed.lumps().unwrap_err().to_string().contains("face source table"));
|
||||
}
|
||||
|
||||
/// A long thin cylinder (a through hole's tool) far from the origin, its seam turned: its
|
||||
/// vertices span only its length, so the coarse orientation mesh can give it a negative volume.
|
||||
/// It must still come out outward, or a Cut with it removes the wrong side (#559).
|
||||
#[test]
|
||||
fn a_long_thin_cylinder_comes_out_outward() {
|
||||
use solidcraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
|
||||
let plane = Plane::from_normal(Vec3::new(0., 24., 30.), Vec3::Z).unwrap();
|
||||
for (lo, seam) in [(-181.0, 0.613), (-100.0, 0.613), (-10.0, 0.613), (-181.0, 0.0)] {
|
||||
let disc = Region2 { outer: Loop2::circle_from(Vec2::ZERO, 0.5, seam), holes: vec![] };
|
||||
let tool = crate::extrude(&plane, &[disc], lo, 0.9).unwrap().pop().unwrap();
|
||||
let volume = tool.tessellate(0.01).unwrap().measure().volume;
|
||||
let want = std::f64::consts::PI * 0.25 * (0.9 - lo);
|
||||
assert!((volume - want).abs() < want * 0.02, "{lo} {seam}: {volume} vs {want}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user