95 lines
3.9 KiB
Rust
95 lines
3.9 KiB
Rust
//! 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}");
|
|
}
|