From 94a0cdef77cba80badc4d11f17c9596029f05194 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Thu, 8 Oct 2026 03:34:01 +0000 Subject: [PATCH] Ellipse dimensions: major radius (type major: centre to axis end) and minor radius (a radius dimension on the ellipse) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL --- crates/engine/src/cmd/sketch.rs | 12 ++++++++++++ crates/engine/src/cmd/sketch_modify_tests.rs | 10 ++++++++++ crates/sketch/src/model.rs | 2 ++ crates/sketch/src/solver.rs | 14 ++++++++++++-- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/sketch.rs b/crates/engine/src/cmd/sketch.rs index d1ac4bab..7b020715 100644 --- a/crates/engine/src/cmd/sketch.rs +++ b/crates/engine/src/cmd/sketch.rs @@ -1060,6 +1060,18 @@ fn dimension(s: &mut Session, p: &Value) -> Result { let len = arc_len(sk, c).ok_or_else(|| bad(cmd, "arc length needs an arc"))?; (ConstraintKind::ArcLength { c, value: len }, len) } + ((None, Some(c)), None) if matches!(sk.curves.get(c).map(|x| &x.kind), Some(CurveKind::Ellipse { .. })) => { + // Ellipse: the major radius is the centre–axis-end distance, the minor its own. + let Some(CurveKind::Ellipse { c: cp, m, r }) = sk.curves.get(c).map(|x| x.kind.clone()) else { + return Err(bad(cmd, "ellipse")); + }; + if ty == "major" { + let d = sk.point(cp).zip(sk.point(m)).map(|(a, b)| a.dist(b)).unwrap_or(0.0); + (ConstraintKind::Distance { p: cp, q: m, value: d }, d) + } else { + (ConstraintKind::Radius { c, value: r }, r) + } + } ((None, Some(c)), None) => { let r = sk.radius(c).ok_or_else(|| bad(cmd, "cannot dimension that curve"))?; let diameter = ty == "diameter" || (ty != "radius" && is_circle(c)); diff --git a/crates/engine/src/cmd/sketch_modify_tests.rs b/crates/engine/src/cmd/sketch_modify_tests.rs index b52481ec..e0bc7a66 100644 --- a/crates/engine/src/cmd/sketch_modify_tests.rs +++ b/crates/engine/src/cmd/sketch_modify_tests.rs @@ -382,3 +382,13 @@ fn trim_and_break_free_form_curves_exactly() { assert!(d < 0.05, "{q:?} {d}"); } } + +#[test] +fn ellipse_radii_dimensions() { + let mut s = new_sketch(); + let e = ids(&run(&mut s, "CircleElipse", json!({"center": [0, 0], "major": [10, 0], "minor_radius": 4}))["curves"])[0].clone(); + run(&mut s, "SketchDimension", json!({"entities": [e], "type": "major", "value": 12})); + run(&mut s, "SketchDimension", json!({"entities": [e], "value": 5})); + let a = profiles(&s)[0]; + assert!((a - std::f64::consts::PI * 60.0).abs() < 1e-6, "{a}"); +} diff --git a/crates/sketch/src/model.rs b/crates/sketch/src/model.rs index a96e8d5f..4aa71ea2 100644 --- a/crates/sketch/src/model.rs +++ b/crates/sketch/src/model.rs @@ -647,6 +647,8 @@ impl Sketch { need_line(a)?; need_line(b)?; } + // On an ellipse, a radius dimension holds its minor radius. + Radius { c, .. } if matches!(self.curves.get(c).map(|x| &x.kind), Some(CurveKind::Ellipse { .. })) => {} Radius { c, .. } | Diameter { c, .. } => need_round(c)?, Concentric { a, b } => { need_round(a)?; diff --git a/crates/sketch/src/solver.rs b/crates/sketch/src/solver.rs index 620e0359..87890ef2 100644 --- a/crates/sketch/src/solver.rs +++ b/crates/sketch/src/solver.rs @@ -157,6 +157,16 @@ impl State<'_> { _ => Vec::new(), } } + /// Radius of a circle or arc; an ellipse's minor radius. + fn radius_of(&self, c: usize) -> f64 { + match self.sk.curves.get(c).map(|x| &x.kind) { + Some(CurveKind::Ellipse { r, .. }) => match self.lay.rvar.get(c).copied().flatten() { + Some(v) => self.x.get(v).copied().unwrap_or(*r), + None => *r, + }, + _ => self.round(c).1, + } + } fn ends(&self, c: usize) -> Option<(usize, usize)> { self.sk.curves.get(c)?.kind.ends() } @@ -358,7 +368,7 @@ fn residuals(s: &State, k: &ConstraintKind, out: &mut Vec) { let (a, b) = s.line(l); out.push(a.dist(b) - value); } - Radius { c, value } => out.push(s.round(c).1 - value), + Radius { c, value } => out.push(s.radius_of(c) - value), Diameter { c, value } => out.push(2.0 * s.round(c).1 - value), Angle { a, b, value, flip } => { let (a0, a1) = s.line(a); @@ -408,7 +418,7 @@ fn measure(s: &State, k: &ConstraintKind) -> Option { let (a, b) = s.line(l); a.dist(b) } - Radius { c, .. } => s.round(c).1, + Radius { c, .. } => s.radius_of(c), Diameter { c, .. } => 2.0 * s.round(c).1, Angle { a, b, flip, .. } => { let (a0, a1) = s.line(a);