Integrate current main before app icon merge

This commit is contained in:
bherbruck committed 2026-10-11 16:48:45 -04:00
commit 6ce472c29e
6 files changed
+68 -11

No files matched your search

+6 -2
View File
@@ -1738,14 +1738,18 @@ fn construction(s: &mut Session, p: &Value) -> Result<Value> {
let cs: Vec<usize> =
ids.iter().map(|id| sk.curve_index(id).ok_or_else(|| bad(cmd, format!("unknown curve `{id}`")))).collect::<Result<_>>()?;
// The selection switches together (a slot or polygon as a whole): to construction
// unless all of it already is. Projected curves may switch too.
// unless all of it already is. Projected curves may switch too. A centerline that
// becomes construction stops being a centerline (one line type at a time).
let to = want.unwrap_or_else(|| !cs.iter().all(|c| sk.curves.get(*c).is_some_and(|c| c.construction)));
let mut n = 0;
for c in cs {
if let Some(cu) = sk.curves.get_mut(c)
&& cu.construction != to
&& (cu.construction != to || to && cu.centerline)
{
cu.construction = to;
if to {
cu.centerline = false;
}
n += 1;
}
}
+15
View File
@@ -237,6 +237,21 @@ fn construction_toggle_takes_shapes_out_of_profiles_and_back() {
assert!(s.execute("sketch.construction", &json!({"curves": ["nope"]})).is_err());
}
#[test]
fn centerline_can_become_construction_directly() {
let mut s = new_sketch();
let l = curve_ids(&run(&mut s, "sketch.line", json!({"points": [[0, 0], [20, 0]]})));
run(&mut s, "sketch.centerline", json!({ "curves": l, "value": true }));
let r = run(&mut s, "sketch.construction", json!({ "curves": l, "value": true }));
assert_eq!(r["changed"], 1);
let sk = sketch(&s);
assert!(sk.curves.iter().filter(|c| l.contains(&c.id)).all(|c| c.construction && !c.centerline));
// And back to a centerline.
run(&mut s, "sketch.centerline", json!({ "curves": l, "value": true }));
let sk = sketch(&s);
assert!(sk.curves.iter().filter(|c| l.contains(&c.id)).all(|c| c.centerline && !c.construction));
}
#[test]
fn projected_geometry_can_become_construction() {
let mut s = Session::default();
+5 -1
View File
@@ -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" => {
+22 -7
View File
@@ -1000,7 +1000,13 @@ fn glyphs(app: &SolveApp, painter: &egui::Painter, proj: &Proj, hover: Option<Po
let tk = crate::theme::Tokens::get();
let picked = PICKED.with(|p| p.borrow().clone());
// Where each glyph goes: beside its entity (to its left, away from the line), every entity
// of a two-entity constraint getting one; glyphs that would overlap step along.
// of a two-entity constraint getting one; glyphs that would overlap step along. A glyph
// that would sit on a dimension's value goes to the other side of its line.
let dim_texts: Vec<egui::Rect> = 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<Po
None => 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));
}
}
+11 -1
View File
@@ -414,6 +414,14 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
}
// Panels.
let tab = app.ui.tab.clone();
// The command whose tool or dialog is open right now, shown pressed in the toolbar.
let running: Option<String> = match (&app.tool, &app.dialog) {
(Some(tool), _) => Some(tool.cmd.to_string()),
// Create Sketch never lands in last_command (it is not repeatable).
(None, Some(d)) if matches!(d.kind, crate::dialogs::Kind::Sketch) => Some("sketch.create".to_string()),
(None, Some(_)) => app.last_command.as_ref().map(|(id, _)| id.clone()),
_ => None,
};
let mut px = r.left() + 112.0;
let top = r.top() + 26.0;
let mut drop: Option<(&'static str, &'static str, Option<&'static str>)> = None;
@@ -463,7 +471,9 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
if resp.secondary_clicked() {
menu = Some((resp.interact_pointer_pos().unwrap_or(br.left_bottom()), c.id));
}
if resp.hovered() && info.enabled {
if running.as_deref() == Some(c.id) {
painter.rect_filled(br, 4.0, t.border.gamma_multiply(0.7));
} else if resp.hovered() && info.enabled {
painter.rect_filled(br, 4.0, t.hover);
}
let (ink, fill, acc) = if info.enabled {
@@ -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}}}
]