57 lines
2.6 KiB
Rust
57 lines
2.6 KiB
Rust
//! A lip swept along the rim of a round body joins it, flush with its side or set in from it:
|
||
//! the lip's foot lies on the body's top, and its sides are round where the rim is.
|
||
|
||
use std::f64::consts::{FRAC_PI_2, PI};
|
||
|
||
use solidcraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
|
||
|
||
use crate::*;
|
||
|
||
/// A 1 × 1 mm lip on the top (z = 10) of a round body of radius 10 about Z, its outer side
|
||
/// `inset` in from the body's side, swept a quarter turn from the XZ plane.
|
||
fn lip(inset: f64) -> Body {
|
||
let plane = Plane::new(Vec3::ZERO, Vec3::X, Vec3::Z).unwrap();
|
||
let (x0, x1) = (9.0 - inset, 10.0 - inset);
|
||
let outer = Loop2::polygon(&[Vec2::new(x0, 10.0), Vec2::new(x1, 10.0), Vec2::new(x1, 11.0), Vec2::new(x0, 11.0)]);
|
||
let path = [PathSeg::Arc { a: Vec3::new(10.0, 0.0, 10.0), center: Vec3::new(0.0, 0.0, 10.0), axis: Vec3::Z, angle: FRAC_PI_2 }];
|
||
sweep(&plane, &Region2 { outer, holes: vec![] }, &path).unwrap()
|
||
}
|
||
|
||
#[test]
|
||
fn a_lip_swept_on_a_round_rim_joins_it() {
|
||
let body = cylinder(Vec3::ZERO, Vec3::Z, 10.0, 10.0).unwrap();
|
||
for inset in [0.0, 0.5] {
|
||
let (r0, r1) = (9.0 - inset, 10.0 - inset);
|
||
// Measured as the result is (the round faces' volume from their meshes).
|
||
let mesh_volume = |b: &Body| b.tessellate(1e-5).unwrap().measure().volume;
|
||
let want = mesh_volume(&body) + mesh_volume(&lip(inset));
|
||
assert!((want - PI * 1000.0 - PI * (r1 * r1 - r0 * r0) / 4.0).abs() < 0.05);
|
||
for (a, b) in [(&body, &lip(inset)), (&lip(inset), &body)] {
|
||
let (r, d) = capture_boolean_diagnostics(|| boolean(a, b, BoolOp::Union));
|
||
let r = r.unwrap().unwrap();
|
||
assert_eq!(r.shell_count(), 1, "inset {inset}: one solid");
|
||
assert!(r.validity().is_empty(), "inset {inset}: {:?}", r.validity());
|
||
let v = mesh_volume(&r);
|
||
assert!((v - want).abs() < 1e-3, "inset {inset}: volume {v} vs {want}");
|
||
assert!(!d.events.iter().any(|e| e.strategy == BooleanStrategy::ToolNudge), "inset {inset}: an exact join: {d:?}");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// The swept lip's foot over the turn is a plane, so it is found lying on the body's top.
|
||
#[test]
|
||
fn a_swept_lips_foot_is_a_plane() {
|
||
let l = lip(0.0);
|
||
let foot = crate::coplanar::faces_in(&l, -Vec3::Z, -10.0, 1e-6);
|
||
let m = l.tessellate(1e-5).unwrap();
|
||
let area: f64 = m
|
||
.triangles
|
||
.iter()
|
||
.zip(&m.tri_face)
|
||
.filter(|(_, f)| foot.contains(&(**f as usize)))
|
||
.filter_map(|(t, _)| m.tri(t))
|
||
.map(|[a, b, c]| (b - a).cross(c - a).len() / 2.0)
|
||
.sum();
|
||
assert!((area - PI * (100.0 - 81.0) / 4.0).abs() < 1e-3, "{} faces, {area} mm²", foot.len());
|
||
}
|