diff --git a/crates/engine/src/cmd/sketch.rs b/crates/engine/src/cmd/sketch.rs index b054b667..4e383532 100644 --- a/crates/engine/src/cmd/sketch.rs +++ b/crates/engine/src/cmd/sketch.rs @@ -1443,6 +1443,8 @@ fn dimension(s: &mut Session, p: &Value) -> Result { place_text(sk, &id, text_at); Ok((pname, name, cur)) })?; + // Report what the dimension measures once the sketch is solved, not the geometry before. + let current = solved_measure(s, p, cmd).unwrap_or(current); if driven { return Ok(json!({"param": Value::Null, "driven": true, "type": kind_name, "measured": current, "sketch": info})); } @@ -1453,6 +1455,12 @@ fn dimension(s: &mut Session, p: &Value) -> Result { Ok(json!({"param": param, "type": kind_name, "expression": v, "measured": current, "sketch": info})) } +/// What the dimension just added (the sketch's last constraint) measures on the solved sketch. +fn solved_measure(s: &Session, p: &Value, cmd: &str) -> Option { + let sk = s.doc.sketch(target_sketch(s, p, cmd).ok()?).ok()?; + solvecraft_sketch::measure_dimension(sk, &sk.constraints.last()?.kind) +} + /// Keep a dimension's text where it was placed (`at`, sketch coordinates), relative to the /// dimension so it follows the geometry. fn place_text(sk: &mut Sketch, id: &str, at: Option) { diff --git a/crates/engine/src/cmd/sketch_constraints_tests.rs b/crates/engine/src/cmd/sketch_constraints_tests.rs index 0c5edd7e..fe07607d 100644 --- a/crates/engine/src/cmd/sketch_constraints_tests.rs +++ b/crates/engine/src/cmd/sketch_constraints_tests.rs @@ -48,6 +48,26 @@ fn redundant_constraints_and_dimensions_are_refused_driven_ones_measure() { assert_eq!(g["fully_constrained"], true); } +/// `measured` is the dimension on the solved sketch: a trough drawn 99.84 long and dimensioned +/// 100 reports 100, not the length it had before solving. +#[test] +fn a_dimension_reports_the_solved_value() { + let mut s = new_sketch(); + run(&mut s, "sketch.rectangle.two_point", json!({"p0": [0, 0], "p1": [99.84, 40.2]})); + run(&mut s, "sketch.constraint.coincident", json!({"a": "p1", "b": "origin"})); + let d = run(&mut s, "sketch.dimension", json!({"entities": ["l1"], "value": 100})); + assert!((d["measured"].as_f64().unwrap() - 100.0).abs() < 1e-6, "{d}"); + let d = run(&mut s, "sketch.dimension", json!({"entities": ["l2"], "value": "40 mm"})); + assert!((d["measured"].as_f64().unwrap() - 40.0).abs() < 1e-6, "{d}"); + // A driven dimension measures the solved geometry too. + let d = run(&mut s, "sketch.dimension", json!({"entities": ["p1", "p3"], "driven": true})); + assert!((d["measured"].as_f64().unwrap() - 100.0_f64.hypot(40.0)).abs() < 1e-6, "{d}"); + let c = run(&mut s, "sketch.circle.center", json!({"center": [50, 20], "radius": 7.3})); + let c = ids(&c["curves"])[0].clone(); + let d = run(&mut s, "sketch.dimension", json!({"entities": [c], "value": 15})); + assert!((d["measured"].as_f64().unwrap() - 15.0).abs() < 1e-6, "{d}"); +} + #[test] fn curvature_polygon_arc_length_linear_diameter() { let mut s = new_sketch(); diff --git a/crates/sketch/src/lib.rs b/crates/sketch/src/lib.rs index 7e507456..2b4cc1d3 100644 --- a/crates/sketch/src/lib.rs +++ b/crates/sketch/src/lib.rs @@ -27,7 +27,7 @@ pub use link::{Link, LinkGeom, LinkKind, LinkSource, MAX_LINK_ENTITIES}; pub use model::{Constraint, ConstraintKind, Curve, CurveKind, SPoint, Sketch, SketchError, SketchView}; pub use profiles::{Profile, find_drawn_profiles, find_profiles, merge_regions}; pub use shape::{Shape, intersections}; -pub use solver::{Hold, SolveReport, SolveStatus, solve, solve_holding}; +pub use solver::{Hold, SolveReport, SolveStatus, measure_dimension, solve, solve_holding}; pub use text::{MAX_TEXT_CHARS, text_geometry}; pub use wire::{MAX_FIT_POINTS, MAX_WIRE_POINTS, Wire, fit_polyline}; diff --git a/crates/sketch/src/solver.rs b/crates/sketch/src/solver.rs index 11b5f578..c703e2e6 100644 --- a/crates/sketch/src/solver.rs +++ b/crates/sketch/src/solver.rs @@ -456,6 +456,13 @@ fn measure(s: &State, k: &ConstraintKind) -> Option { }) } +/// What a dimension measures on the sketch as it stands (mm, radians for angles); `None` for a +/// constraint that is not a dimension. +pub fn measure_dimension(sk: &Sketch, k: &ConstraintKind) -> Option { + let lay = Layout { pvar: vec![None; sk.points.len()], rvar: vec![None; sk.curves.len()], n: 0 }; + measure(&State { sk, lay: &lay, x: &[] }, k).filter(|v| v.is_finite()) +} + /// One residual block: implicit arc radius equality, a user constraint, or a tangency at a known /// touch point (better conditioned than the distance form when an end point is on the curve). enum Block<'a> {