User scenarios in the real UI, headless: scenario::Harness draws the app frame by frame into an egui context and drives it with control-channel input (toolbar starts, clicks on world/sketch/origin-plane/axis points, keys, typing, drags) and checks the model; tests/scenarios/*.json: box with fillets, shell and hole; revolve on XZ; sketch on a face and cut; mirror; delete and undo. Control: ui.at, ui.dialogInput. Fixes found by them: a shape started on an existing point (the origin, a corner) is tied to it; the Dimension tool's value is edited in place with the value selected, so typing replaces it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 09:32:49 +00:00
1 parent 62dbf60eb2
commit d5b8aa0375
12 files changed
+802 -6

No files matched your search

+35 -3
View File
@@ -163,7 +163,8 @@ pub fn on_click(app: &mut SolveApp, proj: &Proj, pos: Pos2) {
if let Ok(v) = app.run("SketchDimension", json!({"entities": tool.picks}))
&& let Some(p) = v["param"].as_str()
{
app.dialog = Some(crate::dialogs::Dialog::edit_param(&app.session, p));
// The value is edited in place, selected so typing replaces it.
crate::dim_view::edit_param(app, p);
}
tool.picks.clear();
}
@@ -237,10 +238,41 @@ fn run_shape(app: &mut SolveApp, t: &Tool) -> Vec<String> {
"DrawPoint" => json!({"point": xy(&p[0])}),
_ => return Vec::new(),
};
app.run(t.cmd, params)
let curves: Vec<String> = app
.run(t.cmd, params)
.ok()
.map(|v| v["curves"].as_array().into_iter().flatten().filter_map(|c| c.as_str().map(str::to_string)).collect())
.unwrap_or_default()
.unwrap_or_default();
tie_snaps(app, t, &curves);
curves
}
/// A click that snapped to an existing point (the origin, a corner) but went in as plain
/// coordinates: tie the new shape's point there to it, as Fusion does.
fn tie_snaps(app: &mut SolveApp, t: &Tool, curves: &[String]) {
let mut ties = Vec::new();
{
let st = app.session.model.state();
let Some(ss) = app.session.active_sketch.and_then(|s| st.sketch(s)) else { return };
let sk = &ss.sketch;
let new_pts: Vec<usize> =
curves.iter().filter_map(|c| sk.curve_index(c)).flat_map(|i| sk.curves.get(i).map(|c| c.kind.point_ids()).unwrap_or_default()).collect();
for (at, r) in &t.pts {
let Some(r) = r else { continue };
let Some(target) = sk.resolve_point(r) else { continue };
if new_pts.contains(&target) {
continue;
}
if let Some(q) = new_pts.iter().copied().find(|q| sk.point(*q).is_some_and(|p| p.dist(*at) < 1e-6))
&& let Some(qid) = sk.points.get(q).map(|p| p.id.clone())
{
ties.push((r.clone(), qid));
}
}
}
for (a, b) in ties {
let _ = app.run("ConstraintCoincident", json!({"a": a, "b": b}));
}
}
fn run_pick(app: &mut SolveApp, t: &Tool) {