Cut a hole tangent inside a stepped boss (zero-thickness curved wall) (#986)
Touching sews now split 4-use straight edges on extruded walls (along their rulings), not only on planes. Faces round such an edge are ordered by the way they leave it and, where they leave it tangent, by their curvature. Sheets bound the material wedges as before, except where that would make a face touch itself (a hole touching the outline from inside); edges ending there pair the empty wedges instead, so each loop gets its own vertex. Coincident faces are still refused, plain sew still refuses vertex contact, and shell_groups is unchanged. A Ø10 hole tangent inside a stepped Ø40/Ø20 boss now cuts exactly instead of failing with "wire is not simple". Robustness seeds 42 and 51 are recorded: their touching hole patterns in stepped bodies now cut exactly instead of by a tool nudge. Co-authored-by: froglemonade2 <froglemonade2@users.noreply.github.com>
This commit is contained in:
1 parent
345c0882cd
commit
3669d4b3d0
3 files changed
+289
-54
No files matched your search
@@ -112,3 +112,96 @@ fn hole_touching_the_outline_stays_one_piece() {
|
||||
let res = boolean(&small, &big, BoolOp::Union).unwrap().unwrap();
|
||||
assert_solid(&res, std::f64::consts::PI * (100.0 + 144.0) * 10.0, 1e-5, 1, "disc tangent inside a larger one");
|
||||
}
|
||||
|
||||
/// A Ø40 base with a Ø20 boss on it touching its rim from inside at (20, 0), and a Ø10 hole at
|
||||
/// (15, 0) touching both rims there: the material on either side of the hole meets along the
|
||||
/// line x = 20, y = 0 only (a zero-thickness curved wall). The hole cut through both, the boss
|
||||
/// only and the base only, all exact (no tool nudge); then the same turned 30° and mirrored.
|
||||
#[test]
|
||||
fn hole_tangent_inside_a_stepped_boss_cuts_exactly() {
|
||||
use std::f64::consts::PI;
|
||||
for (turn, flip) in [(0.0, 1.0), (PI / 6.0, 1.0), (0.0, -1.0)] {
|
||||
let at = |x: f64, y: f64| Vec2::new(x * turn.cos() - flip * y * turn.sin(), x * turn.sin() + flip * y * turn.cos());
|
||||
let circle = |x: f64, r: f64| Loop2::circle(at(x, 0.), r);
|
||||
let base = prism(circle(0., 20.), 0., 5.);
|
||||
let boss = prism(circle(10., 10.), 5., 15.);
|
||||
let stepped = boolean(&base, &boss, BoolOp::Union).unwrap().unwrap();
|
||||
for (z0, z1, want) in [(-1., 20., 2000. + 1000. - 25. * 15.), (6., 20., 2000. + 1000. - 25. * 9.), (-1., 4., 2000. + 1000. - 25. * 4.)] {
|
||||
let what = format!("hole z {z0}…{z1}, turned {turn}, flip {flip}");
|
||||
let hole = prism(circle(15., 5.), z0, z1);
|
||||
let (res, d) = capture_boolean_diagnostics(|| boolean(&stepped, &hole, BoolOp::Cut));
|
||||
let res = res.unwrap_or_else(|e| panic!("{what}: {e}")).unwrap();
|
||||
assert_solid(&res, PI * want, 1e-5, 1, &what);
|
||||
assert!(!d.events.iter().any(|e| e.strategy == BooleanStrategy::ToolNudge), "{what}: {d:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A square hole in a stepped block whose side lies on the block's side: planar walls meeting
|
||||
/// along a line at no angle are not a zero-thickness wall but one flat side, and a hole tangent
|
||||
/// to the side of a square boss on a block cuts exactly.
|
||||
#[test]
|
||||
fn hole_tangent_to_a_flat_side_of_a_stepped_block_cuts_exactly() {
|
||||
let block = prism(rect(0., 0., 40., 40.), 0., 5.);
|
||||
let boss = prism(rect(10., 10., 40., 30.), 5., 15.);
|
||||
let stepped = boolean(&block, &boss, BoolOp::Union).unwrap().unwrap();
|
||||
let hole = prism(disc(35., 20., 5.), -1., 20.);
|
||||
let (res, d) = capture_boolean_diagnostics(|| boolean(&stepped, &hole, BoolOp::Cut));
|
||||
let res = res.unwrap().unwrap();
|
||||
assert_solid(&res, 8000. + 6000. - std::f64::consts::PI * 25. * 15., 1e-5, 1, "hole tangent to a flat side");
|
||||
assert!(!d.events.iter().any(|e| e.strategy == BooleanStrategy::ToolNudge), "{d:?}");
|
||||
}
|
||||
|
||||
/// A stepped block cut by a disc tangent to its back side from inside leaves two posts meeting
|
||||
/// only along the tangent line: the stepped prism route keeps them apart (its edge sheets bound
|
||||
/// the posts, as in the single-height case above) rather than sewing one shell pinched along the
|
||||
/// line. A diamond hole with a corner on the side of a stepped block is one piece: the material
|
||||
/// goes round the hole.
|
||||
#[test]
|
||||
fn stepped_cuts_touching_along_a_line_keep_their_pieces() {
|
||||
let block = prism(rect(0., 0., 31., 29.), 0., 10.);
|
||||
let back = prism(rect(0., 20., 25., 29.), 10., 20.);
|
||||
let stepped = boolean(&block, &back, BoolOp::Union).unwrap().unwrap();
|
||||
let tool = prism(disc(18.5, 4., 25.), -1., 21.);
|
||||
let (res, d) = capture_boolean_diagnostics(|| boolean(&stepped, &tool, BoolOp::Cut));
|
||||
let res = res.unwrap().unwrap();
|
||||
let n = 400_000;
|
||||
let dx = 31.0 / n as f64;
|
||||
let post = |x: f64| (29.0 - (4.0 + (625.0 - (x - 18.5).powi(2)).max(0.0).sqrt()).min(29.0)).max(0.0);
|
||||
let volume: f64 = (0..n)
|
||||
.map(|i| {
|
||||
let x = (i as f64 + 0.5) * dx;
|
||||
post(x) * dx * if x < 25.0 { 20.0 } else { 10.0 }
|
||||
})
|
||||
.sum();
|
||||
let v = res.tessellate(1e-4).unwrap().measure().volume;
|
||||
assert!((v - volume).abs() <= volume * 1e-4, "posts: volume {v} vs {volume}");
|
||||
assert!(!d.events.iter().any(|e| e.strategy == BooleanStrategy::ToolNudge), "{d:?}");
|
||||
let sewn = crate::prism::prism_boolean(&stepped, &tool, BoolOp::Cut);
|
||||
assert!(!matches!(&sewn, Some(Ok(Some(b))) if b.shell_count() < 2), "posts sewn into one shell");
|
||||
|
||||
let block = prism(rect(0., 0., 40., 40.), 0., 5.);
|
||||
let boss = prism(rect(10., 10., 40., 30.), 5., 15.);
|
||||
let stepped = boolean(&block, &boss, BoolOp::Union).unwrap().unwrap();
|
||||
let diamond = prism(poly(&[[30., 20.], [35., 15.], [40., 20.], [35., 25.]]), -1., 20.);
|
||||
let (res, d) = capture_boolean_diagnostics(|| boolean(&stepped, &diamond, BoolOp::Cut));
|
||||
let res = res.unwrap().unwrap();
|
||||
assert_solid(&res, 8000. + 6000. - 50. * 15., 1e-9, 1, "diamond hole with a corner on a stepped side");
|
||||
assert!(!d.events.iter().any(|e| e.strategy == BooleanStrategy::ToolNudge), "{d:?}");
|
||||
}
|
||||
|
||||
/// A row of holes each touching the next (a 5 mm pattern of Ø5 holes) through a stepped plate:
|
||||
/// the walls between them have no thickness, and the cut is exact.
|
||||
#[test]
|
||||
fn row_of_touching_holes_through_a_stepped_plate_cuts_exactly() {
|
||||
let plate = prism(rect(0., 0., 30., 35.), 0., 7.5);
|
||||
let notch = prism(rect(0., 30., 22.5, 50.), 2.5, 25.);
|
||||
let mut body = boolean(&plate, ¬ch, BoolOp::Cut).unwrap().unwrap();
|
||||
for x in [15.45, 20.45, 25.45] {
|
||||
let hole = prism(disc(x, 15.7, 2.5), -5., 7.5);
|
||||
let (res, d) = capture_boolean_diagnostics(|| boolean(&body, &hole, BoolOp::Cut));
|
||||
body = res.unwrap().unwrap();
|
||||
assert!(!d.events.iter().any(|e| e.strategy == BooleanStrategy::ToolNudge), "hole at {x}: {d:?}");
|
||||
}
|
||||
assert_solid(&body, 7312.5 - 3.0 * std::f64::consts::PI * 6.25 * 7.5, 1e-5, 1, "touching holes");
|
||||
}
|
||||
Reference in new issue
Block a user