diff --git a/crates/engine/src/cmd/sketch.rs b/crates/engine/src/cmd/sketch.rs index 11ad1a31..f26e5037 100644 --- a/crates/engine/src/cmd/sketch.rs +++ b/crates/engine/src/cmd/sketch.rs @@ -27,7 +27,7 @@ pub static COMMANDS: &[CommandSpec] = &[ .icon("rect") .key("R") .enabled(in_sketch) - .params("p0, p1: opposite corners [x,y]; construction?"), + .params("p0, p1: opposite corners [x,y]; construction?. Returns sides {bottom, right, top, left} (line ids) and corners {bottom_left, bottom_right, top_right, top_left} (point ids)"), CommandSpec::new("sketch.rectangle.three_point", "3-Point Rectangle", rect_three_point) .at("SKETCH", "CREATE") .icon("rect3") @@ -37,7 +37,7 @@ pub static COMMANDS: &[CommandSpec] = &[ .at("SKETCH", "CREATE") .icon("rect_center") .enabled(in_sketch) - .params("center, corner"), + .params("center, corner. Returns sides {bottom, right, top, left} (line ids) and corners {bottom_left, bottom_right, top_right, top_left} (point ids)"), CommandSpec::new("sketch.circle.center", "Center Diameter Circle", circle_center) .at("SKETCH", "CREATE") .icon("circle") @@ -526,6 +526,27 @@ fn rect_constraints(sk: &mut Sketch, l: &[usize]) -> Result> { Ok(cons) } +/// Which side and corner of an axis-aligned rectangle is which, from its lines and corner +/// points as `polygon_lines` made them (counter-clockwise from the bottom-left corner), so a +/// caller can dimension it without inspecting the sketch first. +fn rect_names(sk: &Sketch, l: &[usize], pts: &[usize]) -> Value { + let curve = |i: usize| l.get(i).and_then(|c| sk.curves.get(*c)).map(|c| c.id.clone()); + let point = |i: usize| pts.get(i).and_then(|q| sk.points.get(*q)).map(|q| q.id.clone()); + json!({ + "sides": {"bottom": curve(0), "right": curve(1), "top": curve(2), "left": curve(3)}, + "corners": {"bottom_left": point(0), "bottom_right": point(1), "top_right": point(2), "top_left": point(3)}, + }) +} + +/// `result` plus the rectangle's named sides and corners. +fn rect_result(sk_out: (Vec, Vec), names: Value, info: Value) -> Value { + let mut out = result(sk_out, info); + if let (Some(o), Value::Object(n)) = (out.as_object_mut(), names) { + o.extend(n); + } + out +} + fn rect_two_point(s: &mut Session, p: &Value) -> Result { let cmd = "sketch.rectangle.two_point"; let (a, b) = (req_vec2(cmd, p, "p0")?, req_vec2(cmd, p, "p1")?); @@ -533,13 +554,13 @@ fn rect_two_point(s: &mut Session, p: &Value) -> Result { return Err(bad(cmd, "the corners must differ in x and y")); } let (lo, hi) = (Vec2::new(a.x.min(b.x), a.y.min(b.y)), Vec2::new(a.x.max(b.x), a.y.max(b.y))); - let (out, info) = edit(s, p, cmd, false, |sk, _| { - let (l, _) = polygon_lines(sk, &[lo, Vec2::new(hi.x, lo.y), hi, Vec2::new(lo.x, hi.y)])?; + let ((out, names), info) = edit(s, p, cmd, false, |sk, _| { + let (l, pts) = polygon_lines(sk, &[lo, Vec2::new(hi.x, lo.y), hi, Vec2::new(lo.x, hi.y)])?; let cons = rect_constraints(sk, &l)?; mark_construction(sk, &l, p); - Ok((ids_of(sk, &l), cons)) + Ok(((ids_of(sk, &l), cons), rect_names(sk, &l, &pts))) })?; - Ok(result(out, info)) + Ok(rect_result(out, names, info)) } fn rect_center(s: &mut Session, p: &Value) -> Result { @@ -549,7 +570,7 @@ fn rect_center(s: &mut Session, p: &Value) -> Result { if h.x < 1e-9 || h.y < 1e-9 { return Err(bad(cmd, "the corner must differ from the centre in x and y")); } - let (out, info) = edit(s, p, cmd, false, |sk, _| { + let ((out, names), info) = edit(s, p, cmd, false, |sk, _| { let (l, pts) = polygon_lines(sk, &[c - h, Vec2::new(c.x + h.x, c.y - h.y), c + h, Vec2::new(c.x - h.x, c.y + h.y)])?; let mut cons = rect_constraints(sk, &l)?; // Both diagonals (construction), the centre at their crossing. Only the first carries @@ -567,9 +588,9 @@ fn rect_center(s: &mut Session, p: &Value) -> Result { cons.push(add_c(sk, ConstraintKind::Midpoint { p: cp, l: diag })?); } mark_construction(sk, &l, p); - Ok((ids_of(sk, &l), cons)) + Ok(((ids_of(sk, &l), cons), rect_names(sk, &l, &pts))) })?; - Ok(result(out, info)) + Ok(rect_result(out, names, info)) } fn rect_three_point(s: &mut Session, p: &Value) -> Result { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 8c8697cc..df715a0b 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1847,3 +1847,43 @@ fn boundary_fill_cells() { assert!(rel(m["total"]["volume_mm3"].as_f64().unwrap_or(0.0), 2.0 * 10.0 * 20.0 * 20.0) < 1e-9, "{m}"); assert!(s.execute("SurfaceSculpt", &json!({"tools": ["A"], "cells": [[100, 100, 100]]})).is_err()); } + +/// Rectangles name their sides and corners, so they can be dimensioned without inspecting the +/// sketch first, whichever way round the corners were given. +#[test] +fn rectangle_names_sides_and_corners() { + let mut s = Session::default(); + run(&mut s, "sketch.create", json!({"plane": "XY"})); + let r = run(&mut s, "sketch.rectangle.two_point", json!({"p0": [40, 30], "p1": [0, 0]})); + let pos = |s: &Session, id: &Value| { + let sk = s.doc.sketch(s.active_sketch.unwrap()).unwrap(); + sk.point(sk.resolve_point(id.as_str().unwrap()).unwrap()).unwrap() + }; + for (corner, at) in [("bottom_left", [0.0, 0.0]), ("bottom_right", [40.0, 0.0]), ("top_right", [40.0, 30.0]), ("top_left", [0.0, 30.0])] { + let p = pos(&s, &r["corners"][corner]); + assert!((p.x - at[0]).abs() < 1e-9 && (p.y - at[1]).abs() < 1e-9, "{corner}: {p:?} in {r}"); + } + for (side, from, to) in [ + ("bottom", "bottom_left", "bottom_right"), + ("right", "bottom_right", "top_right"), + ("top", "top_right", "top_left"), + ("left", "top_left", "bottom_left"), + ] { + let line = r["sides"][side].as_str().unwrap(); + assert!(r["curves"].as_array().unwrap().iter().any(|c| c == line), "{r}"); + assert_eq!(pos(&s, &json!(format!("{line}.start"))), pos(&s, &r["corners"][from]), "{side} in {r}"); + assert_eq!(pos(&s, &json!(format!("{line}.end"))), pos(&s, &r["corners"][to]), "{side} in {r}"); + } + run(&mut s, "sketch.constraint.coincident", json!({"a": r["corners"]["bottom_left"], "b": "origin"})); + run(&mut s, "sketch.dimension", json!({"entities": [r["sides"]["bottom"]], "value": 50})); + run(&mut s, "sketch.dimension", json!({"entities": [r["sides"]["left"]], "value": 20})); + let si = run(&mut s, "sketch.inspect", json!({})); + assert_eq!(si["dof"], 0, "{si}"); + let p = pos(&s, &r["corners"]["top_right"]); + assert!((p.x - 50.0).abs() < 1e-6 && (p.y - 20.0).abs() < 1e-6, "{p:?}"); + + let c = run(&mut s, "sketch.rectangle.center", json!({"center": [100, 0], "corner": [90, 5]})); + let p = pos(&s, &c["corners"]["bottom_left"]); + assert!((p.x - 90.0).abs() < 1e-9 && (p.y + 5.0).abs() < 1e-9, "{c}"); + assert_eq!(pos(&s, &json!(format!("{}.start", c["sides"]["top"].as_str().unwrap()))), pos(&s, &c["corners"]["top_right"]), "{c}"); +}