Export a design's timeline as a replayable command script (#1045)

timeline.export_script (and solidcraft-cli script) writes a design's history as a command script that solidcraft-cli eval replays into the same design, so a bug that only shows on one design becomes a one-file repro. timeline.add restores one stored feature with its id, names and references, through the same checks a design file gets when it opens.

Closes #886. SychPL's #1078 approached the same issue independently; thanks to both.

Co-authored-by: SychPL <[email protected]>
This commit is contained in:
Lasse LianandSychPL authored and GitHub committed 2026-10-14 09:06:24 -04:00
1 parent 08b6a8fe0f
commit ec8e739aad
17 files changed
+755 -6

No files matched your search

+21
View File
@@ -3,6 +3,7 @@
//! ```text
//! solidcraft-cli run <script.json|design.solidcraft|part.step|mesh.3mf> [--in design|part.step|mesh.3mf] [--out FILE]... [--save FILE] [--quiet] [--allow-errors]
//! solidcraft-cli eval <script.json|design.solidcraft|part.step> (alias: inspect) measurements as JSON
//! solidcraft-cli script <design.solidcraft> [--out script.json] the design's history as a command script
//! solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] loading timings as JSON lines
//! solidcraft-cli snapshot <script|design> --out shot.png [--width W] [--height H] [--view iso|front|top|…]
//! solidcraft-cli exec <command> [json-params] run one command on an empty design
@@ -37,6 +38,8 @@ const USAGE: &str = "usage:
solidcraft-cli run --in <design.solidcraft|part.step> [--out FILE]...
(a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)
solidcraft-cli eval <script.json|design.solidcraft|part.step> (alias: inspect; or --in FILE)
solidcraft-cli script <design.solidcraft|script.json> [--out script.json]
(the design's history as a command script that eval and run replay)
solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only]
solidcraft-cli snapshot <script|design> --out shot.png [--width W] [--height H] [--view iso|front|back|top|bottom|left|right]
solidcraft-cli exec <command> [json-params]
@@ -55,6 +58,7 @@ fn main() -> ExitCode {
let r = match args.first().map(String::as_str) {
Some("run") => cmd_run(&args[1..]),
Some("eval" | "inspect") => cmd_eval(&args[1..]),
Some("script") => cmd_script(&args[1..]),
Some("snapshot") => cmd_snapshot(&args[1..]),
Some("exec") => cmd_exec(&args[1..]),
Some("commands") => {
@@ -242,6 +246,23 @@ fn cmd_eval(args: &[String]) -> Result<(), String> {
Ok(())
}
/// A design's history as a command script (`timeline.export_script`): printed, or written to
/// `--out`; what the script leaves out goes to stderr.
fn cmd_script(args: &[String]) -> Result<(), String> {
let path = positional(args).ok_or_else(|| USAGE.to_string())?;
let s = &mut load(path)?;
let out = flag(args, "--out");
let r = s.execute("timeline.export_script", &json!({"path": out})).map_err(|e| e.to_string())?;
for what in r["omitted"].as_array().into_iter().flatten().filter_map(Value::as_str) {
eprintln!("note: the script leaves out {what}");
}
match out {
Some(out) => eprintln!("{out}: {} steps", r["steps"]),
None => println!("{}", pretty(&r["script"])),
}
Ok(())
}
fn cmd_snapshot(args: &[String]) -> Result<(), String> {
let path = args.first().ok_or_else(|| USAGE.to_string())?;
let out = flag(args, "--out").ok_or("snapshot needs --out FILE.png")?;