* WIP deep rename (not for push) * WIP2 * WIP3 * WIP4 wxs fix * rustfmt * Rename the remaining identifiers in merged crash-report code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
5.7 KiB
Rust
119 lines
5.7 KiB
Rust
#![allow(clippy::unwrap_used, clippy::panic)]
|
|
use crate::*;
|
|
use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
|
|
|
|
fn fixture() -> (Body, Body, Vec3, f64) {
|
|
let slope = 1f64.to_radians().tan();
|
|
let hs = |normal: Vec3, point: Vec3| {
|
|
let n = normal.normalized().unwrap();
|
|
HalfSpace { n, d: n.dot(point) }
|
|
};
|
|
let stock = convex_polyhedron(&[
|
|
hs(Vec3::X, Vec3::new(40., 0., 0.)),
|
|
hs(-Vec3::X, Vec3::ZERO),
|
|
hs(Vec3::Y, Vec3::new(0., 30., 0.)),
|
|
hs(-Vec3::Y, Vec3::ZERO),
|
|
hs(-Vec3::Z, Vec3::ZERO),
|
|
hs(Vec3::new(-slope, 0., 1.), Vec3::new(0., 0., 20.)),
|
|
])
|
|
.unwrap();
|
|
let cutter = cylinder(Vec3::new(20., 15., -5.), Vec3::Z, 5., 40.).unwrap();
|
|
let bored = boolean(&stock, &cutter, BoolOp::Cut).unwrap().unwrap();
|
|
let web = box_solid(Vec3::new(14., 14., 16.05), Vec3::new(26., 16., 16.07)).unwrap();
|
|
let target = boolean(&bored, &web, BoolOp::Union).unwrap().unwrap();
|
|
let region = Region2 { outer: Loop2::circle(Vec2::new(20., 15.), 8.), holes: vec![Loop2::circle(Vec2::new(20., 15.), 5.).reversed()] };
|
|
let ring = extrude(&Plane::XY, &[region], 14., 24.).unwrap().remove(0);
|
|
let web_volume = 0.02 * 2. * (24f64.sqrt() + 25. * 0.2f64.asin());
|
|
let fine_target = target.tessellate_with(1e-4, true).unwrap();
|
|
let added = fine_target.measure().volume - bored.tessellate_with(1e-4, true).unwrap().measure().volume;
|
|
assert!((added - web_volume).abs() < 0.005, "actual connected web adds {added}, expected {web_volume}");
|
|
for x in [16., 20., 24.] {
|
|
for y in [14.2, 15., 15.8] {
|
|
assert!(fine_target.contains(Vec3::new(x, y, 16.06)), "source web must exist at {x},{y}");
|
|
}
|
|
}
|
|
assert!(!fine_target.contains(Vec3::new(20., 12., 16.06)));
|
|
let floor_volume = (20. + slope * 20.) * (1200. - 25. * std::f64::consts::PI);
|
|
let ring_added = std::f64::consts::PI * (64. - 25.) * (24. - (20. + slope * 20.));
|
|
(target, ring, Vec3::new(20., 15., 16.06), floor_volume + web_volume + ring_added)
|
|
}
|
|
|
|
#[test]
|
|
fn thin_connected_web_between_bore_stations_survives_union() {
|
|
let (target, ring, probe, expected) = fixture();
|
|
let target_before = step_export(&[&target], "source").unwrap();
|
|
let ring_before = step_export(&[&ring], "ring").unwrap();
|
|
let size = target.size().max(ring.size());
|
|
let tol = (size * 5e-4).max(size * 1e-5);
|
|
let ma = target.tessellate_with(tol, true).unwrap();
|
|
let mb = ring.tessellate_with(tol, true).unwrap();
|
|
assert!(ma.contains(probe), "fixture must contain the real connected web");
|
|
assert!(!mb.contains(probe));
|
|
let va = ma.measure().volume;
|
|
let vb = mb.measure().volume;
|
|
let slack = 2e-3 * (va + vb) + 1e-9;
|
|
let accept = |body: &Body| {
|
|
let mr = body.tessellate_with(tol, true).unwrap();
|
|
let v = mr.measure().volume;
|
|
let mismatch = super::mismatch(&ma, &mb, &mr, BoolOp::Union);
|
|
eprintln!("fallback candidate V={v}, expected={expected}, web={}, mismatch={mismatch:?}", mr.contains(probe));
|
|
v > 0. && v >= va.max(vb) - slack && v <= va + vb + slack && mismatch.is_none_or(|f| f < 0.004)
|
|
};
|
|
let direct = crate::coplanar::bore_union(&target, &ring, &accept);
|
|
eprintln!("direct fallback result={}", direct.is_some());
|
|
let public = boolean(&target, &ring, BoolOp::Union);
|
|
if let Ok(dir) = solidcraft_geom::env::var("BORE_AUDIT_DIR").ok_or(()) {
|
|
let dir = std::path::Path::new(&dir);
|
|
std::fs::create_dir_all(dir).unwrap();
|
|
std::fs::write(dir.join("target-with-web.step"), step_export(&[&target], "target with web").unwrap()).unwrap();
|
|
if let Some(result) = &direct {
|
|
std::fs::write(dir.join("direct-union.step"), step_export(&[result], "direct union").unwrap()).unwrap();
|
|
}
|
|
if let Ok(Some(result)) = &public {
|
|
std::fs::write(dir.join("public-union.step"), step_export(&[result], "public union").unwrap()).unwrap();
|
|
}
|
|
}
|
|
let direct_keeps_web = direct.as_ref().is_none_or(|result| result.tessellate_with(1e-3, true).unwrap().contains(probe));
|
|
let public_keeps_web = match &public {
|
|
Ok(Some(result)) => {
|
|
let keep = result.tessellate_with(1e-3, true).unwrap().contains(probe);
|
|
eprintln!("public Union result web={keep}");
|
|
keep
|
|
}
|
|
result => {
|
|
eprintln!("public Union honestly refused: {result:?}");
|
|
true
|
|
}
|
|
};
|
|
assert!(direct_keeps_web, "accepted fallback removed real web material");
|
|
assert!(public_keeps_web, "public Union removed real web material");
|
|
assert_eq!(step_export(&[&target], "source").unwrap(), target_before);
|
|
assert_eq!(step_export(&[&ring], "ring").unwrap(), ring_before);
|
|
}
|
|
|
|
#[test]
|
|
fn unproved_curved_boundary_between_clear_endpoints_refuses_recut() {
|
|
let a = Vec2::new(6., -1.);
|
|
let b = Vec2::new(6., 1.);
|
|
let c = Vec2::new(8., 1.);
|
|
let d = Vec2::new(8., -1.);
|
|
let region = Region2 {
|
|
outer: Loop2 {
|
|
segs: vec![
|
|
Seg2::Cubic { p0: a, p1: Vec2::new(3., -0.5), p2: Vec2::new(3., 0.5), p3: b },
|
|
Seg2::Line { a: b, b: c },
|
|
Seg2::Line { a: c, b: d },
|
|
Seg2::Line { a: d, b: a },
|
|
],
|
|
}
|
|
.reversed(),
|
|
holes: vec![],
|
|
};
|
|
let body = extrude(&Plane::XY, &[region], 16.05, 16.07).unwrap().remove(0);
|
|
assert!(a.len() > 5. && b.len() > 5., "endpoint samples miss the interior excursion");
|
|
assert!(body.tessellate_with(1e-4, true).unwrap().contains(Vec3::new(4.5, 0., 16.06)));
|
|
let before = step_export(&[&body], "curved source").unwrap();
|
|
assert!(crate::bore_guard::boundaries_clear(&body, &[], Vec3::ZERO, Vec3::Z, 5., 14., 24., 1e-6).is_none());
|
|
assert_eq!(step_export(&[&body], "curved source").unwrap(), before);
|
|
}
|