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
restart_for_addin: no Unsaved changes box when the add-in cannot save (#129)
2 files changed
+38−2
CHANGELOG.md+6@@ -5,6 +5,12 @@ Canonical: https://wiki.adom.inc/adom/fusion-bridge ยท GitHub backup: `adom-inc/ ## 1.13.28 (unreleased) +- `fusion_restart_for_addin` no longer meets Fusion's Unsaved changes box when the add-in cannot save (issue #129).+ A save whose add-in connection dropped is retried once after 5 s (the add-in can be unreachable for a moment+ right after `bridge_install`). If the add-in still answers its status probe but the save failed, the restart+ stops with `restart_needs_decision` before anything is closed. Fusion is closed without the add-in's save only+ when the add-in is not loaded at all.+ From Aditya Angajala (PR #17): `fusion_export_optimized_glb` produces a publishable molecule, not just a GLB. - It exports the EAGLE `.brd` (new `board` arg, default true) and sends it to service-step2glb as the `board` part, the same contract as `step2glb convert --board`. With the board the service generates the footprint and symbol
rust/bridge/src/lifecycle.rs+32−2@@ -194,6 +194,12 @@ pub fn restart_policy(state_dir: &std::path::Path) -> String { crate::small_verbs::get_pref(state_dir, "restartPolicy").and_then(|v| v.as_str().map(|s| s.to_lowercase())).unwrap_or_else(|| "auto".into()) } +/// The add-in request never reached Fusion (a dropped or refused connection), as opposed to the+/// add-in answering that it could not save.+fn save_unreachable(r: &Value) -> bool {+ r["error"].as_str().map(|e| e.starts_with("Add-in request failed") && !e.contains("non-JSON")).unwrap_or(false)+}+ /// An auto restart stopped short of someone's unsaved work: for the next 30 minutes the gate asks /// (the toast with Save and restart) instead of trying again behind the person's back. pub fn note_auto_restart_declined(state_dir: &std::path::Path, why: &Value) {@@ -380,7 +386,14 @@ pub fn restart_for_addin(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) -> let mut graceful = false; let mut discarded: Vec<String> = Vec::new(); if save {- let sv = pass("close_all_documents", json!({"save": true}));+ let mut sv = pass("close_all_documents", json!({"save": true}));+ // issue #129: right after bridge_install the add-in's connection can drop for a moment even+ // though it answered seconds before; one retry before treating the save as impossible+ if sv["success"].as_bool() != Some(true) && save_unreachable(&sv) {+ steps.push(json!({"step": "save", "ok": false, "error": sv["error"].as_str().unwrap_or("").chars().take(160).collect::<String>(), "retry": true}));+ std::thread::sleep(Duration::from_secs(5));+ sv = pass("close_all_documents", json!({"save": true}));+ } steps.push(json!({"step": "save", "ok": sv["success"].as_bool().unwrap_or(false), "closed": sv["data"]["closed"], "wouldPrompt": sv["data"]["wouldPrompt"]})); let wp: Vec<String> = sv["data"]["wouldPrompt"].as_array().map(|a| a.iter().filter_map(|v| v.as_str().map(str::to_string)).collect()).unwrap_or_default(); if !wp.is_empty() {@@ -399,8 +412,19 @@ pub fn restart_for_addin(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) -> "_hint": "Ask the user where to save it (fusion_save_to_cloud {name, projectName}), then retry. Nothing was closed and no dialog was raised."}); } }+ if sv["success"].as_bool() != Some(true) && !force && crate::addin::addin_status_probe(Duration::from_secs(10)).is_some() {+ // issue #129: the add-in is loaded and answering but could not save. A close now would+ // only meet Fusion's own Unsaved changes box, so stop here: nothing closed, no dialog.+ let why = json!({"save": sv["errorCode"], "error": sv["error"]});+ if auto_policy { note_auto_restart_declined(&sd, &why); }+ crate::fusion_prefs::action_ledger(&sd, "restart_stopped_short", json!({"steps": steps, "why": why}));+ return json!({"success": false, "errorCode": "restart_needs_decision", "restartAborted": true, "steps": steps,+ "error": format!("Fusion was not restarted: the add-in answers but could not save the open documents ({})", sv["error"].as_str().unwrap_or("no reason given").chars().take(200).collect::<String>()),+ "userMessage": "Fusion needs a restart to load the Adom add-in, and the bridge could not save the open documents first. Save or close them, then restart.",+ "_hint": "Nothing was closed and no dialog was raised. Save the documents (fusion_save_document) or close them, then call fusion_restart_for_addin {save:true} again."});+ } if sv["success"].as_bool() != Some(true) && auto_policy {- // the add-in cannot save (it is the thing that is not answering). Close Fusion the way+ // the add-in is not loaded at all (it is the thing that is not answering). Close Fusion the way // a person would: it closes at once when nothing is unsaved, and a save prompt for // someone's work stops the restart instead of a kill losing it (force: kill, as before) steps.push(json!({"step": "save-unavailable", "error": sv["error"].as_str().unwrap_or("").chars().take(160).collect::<String>()}));@@ -549,6 +573,12 @@ pub fn diagnose_addin_timeout(ctx: &Ctx, command: &str, original: &Value) -> Val mod tests { use super::*; #[test]+ fn only_a_connection_that_never_reached_fusion_is_retried() {+ assert!(save_unreachable(&json!({"success": false, "error": "Add-in request failed: Network Error: A connection attempt failed"})));+ assert!(!save_unreachable(&json!({"success": false, "error": "Add-in request failed: non-JSON answer"})));+ assert!(!save_unreachable(&json!({"success": false, "error": "could not save lane-BQ25792-copy"})));+ }+ #[test] fn a_save_prompt_is_answered_only_for_the_bridges_own_document() { assert_eq!(prompt_answer(true), "dont_save"); assert_eq!(prompt_answer(false), "cancel_and_stop");