Preserve circle and arc centre heights when measuring straight edges (#1546)
Thanks @crono2250 for the fix. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
584bbe923d
commit
abb15e4b0e
2 files changed
+86
-4
No files matched your search
@@ -215,11 +215,22 @@ fn from_round(a: &Shape, b: &Shape) -> Option<Centre> {
|
||||
}
|
||||
Shape::Line { a: la, b: lb } => {
|
||||
let dir = (lb - la).normalized()?;
|
||||
let (p, q) = line_line(ca, na, la, dir);
|
||||
let d = p.dist(q);
|
||||
let across = dir.dot(na).abs() < 1e-6;
|
||||
let min = (full_a && across && d >= ra).then_some(d - ra);
|
||||
Centre { distance: d, from: p, to: q, min, max: None }
|
||||
if pa {
|
||||
// A circle/arc has a fixed centre. Project that point onto the picked
|
||||
// finite edge, retaining the height difference and clamping to its ends.
|
||||
let to = la + dir * (ca - la).dot(dir).clamp(0.0, (lb - la).len());
|
||||
let offset = to - ca;
|
||||
let height = offset.dot(na);
|
||||
let radial = (offset - na * height).len();
|
||||
let min = (full_a && across && radial >= ra).then(|| (radial - ra).hypot(height));
|
||||
Centre { distance: ca.dist(to), from: ca, to, min, max: None }
|
||||
} else {
|
||||
let (p, q) = line_line(ca, na, la, dir);
|
||||
let d = p.dist(q);
|
||||
let min = (full_a && across && d >= ra).then_some(d - ra);
|
||||
Centre { distance: d, from: p, to: q, min, max: None }
|
||||
}
|
||||
}
|
||||
Shape::Point(p) => {
|
||||
if pa {
|
||||
@@ -410,6 +421,48 @@ mod tests {
|
||||
assert!(c.from.dist(Vec3::new(10.0, 15.0, 10.0)) < 1e-12 && c.to.dist(Vec3::new(10.0, 0.0, 10.0)) < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_circle_or_arc_to_a_line_keeps_its_real_center() {
|
||||
let edge = Shape::Line { a: Vec3::new(0.0, 0.0, 6.0), b: Vec3::new(68.0, 0.0, 6.0) };
|
||||
for angle in [TAU, PI / 2.0] {
|
||||
let round = Shape::Round { center: Vec3::new(34.0, 25.0, 20.0), axis: Vec3::Z, radius: 12.0, angle, planar: true, lo: 0.0, hi: 0.0 };
|
||||
let c = centre(&round, &edge).unwrap();
|
||||
assert_eq!((c.from, c.to), (Vec3::new(34.0, 25.0, 20.0), Vec3::new(34.0, 0.0, 6.0)));
|
||||
assert!((c.distance - 25_f64.hypot(14.0)).abs() < 1e-12);
|
||||
if angle == TAU {
|
||||
assert!((c.min.unwrap() - 13_f64.hypot(14.0)).abs() < 1e-12);
|
||||
} else {
|
||||
assert!(c.min.is_none());
|
||||
}
|
||||
let swapped = centre(&edge, &round).unwrap();
|
||||
assert_eq!((swapped.from, swapped.to, swapped.distance), (c.to, c.from, c.distance));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tilted_arc_to_a_sloped_line_keeps_all_components() {
|
||||
let edge = Shape::Line { a: Vec3::ZERO, b: Vec3::new(6.0, 0.0, 6.0) };
|
||||
for axis in [Vec3::X, Vec3::Y, Vec3::Z] {
|
||||
let arc = Shape::Round { center: Vec3::new(3.0, 4.0, 6.0), axis, radius: 2.0, angle: PI, planar: true, lo: 0.0, hi: 0.0 };
|
||||
let c = centre(&arc, &edge).unwrap();
|
||||
assert!(c.from.dist(Vec3::new(3.0, 4.0, 6.0)) < 1e-12);
|
||||
assert!(c.to.dist(Vec3::new(4.5, 0.0, 4.5)) < 1e-12);
|
||||
assert!((c.distance - 20.5_f64.sqrt()).abs() < 1e-12);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_circle_center_to_a_finite_edge_does_not_use_its_extension() {
|
||||
for (center, expected) in [(Vec3::new(20.0, 3.0, 4.0), Vec3::new(10.0, 0.0, 0.0)), (Vec3::new(-10.0, 3.0, 4.0), Vec3::ZERO)] {
|
||||
for (a, b) in [(Vec3::ZERO, Vec3::new(10.0, 0.0, 0.0)), (Vec3::new(10.0, 0.0, 0.0), Vec3::ZERO)] {
|
||||
let c = centre(&circle(center, 2.0), &Shape::Line { a, b }).unwrap();
|
||||
assert_eq!((c.from, c.to), (center, expected));
|
||||
assert!((c.distance - 125_f64.sqrt()).abs() < 1e-12);
|
||||
}
|
||||
}
|
||||
assert!(centre(&circle(Vec3::ZERO, 2.0), &Shape::Line { a: Vec3::ZERO, b: Vec3::ZERO }).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coverage_of_a_half_and_a_whole_ring() {
|
||||
let ring = |n: usize, span: f64| -> Vec<Vec3> {
|
||||
|
||||
@@ -137,6 +137,35 @@ fn elevated_cylinder_rims_measure_between_the_picked_circles_centres() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_elevated_circle_to_a_straight_edge_keeps_its_height_difference() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.box", json!({"length":68,"width":50,"height":6,"body_name":"Plate"}));
|
||||
run(&mut s, "solid.cylinder", json!({"base":[34,25,6],"radius":12,"height":14,"body_name":"Boss","operation":"new_body"}));
|
||||
let straight = edge(&s, |p| p.y.abs() < 1e-9 && (p.z - 6.0).abs() < 1e-9);
|
||||
let round = {
|
||||
let st = s.model.state();
|
||||
let b = st.bodies.iter().find(|b| b.name == "Boss").unwrap();
|
||||
let m = b.mesh();
|
||||
let index = m.edges.iter().position(|e| e.len() > 2 && e.iter().all(|p| (p.z - 20.0).abs() < 1e-9)).unwrap();
|
||||
let e = &m.edges[index];
|
||||
json!({"type":"edge","body":b.name,"index":index,"point":e[e.len()/2]})
|
||||
};
|
||||
for items in [vec![straight.clone(), round.clone()], vec![round, straight]] {
|
||||
let r = measure(&mut s, items);
|
||||
assert!(close(&r["center_distance"], 25_f64.hypot(14.0), 1e-9), "{r}");
|
||||
let (line, circle) = if r["items"][0]["round"] == "circle" { ("center_to", "center_from") } else { ("center_from", "center_to") };
|
||||
for (key, expected) in [(line, [34.0, 0.0, 6.0]), (circle, [34.0, 25.0, 20.0])] {
|
||||
for (axis, value) in expected.into_iter().enumerate() {
|
||||
assert!(close(&r[key][axis], value, 1e-9), "{r}");
|
||||
}
|
||||
}
|
||||
assert!((r["center_delta"][2].as_f64().unwrap().abs() - 14.0).abs() < 1e-9, "{r}");
|
||||
assert!(close(&r["min"], 13_f64.hypot(14.0), 1e-9), "{r}");
|
||||
assert!(close(&r["max"], (34_f64.hypot(25.0) + 12.0).hypot(14.0), 0.05), "{r}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hole_to_the_plate_edge_and_side_measures_from_its_axis() {
|
||||
let mut s = Session::default();
|
||||
|
||||
Reference in new issue
Block a user