UI scenarios: the user's four open bugs as ignored targets (s31 sketch on a face projects its loops, s32 a cut in the active component leaves others alone, s33 Move opens at 0 and its X arrow moves along X, s34 moving a component carries its sketches); Fold, configurations, motion study and autosave/recovery enabled against the landed features (s22–s25); retests: fillets follow their edge after a sketch edit (s06 volume, s15 widens the sample plate), one canvas panel (s19); s30 patterns a hole as a feature (ignored: one hole results). Harness: manipulator handles published by name (scenario::publish_handle; ui.at {handle}; timeline items as timeline:<name>), restart after a crash or close, autosave into the scenario's folder, exact widget text before menu-label prefixes, checks for sketch curves, body volume and sketch points in the world
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
0a1b6f0fff
commit
59b59a83e6
18 files changed
+1328
-117
No files matched your search
@@ -254,6 +254,7 @@ fn drag_arrow(
|
|||||||
let min = 40.0 * 2.0 * half_height / f64::from(proj.rect.height().max(1.0));
|
let min = 40.0 * 2.0 * half_height / f64::from(proj.rect.height().max(1.0));
|
||||||
let shown = if l.abs() < min { min * if l < 0.0 { -1.0 } else { 1.0 } } else { l };
|
let shown = if l.abs() < min { min * if l < 0.0 { -1.0 } else { 1.0 } } else { l };
|
||||||
let ts = proj.to_screen(base + n * shown)?;
|
let ts = proj.to_screen(base + n * shown)?;
|
||||||
|
crate::scenario::publish_handle(if id == egui::Id::new("sc_manipulator") { "arrow" } else { "arrow2" }, ts);
|
||||||
let r = ui.interact(egui::Rect::from_center_size(ts, vec2(20.0, 20.0)), id, egui::Sense::drag());
|
let r = ui.interact(egui::Rect::from_center_size(ts, vec2(20.0, 20.0)), id, egui::Sense::drag());
|
||||||
let hot = r.hovered() || r.dragged();
|
let hot = r.hovered() || r.dragged();
|
||||||
let col = if hot { t.accent } else { t.manipulator };
|
let col = if hot { t.accent } else { t.manipulator };
|
||||||
|
|||||||
@@ -251,6 +251,12 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
|||||||
"ui.at" => {
|
"ui.at" => {
|
||||||
// A scenario point: {world: [x,y,z]} | {sketch: [x,y]} | {plane: "XY"} (the middle of an
|
// A scenario point: {world: [x,y,z]} | {sketch: [x,y]} | {plane: "XY"} (the middle of an
|
||||||
// origin plane's square) → screen point.
|
// origin plane's square) → screen point.
|
||||||
|
if let Some(h) = p.get("handle").and_then(Value::as_str) {
|
||||||
|
return match crate::scenario::handle_at(h) {
|
||||||
|
Some(q) => ok(json!([q.x, q.y])),
|
||||||
|
None => err(format!("handle {h} is not shown")),
|
||||||
|
};
|
||||||
|
}
|
||||||
if let Some(d) = p.get("dimension").and_then(Value::as_str) {
|
if let Some(d) = p.get("dimension").and_then(Value::as_str) {
|
||||||
return match crate::dim_view::text_at(d) {
|
return match crate::dim_view::text_at(d) {
|
||||||
Some(q) => ok(json!([q.x, q.y])),
|
Some(q) => ok(json!([q.x, q.y])),
|
||||||
|
|||||||
@@ -683,6 +683,7 @@ impl SolveApp {
|
|||||||
/// Lay out the whole window.
|
/// Lay out the whole window.
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||||
let t0 = now_ms();
|
let t0 = now_ms();
|
||||||
|
scenario::clear_handles();
|
||||||
toolbar::app_bar(self, ui);
|
toolbar::app_bar(self, ui);
|
||||||
documents::prompt(self, ui.ctx());
|
documents::prompt(self, ui.ctx());
|
||||||
if self.home.open {
|
if self.home.open {
|
||||||
|
|||||||
+114
-12
@@ -17,7 +17,10 @@
|
|||||||
//! - `{"shot": "name"}`: a screenshot when run in a window; headless, a render of the model
|
//! - `{"shot": "name"}`: a screenshot when run in a window; headless, a render of the model
|
||||||
//! into `$SOLVECRAFT_SCENARIO_SHOTS` when that is set; `{"debug": 1}`
|
//! into `$SOLVECRAFT_SCENARIO_SHOTS` when that is set; `{"debug": 1}`
|
||||||
//! prints the UI state
|
//! prints the UI state
|
||||||
//! - AT may also be `{"plane": "XY"}`: the middle of an origin plane's square
|
//! - AT may also be `{"plane": "XY"}` (the middle of an origin plane's square), `{"axis": "Z"}`,
|
||||||
|
//! `{"dimension": "d1"}` or `{"handle": "arrow"}` (a manipulator handle: see [`publish_handle`])
|
||||||
|
//! - `{"autosave": true}`: autosave into the scenario's recovery folder; `{"restart": "crash" |
|
||||||
|
//! "close"}`: the app dies (or closes) and a new one starts on the same folders
|
||||||
//! - `{"note": "…"}`: a comment
|
//! - `{"note": "…"}`: a comment
|
||||||
//! - `{"expect": {…}}`: checks, see [`check`]; `{"until": {…}}` waits (frames) until they pass
|
//! - `{"expect": {…}}`: checks, see [`check`]; `{"until": {…}}` waits (frames) until they pass
|
||||||
//!
|
//!
|
||||||
@@ -33,6 +36,32 @@ use solvecraft_engine::Session;
|
|||||||
use crate::control::{ControlRequest, Outcome, handle};
|
use crate::control::{ControlRequest, Outcome, handle};
|
||||||
use crate::{Services, SolveApp};
|
use crate::{Services, SolveApp};
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
/// Drag handles drawn this frame (name, screen point): manipulators publish theirs so
|
||||||
|
/// scenarios (and the control channel's `ui.at {handle}`) can grab them.
|
||||||
|
static HANDLES: RefCell<Vec<(String, egui::Pos2)>> = const { RefCell::new(Vec::new()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A manipulator's handle drawn this frame at `at` (screen), by name: "arrow" (a dialog's value
|
||||||
|
/// arrow), "triad_x"… Call it from the code that draws the handle, every frame it shows.
|
||||||
|
pub fn publish_handle(name: &str, at: egui::Pos2) {
|
||||||
|
HANDLES.with(|h| {
|
||||||
|
let mut h = h.borrow_mut();
|
||||||
|
h.retain(|(n, _)| n != name);
|
||||||
|
h.push((name.to_string(), at));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where a handle was drawn last frame.
|
||||||
|
pub fn handle_at(name: &str) -> Option<egui::Pos2> {
|
||||||
|
HANDLES.with(|h| h.borrow().iter().find(|(n, _)| n == name).map(|x| x.1))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Forget last frame's handles (called as a frame starts).
|
||||||
|
pub fn clear_handles() {
|
||||||
|
HANDLES.with(|h| h.borrow_mut().clear());
|
||||||
|
}
|
||||||
|
|
||||||
/// A fresh folder for one scenario's files.
|
/// A fresh folder for one scenario's files.
|
||||||
fn scratch_dir() -> PathBuf {
|
fn scratch_dir() -> PathBuf {
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
@@ -83,17 +112,26 @@ impl Harness {
|
|||||||
time: 0.0,
|
time: 0.0,
|
||||||
size: egui::vec2(1600.0, 1000.0),
|
size: egui::vec2(1600.0, 1000.0),
|
||||||
};
|
};
|
||||||
// File pickers answer like a person would: save into the scenario's folder, open the
|
h.install();
|
||||||
// file the scenario chose.
|
|
||||||
let dir = h.dir.clone();
|
|
||||||
h.app.services.pick_save = Some(Box::new(move |name, _| Some(dir.join(name).to_string_lossy().into_owned())));
|
|
||||||
let next = h.next_file.clone();
|
|
||||||
h.app.services.pick_open = Some(Box::new(move || next.borrow_mut().take()));
|
|
||||||
h.ctx.enable_accesskit();
|
h.ctx.enable_accesskit();
|
||||||
h.frames(3);
|
h.frames(3);
|
||||||
h
|
h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// File pickers answer like a person would: save into the scenario's folder, open the file
|
||||||
|
/// the scenario chose.
|
||||||
|
fn install(&mut self) {
|
||||||
|
let dir = self.dir.clone();
|
||||||
|
self.app.services.pick_save = Some(Box::new(move |name, _| Some(dir.join(name).to_string_lossy().into_owned())));
|
||||||
|
let next = self.next_file.clone();
|
||||||
|
self.app.services.pick_open = Some(Box::new(move || next.borrow_mut().take()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Autosave into the scenario's recovery folder (as the desktop app does into the user's).
|
||||||
|
fn start_autosave(&mut self) {
|
||||||
|
crate::recovery_ui::start(&mut self.app, &self.dir.join("recovery"), std::time::Duration::ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
/// Lay out and draw one frame (feeding queued pointer and key events).
|
/// Lay out and draw one frame (feeding queued pointer and key events).
|
||||||
pub fn frame(&mut self) {
|
pub fn frame(&mut self) {
|
||||||
self.time += 1.0 / 60.0;
|
self.time += 1.0 / 60.0;
|
||||||
@@ -161,16 +199,21 @@ impl Harness {
|
|||||||
/// Centres of the visible widgets whose label or value is `text`, last drawn first.
|
/// Centres of the visible widgets whose label or value is `text`, last drawn first.
|
||||||
fn widgets(&self, text: &str) -> Vec<[f64; 2]> {
|
fn widgets(&self, text: &str) -> Vec<[f64; 2]> {
|
||||||
let Some(tree) = self.output.accesskit_update.as_ref() else { return Vec::new() };
|
let Some(tree) = self.output.accesskit_update.as_ref() else { return Vec::new() };
|
||||||
|
let centre =
|
||||||
|
|n: &egui::accesskit::Node| n.bounds().filter(|b| b.x1 > b.x0 && b.y1 > b.y0).map(|b| [(b.x0 + b.x1) / 2.0, (b.y0 + b.y1) / 2.0]);
|
||||||
|
let exact: Vec<[f64; 2]> =
|
||||||
|
tree.nodes.iter().rev().filter(|(_, n)| n.label() == Some(text) || n.value() == Some(text)).filter_map(|(_, n)| centre(n)).collect();
|
||||||
|
if !exact.is_empty() {
|
||||||
|
return exact;
|
||||||
|
}
|
||||||
|
// A menu button's label ends with its shortcut ("Save Ctrl+S").
|
||||||
tree.nodes
|
tree.nodes
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
// A menu button's label ends with its shortcut ("Save Ctrl+S").
|
|
||||||
.filter(|(_, n)| {
|
.filter(|(_, n)| {
|
||||||
n.label().is_some_and(|l| l == text || l.strip_prefix(text).is_some_and(|r| r.starts_with(' '))) || n.value() == Some(text)
|
n.role() == egui::accesskit::Role::Button && n.label().is_some_and(|l| l.strip_prefix(text).is_some_and(|r| r.starts_with(' ')))
|
||||||
})
|
})
|
||||||
.filter_map(|(_, n)| n.bounds())
|
.filter_map(|(_, n)| centre(n))
|
||||||
.filter(|b| b.x1 > b.x0 && b.y1 > b.y0)
|
|
||||||
.map(|b| [(b.x0 + b.x1) / 2.0, (b.y0 + b.y1) / 2.0])
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,6 +279,23 @@ impl Harness {
|
|||||||
self.call("ui.render", json!({"path": path.to_string_lossy(), "width": 1000, "height": 700}));
|
self.call("ui.render", json!({"path": path.to_string_lossy(), "width": 1000, "height": 700}));
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
} else if s.get("autosave").is_some() {
|
||||||
|
self.start_autosave();
|
||||||
|
return Ok(());
|
||||||
|
} else if let Some(how) = s.get("restart").and_then(Value::as_str) {
|
||||||
|
// "crash": the app dies (its autosave lock goes with it, the entry stays);
|
||||||
|
// "close": it closes normally. Then a new app starts on the same folders.
|
||||||
|
let mut old = std::mem::replace(&mut self.app, SolveApp::new(Session::default(), Services::default()));
|
||||||
|
if how == "close" {
|
||||||
|
crate::recovery_ui::close(&mut old);
|
||||||
|
}
|
||||||
|
drop(old);
|
||||||
|
self.ctx = egui::Context::default();
|
||||||
|
self.ctx.enable_accesskit();
|
||||||
|
self.install();
|
||||||
|
self.start_autosave();
|
||||||
|
self.frames(3);
|
||||||
|
return Ok(());
|
||||||
} else if let Some(f) = s.get("make_image").and_then(Value::as_str) {
|
} else if let Some(f) = s.get("make_image").and_then(Value::as_str) {
|
||||||
// A picture to insert (a 200 x 100 checkerboard PNG in the scenario's folder).
|
// A picture to insert (a 200 x 100 checkerboard PNG in the scenario's folder).
|
||||||
let img = image::RgbImage::from_fn(200, 100, |x, y| {
|
let img = image::RgbImage::from_fn(200, 100, |x, y| {
|
||||||
@@ -461,6 +521,48 @@ pub fn check(h: &mut Harness, e: &Value) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"sketch_curves" => {
|
||||||
|
// {sketch?, linked?, own?}: curves projected (linked) and drawn in a sketch.
|
||||||
|
let mut params = json!({});
|
||||||
|
if let Some(n) = v.get("sketch") {
|
||||||
|
params["sketch"] = n.clone();
|
||||||
|
}
|
||||||
|
let si = h.call("engine.execute", json!({"command": "sketch.inspect", "params": params}))["result"].clone();
|
||||||
|
let curves = si["curves"].as_array().cloned().unwrap_or_default();
|
||||||
|
let linked = curves.iter().filter(|c| !c["link"].is_null()).count();
|
||||||
|
let own = curves.len() - linked;
|
||||||
|
for (k, got) in [("linked", linked), ("own", own)] {
|
||||||
|
if let Some(w) = v.get(k).and_then(Value::as_u64)
|
||||||
|
&& w != got as u64
|
||||||
|
{
|
||||||
|
return Err(format!("sketch curves {k}: got {got}, want {w}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"body_volume" => {
|
||||||
|
// {body, volume}: one body's volume.
|
||||||
|
let name = v["body"].as_str().unwrap_or_default();
|
||||||
|
let m = h.call("engine.execute", json!({"command": "MeasureCommand", "params": {"bodies": [name]}}))["result"].clone();
|
||||||
|
let got = m["bodies"][0]["volume_mm3"].as_f64().unwrap_or(f64::NAN);
|
||||||
|
approx(got, &v["volume"], &format!("{name} volume"))?;
|
||||||
|
}
|
||||||
|
"sketch_world" => {
|
||||||
|
// {sketch, point, at: [x,y,z]}: where a sketch point is in the world (±1e-3 mm).
|
||||||
|
let si = h.call("engine.execute", json!({"command": "sketch.inspect", "params": {"sketch": v["sketch"]}}))["result"].clone();
|
||||||
|
let p = si["points"]
|
||||||
|
.as_array()
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.find(|p| p["id"] == v["point"])
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| format!("no point {}", v["point"]))?;
|
||||||
|
for i in 0..3 {
|
||||||
|
let (g, w) = (p["world"][i].as_f64().unwrap_or(f64::NAN), v["at"][i].as_f64().unwrap_or(f64::NAN));
|
||||||
|
if (g - w).abs() > 1e-3 {
|
||||||
|
return Err(format!("{} of {} is at {}, want {}", v["point"], v["sketch"], p["world"], v["at"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
"home" => {
|
"home" => {
|
||||||
if ui["home"] != *v {
|
if ui["home"] != *v {
|
||||||
return Err(format!("start page open: got {}, want {v}", ui["home"]));
|
return Err(format!("start page open: got {}, want {v}", ui["home"]));
|
||||||
|
|||||||
@@ -346,6 +346,9 @@ pub fn show(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
|||||||
if !active(app) {
|
if !active(app) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
for (name, tip) in handle_points(app, proj) {
|
||||||
|
crate::scenario::publish_handle(&format!("triad_{name}"), tip);
|
||||||
|
}
|
||||||
for (axis, tip, base) in handles(app, proj) {
|
for (axis, tip, base) in handles(app, proj) {
|
||||||
match axis {
|
match axis {
|
||||||
Some(a) => {
|
Some(a) => {
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ pub fn timeline(app: &mut SolveApp, ui: &mut egui::Ui) {
|
|||||||
}
|
}
|
||||||
xs.push(x);
|
xs.push(x);
|
||||||
let br = Rect::from_min_size(pos2(x, r.top() + 6.0), vec2(28.0, 28.0));
|
let br = Rect::from_min_size(pos2(x, r.top() + 6.0), vec2(28.0, 28.0));
|
||||||
|
crate::scenario::publish_handle(&format!("timeline:{name}"), br.center());
|
||||||
let resp = ui.interact(br, ui.id().with(("feat", *id)), Sense::click_and_drag());
|
let resp = ui.interact(br, ui.id().with(("feat", *id)), Sense::click_and_drag());
|
||||||
let res = app.session.model.result(*id);
|
let res = app.session.model.result(*id);
|
||||||
let err = res.and_then(|r| r.error.clone());
|
let err = res.and_then(|r| r.error.clone());
|
||||||
|
|||||||
@@ -47,11 +47,12 @@ scenarios!(
|
|||||||
s26_drive_joint_typed,
|
s26_drive_joint_typed,
|
||||||
s22_sheet_metal_fold,
|
s22_sheet_metal_fold,
|
||||||
s23_configurations,
|
s23_configurations,
|
||||||
s24_motion_study
|
s24_motion_study,
|
||||||
|
s25_autosave_recovery
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Scenarios written ahead of their features (the commands are in solvecraft-params' unpushed
|
/// Scenarios written ahead of their fixes or features (the user's open bugs): run with
|
||||||
/// work, their dialogs follow): run with `--ignored`, enabled once the features land.
|
/// `--ignored`; each moves to the list above once its fix lands.
|
||||||
macro_rules! pending {
|
macro_rules! pending {
|
||||||
($($name:ident: $why:literal),* $(,)?) => {
|
($($name:ident: $why:literal),* $(,)?) => {
|
||||||
$(#[test]
|
$(#[test]
|
||||||
@@ -63,5 +64,9 @@ macro_rules! pending {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pending!(
|
pending!(
|
||||||
s25_autosave_recovery: "waits for autosave and Recover unsaved design",
|
s31_sketch_on_face_projects: "user bug: a sketch on a face projects its loops as reference (solvecraft-sketch)",
|
||||||
|
s32_cut_in_active_component: "user bug: a cut in the active component leaves others alone (solvecraft-params)",
|
||||||
|
s33_move_x_arrow: "user bug: Move opens at 0 and its X arrow moves along X only (solvecraft-ui)",
|
||||||
|
s34_component_move_carries_sketches: "user bug: moving a component carries its sketches (solvecraft-repo)",
|
||||||
|
s30_pattern_a_hole: "a hole patterned as a feature gives one hole, not three (solvecraft-ui)",
|
||||||
);
|
);
|
||||||
@@ -136,7 +136,11 @@
|
|||||||
"expect": {
|
"expect": {
|
||||||
"sketching": false,
|
"sketching": false,
|
||||||
"errors": 0,
|
"errors": 0,
|
||||||
"bodies": 1
|
"bodies": 1,
|
||||||
|
"volume": [
|
||||||
|
17974.25,
|
||||||
|
1e-05
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
{"expect": {"errors": 0, "bodies": 1, "name": "Sample Plate", "param": {"width": 80}}},
|
{"expect": {"errors": 0, "bodies": 1, "name": "Sample Plate", "param": {"width": 80}}},
|
||||||
{"shot": "s15_sample"},
|
{"shot": "s15_sample"},
|
||||||
{"start": "ChangeParameterCommand"},
|
{"start": "ChangeParameterCommand"},
|
||||||
{"widget": "8 mm"},
|
{"widget": "80 mm"},
|
||||||
{"key": "A", "cmd": true},
|
{"key": "A", "cmd": true},
|
||||||
{"text": "10 mm"},
|
{"text": "100 mm"},
|
||||||
{"key": "Enter"},
|
{"key": "Enter"},
|
||||||
{"widget": "Close window"},
|
{"widget": "Close window"},
|
||||||
{"expect": {"param": {"thickness": 10}, "errors": 0}},
|
{"expect": {"param": {"width": 100}, "errors": 0}},
|
||||||
{"key": "S", "cmd": true},
|
{"key": "S", "cmd": true},
|
||||||
{"expect": {"file": "Sample Plate.solvecraft"}},
|
{"expect": {"file": "Sample Plate.solvecraft"}},
|
||||||
{"key": "N", "cmd": true},
|
{"key": "N", "cmd": true},
|
||||||
@@ -19,6 +19,6 @@
|
|||||||
{"choose_file": "Sample Plate.solvecraft"},
|
{"choose_file": "Sample Plate.solvecraft"},
|
||||||
{"key": "O", "cmd": true},
|
{"key": "O", "cmd": true},
|
||||||
{"debug": 1},
|
{"debug": 1},
|
||||||
{"expect": {"bodies": 1, "errors": 0, "param": {"thickness": 10}}},
|
{"expect": {"bodies": 1, "errors": 0, "param": {"width": 100}}},
|
||||||
{"shot": "s15_reopened"}
|
{"shot": "s15_reopened"}
|
||||||
]
|
]
|
||||||
@@ -1,32 +1,126 @@
|
|||||||
[
|
[
|
||||||
{"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"},
|
"note": "Configurations: a parameter column (d1) in the configuration table switches the design (FusionStartDesignConfigModeCmd)."
|
||||||
{"click": {"plane": "XY"}},
|
},
|
||||||
{"start": "ShapeRectangleTwoPoint"},
|
{
|
||||||
{"click": {"sketch": [0, 0]}},
|
"start": "SketchCreate"
|
||||||
{"click": {"sketch": [40, 20]}},
|
},
|
||||||
{"key": "Escape"},
|
{
|
||||||
{"start": "SketchDimension"},
|
"click": {
|
||||||
{"click": {"sketch": [20, 0]}},
|
"plane": "XY"
|
||||||
{"text": "40"},
|
}
|
||||||
{"key": "Enter"},
|
},
|
||||||
{"key": "Escape"},
|
{
|
||||||
{"start": "SketchStop"},
|
"start": "ShapeRectangleTwoPoint"
|
||||||
{"start": "Extrude"},
|
},
|
||||||
{"text": "10"},
|
{
|
||||||
{"key": "Enter"},
|
"click": {
|
||||||
{"expect": {"volume": 8000}},
|
"sketch": [
|
||||||
{"start": "FusionStartDesignConfigModeCmd"},
|
0,
|
||||||
{"widget": "Add Column"},
|
0
|
||||||
{"widget": "d1"},
|
]
|
||||||
{"widget": "Add Row"},
|
}
|
||||||
{"widget": "40 mm", "near": "Configuration2"},
|
},
|
||||||
{"key": "A", "cmd": true},
|
{
|
||||||
{"text": "60 mm"},
|
"click": {
|
||||||
{"key": "Enter"},
|
"sketch": [
|
||||||
{"widget": "Activate", "near": "Configuration2"},
|
40,
|
||||||
{"expect": {"param": {"d1": 60}, "volume": 12000, "errors": 0}},
|
20
|
||||||
{"widget": "Activate", "near": "Default"},
|
]
|
||||||
{"expect": {"param": {"d1": 40}, "volume": 8000}},
|
}
|
||||||
{"shot": "s23_configs"}
|
},
|
||||||
]
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,49 +1,226 @@
|
|||||||
[
|
[
|
||||||
{"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"},
|
"note": "Motion study: key a revolute joint and play it (FusionMotionStudyCommand, motion.play)."
|
||||||
{"key": "Enter"},
|
},
|
||||||
{"start": "SketchCreate"},
|
{
|
||||||
{"click": {"plane": "XY"}},
|
"start": "FusionCreateNewComponentCommand"
|
||||||
{"start": "ShapeRectangleTwoPoint"},
|
},
|
||||||
{"click": {"sketch": [0, 0]}},
|
{
|
||||||
{"click": {"sketch": [20, 20]}},
|
"key": "Enter"
|
||||||
{"key": "Escape"},
|
},
|
||||||
{"start": "SketchStop"},
|
{
|
||||||
{"call": "ui.view", "params": {"view": "iso"}},
|
"start": "SketchCreate"
|
||||||
{"start": "Extrude"},
|
},
|
||||||
{"text": "10"},
|
{
|
||||||
{"key": "Enter"},
|
"click": {
|
||||||
{"call": "ui.menu", "params": {"x": 100, "y": 170, "target": {"type": "component", "id": 0}}},
|
"plane": "XY"
|
||||||
{"call": "ui.menuPick", "params": {"item": "Activate"}},
|
}
|
||||||
{"start": "FusionCreateNewComponentCommand"},
|
},
|
||||||
{"key": "Enter"},
|
{
|
||||||
{"start": "SketchCreate"},
|
"start": "ShapeRectangleTwoPoint"
|
||||||
{"click": {"plane": "XZ"}},
|
},
|
||||||
{"start": "ShapeRectangleTwoPoint"},
|
{
|
||||||
{"click": {"sketch": [30, 0]}},
|
"click": {
|
||||||
{"click": {"sketch": [50, 10]}},
|
"sketch": [
|
||||||
{"key": "Escape"},
|
0,
|
||||||
{"start": "SketchStop"},
|
0
|
||||||
{"call": "ui.view", "params": {"view": "iso"}},
|
]
|
||||||
{"start": "Extrude"},
|
}
|
||||||
{"click": {"world": [40, 0, 5]}},
|
},
|
||||||
{"text": "20"},
|
{
|
||||||
{"key": "Enter"},
|
"click": {
|
||||||
{"start": "JointAssembleCmdNew"},
|
"sketch": [
|
||||||
{"click": {"world": [10, 10, 10]}},
|
20,
|
||||||
{"click": {"world": [40, -20, 5]}},
|
20
|
||||||
{"widget": "Rigid"},
|
]
|
||||||
{"widget": "Revolute"},
|
}
|
||||||
{"widget": "OK"},
|
},
|
||||||
{"start": "FusionMotionStudyCommand"},
|
{
|
||||||
{"widget": "Add Key"},
|
"key": "Escape"
|
||||||
{"widget": "0 deg", "near": "Value"},
|
},
|
||||||
{"key": "A", "cmd": true},
|
{
|
||||||
{"text": "90"},
|
"start": "SketchStop"
|
||||||
{"key": "Enter"},
|
},
|
||||||
{"widget": "OK"},
|
{
|
||||||
{"expect": {"dialog": null, "errors": 0}},
|
"call": "ui.view",
|
||||||
{"call": "engine.execute", "params": {"command": "motion.play", "params": {"study": 1, "step": 100}}},
|
"params": {
|
||||||
{"expect": {"query": {"command": "joint.list", "path": ["joints", 0, "values", 0, "value"], "value": 90}}},
|
"view": "iso"
|
||||||
{"shot": "s24_motion_end"}
|
}
|
||||||
]
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,17 +1,85 @@
|
|||||||
[
|
[
|
||||||
{"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"},
|
"note": "Autosave and recovery: unsaved work is autosaved; after a crash the next launch offers it back."
|
||||||
{"click": {"plane": "XY"}},
|
},
|
||||||
{"start": "ShapeRectangleTwoPoint"},
|
{
|
||||||
{"click": {"sketch": [0, 0]}},
|
"autosave": true
|
||||||
{"click": {"sketch": [30, 20]}},
|
},
|
||||||
{"key": "Escape"},
|
{
|
||||||
{"start": "SketchStop"},
|
"start": "SketchCreate"
|
||||||
{"start": "Extrude"},
|
},
|
||||||
{"text": "10"},
|
{
|
||||||
{"key": "Enter"},
|
"click": {
|
||||||
{"expect": {"bodies": 1, "volume": 6000}},
|
"plane": "XY"
|
||||||
{"restart": "crash"},
|
}
|
||||||
{"widget": "Recover"},
|
},
|
||||||
{"expect": {"bodies": 1, "volume": 6000, "errors": 0}}
|
{
|
||||||
]
|
"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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"bodies": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widget": "Recover"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"bodies": 1,
|
||||||
|
"volume": 6000,
|
||||||
|
"errors": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"restart": "close"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"bodies": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "a clean close leaves nothing to offer"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"note": "Pattern a hole (Object Type: Features), the beginner workflow box \u2192 hole \u2192 pattern."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchCreate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"plane": "XY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "ShapeRectangleTwoPoint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
50,
|
||||||
|
25
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Escape"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchStop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call": "ui.view",
|
||||||
|
"params": {
|
||||||
|
"view": "iso"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "Extrude"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Enter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"volume": 18750
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "FusionHoleCommand"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"world": [
|
||||||
|
10,
|
||||||
|
12,
|
||||||
|
15
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Enter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"errors": 0,
|
||||||
|
"dialog": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"volume": [
|
||||||
|
18543.855,
|
||||||
|
1e-05
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "PatternRectangular"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widget": "Bodies"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widget": "Features"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"handle": "timeline:Hole1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call": "ui.dialogInput",
|
||||||
|
"params": {
|
||||||
|
"index": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"axis": "X"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widget": "20 mm",
|
||||||
|
"near": "Spacing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "A",
|
||||||
|
"cmd": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "15 mm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Tab"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Enter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"errors": 0,
|
||||||
|
"dialog": null,
|
||||||
|
"volume": [
|
||||||
|
18131.58,
|
||||||
|
0.0001
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shot": "s30_pattern"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"note": "User bug 1 (owner solvecraft-sketch): Create Sketch on a body face projects the face's loops as reference geometry (linked), with no big square of real lines, and the face stays pickable for Project."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchCreate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"plane": "XY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "ShapeRectangleTwoPoint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
40,
|
||||||
|
30
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Escape"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchStop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call": "ui.view",
|
||||||
|
"params": {
|
||||||
|
"view": "iso"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "Extrude"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Enter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchCreate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"world": [
|
||||||
|
20,
|
||||||
|
15,
|
||||||
|
15
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"sketching": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"sketch_curves": {
|
||||||
|
"linked": 4,
|
||||||
|
"own": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shot": "s26_face_sketch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "ProjectNewCmd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"world": [
|
||||||
|
5,
|
||||||
|
5,
|
||||||
|
15
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Escape"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"sketch_curves": {
|
||||||
|
"linked": 8,
|
||||||
|
"own": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "CircleCenterRadius"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
20,
|
||||||
|
15
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
25,
|
||||||
|
15
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Escape"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"sketch_curves": {
|
||||||
|
"own": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchStop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"errors": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"note": "User bug 2 (owner solvecraft-params): with two components, a cut made in the active one leaves the other untouched."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "FusionCreateNewComponentCommand"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Enter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchCreate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"plane": "XY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "ShapeRectangleTwoPoint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
0,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
40,
|
||||||
|
30
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": [
|
||||||
|
42,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
52,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Escape"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchStop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call": "ui.view",
|
||||||
|
"params": {
|
||||||
|
"view": "iso"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "Extrude"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Enter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"bodies": 2,
|
||||||
|
"body_volume": {
|
||||||
|
"body": "Body1",
|
||||||
|
"volume": 8000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"body_volume": {
|
||||||
|
"body": "Body2",
|
||||||
|
"volume": 2000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Component2 (Body2) is active: cut a slot across both boxes, sketched on XY seen from the top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call": "ui.view",
|
||||||
|
"params": {
|
||||||
|
"view": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchCreate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"plane": "XY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"sketching": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call": "ui.view",
|
||||||
|
"params": {
|
||||||
|
"view": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "ShapeRectangleTwoPoint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
30,
|
||||||
|
-15
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
48,
|
||||||
|
25
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Escape"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"sketch_curves": {
|
||||||
|
"own": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchStop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "Extrude"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widget": "New Body"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widget": "Cut"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Enter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"errors": 0,
|
||||||
|
"body_volume": {
|
||||||
|
"body": "Body1",
|
||||||
|
"volume": 8000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"body_volume": {
|
||||||
|
"body": "Body2",
|
||||||
|
"volume": 1100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shot": "s27_cut"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{"note": "User bug 3 (owner solvecraft-ui): Move opens at 0, and dragging its X arrow moves only along X. The X arrow must be published as handle move_x (scenario::publish_handle)."},
|
||||||
|
{"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": 250, "target": {"type": "body", "name": "Body1"}}},
|
||||||
|
{"call": "ui.menuPick", "params": {"item": "Move/Copy"}},
|
||||||
|
{"expect": {"dialog": "z: \"0 mm\""}},
|
||||||
|
{"expect": {"dialog": "x: \"0 mm\""}},
|
||||||
|
{"drag": [{"handle": "move_x"}, {"world": [30, 0, 10]}], "steps": 10},
|
||||||
|
{"key": "Enter"},
|
||||||
|
{"expect": {"errors": 0, "bbox": {"body": "Body1", "min": [null, 0, 0], "max": [null, 20, 10]}}},
|
||||||
|
{"shot": "s28_moved"}
|
||||||
|
]
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"note": "User bug 4 (owner solvecraft-repo): moving a component carries its sketches, and a sketch on a body face follows the body."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "FusionCreateNewComponentCommand"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Enter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchCreate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"plane": "XY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "ShapeRectangleTwoPoint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
30,
|
||||||
|
20
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Escape"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchStop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call": "ui.view",
|
||||||
|
"params": {
|
||||||
|
"view": "iso"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "Extrude"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Enter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchCreate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"world": [
|
||||||
|
15,
|
||||||
|
10,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "CircleCenterRadius"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
15,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"click": {
|
||||||
|
"sketch": [
|
||||||
|
18,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Escape"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": "SketchStop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"sketch_world": {
|
||||||
|
"sketch": "Sketch2",
|
||||||
|
"point": "c1.center",
|
||||||
|
"at": [
|
||||||
|
15,
|
||||||
|
10,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"sketch_world": {
|
||||||
|
"sketch": "Sketch1",
|
||||||
|
"point": "p1",
|
||||||
|
"at": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call": "ui.menu",
|
||||||
|
"params": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 170,
|
||||||
|
"target": {
|
||||||
|
"type": "component",
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call": "ui.menuPick",
|
||||||
|
"params": {
|
||||||
|
"item": "Move/Copy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widget": "0.0 mm",
|
||||||
|
"near": "X distance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "A",
|
||||||
|
"cmd": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Enter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widget": "Capture Position"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"bbox": {
|
||||||
|
"body": "Body1",
|
||||||
|
"min": [
|
||||||
|
50,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"max": [
|
||||||
|
80,
|
||||||
|
20,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"sketch_world": {
|
||||||
|
"sketch": "Sketch1",
|
||||||
|
"point": "p1",
|
||||||
|
"at": [
|
||||||
|
50,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expect": {
|
||||||
|
"sketch_world": {
|
||||||
|
"sketch": "Sketch2",
|
||||||
|
"point": "c1.center",
|
||||||
|
"at": [
|
||||||
|
65,
|
||||||
|
10,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shot": "s29_moved"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -36,7 +36,7 @@ Commands never open dialogs when run this way. `solvecraft-cli commands` prints
|
|||||||
| `ui.triad` | | screen points of the 3D sketch move triad's handles (`x`, `y`, `z`, `plane`) |
|
| `ui.triad` | | screen points of the 3D sketch move triad's handles (`x`, `y`, `z`, `plane`) |
|
||||||
| `ui.sketchToScreen` | `points`: `[[x, y]…]` in active-sketch coordinates | their screen points (null when behind the camera) |
|
| `ui.sketchToScreen` | `points`: `[[x, y]…]` in active-sketch coordinates | their screen points (null when behind the camera) |
|
||||||
| `ui.dialogInput` | `index` | what clicking an input's Select chip does: picks go to that input |
|
| `ui.dialogInput` | `index` | what clicking an input's Select chip does: picks go to that input |
|
||||||
| `ui.at` | `world: [x,y,z]` \| `sketch: [x,y]` \| `plane: "XY"` \| `axis: "Z"` \| `dimension: "d1"` (its value text) | the screen point (a scenario step's target; see `crates/ui-egui/src/scenario.rs`) |
|
| `ui.at` | `world: [x,y,z]` \| `sketch: [x,y]` \| `plane: "XY"` \| `axis: "Z"` \| `dimension: "d1"` (its value text) \| `handle: "arrow"` (a manipulator handle, see `scenario::publish_handle`) | the screen point (a scenario step's target; see `crates/ui-egui/src/scenario.rs`) |
|
||||||
| `ui.editFeature` | `feature` (id or name) | what double-clicking a timeline item does: rolls back to it and opens its dialog filled in (sketches open in sketch mode) |
|
| `ui.editFeature` | `feature` (id or name) | what double-clicking a timeline item does: rolls back to it and opens its dialog filled in (sketches open in sketch mode) |
|
||||||
| `ui.scroll` | `x`, `y`, `delta?` | wheel zoom at the cursor |
|
| `ui.scroll` | `x`, `y`, `delta?` | wheel zoom at the cursor |
|
||||||
| `ui.key` | `key` (egui key name, `Enter`, `Escape`…), `cmd?`, `shift?` | |
|
| `ui.key` | `key` (egui key name, `Enter`, `Escape`…), `cmd?`, `shift?` | |
|
||||||
|
|||||||
Reference in new issue
Block a user