UI scenarios: named views (save the view from the browser, change it, restore it); targets written ahead of their features, ignored until they land: sheet metal Fold, configurations, motion study, autosave and recovery. Harness: note steps, named-view and camera checks

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 11:04:35 +00:00
1 parent 829c4a2811
commit 2a54c83bf8
7 files changed
+271 -1

No files matched your search

+18
View File
@@ -18,6 +18,7 @@
//! into `$SOLVECRAFT_SCENARIO_SHOTS` when that is set; `{"debug": 1}`
//! prints the UI state
//! - AT may also be `{"plane": "XY"}`: the middle of an origin plane's square
//! - `{"note": "…"}`: a comment
//! - `{"expect": {…}}`: checks, see [`check`]; `{"until": {…}}` waits (frames) until they pass
//!
//! The same files drive the live app over the control channel for screenshots.
@@ -226,6 +227,8 @@ impl Harness {
self.call("ui.drag", json!({"x0": x0, "y0": y0, "x1": x1, "y1": y1, "steps": steps, "shift": flag("shift")}))
} else if let Some(m) = s.get("call").and_then(Value::as_str) {
self.call(m, s.get("params").cloned().unwrap_or(json!({})))
} else if s.get("note").is_some() {
return Ok(());
} else if let Some(name) = s.get("shot").and_then(Value::as_str) {
// With SOLVECRAFT_SCENARIO_SHOTS set: a render of the model as the camera sees it.
if let Ok(dir) = std::env::var("SOLVECRAFT_SCENARIO_SHOTS") {
@@ -428,6 +431,21 @@ pub fn check(h: &mut Harness, e: &Value) -> Result<(), String> {
return Err(format!("section view: want {v}"));
}
}
"camera_matches" => {
// The camera is at the saved named view (yaw, pitch, target).
let n = v.as_str().unwrap_or_default();
let nv = h.app.session.doc.named_views.iter().find(|x| x.name == n).ok_or_else(|| format!("no named view {n}"))?;
let c = &h.app.cam;
if (c.yaw - nv.yaw).abs() > 1e-6 || (c.pitch - nv.pitch).abs() > 1e-6 || c.target.dist(nv.target) > 1e-6 {
return Err(format!("camera is not at view {n}: yaw {} pitch {}, view yaw {} pitch {}", c.yaw, c.pitch, nv.yaw, nv.pitch));
}
}
"named_views" => {
let n = h.app.session.doc.named_views.len();
if v.as_u64() != Some(n as u64) {
return Err(format!("named views: got {n}, want {v}"));
}
}
"home" => {
if ui["home"] != *v {
return Err(format!("start page open: got {}, want {v}", ui["home"]));
+21 -1
View File
@@ -42,5 +42,25 @@ scenarios!(
s17_toolbox_and_visibility,
s18_3d_sketch_pipe,
s19_canvas_calibrate,
s20_section_with_joint_drive
s20_section_with_joint_drive,
s21_named_views
);
/// Scenarios written ahead of their features (the commands are in solvecraft-params' unpushed
/// work, their dialogs follow): run with `--ignored`, enabled once the features land.
macro_rules! pending {
($($name:ident: $why:literal),* $(,)?) => {
$(#[test]
#[ignore = $why]
fn $name() {
run(stringify!($name));
})*
};
}
pending!(
s22_sheet_metal_fold: "waits for SheetMetalFoldCmd and its dialog",
s23_configurations: "waits for configurations (FusionStartDesignConfigModeCmd, config.activate) and their panel",
s24_motion_study: "waits for FusionMotionStudyCommand and its dialog",
s25_autosave_recovery: "waits for autosave and Recover unsaved design",
);
@@ -0,0 +1,102 @@
[
{
"start": "SketchCreate"
},
{
"click": {
"plane": "XY"
}
},
{
"start": "ShapeRectangleTwoPoint"
},
{
"click": {
"sketch": [
0,
0
]
}
},
{
"click": {
"sketch": [
40,
20
]
}
},
{
"key": "Escape"
},
{
"start": "SketchStop"
},
{
"start": "Extrude"
},
{
"text": "10"
},
{
"key": "Enter"
},
{
"call": "ui.view",
"params": {
"view": "iso"
}
},
{
"call": "ui.menu",
"params": {
"x": 100,
"y": 300,
"target": {
"type": "named_views"
}
}
},
{
"call": "ui.menuPick",
"params": {
"item": "New Named View"
}
},
{
"expect": {
"named_views": 1
}
},
{
"call": "ui.view",
"params": {
"view": "top"
}
},
{
"call": "ui.menu",
"params": {
"x": 100,
"y": 300,
"target": {
"type": "named_view",
"name": "Named View1"
}
}
},
{
"call": "ui.menuPick",
"params": {
"item": "Restore"
}
},
{
"expect": {
"camera_matches": "Named View1"
}
},
{
"shot": "s21_restored"
}
]
@@ -0,0 +1,32 @@
[
{"note": "Target: sheet metal plate folded along a sketch line (SheetMetalFoldCmd, solvecraft-params; dialog by solvecraft-dialogs). Adjust the dialog steps when it lands."},
{"call": "ui.set", "params": {"tab": "SHEET METAL"}},
{"start": "SketchCreate"},
{"click": {"plane": "XY"}},
{"start": "ShapeRectangleTwoPoint"},
{"click": {"sketch": [0, 0]}},
{"click": {"sketch": [60, 40]}},
{"key": "Escape"},
{"start": "SketchStop"},
{"call": "ui.view", "params": {"view": "iso"}},
{"start": "FusionSheetMetalFlangeCommand"},
{"click": {"world": [30, 20, 0]}},
{"key": "Enter"},
{"expect": {"bodies": 1, "volume": 6000}},
{"start": "SketchCreate"},
{"click": {"world": [30, 20, 2.5]}},
{"start": "DrawPolyline"},
{"click": {"sketch": [40, -5]}},
{"click": {"sketch": [40, 45]}},
{"key": "Escape"},
{"key": "Escape"},
{"start": "SketchStop"},
{"start": "SheetMetalFoldCmd"},
{"click": {"world": [40, 20, 2.5]}},
{"key": "Enter"},
{"expect": {"bodies": 1, "errors": 0, "dialog": null, "volume": [6000, 1e-3]}},
{"shot": "s22_folded"},
{"start": "FusionSheetMetalFlatPatternCmd"},
{"widget": "Export DXF…"},
{"expect": {"file": "Untitled-Body1.dxf"}}
]
@@ -0,0 +1,32 @@
[
{"note": "Target: a configuration table switches the design (FusionStartDesignConfigModeCmd, config.row/column/activate by solvecraft-params; panel by solvecraft-dialogs). Adjust the panel steps when it lands."},
{"start": "SketchCreate"},
{"click": {"plane": "XY"}},
{"start": "ShapeRectangleTwoPoint"},
{"click": {"sketch": [0, 0]}},
{"click": {"sketch": [40, 20]}},
{"key": "Escape"},
{"start": "SketchDimension"},
{"click": {"sketch": [20, 0]}},
{"text": "40"},
{"key": "Enter"},
{"key": "Escape"},
{"start": "SketchStop"},
{"start": "Extrude"},
{"text": "10"},
{"key": "Enter"},
{"expect": {"volume": 8000}},
{"start": "FusionStartDesignConfigModeCmd"},
{"widget": "Add Column"},
{"widget": "d1"},
{"widget": "Add Row"},
{"widget": "40 mm", "near": "Configuration2"},
{"key": "A", "cmd": true},
{"text": "60 mm"},
{"key": "Enter"},
{"widget": "Activate", "near": "Configuration2"},
{"expect": {"param": {"d1": 60}, "volume": 12000, "errors": 0}},
{"widget": "Activate", "near": "Default"},
{"expect": {"param": {"d1": 40}, "volume": 8000}},
{"shot": "s23_configs"}
]
@@ -0,0 +1,49 @@
[
{"note": "Target: a motion study keys a revolute joint and plays it (FusionMotionStudyCommand, motion.play by solvecraft-params; dialog by solvecraft-dialogs). Starts like s07; adjust the study steps when the dialog lands."},
{"start": "FusionCreateNewComponentCommand"},
{"key": "Enter"},
{"start": "SketchCreate"},
{"click": {"plane": "XY"}},
{"start": "ShapeRectangleTwoPoint"},
{"click": {"sketch": [0, 0]}},
{"click": {"sketch": [20, 20]}},
{"key": "Escape"},
{"start": "SketchStop"},
{"call": "ui.view", "params": {"view": "iso"}},
{"start": "Extrude"},
{"text": "10"},
{"key": "Enter"},
{"call": "ui.menu", "params": {"x": 100, "y": 170, "target": {"type": "component", "id": 0}}},
{"call": "ui.menuPick", "params": {"item": "Activate"}},
{"start": "FusionCreateNewComponentCommand"},
{"key": "Enter"},
{"start": "SketchCreate"},
{"click": {"plane": "XZ"}},
{"start": "ShapeRectangleTwoPoint"},
{"click": {"sketch": [30, 0]}},
{"click": {"sketch": [50, 10]}},
{"key": "Escape"},
{"start": "SketchStop"},
{"call": "ui.view", "params": {"view": "iso"}},
{"start": "Extrude"},
{"click": {"world": [40, 0, 5]}},
{"text": "20"},
{"key": "Enter"},
{"start": "JointAssembleCmdNew"},
{"click": {"world": [10, 10, 10]}},
{"click": {"world": [40, -20, 5]}},
{"widget": "Rigid"},
{"widget": "Revolute"},
{"widget": "OK"},
{"start": "FusionMotionStudyCommand"},
{"widget": "Add Key"},
{"widget": "0 deg", "near": "Value"},
{"key": "A", "cmd": true},
{"text": "90"},
{"key": "Enter"},
{"widget": "OK"},
{"expect": {"dialog": null, "errors": 0}},
{"call": "engine.execute", "params": {"command": "motion.play", "params": {"study": 1, "step": 100}}},
{"expect": {"query": {"command": "joint.list", "path": ["joints", 0, "values", 0, "value"], "value": 90}}},
{"shot": "s24_motion_end"}
]
@@ -0,0 +1,17 @@
[
{"note": "Target: unsaved work is autosaved and offered back after a crash (autosave, Recover unsaved design?, doc.recovery_list/doc.recover by solvecraft-params). The harness needs a 'restart' step (drop the app without saving, start a new one on the same recovery folder) once the recovery folder can be pointed at the scenario's folder."},
{"start": "SketchCreate"},
{"click": {"plane": "XY"}},
{"start": "ShapeRectangleTwoPoint"},
{"click": {"sketch": [0, 0]}},
{"click": {"sketch": [30, 20]}},
{"key": "Escape"},
{"start": "SketchStop"},
{"start": "Extrude"},
{"text": "10"},
{"key": "Enter"},
{"expect": {"bodies": 1, "volume": 6000}},
{"restart": "crash"},
{"widget": "Recover"},
{"expect": {"bodies": 1, "volume": 6000, "errors": 0}}
]