← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.13.63\ No newline at end of file+1.13.64\ No newline at end of file
CHANGELOG.md+6
@@ -3,6 +3,12 @@ All notable changes to the Adom Bridge Fusion 360 bridge, newest first. Canonical: https://wiki.adom.inc/adom/fusion-bridge ยท GitHub backup: `adom-inc/fusion-bridge` +## 1.13.64 (2026-11-03)++`fusion_schematic_run` with a long list (#137): the call waits about 100 s, then answers `stillRunning` with a `runId`, and `fusion_schematic_run {runId}` polls it. Adom Bridge gives a call to another machine 120 s, so a long list used to read as `timed_out` with Fusion still working. A list that finishes in time still answers as one call.++`fusion_sch_reset_fields` lists a field already in place under `untouched`, rather than answering with nothing.+ ## 1.13.63 (2026-11-03)  `fusion_schematic_run` has a 20 minute budget in Adom Bridge. It had the 60 second default, so a long list reported `timed_out` while Fusion carried on working (#137).
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.13.63",+  "version": "1.13.64",   "description": "Drive Autodesk Fusion 360 from the cloud: launch Fusion, electronics board layout, design rules, exports (STEP/IGES/STL/3MF/USDZ/OBJ/DXF/DWG/Gerbers/BOM/CPL), fast APS server-indexed cloud search plus browse/recent/file-info/versions, cloud file download/upload and folder creation, and in-app parametric modeling (fusion_run_modeling_script). Never-charge: APS calls are capped to the free tier.",   "homepage": "https://wiki.adom.inc/adom/fusion-bridge",   "author": "Adom Inc.",
contracts/describe.json+1−1
@@ -22675,7 +22675,7 @@         "verified": "bool"       },       "statusVerb": "fusion_get_app_state",-      "summary": "Build a schematic live: a list of complete, non-interactive EAGLE commands run one at a time on the active schematic (ADD 'dev@lib' 'NAME' R0 (x y), VALUE, NET 'name' (pts), LABEL, WIRE, TEXT, LAYER, CHANGE STYLE/SIZE/WIDTH/RATIO, WINDOW FIT), with an optional pause between them so a viewer sees it grow. Anything else is refused before the first command runs. A list of any length: past 300 commands the bridge runs it in batches and answers as one call (batches; on a failure failedAt in your list, batchesDone). ADD is proven against libraryPath on disk first (the deviceset must be there; the library is USEd by the bridge), so the component picker can never open. A .lbr Fusion 2706 cannot ADD from (no line breaks between elements, a byte-order mark, single-quoted attributes, a 3D package naming a footprint the file does not have) is rewritten in place first, keeping its element tree apart from those dead 3D entries (libraryReformatted, library3dEntriesRemoved). The bridge sends USE itself, for the file named in libraryLoaded. Readback: the parts and nets on the sheet from a fresh export.",+      "summary": "Build a schematic live: a list of complete, non-interactive EAGLE commands run one at a time on the active schematic (ADD 'dev@lib' 'NAME' R0 (x y), VALUE, NET 'name' (pts), LABEL, WIRE, TEXT, LAYER, CHANGE STYLE/SIZE/WIDTH/RATIO, WINDOW FIT), with an optional pause between them so a viewer sees it grow. Anything else is refused before the first command runs. A list of any length: past 300 commands the bridge checks all of it first, then runs it in batches (batches; on a failure failedAt in your list, batchesDone). The call answers with the whole reply when it finishes within about 100 s; otherwise it answers stillRunning with a runId, and fusion_schematic_run {runId} polls until stillRunning is false, which is the whole reply. ADD is proven against libraryPath on disk first (the deviceset must be there; the library is USEd by the bridge), so the component picker can never open. A .lbr Fusion 2706 cannot ADD from (no line breaks between elements, a byte-order mark, single-quoted attributes, a 3D package naming a footprint the file does not have) is rewritten in place first, keeping its element tree apart from those dead 3D entries (libraryReformatted, library3dEntriesRemoved). The bridge sends USE itself, for the file named in libraryLoaded. Readback: the parts and nets on the sheet from a fresh export.",       "timeoutSeconds": 120     },     {
dashboard/Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "fusion-dashboard"-version = "1.13.63"+version = "1.13.64" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.13.63"+version = "1.13.64" edition = "2021"  [dependencies]
describe.py+1−1
@@ -662,7 +662,7 @@ _T = [     ("fusion_ripup_nets", "Take whole nets' tracks and vias back up (RIPUP per net, one undo step each) so they can be routed again, wider or another way. Their pours stay. Readback proves no track or via is left on those nets and no pour was lost.",      {"expectDocument":"required exact board name","boardId":"required","expectedRevision":"required","nets":"required [net name]"},      {"success":"bool","verified":"bool","ripped":"[net]","revision":"str"},120,"fusion_routing_state",False,{}),-    ("fusion_schematic_run", "Build a schematic live: a list of complete, non-interactive EAGLE commands run one at a time on the active schematic (ADD 'dev@lib' 'NAME' R0 (x y), VALUE, NET 'name' (pts), LABEL, WIRE, TEXT, LAYER, CHANGE STYLE/SIZE/WIDTH/RATIO, WINDOW FIT), with an optional pause between them so a viewer sees it grow. Anything else is refused before the first command runs. A list of any length: past 300 commands the bridge runs it in batches and answers as one call (batches; on a failure failedAt in your list, batchesDone). ADD is proven against libraryPath on disk first (the deviceset must be there; the library is USEd by the bridge), so the component picker can never open. A .lbr Fusion 2706 cannot ADD from (no line breaks between elements, a byte-order mark, single-quoted attributes, a 3D package naming a footprint the file does not have) is rewritten in place first, keeping its element tree apart from those dead 3D entries (libraryReformatted, library3dEntriesRemoved). The bridge sends USE itself, for the file named in libraryLoaded. Readback: the parts and nets on the sheet from a fresh export.",+    ("fusion_schematic_run", "Build a schematic live: a list of complete, non-interactive EAGLE commands run one at a time on the active schematic (ADD 'dev@lib' 'NAME' R0 (x y), VALUE, NET 'name' (pts), LABEL, WIRE, TEXT, LAYER, CHANGE STYLE/SIZE/WIDTH/RATIO, WINDOW FIT), with an optional pause between them so a viewer sees it grow. Anything else is refused before the first command runs. A list of any length: past 300 commands the bridge checks all of it first, then runs it in batches (batches; on a failure failedAt in your list, batchesDone). The call answers with the whole reply when it finishes within about 100 s; otherwise it answers stillRunning with a runId, and fusion_schematic_run {runId} polls until stillRunning is false, which is the whole reply. ADD is proven against libraryPath on disk first (the deviceset must be there; the library is USEd by the bridge), so the component picker can never open. A .lbr Fusion 2706 cannot ADD from (no line breaks between elements, a byte-order mark, single-quoted attributes, a 3D package naming a footprint the file does not have) is rewritten in place first, keeping its element tree apart from those dead 3D entries (libraryReformatted, library3dEntriesRemoved). The bridge sends USE itself, for the file named in libraryLoaded. Readback: the parts and nets on the sheet from a fresh export.",      {"expectDocument":"optional exact schematic name","libraryPath":"required for ADD: the .lbr on this machine (its file name is the library name in ADD)","commands":"required [command] (1..400)","pauseMs":"optional 0..3000 between commands"},      {"success":"bool","verified":"bool","parts":"[name]","added":"[name]","nets":"{net:[part.pin]}","commands":"int","libraryLoaded":"path","libraryReformatted":"bool","library3dEntriesRemoved":"[name]"},120,"fusion_get_app_state",False,{}),     ("fusion_update_pcb", "The standard's update_pcb. Fusion forward-annotates the board from the schematic as you edit, so there is no change order to run: the preview compares the sheet and the board (addParts on the sheet with no package, removeParts only on the board), and execute has nothing to carry over. allowRemovals with parts to remove is refused: Fusion cannot delete a part from a linked board by command.",
install.sh+1−1
@@ -21,7 +21,7 @@ echo "Installed fusion-bridge skills: $(ls "$PKG_DIR/skills" 2>/dev/null | tr '\ # binary (the three-artifact pattern: bridge zip -> ab, THIS pkg -> container skills + # this download, page media -> wiki). Per the wiki's PKG_VS_RELEASE policy, binaries are # platform-tagged RELEASE assets, never tarball content - the installer fetches them.-DASH_VERSION="1.13.63"+DASH_VERSION="1.13.64" DASH_URL="https://wiki.adom.inc/download/adom/fusion-bridge/${DASH_VERSION}/fusion-dashboard" DEST="" if [ -w /usr/local/bin ]; then DEST=/usr/local/bin
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.13.63",+  "version": "1.13.64",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {
page.json+2−2
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.13.63",+  "version": "1.13.64",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {@@ -152,4 +152,4 @@   "confirmed_user_skills": [     "fusion-design-efficiency"   ]-}\ No newline at end of file+}
rust/Cargo.lock+5−5
@@ -19,7 +19,7 @@ dependencies = [  [[package]] name = "adom-fusion-bridge"-version = "1.13.63"+version = "1.13.64" dependencies = [  "adom-eda-checks",  "base64",@@ -299,7 +299,7 @@ dependencies = [  [[package]] name = "fusion-core"-version = "1.13.63"+version = "1.13.64" dependencies = [  "serde",  "serde_json",@@ -310,7 +310,7 @@ dependencies = [  [[package]] name = "fusion-lbr"-version = "1.13.63"+version = "1.13.64" dependencies = [  "regex",  "roxmltree",@@ -322,14 +322,14 @@ dependencies = [  [[package]] name = "fusion-os-mac"-version = "1.13.63"+version = "1.13.64" dependencies = [  "fusion-core", ]  [[package]] name = "fusion-os-win"-version = "1.13.63"+version = "1.13.64" dependencies = [  "fusion-core",  "windows-sys 0.59.0",
rust/Cargo.toml+1−1
@@ -4,7 +4,7 @@ members = ["core", "lbr", "os-win", "os-mac", "bridge"]  # One version for the whole bridge, in lockstep with BRIDGE_VERSION (tools/release.sh bumps both). [workspace.package]-version = "1.13.63"+version = "1.13.64" edition = "2021" license = "MIT" 
rust/bridge/src/main.rs+1−1
@@ -531,7 +531,7 @@ fn main() {                         let dlg = |r: Value| r;                         let ctx = addin::Ctx { host: &**h, fusion_exe:h.fusion_executable(), short_path:|p|host().short_path(p), state_dir:h.bridge_state_dir(), caller:caller.clone(), pids:h.fusion_processes().iter().map(|p|p.pid).collect(), sidecar:&side, dialogs:&dlg };                         let cmd=v.strip_prefix("fusion_").unwrap_or(v);-                        let mut r=match cmd { "bodies" => bodies::bodies(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "replicate_layout" => replicate::replicate_layout(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "silkscreen_prep" => checks::silkscreen_prep(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "list_installed_libraries" => appverbs::list_installed_libraries(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "update_footprints_from_library" => boardgen::update_footprints_from_library(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "simulate" => spice::simulate(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "jlcpcb_order" => order::jlcpcb_order(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "export_bom" => routing::export_bom(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "save_document" => savedoc::save_document(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "save_to_cloud" => savedoc::save_to_cloud(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), c if sch::owns(c) => sch::call(c,&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), c if docs::owns(c) => docs::call(c,&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), c if writers::owns(c) => writers::call(c,&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), c if checks::owns(c) => checks::call(c,&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), _ => routing::call(cmd,&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)) };+                        let mut r=match cmd { "bodies" => bodies::bodies(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "replicate_layout" => replicate::replicate_layout(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "silkscreen_prep" => checks::silkscreen_prep(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "list_installed_libraries" => appverbs::list_installed_libraries(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "update_footprints_from_library" => boardgen::update_footprints_from_library(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "simulate" => spice::simulate(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "jlcpcb_order" => order::jlcpcb_order(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "export_bom" => routing::export_bom(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "save_document" => savedoc::save_document(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "save_to_cloud" => savedoc::save_to_cloud(&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), c if sch::owns(c) => sch::call(c,&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), c if docs::owns(c) => docs::call(c,&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), c if writers::owns(c) => writers::call(c,&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), c if checks::owns(c) => checks::call(c,&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), "schematic_run" => routing::schematic_run(&ctx.state_dir,&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)), _ => routing::call(cmd,&parsed["args"],&|v,a|addin::pass_through(v,&a,&ctx)) };                         if cmd=="netlist" { r=sheet::shape_board_netlist(r,&parsed["args"]); }                         r["bridgeVersion"]=json!(VERSION); r["shell"]=json!("rust"); r                     },
rust/bridge/src/routing.rs+102
@@ -50,6 +50,74 @@ fn schematic_run_batched(args: &Value, list: &[Value], run: &dyn Fn(&Value) -> V     last } +/// How long the call itself waits for a long list. Adom Bridge gives a call to another machine 120 s,+/// after which the caller is told timed_out with Fusion still working, so this answers before that.+const SCH_FOREGROUND_SECS: u64 = 100;++fn sch_run_path(state_dir: &std::path::Path, id: &str) -> std::path::PathBuf {+    let safe: String = id.chars().filter(|c| c.is_ascii_alphanumeric()).collect();+    state_dir.join("schematic-runs").join(format!("{safe}.json"))+}++fn sch_run_write(state_dir: &std::path::Path, id: &str, v: &Value) {+    let p = sch_run_path(state_dir, id);+    if let Some(d) = p.parent() { let _ = std::fs::create_dir_all(d); }+    let tmp = p.with_extension("tmp");+    if std::fs::write(&tmp, v.to_string()).is_ok() { let _ = std::fs::rename(&tmp, &p); }+}++fn sch_run_read(state_dir: &std::path::Path, id: &str) -> Option<Value> {+    std::fs::read_to_string(sch_run_path(state_dir, id)).ok().and_then(|t| serde_json::from_str(&t).ok())+}++/// What a poll answers: the run's reply once it has ended, or that it is still going.+fn sch_run_answer(state_dir: &std::path::Path, id: &str) -> Value {+    match sch_run_read(state_dir, id) {+        None => json!({"success": false, "errorCode": "unknown_run_id", "error": format!("No schematic run {id} on this machine")}),+        Some(rec) if rec["stage"] == "running" => json!({"success": true, "stillRunning": true, "runId": id, "commandsAsked": rec["commandsAsked"], "startedAt": rec["startedAt"],+            "_hint": "Fusion is still working through the list. Poll fusion_schematic_run {runId} again; don't send the list again."}),+        Some(mut rec) => { let mut r = rec["result"].take(); r["runId"] = json!(id); r["stillRunning"] = json!(false); r }+    }+}++/// The verb's front door. A list of up to one batch is one call. A longer one is checked, then runs on+/// the bridge's own thread: the call waits for it as long as it safely can and answers with the whole+/// reply, or else with a runId to poll, so the caller never reads a false timed_out (#137).+pub fn schematic_run(state_dir: &std::path::Path, args: &Value, pass: &dyn Fn(&str, Value) -> Value) -> Value {+    if let Some(id) = args["runId"].as_str().filter(|_| args["commands"].is_null()) { return sch_run_answer(state_dir, id); }+    let long = args["commands"].as_array().map(|l| l.len()).filter(|n| *n > SCH_BATCH);+    let Some(asked) = long.filter(|_| args["_onThread"].is_null() && args["validateOnly"].is_null()) else { return call("schematic_run", args, pass) };+    let mut check = args.clone();+    check["validateOnly"] = json!(true);+    let checked = call("schematic_run", &check, pass);+    if checked["success"].as_bool() != Some(true) { return checked; }+    let id = format!("{:x}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis()).unwrap_or(0) as u64 ^ (std::process::id() as u64) << 40);+    let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs_f64()).unwrap_or(0.0);+    sch_run_write(state_dir, &id, &json!({"stage": "running", "startedAt": now, "commandsAsked": asked}));+    let url = crate::launch::SELF_URL.lock().unwrap().clone();+    let mut a2 = args.clone();+    a2["_onThread"] = json!(true);+    let (sd, rid) = (state_dir.to_path_buf(), id.clone());+    std::thread::spawn(move || {+        let r = url.and_then(|u| ureq::post(&u).timeout(std::time::Duration::from_secs(3600)).send_json(json!({"command": "fusion_schematic_run", "args": a2})).ok())+            .and_then(|r| r.into_json::<Value>().ok())+            .unwrap_or(json!({"success": false, "errorCode": "run_lost", "error": "The bridge lost this schematic run before it answered: read the sheet (fusion_schematic_data) to see what landed before running it again"}));+        sch_run_write(&sd, &rid, &json!({"stage": "ended", "result": r}));+    });+    sch_wait_or_hand_over(state_dir, &id, asked, SCH_FOREGROUND_SECS)+}++fn sch_wait_or_hand_over(state_dir: &std::path::Path, id: &str, asked: usize, secs: u64) -> Value {+    let until = std::time::Instant::now() + std::time::Duration::from_secs(secs);+    loop {+        if sch_run_read(state_dir, id).map(|r| r["stage"] != "running").unwrap_or(false) { return sch_run_answer(state_dir, id); }+        if std::time::Instant::now() >= until { break; }+        std::thread::sleep(std::time::Duration::from_millis(500));+    }+    json!({"success": true, "mutated": true, "started": true, "stillRunning": true, "runId": id, "statusVerb": "fusion_schematic_run", "commandsAsked": asked,+        "_hint": format!("The list passed its checks and is running in Fusion. Poll fusion_schematic_run {{runId:\"{id}\"}} until stillRunning is false: that answer is the run's whole reply. Don't send the list again.")})+}+ pub fn call(cmd: &str, args: &Value, pass: &dyn Fn(&str, Value) -> Value) -> Value {     if cmd == "schematic_run" && args["validateOnly"].is_null() {         if let Some(list) = args["commands"].as_array().filter(|l| l.len() > SCH_BATCH) {@@ -113,6 +181,40 @@ pub fn export_bom(args: &Value, pass: &dyn Fn(&str, Value) -> Value) -> Value { #[cfg(test)] mod tests {     use super::*;     fn commands(n: usize) -> Vec<Value> { (0..n).map(|i| json!(format!("c{i}"))).collect() }+    fn run_dir(tag: &str) -> std::path::PathBuf { let d = std::env::temp_dir().join(format!("adom-sch-run-{tag}-{}", std::process::id())); let _ = std::fs::remove_dir_all(&d); d }+    #[test] fn a_run_still_going_hands_over_a_run_id_and_a_poll_answers() {+        let d = run_dir("hand");+        sch_run_write(&d, "abc", &json!({"stage": "running", "startedAt": 1.0, "commandsAsked": 560}));+        let r = sch_wait_or_hand_over(&d, "abc", 560, 0);+        assert_eq!(r["stillRunning"], true);+        assert_eq!(r["runId"], "abc");+        assert_eq!(r["success"], true);+        let noop = |_: &str, _: Value| panic!("a poll must not touch Fusion");+        let p = schematic_run(&d, &json!({"runId": "abc"}), &noop);+        assert_eq!(p["stillRunning"], true);+        assert_eq!(p["commandsAsked"], 560);+        sch_run_write(&d, "abc", &json!({"stage": "ended", "result": {"success": true, "commands": 560, "batches": 2}}));+        let p = schematic_run(&d, &json!({"runId": "abc"}), &noop);+        assert_eq!(p["stillRunning"], false);+        assert_eq!(p["commands"], 560);+        let p = schematic_run(&d, &json!({"runId": "nope"}), &noop);+        assert_eq!(p["errorCode"], "unknown_run_id");+    }+    #[test] fn a_run_that_ended_in_time_answers_as_one_call() {+        let d = run_dir("done");+        sch_run_write(&d, "abc", &json!({"stage": "ended", "result": {"success": false, "errorCode": "add_failed", "failedAt": 305}}));+        let r = sch_wait_or_hand_over(&d, "abc", 560, 5);+        assert_eq!(r["errorCode"], "add_failed");+        assert_eq!(r["failedAt"], 305);+    }+    #[test] fn a_bad_long_list_is_refused_in_the_call_and_starts_nothing() {+        let d = run_dir("bad");+        let list = commands(560);+        let pass = |_: &str, _: Value| json!({"success": true, "data": {"result": {"success": false, "errorCode": "off_the_sheet"}}});+        let r = schematic_run(&d, &json!({"commands": list}), &pass);+        assert_eq!(r["errorCode"], "off_the_sheet");+        assert!(!d.join("schematic-runs").exists());+    }     #[test] fn a_long_schematic_list_runs_in_batches_after_one_check() {         let list = commands(700);         let seen = std::cell::RefCell::new(Vec::new());
rust/bridge/src/sch_script.py+2
@@ -1708,6 +1708,8 @@ def sch_reset_fields(app, args):                 steps.append('ROTATE =%s %s;' % (home['rot'], mm(home['at'])))             if (a.get('align') or 'bottom-left') != home['align']:                 steps.append('CHANGE ALIGN %s %s;' % (home['align'], mm(home['at'])))+            if not steps:+                untouched.append({'ref': ref, 'field': f, 'why': 'already where the symbol puts it'})             if steps:                 cmds += steps                 plan.append({'ref': ref, 'field': f, 'from': list(map(_r4, cur)), 'to': list(map(_r4, home['at'])), 'rot': home['rot'], 'align': home['align']})
skills/fusion-board-from-netlist/SKILL.md+2−1
@@ -52,7 +52,8 @@ later step targets it by name. `fusion_schematic_plan {libraryPath, parts:[{ref, deviceset, value, block}], nets}` returns one command group per block. Run each group with `fusion_schematic_run {libraryPath, commands, pauseMs}`: the sheet grows block by block, ADD is proven against the library first (no picker), and the reply carries the-sheet's nets. A long list is fine: the bridge checks all of it first, then runs it in batches of 300. Compare them with the netlist pin by pin before going on. The board follows the schematic:+sheet's nets. A long list is fine: the bridge checks all of it first, then runs it in batches of 300. If the reply says+`stillRunning`, poll `fusion_schematic_run {runId}` until it's false; that answer is the whole reply. Compare them with the netlist pin by pin before going on. The board follows the schematic: `fusion_board_info` should count every part.  ## 4. The board: adom-aiflow's Fusion lane
tests/sch_drill.py+6
@@ -117,6 +117,12 @@ results.append(('long list, bad command late: nothing ran', expect(r.get('errorC print('%-34s %s' % (results[-1][0], 'PASS' if results[-1][1] is None else 'FAIL  ' + str(results[-1][1]))) t_long = time.time() r = call('fusion_schematic_run', {'libraryPath': LBR, 'commands': long_cmds})+print('   the call answered in %.0f s, stillRunning %s' % (time.time() - t_long, r.get('stillRunning')))+results.append(('long list: no false timeout', expect(r.get('success') is True and r.get('errorCode') != 'timed_out', '%s %s' % (r.get('errorCode'), str(r.get('error'))[:120]))))+print('%-34s %s' % (results[-1][0], 'PASS' if results[-1][1] is None else 'FAIL  ' + str(results[-1][1])))+while r.get('stillRunning') and time.time() - t_long < 1200:+    time.sleep(10)+    r = call('fusion_schematic_run', {'runId': r.get('runId')}) print('   560 commands took %.0f s, %s batches, %s commands' % (time.time() - t_long, r.get('batches'), r.get('commands'))) added_long = set(parts()) - before_long results.append(('long list runs whole', expect(r.get('success') and r.get('batches') == 2 and r.get('commands') == 560 and len(added_long) == 80, '%s %s, %d parts' % (r.get('errorCode'), str(r.get('error'))[:120], len(added_long)))))