app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
← Commit history
#133: approval before the import's dialogs
2 files changed
+38−1
CHANGELOG.md+9@@ -3,6 +3,15 @@ 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.43 (2026-10-30)++- Issue #133, the cause on Andy's PC: ab gates UI Automation and keystrokes behind its shell approval even for a+ bridge, so with no grant every way of filling the Open dialog was refused (needs_approval, reproduced on the test+ laptop with approval off). The import now checks approval before any dialog opens: without it, it asks ab (the+ prompt shows in Adom Bridge or Hydrogen), the start reply carries `needsApproval` and `tellUser`, the run's status+ reads `waiting_for_approval`, and it waits up to five minutes for the click. With no click it stops with+ needs_approval and no dialog opened.+ ## 1.13.42 (2026-10-30) Issue #133, after Andy's run on 1.13.41 failed on his PC ("the File name box could not be set"):
rust/bridge/src/orchestrations.rs+29−1@@ -1284,7 +1284,10 @@ pub fn new_electronics_from_eagle(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, V v["stage"] = json!(if r["success"].as_bool() == Some(true) { "done" } else { "failed" }); v["finishedAt"] = json!(now_f()); v["result"] = r; eagle_run_write(&sd, &rid, &v); });- return crate::display::annotate(json!({"success": true, "started": true, "runId": run_id, "statusVerb": "fusion_eagle_import_status", "partsExpected": expected,+ let needs_ok = fusion_core::ab::Loopback::from_env().and_then(|ab| { use fusion_core::ab::AbClient; ab.call("status", json!({}), &ctx.caller, 10).ok() })+ .map(|s| { let x = &s["permissions"]["shell"]; let x = if x.is_object() { x } else { &s["data"]["permissions"]["shell"] }; x["autoApproved"].as_bool() == Some(false) }).unwrap_or(false);+ return crate::display::annotate(json!({"success": true, "started": true, "runId": run_id, "statusVerb": "fusion_eagle_import_status", "partsExpected": expected, "needsApproval": needs_ok,+ "tellUser": if needs_ok { json!("Adom Bridge is asking for your OK to fill in Fusion's Open dialog for the import: click Approve in the Adom Bridge window, on its notification, or in Hydrogen.") } else { Value::Null }, "dialogsDriven": "Fusion's own Open dialogs (sch, then brd), driven in the background by accessible name; Autodesk offers no API for a sch+brd import", "_hint": format!("Import running in the background (it outlives the relay window). Poll fusion_eagle_import_status {{runId:\"{run_id}\"}} until stage is done or failed; fusion_get_app_state keeps answering meanwhile. The record reports documentName, partsExpected ({expected} elements in the .brd) and partsPlaced from the live board; success needs parts on the board.")})); }@@ -1326,6 +1329,31 @@ pub fn new_electronics_from_eagle(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, V }; let find_open_dialog = || -> Option<i64> { ab.call("desktop_list_windows", json!({}), &ctx.caller, 10).ok().and_then(|v| v["data"]["windows"].as_array().or_else(|| v["windows"].as_array()).cloned()).unwrap_or_default().iter().find(|w| w["title"].as_str().unwrap_or("").trim().to_lowercase() == "open").and_then(|w| w["hwnd"].as_i64()) }; let (sch_base, brd_base) = (sch.rsplit('/').next().unwrap_or("").to_string(), brd.rsplit('/').next().unwrap_or("").to_string());+ // The Open dialogs are filled with UI Automation and keystrokes, which ab gates behind its shell+ // approval even for a bridge (#133: on a PC with no grant every fill was refused, needs_approval).+ // Ask BEFORE any dialog opens: the prompt shows in Adom Bridge (or Hydrogen), and the run waits up to+ // five minutes for the click; no grant, no dialog.+ let approved = |st: &Value| { let s = &st["permissions"]["shell"]; let s = if s.is_object() { s } else { &st["data"]["permissions"]["shell"] }; s["autoApproved"].as_bool() == Some(true) };+ if !ab.call("status", json!({}), &ctx.caller, 10).map(|s| approved(&s)).unwrap_or(true) {+ let tell = "Adom Bridge needs your OK to fill in Fusion's Open dialog for the EAGLE import: click Approve (1 hr, 24 hr or Auto) in the Adom Bridge window, on its notification, or in Hydrogen.";+ if let Some(rid) = args["_runId"].as_str() {+ let mut v = std::fs::read_to_string(eagle_run_path(&ctx.state_dir, rid)).ok().and_then(|t| serde_json::from_str::<Value>(&t).ok()).unwrap_or(json!({}));+ v["stage"] = json!("waiting_for_approval"); v["tellUser"] = json!(tell);+ eagle_run_write(&ctx.state_dir, rid, &v);+ }+ let g = ab.call("request_shell_approval", json!({"wait": true, "timeoutSec": 300, "reason": "Fill in Fusion's Open dialog with the EAGLE .sch and .brd paths for the design import"}), &ctx.caller, 320);+ let granted = g.as_ref().map(|v| v["granted"].as_bool() == Some(true) || v["data"]["granted"].as_bool() == Some(true)).unwrap_or(false);+ if let Some(rid) = args["_runId"].as_str() {+ let mut v = std::fs::read_to_string(eagle_run_path(&ctx.state_dir, rid)).ok().and_then(|t| serde_json::from_str::<Value>(&t).ok()).unwrap_or(json!({}));+ v["stage"] = json!("started"); v["approval"] = json!({"granted": granted});+ eagle_run_write(&ctx.state_dir, rid, &v);+ }+ if !granted {+ return json!({"success": false, "errorCode": "needs_approval", "imported": [], "dialogOpened": false,+ "error": "Not started: the import fills Fusion's Open dialog, which needs Adom Bridge's approval, and no approval was given (the request waited five minutes). No dialog was opened.",+ "tellUser": tell, "_hint": "Ask the user to click Approve in Adom Bridge (or Hydrogen), then run the import again. With approval on (Auto mode) the import needs no prompt."});+ }+ } let mut stale_cancelled = false; if let Some(h) = find_open_dialog() { stale_cancelled = crate::small_verbs::close_native_dialog(&ab, &ctx.caller, h).0; } // the documents open before the import: a failed import closes only the empty Untitled ones it made