mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-07 12:31:56 +00:00
2a09b99431
mesh memo (one remembered mesh per tolerance), history (a bare body from a record), tests.rs (no coarse-missing field, lumps for touching shells), outline (the foot found by the planar faces in its plane), chamfer sides (the rim from the record's edges), seam faces, coplanar and issue 679 and pinch tests (the boolean is SMLib's: the diagnostics assertions go, every volume, shell and validity check stays), boolean history (a cut of two pieces through the boolean), integration tests naming solidcraft_kernel::MeshTolerance. Deleted, testing internals that are gone: boolean_history_tests' glue_merges_across_the_seam and faces_pushed_apart (coplanar::glue, boolean_apart), planar_fallback (polybool's planar_boolean) and bore_union_of_a_bore_in_two_arcs_has_no_history (coplanar::bore_union); issue_851's the_sphere_at_a_box_corner_is_the_planar_corner_builders (blend_vertex); pinch's prism_boolean shell check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
87 lines
4.4 KiB
Rust
87 lines
4.4 KiB
Rust
//! Revolves of profiles with an edge on the axis that come back to it along a curve or a line
|
|
//! (#1853): a half slot about its centreline, a quarter disc and a bullet whose arc ends on the
|
|
//! axis, a drill point whose tip does. Their volumes are analytic, and a half slot revolved about
|
|
//! a centreline lying in a face of a plate, as a cut, takes what it should.
|
|
|
|
use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
|
|
use solidcraft_kernel::{BoolOp, boolean, box_solid, measure, revolve};
|
|
use std::f64::consts::{FRAC_PI_2, PI, TAU};
|
|
|
|
const R: f64 = 3.0;
|
|
|
|
fn line(a: (f64, f64), b: (f64, f64)) -> Seg2 {
|
|
Seg2::Line { a: Vec2::new(a.0, a.1), b: Vec2::new(b.0, b.1) }
|
|
}
|
|
|
|
/// A quarter of a circle of radius `R` about `c`, counter-clockwise from `start`.
|
|
fn quarter(c: (f64, f64), start: f64) -> Seg2 {
|
|
Seg2::Arc { center: Vec2::new(c.0, c.1), radius: R, start, sweep: FRAC_PI_2 }
|
|
}
|
|
|
|
fn region(segs: Vec<Seg2>) -> Region2 {
|
|
Region2 { outer: Loop2 { segs }, holes: vec![] }
|
|
}
|
|
|
|
/// Half a slot (radius `R`, centres 14 apart) cut along its centreline on the x axis.
|
|
fn half_slot() -> Region2 {
|
|
region(vec![line((0.0, 0.0), (20.0, 0.0)), quarter((17.0, 0.0), 0.0), line((17.0, R), (3.0, R)), quarter((3.0, 0.0), FRAC_PI_2)])
|
|
}
|
|
|
|
#[test]
|
|
fn revolves_that_end_on_the_axis_along_a_curve_have_their_volume() {
|
|
let sphere = 4.0 / 3.0 * PI * R.powi(3);
|
|
let cylinder = PI * R * R * 14.0;
|
|
let shapes = [
|
|
("half slot", half_slot(), cylinder + sphere),
|
|
("quarter disc", region(vec![line((-R, 0.0), (0.0, 0.0)), line((0.0, 0.0), (0.0, R)), quarter((0.0, 0.0), FRAC_PI_2)]), sphere / 2.0),
|
|
(
|
|
"bullet",
|
|
region(vec![line((0.0, 0.0), (17.0, 0.0)), line((17.0, 0.0), (17.0, R)), line((17.0, R), (3.0, R)), quarter((3.0, 0.0), FRAC_PI_2)]),
|
|
cylinder + sphere / 2.0,
|
|
),
|
|
// A drill point: a line coming back to the axis (a full turn keeps truck's cone).
|
|
(
|
|
"drill point",
|
|
region(vec![line((0.0, 0.0), (20.0, 0.0)), line((20.0, 0.0), (20.0, R)), line((20.0, R), (3.0, R)), line((3.0, R), (0.0, 0.0))]),
|
|
PI * R * R * 17.0 + PI * R * R * 3.0 / 3.0,
|
|
),
|
|
];
|
|
// The plane through the origin, and one moved and turned away from it.
|
|
let planes = [Plane::XY, Plane { origin: Vec3::new(5.0, 7.0, 10.0), x: Vec3::Y, y: Vec3::Z }];
|
|
for (name, r, full) in &shapes {
|
|
for plane in &planes {
|
|
for turn in [FRAC_PI_2, PI, -PI, TAU] {
|
|
let body = revolve(plane, std::slice::from_ref(r), Vec2::ZERO, Vec2::new(1.0, 0.0), turn).unwrap().pop().unwrap();
|
|
assert!(body.validity().is_empty(), "{name}, {turn}: {:?}", body.validity());
|
|
let want = full * turn.abs() / TAU;
|
|
let got = measure(&body).unwrap().volume;
|
|
assert!((got - want).abs() < 1e-3 * want, "{name} turned {turn} on {:?}: {got} instead of {want}", plane.origin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_half_slot_revolved_about_a_centreline_in_a_face_cuts_it() {
|
|
let plate = box_solid(Vec3::ZERO, Vec3::new(40.0, 20.0, 10.0)).unwrap();
|
|
let whole = measure(&plate).unwrap().volume;
|
|
// The half slot from x = 10 to 30 with its centreline on y = 10 of the top face; the plate
|
|
// holds the half of the capsule below it.
|
|
let inside = (PI * R * R * 14.0 + 4.0 / 3.0 * PI * R.powi(3)) / 2.0;
|
|
let r = half_slot();
|
|
// The sketch plane facing out of the plate and into it (its y running the other way).
|
|
for flip in [1.0, -1.0] {
|
|
let plane = Plane { origin: Vec3::new(10.0, 10.0, 10.0), x: Vec3::X, y: Vec3::Y * flip };
|
|
for turn in [PI, -PI, TAU] {
|
|
let tool = revolve(&plane, std::slice::from_ref(&r), Vec2::ZERO, Vec2::new(1.0, 0.0), turn).unwrap().pop().unwrap();
|
|
// A half turn into the plate, or a full one, cuts a groove; a half turn out of it misses.
|
|
let into = turn.abs() > PI || tool.mesh(&solidcraft_kernel::MeshTolerance::chord(0.01)).unwrap().bounds().min.z < 9.0;
|
|
let cut = boolean(&plate, &tool, BoolOp::Cut).unwrap().unwrap();
|
|
assert!(cut.validity().is_empty(), "flip {flip}, {turn}: {:?}", cut.validity());
|
|
let removed = whole - measure(&cut).unwrap().volume;
|
|
let want = if into { inside } else { 0.0 };
|
|
assert!((removed - want).abs() < 1e-3 * inside, "flip {flip}, turned {turn}: removed {removed} instead of {want}");
|
|
}
|
|
}
|
|
}
|