fix: booleans with whole-turn round faces read from STEP are exact instead of failing (#1892)
Thanks @NOWN-DEV.
This commit is contained in:
1 parent
962070e955
commit
c38992fc09
6 files changed
+233
-4
No files matched your search
@@ -0,0 +1,94 @@
|
||||
//! Round faces read from STEP: a whole turn bounded by one wire that runs along its seam twice.
|
||||
//! A boolean whose intersection loop lies wholly inside such a face takes it in two halves
|
||||
//! (exactly), where it used to end in a nudged tool or fail.
|
||||
use crate::*;
|
||||
use solidcraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
|
||||
use std::f64::consts::PI;
|
||||
|
||||
const R: f64 = 4.5;
|
||||
|
||||
fn volume(b: &Body) -> f64 {
|
||||
measure(b).unwrap().volume
|
||||
}
|
||||
|
||||
fn rel(a: f64, b: f64) -> f64 {
|
||||
(a - b).abs() / b.abs().max(1e-12)
|
||||
}
|
||||
|
||||
/// `body` written to STEP and read back: its round faces come back whole, as other systems
|
||||
/// write them.
|
||||
fn through_step(body: &Body) -> Body {
|
||||
step_import(&step_export(&[body], "t").unwrap()).unwrap().bodies.into_iter().next().unwrap().body
|
||||
}
|
||||
|
||||
/// A pin of radius `R` along X, 40 long, centred on the origin.
|
||||
fn pin() -> Body {
|
||||
cylinder(Vec3::new(-20.0, 0.0, 0.0), Vec3::X, R, 40.0).unwrap()
|
||||
}
|
||||
|
||||
/// A block pressed into the top of the pin's side: 10 long, 6 wide, from 2 above the axis up.
|
||||
fn block() -> Body {
|
||||
box_solid(Vec3::new(-5.0, -3.0, 2.0), Vec3::new(5.0, 3.0, 9.0)).unwrap()
|
||||
}
|
||||
|
||||
/// The part of the pin inside [`block`]: the circle's segment above 2 and within 3 of the
|
||||
/// middle, 10 long.
|
||||
fn pressed_volume() -> f64 {
|
||||
let segment = 3.0 * (R * R - 9.0).sqrt() + R * R * (3.0 / R).asin() - 6.0 * 2.0;
|
||||
10.0 * segment
|
||||
}
|
||||
|
||||
/// The boolean, which must be exact: no retry that moves the tool or ignores part of where the
|
||||
/// bodies meet.
|
||||
fn exact(a: &Body, b: &Body, op: BoolOp) -> Option<Body> {
|
||||
let (r, report) = capture_boolean_diagnostics(|| boolean(a, b, op));
|
||||
assert!(report.warning().is_none(), "{op:?}: {:?}", report.warning());
|
||||
r.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pin_read_from_step_has_one_face_along_its_seam() {
|
||||
let read = through_step(&pin());
|
||||
assert_eq!(read.face_count(), 3);
|
||||
assert!(super::needed(&read, &block()));
|
||||
assert!(!super::needed(&pin(), &block()));
|
||||
// Split in two halves, with the same solid.
|
||||
let (halved, history) = super::halved(&read).unwrap();
|
||||
assert!(super::next_split(&halved).is_none() && halved.face_count() > 3);
|
||||
assert!(history.is_some());
|
||||
assert!(rel(volume(&halved), PI * R * R * 40.0) < 1e-3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_block_pressed_into_a_read_pins_side_is_intersected_and_cut_exactly() {
|
||||
let (read, block) = (through_step(&pin()), block());
|
||||
let want = pressed_volume();
|
||||
let shared = exact(&read, &block, BoolOp::Intersect).unwrap();
|
||||
assert!(rel(volume(&shared), want) < 1e-3, "{} {want}", volume(&shared));
|
||||
let dented = exact(&read, &block, BoolOp::Cut).unwrap();
|
||||
assert!(rel(volume(&dented), PI * R * R * 40.0 - want) < 1e-3, "{}", volume(&dented));
|
||||
// The operands' faces carry over: the dented pin's round face is still the pin's.
|
||||
assert!(dented.history().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_ball_pressed_into_a_read_pins_side_matches_the_built_pin() {
|
||||
let ball = sphere(Vec3::new(3.0, 1.0, 5.5), 3.0).unwrap();
|
||||
let built = volume(&boolean(&pin(), &ball, BoolOp::Intersect).unwrap().unwrap());
|
||||
let read = volume(&exact(&through_step(&pin()), &ball, BoolOp::Intersect).unwrap());
|
||||
assert!(rel(read, built) < 1e-3, "{read} {built}");
|
||||
}
|
||||
|
||||
/// A cone's side read from STEP is one face along its seam as well.
|
||||
#[test]
|
||||
fn a_block_pressed_into_a_read_cones_side_matches_the_built_cone() {
|
||||
let outline =
|
||||
Region2 { outer: Loop2::polygon(&[Vec2::new(0.0, 0.0), Vec2::new(8.0, 0.0), Vec2::new(5.0, 20.0), Vec2::new(0.0, 20.0)]), holes: vec![] };
|
||||
let cone = revolve(&Plane::YZ, &[outline], Vec2::ZERO, Vec2::Y, 2.0 * PI).unwrap().pop().unwrap();
|
||||
let read = through_step(&cone);
|
||||
assert!(super::needed(&read, &cone));
|
||||
let block = box_solid(Vec3::new(-2.0, 4.0, 6.0), Vec3::new(2.0, 12.0, 12.0)).unwrap();
|
||||
let built = volume(&boolean(&cone, &block, BoolOp::Intersect).unwrap().unwrap());
|
||||
let read = volume(&exact(&read, &block, BoolOp::Intersect).unwrap());
|
||||
assert!(rel(read, built) < 1e-3, "{read} {built}");
|
||||
}
|
||||
Reference in new issue
Block a user