M0: truck-based kernel with local fillets and chamfers, parametric document timeline

The kernel moved from monstertruck to truck: truck's booleans handle blind
holes, pockets and bosses that monstertruck's did not (see the ADR). Fillets
and chamfers on straight convex edges between planar faces are our own exact
local B-rep operation. The document holds parameters with unit-aware
expressions and a timeline that re-evaluates incrementally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-07 17:18:16 +00:00
1 parent dafce1f4e2
commit a97af44979
18 files changed
+2740 -625

No files matched your search

+81
View File
@@ -119,6 +119,32 @@ fn hostile_inputs_are_errors() {
assert!(transform(&b, Vec3::new(f64::NAN, 0.0, 0.0), Vec3::ZERO, Vec3::Z, 0.0).is_err());
}
#[test]
fn fillets_on_several_edges_and_angles() {
let b = box_solid(Vec3::ZERO, Vec3::new(40.0, 30.0, 20.0)).unwrap();
// Two opposite vertical edges, then a horizontal top edge between them is not touching either.
let f = fillet(&b, &[Vec3::new(0.0, 0.0, 10.0), Vec3::new(40.0, 30.0, 10.0)], 4.0).unwrap();
let corner = 16.0 - PI * 4.0;
assert!(rel(measure(&f).unwrap().volume, 24000.0 - 2.0 * corner * 20.0) < 2e-4);
let top = fillet(&b, &[Vec3::new(20.0, 0.0, 20.0)], 2.0).unwrap();
assert!(rel(measure(&top).unwrap().volume, 24000.0 - (4.0 - PI) * 40.0) < 2e-4);
// Triangular prism: 60° edges.
let tri = Region2 { outer: Loop2::polygon(&[Vec2::new(0.0, 0.0), Vec2::new(30.0, 0.0), Vec2::new(15.0, 25.980762113533)]), holes: vec![] };
let p = extrude(&Plane::XY, &[tri], 0.0, 10.0).unwrap().pop().unwrap();
let r = 2.0;
let fp = fillet(&p, &[Vec3::new(0.0, 0.0, 5.0)], r).unwrap();
// Removed area for interior angle φ: r²(cot(φ/2)) − r²(π − φ)/2.
let phi = PI / 3.0;
let removed = r * r / (phi / 2.0).tan() - r * r * (PI - phi) / 2.0;
let v0 = measure(&p).unwrap().volume;
assert!(rel(measure(&fp).unwrap().volume, v0 - removed * 10.0) < 2e-4, "{} vs {}", measure(&fp).unwrap().volume, v0 - removed * 10.0);
// Adjacent edges at a blended corner are not supported yet (an error, not a crash).
assert!(fillet(&b, &[Vec3::new(0.0, 0.0, 10.0), Vec3::new(0.0, 15.0, 20.0)], 2.0).is_err());
// Concave edge: not supported.
let u = boolean(&b, &box_solid(Vec3::new(10.0, 10.0, 15.0), Vec3::new(20.0, 20.0, 30.0)).unwrap(), BoolOp::Union).unwrap().unwrap();
assert!(fillet(&u, &[Vec3::new(15.0, 10.0, 20.0)], 1.0).is_err());
}
#[test]
#[ignore]
fn debug_convergence() {
@@ -135,3 +161,58 @@ fn debug_convergence() {
println!("face {f}: {} normals, first {:?}, max |n.z| {}", ns.len(), ns.first(), ns.iter().map(|n| n.z.abs()).fold(0.0, f64::max));
}
}
#[test]
#[ignore]
fn debug_coplanar() {
let b = box_solid(Vec3::ZERO, Vec3::new(40.0, 30.0, 20.0)).unwrap();
for (z0, z1) in [(0.0, 20.0), (-5.0, 20.0), (0.0, 25.0), (-5.0, 25.0), (5.0, 20.0), (5.0, 25.0)] {
let c = cylinder(Vec3::new(20.0, 15.0, z0), Vec3::Z, 5.0, z1 - z0).unwrap();
let r = boolean(&b, &c, BoolOp::Cut);
println!("cut z {z0}..{z1}: {:?}", r.map(|o| o.map(|b| measure(&b).unwrap().volume)));
let r = boolean(&b, &c, BoolOp::Union);
println!("union z {z0}..{z1}: {:?}", r.map(|o| o.map(|b| measure(&b).unwrap().volume)));
}
let r =
Region2 { outer: Loop2::polygon(&[Vec2::new(0.0, 0.0), Vec2::new(40.0, 0.0), Vec2::new(40.0, 30.0), Vec2::new(0.0, 30.0)]), holes: vec![] };
for ang in [PI / 2.0, PI, 1.5 * PI, 2.0 * PI] {
let q = revolve(&Plane::XY, std::slice::from_ref(&r), Vec2::ZERO, Vec2::Y, ang).unwrap().pop().unwrap();
let m = measure(&q).unwrap();
println!("revolve {ang}: vol {} expect {} faces {}", m.volume, ang / 2.0 * 1600.0 * 30.0, m.faces);
}
}
#[test]
#[ignore]
fn debug_blind() {
let b = box_solid(Vec3::ZERO, Vec3::new(40.0, 30.0, 20.0)).unwrap();
let t = box_solid(Vec3::new(10.0, 10.0, 5.0), Vec3::new(20.0, 20.0, 25.0)).unwrap();
println!("box pocket: {:?}", boolean(&b, &t, BoolOp::Cut).map(|o| o.map(|b| measure(&b).unwrap().volume)));
let t = box_solid(Vec3::new(10.0, 10.0, 5.0), Vec3::new(20.0, 20.0, 15.0)).unwrap();
println!("box void: {:?}", boolean(&b, &t, BoolOp::Cut).map(|o| o.map(|b| measure(&b).unwrap().volume)));
for r in [5.0, 4.3] {
for z in [5.0, 7.3, 13.1] {
let c = cylinder(Vec3::new(20.3, 15.1, z), Vec3::Z, r, 30.0).unwrap();
println!("cyl r{r} z{z}: {:?}", boolean(&b, &c, BoolOp::Cut).map(|o| o.map(|b| measure(&b).unwrap().volume)));
}
}
let c = cylinder(Vec3::new(20.0, 15.0, 25.0), -Vec3::Z, 5.0, 20.0).unwrap();
println!("cyl down: {:?}", boolean(&b, &c, BoolOp::Cut).map(|o| o.map(|b| measure(&b).unwrap().volume)));
let s = sphere(Vec3::new(20.0, 15.0, 20.0), 5.0).unwrap();
println!("sphere: {:?}", boolean(&b, &s, BoolOp::Cut).map(|o| o.map(|b| measure(&b).unwrap().volume)));
}
#[test]
#[ignore]
fn debug_revolve_doc_case() {
let r =
Region2 { outer: Loop2::polygon(&[Vec2::new(0.0, 0.0), Vec2::new(40.0, 0.0), Vec2::new(40.0, 30.0), Vec2::new(0.0, 30.0)]), holes: vec![] };
for ang in [PI / 2.0, -PI / 2.0] {
let q = revolve(&Plane::XY, std::slice::from_ref(&r), Vec2::ZERO, Vec2::Y, ang).unwrap().pop().unwrap();
let m = measure(&q).unwrap();
println!("ang {ang}: vol {} bbox {:?}", m.volume, m.bbox);
}
let r2 = Region2 { outer: r.outer.reversed(), holes: vec![] };
let q = revolve(&Plane::XY, std::slice::from_ref(&r2), Vec2::ZERO, Vec2::Y, PI / 2.0).unwrap().pop().unwrap();
println!("reversed: {}", measure(&q).unwrap().volume);
}