mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-07 20:41:50 +00:00
* claim: #4625 * fix: measure obtuse corners independently of edge direction (#4625)
This commit is contained in:
@@ -410,6 +410,20 @@ fn closest(a: &Geo, b: &Geo) -> Option<(Vec3, Vec3)> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Two straight edges sharing exactly one end define rays away from that corner. Their
|
||||
/// stored directions are arbitrary, so use the rays to preserve obtuse (and straight) angles.
|
||||
/// Separate edges and coincident copies have no unique corner and keep the undirected angle.
|
||||
fn corner_cos(a: Shape, b: Shape) -> Option<f64> {
|
||||
let (Shape::Line { a, b }, Shape::Line { a: c, b: d }) = (a, b) else { return None };
|
||||
let tol = a.dist(b).min(c.dist(d)).max(1.0) * 1e-6;
|
||||
let mut corners = [(a, b, c, d), (a, b, d, c), (b, a, c, d), (b, a, d, c)].into_iter().filter(|(p, _, q, _)| p.dist(*q) <= tol);
|
||||
let (p, end_a, q, end_b) = corners.next()?;
|
||||
if corners.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
Some((end_a - p).normalized()?.dot((end_b - q).normalized()?))
|
||||
}
|
||||
|
||||
/// `inspect.measure` with `items` (one or two selections).
|
||||
pub(crate) fn measure_items(s: &Session, items: &Value) -> Result<Value> {
|
||||
let cmd = "inspect.measure";
|
||||
@@ -509,9 +523,8 @@ pub(crate) fn measure_items(s: &Session, items: &Value) -> Result<Value> {
|
||||
out["measure"] = json!(if c.is_some() { "center" } else { "min" });
|
||||
if let (Some(da), Some(db)) = (a.dir, b.dir) {
|
||||
let both_faces = matches!(sels.as_slice(), [Sel::Face { .. }, Sel::Face { .. }]);
|
||||
let cos = da.dot(db).clamp(-1.0, 1.0);
|
||||
let cos = if both_faces { cos } else { cos.abs() };
|
||||
out["angle_deg"] = json!(cos.acos().to_degrees());
|
||||
let cos = if both_faces { da.dot(db) } else { corner_cos(a.shape, b.shape).unwrap_or_else(|| da.dot(db).abs()) };
|
||||
out["angle_deg"] = json!(cos.clamp(-1.0, 1.0).acos().to_degrees());
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
|
||||
@@ -74,6 +74,64 @@ fn measure(s: &mut Session, items: Vec<Value>) -> Value {
|
||||
run(s, "inspect.measure", json!({ "items": items }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connected_lines_measure_the_corner_regardless_of_endpoint_or_selection_order() {
|
||||
// cos(angle) = -3/5: the corner is 126.869898°, not its 53.130102° supplement.
|
||||
for (end, want) in
|
||||
[([-24.0, 32.0], (-0.6f64).acos().to_degrees()), ([24.0, 32.0], 0.6f64.acos().to_degrees()), ([0.0, 40.0], 90.0), ([-40.0, 0.0], 180.0)]
|
||||
{
|
||||
for reverse_a in [false, true] {
|
||||
for reverse_b in [false, true] {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY"}));
|
||||
let mut a = [[0.0, 0.0], [40.0, 0.0]];
|
||||
let mut b = [[0.0, 0.0], end];
|
||||
if reverse_a {
|
||||
a.reverse();
|
||||
}
|
||||
if reverse_b {
|
||||
b.reverse();
|
||||
}
|
||||
run(&mut s, "sketch.line", json!({"points": a}));
|
||||
run(&mut s, "sketch.line", json!({"points": b}));
|
||||
let (a, b) = (json!({"type": "sketch_curve", "id": "l1"}), json!({"type": "sketch_curve", "id": "l2"}));
|
||||
for items in [vec![a.clone(), b.clone()], vec![b, a]] {
|
||||
let r = measure(&mut s, items);
|
||||
assert!(close(&r["angle_deg"], want, 1e-8), "{reverse_a} {reverse_b}: {r}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solid_edges_measure_the_obtuse_corner() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY"}));
|
||||
run(&mut s, "sketch.line", json!({"points": [[40, 0], [0, 0], [-24, 32]], "closed": true}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"distance": 10}));
|
||||
let a = edge(&s, |p| p.z.abs() < 1e-8 && p.y.abs() < 1e-8);
|
||||
let b = edge(&s, |p| p.z.abs() < 1e-8 && (4.0 * p.x + 3.0 * p.y).abs() < 1e-8);
|
||||
for items in [vec![a.clone(), b.clone()], vec![b, a]] {
|
||||
let r = measure(&mut s, items);
|
||||
assert!(close(&r["angle_deg"], (-0.6f64).acos().to_degrees(), 1e-8), "{r}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn separate_and_duplicate_lines_keep_the_undirected_angle() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY"}));
|
||||
run(&mut s, "sketch.line", json!({"points": [[0, 0], [40, 0]]}));
|
||||
run(&mut s, "sketch.line", json!({"points": [[0, 10], [-24, 42]]}));
|
||||
let (a, b) = (json!({"type": "sketch_curve", "id": "l1"}), json!({"type": "sketch_curve", "id": "l2"}));
|
||||
let r = measure(&mut s, vec![a.clone(), b]);
|
||||
assert!(close(&r["angle_deg"], 0.6f64.acos().to_degrees(), 1e-8), "{r}");
|
||||
let r = measure(&mut s, vec![a.clone(), a]);
|
||||
assert!(close(&r["angle_deg"], 0.0, 1e-8), "{r}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_holes_measure_centre_to_centre_with_min_and_max() {
|
||||
let mut s = Session::default();
|
||||
|
||||
@@ -41,6 +41,22 @@ fn component_rects() -> Vec<egui::Rect> {
|
||||
["measure:label", "measure:label:x", "measure:label:y", "measure:label:z"].into_iter().map(|key| crate::scenario::rect_of(key).unwrap()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn measure_dialog_shows_the_obtuse_corner_of_selected_lines() {
|
||||
let mut h = Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.run("sketch.create", json!({"plane": "XY"})).unwrap();
|
||||
h.app.run("sketch.line", json!({"points": [[40, 0], [0, 0], [-24, 32]]})).unwrap();
|
||||
h.app.session.selection = ["l1", "l2"].into_iter().map(|id| Sel::SketchCurve { id: id.into() }).collect();
|
||||
h.app.start("inspect.measure");
|
||||
for layout in ["floating", "compact"] {
|
||||
h.call("ui.set", json!({"layout": layout}));
|
||||
let texts = painted_text(&mut h);
|
||||
assert!(texts.contains(&"126.870°".into()), "{layout}: {texts:?}");
|
||||
assert!(!texts.contains(&"53.130°".into()), "the supplementary angle must not replace the corner: {texts:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn measurement_axis_distances_follow_the_upstream_centre_min_and_max_choices() {
|
||||
let mut h = Harness::new();
|
||||
|
||||
Reference in New Issue
Block a user