← Commit history

2.0.41: per-surface KiCad/Fusion buttons

John Lauer ·fa54a40375 ·2mo ago ·parent 1fced36
1 file changed +94−13
web-control-demo.html+94−13
@@ -78,6 +78,12 @@   <div class="grp">Do something - each asks permission, streams live status, then captures the app's FULL window state</div>   <div class="cards" id="grp-do"></div> +  <div class="grp">KiCad surfaces - one first-class verb per surface (kicad_show_*, runtime 0.9.96+). Each opens in the BACKGROUND, then we render it from kicad_state.</div>+  <div class="cards" id="grp-kicad"></div>++  <div class="grp">Fusion surfaces - one first-class verb per content type (fusion_show_*, v1.9.10+). Design surfaces need a project open; library surfaces need a library open (the tour above stages its own).</div>+  <div class="cards" id="grp-fusion"></div>+   <div class="grp">Multi-machine - fan a verb across every connected computer</div>   <div class="cards" id="grp-multi"></div> @@ -375,6 +381,15 @@ for (const w of mine) {         c.btn.disabled = false; return;       }       var est = res.j.estimatedSec || 100;+      // Thread an id from the START response into the poll args when the bridge+      // hands one back (e.g. Fusion's fusion_demo returns a demoId that+      // fusion_demo_progress needs). KiCad's poll is stateless, so no idFrom.+      var pollArgs = Object.assign({}, cfg.pollArgs || { progress: true });+      if (cfg.idFrom) {+        var idVal = res.j[cfg.idFrom];+        if (idVal == null && res.j.data) idVal = res.j.data[cfg.idFrom];+        if (idVal != null) { pollArgs[cfg.idAs || cfg.idFrom] = idVal; clog(c, "  tracking " + (cfg.idAs || cfg.idFrom) + "=" + idVal); }+      }       clog(c, "Started (bridge estimates ~" + est + "s). Polling for live progress ...");       setProgress(c.card, res.j.percent || 2, "preparing ...", 0, 0, 0, est);       setRes(c.res, "pending", "running the demo ...", "usually ~" + est + "s - watch the bar");@@ -383,7 +398,7 @@ for (const w of mine) {       function poll() {         polls++;         if (polls > maxPolls) { setRes(c.res, "err", "timed out", "the demo did not finish in time"); c.btn.disabled = false; return; }-        postVerb(cfg.pollCmd || cfg.startCmd, cfg.pollArgs || { progress: true }).then(function (pr) {+        postVerb(cfg.pollCmd || cfg.startCmd, pollArgs).then(function (pr) {           if (pr.s !== 200 || !pr.j) { return new Promise(function (r) { setTimeout(r, 2500); }).then(poll); }           var d = pr.j;           if (d.error && d.active === undefined && d.done === undefined) {@@ -447,6 +462,36 @@ for (const w of mine) {     return true;   } +  // Open ONE surface via a bridge's first-class show verb, then render it from the+  // bridge's state verb. The show verbs open in the BACKGROUND by design, so we+  // SHOW the window by screenshotting state - we never foreground the app. On a+  // prerequisite miss the bridge returns a _hint; we surface it.+  function showSurface(c, cfg) {+    c.btn.disabled = true;+    cardLog(c.card).textContent = "";+    var sb = c.card.querySelector("div.shots"); if (sb) sb.innerHTML = "";+    setRes(c.res, "pending", "asking permission (choose “Allow 1 hr”) ...", cfg.note || "opens in the background, then we screenshot it");+    clog(c, "Calling " + cfg.showCmd + " " + JSON.stringify(cfg.showArgs || {}) + (cfg.note ? "   (" + cfg.note + ")" : ""));+    postVerb(cfg.showCmd, cfg.showArgs || {}).then(function (res) {+      if (res.j && res.j._hint) clog(c, "  bridge hint: " + res.j._hint);+      var ok = res.s === 200 && res.j && (res.j.success === true || res.j.ok === true || res.j.surface || res.j.shots || res.j.result || res.j.state);+      if (!ok) {+        if (res.j && res.j.errorCode === "web_control_disabled") setRes(c.res, "err", "web control OFF", "re-enable in Settings → Web Control");+        else setRes(c.res, "err", "not opened", (res.j && (res.j.error || res.j.errorCode || res.j._hint)) || ("HTTP " + res.s));+        c.btn.disabled = false; return;+      }+      clog(c, "  opened" + (res.j.surface ? " (surface: " + res.j.surface + ")" : "") + " - capturing state ...");+      // The show verb MAY already carry shots; else pull the state verb.+      if (renderShots(c, res.j)) return;+      return postVerb(cfg.stateCmd, { maxWidth: 900 }).then(function (sr) {+        if (!(sr.s === 200 && sr.j && renderShots(c, sr.j))) {+          setRes(c.res, "ok", "Opened" + (res.j.surface ? " (" + res.j.surface + ")" : ""), "no window captured yet (may need a prerequisite - see the hint)");+          c.btn.disabled = false;+        }+      });+    }).catch(function (e) { setRes(c.res, "err", "blocked", String(e)); c.btn.disabled = false; });+  }+   // summaries   function n(x) { return (x === undefined || x === null) ? 0 : x; }   function bridgesSummary(j) { var d = j.data || j; var a = d.bridges || []; return a.length + " bridge(s): " + a.map(function (b) { return b.name; }).slice(0, 6).join(", "); }@@ -493,20 +538,56 @@ for (const w of mine) {       stateCmd: "kicad_state"     });   };-  var cFusion = card("grp-do", "verb", "Open Fusion 360");+  var cFusion = card("grp-do", "verb", "Run the Fusion demo (bridge-driven, live progress)");   cFusion.btn.onclick = function () {-    streamRecipe(cFusion, [-      { command: "fusion_start", args: {}, label: "Launching Autodesk Fusion", usuallySec: 90 },-      { waitForWindow: { titleContains: "Fusion", timeoutSec: 280 }, label: "Waiting for Fusion to come up", usuallySec: 120 }-    ], { include: ["fusion", "autodesk"], exclude: ["for testing", "harness"] });-  };-  var cSym = card("grp-do", "verb", "Open KiCad (Symbol Editor)");-  cSym.btn.onclick = function () {-    streamRecipe(cSym, [-      { command: "kicad_open_symbol_editor", args: {}, label: "Opening the KiCad Symbol Editor", usuallySec: 30 },-      { waitForWindow: { titleContains: "Symbol Editor", timeoutSec: 90 }, label: "Waiting for the Symbol Editor", usuallySec: 8 }-    ], { include: ["kicad", "symbol editor", "pcb editor", "schematic editor"], exclude: ["for testing", "harness", "adom bridge web", "adom wiki"] });+    // The Fusion bridge OWNS this demo (fusion_demo, v1.9.9+): an async tour over+    // symbol / footprint / device / 3D-package (+ schematic/2D/3D board) off its+    // bundled sample library. Start returns a demoId that fusion_demo_progress+    // needs, and the final poll carries the screenshot array. Same harness as+    // KiCad, just a demoId to thread through.+    runBridgeDemo(cFusion, {+      startCmd: "fusion_demo", startArgs: { mode: "tour" },+      pollCmd: "fusion_demo_progress", pollArgs: {},+      idFrom: "demoId", idAs: "demoId",+      stateCmd: "fusion_state"+    });   };+  // ---- KiCad surfaces: one first-class verb per surface (kicad_show_*) ----+  // Zero-setup surfaces hit KiCad's STOCK libraries (nothing to push, solid cold).+  // Design-file surfaces point at the project the KiCad demo generates (run it+  // first). 3D surfaces are WARM-ONLY - reliable once KiCad is open (kicad-bridge #26).+  var KDEMO = "%USERPROFILE%\\Documents\\adom-kicad-demo\\adom-demo";+  [+    { label: "Symbol (R, Device lib)", cmd: "kicad_show_symbol", args: { symbolName: "R", libraryName: "Device" }, note: "stock library, works cold" },+    { label: "Footprint (R_0603, Resistor_SMD)", cmd: "kicad_show_footprint", args: { footprintName: "R_0603_1608Metric", library: "Resistor_SMD" }, note: "stock library, works cold" },+    { label: "Library browser (Device)", cmd: "kicad_show_library", args: { libraryName: "Device" }, note: "stock library, works cold" },+    { label: "Project (Project Manager)", cmd: "kicad_show_project", args: { filePath: KDEMO + ".kicad_pro" }, note: "run the KiCad demo first to generate this project" },+    { label: "Schematic", cmd: "kicad_show_schematic", args: { filePath: KDEMO + ".kicad_sch" }, note: "run the KiCad demo first" },+    { label: "2D board", cmd: "kicad_show_2d_board", args: { filePath: KDEMO + ".kicad_pcb" }, note: "run the KiCad demo first" },+    { label: "3D chip (warm-only)", cmd: "kicad_show_3d_chip", args: { footprintName: "R_0603_1608Metric", library: "Resistor_SMD" }, note: "run the KiCad demo first; 3D window is flaky cold" },+    { label: "3D board (warm-only)", cmd: "kicad_show_3d_board", args: { filePath: KDEMO + ".kicad_pcb" }, note: "run the KiCad demo first; 3D window is warm-only" }+  ].forEach(function (s) {+    var cc = card("grp-kicad", "verb", s.label);+    cc.btn.onclick = function () { showSurface(cc, { showCmd: s.cmd, showArgs: s.args, stateCmd: "kicad_state", note: s.note }); };+  });+  var cKState = card("grp-kicad", "read", "Show KiCad state (every window)");+  cKState.btn.onclick = function () { showSurface(cKState, { showCmd: "kicad_state", showArgs: { maxWidth: 900 }, stateCmd: "kicad_state", note: "captures every KiCad window as a screenshot" }); };++  // ---- Fusion surfaces: one first-class verb per content type (fusion_show_*) ----+  // Design surfaces need a project open; library surfaces need a library open.+  // (The Fusion tour above stages its own sample content.) On a prerequisite miss+  // the bridge returns a _hint telling you the next move - we surface it.+  [+    { label: "Schematic", cmd: "fusion_show_schematic", args: {}, note: "needs an electronics project open (run the Fusion demo, or fusion_open_cloud_file)" },+    { label: "2D board", cmd: "fusion_show_2d_board", args: {}, note: "needs a project open" },+    { label: "3D board", cmd: "fusion_show_3d_board", args: {}, note: "needs a project open" },+    { label: "Symbol", cmd: "fusion_show_symbol", args: { name: "R" }, note: "needs a library open (fusion_open_lbr), or pass symbolXml" },+    { label: "Footprint", cmd: "fusion_show_footprint", args: { name: "R" }, note: "needs a library open, or pass packageXml" },+    { label: "3D package", cmd: "fusion_show_3d_package", args: { name: "R" }, note: "needs a library open; add stepPath to attach a model" }+  ].forEach(function (s) {+    var cc = card("grp-fusion", "verb", s.label);+    cc.btn.onclick = function () { showSurface(cc, { showCmd: s.cmd, showArgs: s.args, stateCmd: "fusion_state", note: s.note }); };+  });   // Single full-screen shot (keep the simple case too).   var cShot = card("grp-do", "verb", "Take a screenshot (whole screen)");   cShot.btn.onclick = function () {