diff --git a/crates/ui-egui/src/scenario.rs b/crates/ui-egui/src/scenario.rs index 1f0d31b4..c035b58d 100644 --- a/crates/ui-egui/src/scenario.rs +++ b/crates/ui-egui/src/scenario.rs @@ -535,7 +535,7 @@ fn approx(got: f64, want: &Value, what: &str) -> Result<(), String> { /// Checks: `bodies` (count), `volume` / `area` (total, number or [value, rel tol]), `faces`, /// `features` (count), `errors` (timeline features in error, default 0), `dialog` (null: none /// open, or a word its kind must contain), `tool` (null or command id), `sketching` (bool), -/// `selection` (count), `glyphs` ({constraint id: count}, none overlapping), `param` ({name: value}), `sketch_status` (solved|…), `dof`, `query` +/// `selection` (count), `glyphs` ({constraint id: count}, none overlapping one another or a dimension value), `param` ({name: value}), `sketch_status` (solved|…), `dof`, `query` /// ({command, params?, path, value}: a command's result). pub fn check(h: &mut Harness, e: &Value) -> Result<(), String> { let doc = h.call("document.inspect", json!({"measure": true}))["result"].clone(); @@ -695,6 +695,10 @@ pub fn check(h: &mut Harness, e: &Value) -> Result<(), String> { if let Some((b, _)) = g.iter().skip(i + 1).find(|(_, q)| q.distance(*p) < 14.0) { return Err(format!("glyphs of {a} and {b} overlap at {p:?}")); } + let r = egui::Rect::from_center_size(*p, egui::vec2(14.0, 14.0)); + if let Some((d, _)) = crate::dim_view::text_rects().into_iter().find(|(_, t)| t.intersects(r)) { + return Err(format!("glyph of {a} at {p:?} overlaps the value of dimension {d}")); + } } } "sketching" => { diff --git a/crates/ui-egui/src/sketch_tools.rs b/crates/ui-egui/src/sketch_tools.rs index 1c0eeaa9..61de6b4b 100644 --- a/crates/ui-egui/src/sketch_tools.rs +++ b/crates/ui-egui/src/sketch_tools.rs @@ -1000,7 +1000,13 @@ fn glyphs(app: &SolveApp, painter: &egui::Painter, proj: &Proj, hover: Option = crate::dim_view::text_rects().into_iter().map(|(_, r)| r.expand(2.0)).collect(); + let on_dim = |p: Pos2| { + let r = egui::Rect::from_center_size(p, egui::vec2(GLYPH, GLYPH)); + dim_texts.iter().any(|d| d.intersects(r)) + }; let mut placed: Vec<(String, Pos2, Pos2)> = Vec::new(); for c in &ss.sketch.constraints { if c.kind.is_dimension() { @@ -1017,13 +1023,22 @@ fn glyphs(app: &SolveApp, painter: &egui::Painter, proj: &Proj, hover: Option egui::vec2(0.7, -0.7), }; let step = if dir.is_some() { egui::vec2(side.y, -side.x) } else { egui::vec2(1.0, 0.0) }; - let mut sp = base + side * (GLYPH * 0.9); - for _ in 0..12 { - if !placed.iter().any(|(_, q, _)| q.distance(sp) < GLYPH + 1.0) { - break; + let free = |sp: Pos2| !placed.iter().any(|(_, q, _)| q.distance(sp) < GLYPH + 1.0); + let start = |s: egui::Vec2| base + s * (GLYPH * 0.9); + let side = if on_dim(start(side)) && !on_dim(start(-side)) { -side } else { side }; + // Step along until clear of other glyphs, preferring a spot clear of dimension + // values too. + let settle = |avoid_dims: bool| { + let mut sp = start(side); + for _ in 0..12 { + if free(sp) && !(avoid_dims && on_dim(sp)) { + return Some(sp); + } + sp += step * (GLYPH + 2.0); } - sp += step * (GLYPH + 2.0); - } + None + }; + let sp = settle(true).or_else(|| settle(false)).unwrap_or_else(|| start(side) + step * (12.0 * (GLYPH + 2.0))); placed.push((c.id.clone(), sp, base)); } } diff --git a/crates/ui-egui/tests/scenarios/constraint_glyphs_clear_of_dimensions.json b/crates/ui-egui/tests/scenarios/constraint_glyphs_clear_of_dimensions.json new file mode 100644 index 00000000..b879774a --- /dev/null +++ b/crates/ui-egui/tests/scenarios/constraint_glyphs_clear_of_dimensions.json @@ -0,0 +1,9 @@ +[ + {"call": "engine.execute", "params": {"command": "sketch.create", "params": {"plane": "XY"}}}, + {"call": "engine.execute", "params": {"command": "sketch.rectangle.two_point", "params": {"p0": [0, 0], "p1": [10, 10]}}}, + {"call": "engine.execute", "params": {"command": "sketch.dimension", "params": {"entities": ["l1"], "value": 10, "text_at": [5, -0.3]}}}, + {"call": "engine.execute", "params": {"command": "sketch.dimension", "params": {"entities": ["l2"], "value": 10, "text_at": [10.3, 5]}}}, + {"call": "ui.view", "params": {"view": "top"}}, + {"call": "ui.view", "params": {"view": "fit"}}, + {"expect": {"glyphs": {"k1": 1, "k2": 1, "k3": 1, "k4": 1}}} +]