app
Adom Bridge
Public Made by Adomby adom
Adom Bridge unleashes your AI onto your full PC: full power, full safety. The AI breaks out of the container it runs in and onto your real machine, managing and running your entire laptop as you: launch apps, move files, drive any window, control KiCad, Fusion 360 or your real signed-in browser. Works with any AI, cloud or local (Claude Desktop, Claude Code, Codex), no Hydrogen required. Bridge and its bridges are an operating system for AI, with managed Node and Python runtimes, and a human-onl
← Commit history
web-control-demo.html+298−49@@ -78,10 +78,10 @@ <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="grp">KiCad surfaces - a REAL wiki part, end to end: <a href="https://wiki.adom.inc/aravk/in-s42atr" target="_blank" rel="noopener">Inolux IN-S42ATR</a> (red 0402 SMD LED). Each button fetches the part's files from its wiki page, installs them into your real KiCad, opens JUST that surface in the background, and renders it from kicad_state. This is the whole circle: wiki component to the app the EE designs in.</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="grp">Fusion surfaces - the SAME wiki part (IN-S42ATR.lbr + .step from its page), one first-class verb per content type (fusion_show_*, v1.9.10+). Component surfaces stage the library automatically; design surfaces additionally need the part project staged (fusion-bridge #30).</div> <div class="cards" id="grp-fusion"></div> <div class="grp">Multi-machine - fan a verb across every connected computer</div>@@ -436,10 +436,18 @@ for (const w of mine) { // Find the bridge's window array wherever it rides: the final poll embeds it at // result.state.shots; kicad_state returns it at shots. Handle every known path. function findShots(d) {- var cands = [d.shots, d.screenshots, d.windows,- d.data && d.data.shots, d.state && d.state.shots,+ // Bridges ride their window array on the final frame in various shapes -+ // KiCad at result.state.shots, Fusion's final fusion_demo_progress poll at+ // (an array called shots OR screenshots, possibly nested under data/result).+ // Check every plausible path so a new bridge's demo renders with no rewrite.+ var cands = [+ d.shots, d.screenshots, d.images, d.windows,+ d.data && d.data.shots, d.data && d.data.screenshots, d.data && d.data.images,+ d.state && d.state.shots, d.state && d.state.screenshots,+ d.result && d.result.shots, d.result && d.result.screenshots, d.result && d.result.state && d.result.state.shots,- d.result && d.result.shots];+ d.result && d.result.state && d.result.state.screenshots+ ]; for (var i = 0; i < cands.length; i++) { if (Array.isArray(cands[i]) && cands[i].length) return cands[i]; }@@ -447,7 +455,11 @@ for (const w of mine) { } // Render a bridge state payload's screenshots (each item: {title, image, ...}). // Returns true if it rendered any window, false if there was nothing to show.- function renderShots(c, d) {+ // pickTitle (optional): show JUST the window(s) whose title contains it — the+ // surface buttons ask for ONE surface ("just the symbol"), and the state verb+ // returns every open window. A zero-match filter falls back to all windows+ // (better to over-show than render nothing when a title changes).+ function renderShots(c, d, pickTitle) { var narrate = (d.result && d.result.narrate) || d.narrate; var failed = (d.result && d.result.failedSteps) || d.failedSteps; if (narrate) clog(c, " bridge says: " + narrate);@@ -455,9 +467,15 @@ for (const w of mine) { var shots = findShots(d); var withImg = shots.filter(function (s) { return s && (s.image || s.base64 || s.png); }); if (!withImg.length) return false;- clog(c, "Captured the bridge's full window state: " + withImg.length + " window(s).");+ if (pickTitle) {+ var want = pickTitle.toLowerCase();+ var picked = withImg.filter(function (s) { return ((s.title || s.label || "") + "").toLowerCase().indexOf(want) >= 0; });+ if (picked.length) { withImg = picked; }+ else clog(c, ' (no window titled like "' + pickTitle + '" — showing all ' + withImg.length + ')');+ }+ clog(c, "Rendering " + withImg.length + " window(s)."); withImg.forEach(function (s) { addShot(c.card, s.image || s.base64 || s.png, s.title || s.label || s.window || "window"); });- setRes(c.res, "ok", "Demo done - full state below", withImg.length + " window(s)");+ setRes(c.res, "ok", withImg.length === 1 ? "Done - the surface is below" : "Demo done - full state below", withImg.length + " window(s)"); c.btn.disabled = false; return true; }@@ -466,30 +484,117 @@ for (const w of mine) { // 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.+ // cfg.stage (optional): a function(c) -> Promise run BEFORE the show verb — the+ // staging chain (fetch the wiki part, install its library, generate the demo+ // project). Electronics is a dependency ladder: you cannot show a 3D board+ // without a routed 2D board, which needs a schematic, which needs the part's+ // library installed. stage() owns climbing that ladder idempotently.+ // cfg.showArgs may be a FUNCTION (called after stage) so args can carry paths+ // the staging step just produced.+ // cfg.pickTitle: render just the one window this surface is about.+ // cfg.fallback {cmd,args}: retried when the primary verb is unknown (a not-yet-+ // shipped show verb degrades to today's open verb instead of a dead button). 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))) {+ var staged = cfg.stage ? cfg.stage(c) : Promise.resolve();+ staged.then(function () {+ var args = (typeof cfg.showArgs === "function") ? cfg.showArgs() : (cfg.showArgs || {});+ clog(c, "Calling " + cfg.showCmd + " " + JSON.stringify(args) + (cfg.note ? " (" + cfg.note + ")" : ""));+ return postVerb(cfg.showCmd, args).then(function (res) {+ if (res.j && res.j._hint) clog(c, " bridge hint: " + res.j._hint);+ var unknown = res.j && /unknown command/i.test(res.j.error || "");+ if (unknown && cfg.fallback) {+ var fb = (typeof cfg.fallback === "function") ? cfg.fallback() : cfg.fallback;+ clog(c, " " + cfg.showCmd + " is not shipped yet on this bridge - falling back to " + fb.cmd);+ return postVerb(fb.cmd, fb.args || args).then(function (r2) { return handleShow(r2); });+ }+ return handleShow(res);+ });+ function handleShow(res) {+ 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);+ // The host app may simply not be running yet (fusion_not_running). If the+ // bridge names a start verb, run it (it blocks until ready) and retry ONCE.+ if (!ok && cfg.startCmd && !c._autoStarted && res.j && /not running/i.test((res.j.error || "") + " " + (res.j._hint || ""))) {+ c._autoStarted = true;+ clog(c, " " + (res.j.error || "app not running") + " - calling " + cfg.startCmd + " (blocks until the add-in is ready) ...");+ setRes(c.res, "pending", "starting the app ...", "cold start can take a couple of minutes");+ return postVerb(cfg.startCmd, {}).then(function (st) {+ if (!(st.s === 200 && st.j && st.j.success !== false)) {+ setRes(c.res, "err", "could not start the app", (st.j && (st.j.error || st.j._hint)) || ("HTTP " + st.s));+ c.btn.disabled = false; return;+ }+ clog(c, " app is ready - retrying " + cfg.showCmd);+ return postVerb(cfg.showCmd, args).then(handleShow);+ });+ }+ 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, cfg.pickTitle)) return;+ return settleThenCapture(c, cfg.stateCmd).then(function (sr) {+ if (sr.s === 200 && sr.j && renderShots(c, sr.j, cfg.pickTitle)) return;+ // The state verb may not be shipped on this bridge yet (fusion_state is+ // "next release" per fusion-bridge #28). Fall back to a GENERIC window+ // capture: ask the bridge which window is its app's, then screenshot it.+ if (cfg.windowInfoCmd) {+ clog(c, " " + cfg.stateCmd + " returned no captures - falling back to a window screenshot (" + cfg.windowInfoCmd + " + desktop_screenshot_base64)");+ return postVerb(cfg.windowInfoCmd, {}).then(function (wi) {+ var wd = (wi.j && (wi.j.data || wi.j)) || {};+ if (!wd.hwnd) throw new Error("no app window reported");+ return postVerb("desktop_screenshot_base64", { hwnd: wd.hwnd, maxWidth: 900 }).then(function (sh) {+ if (!(sh.s === 200 && sh.j && sh.j.image)) throw new Error("screenshot failed");+ addShot(c.card, sh.j.image, wd.title || "app window");+ setRes(c.res, "ok", "Done - captured the app window", "generic capture (the bridge's state verb isn't shipped yet)");+ c.btn.disabled = false;+ });+ }).catch(function (e2) {+ setRes(c.res, "ok", "Opened" + (res.j.surface ? " (" + res.j.surface + ")" : ""), "no window captured: " + String(e2 && e2.message || e2).slice(0, 90));+ c.btn.disabled = false;+ });+ } 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", e && e.staged ? "staging failed" : "blocked", String(e && e.message || e).slice(0, 140));+ c.btn.disabled = false;+ });+ }++ // The app may still be mid-load when the show verb returns (KiCad's "Load PCB"+ // progress dialog raced the first capture and produced a bounding-box 3D render).+ // Poll the state verb CHEAPLY (no images) until the window set is stable and no+ // modal dialog remains, then take the real capture. Errors fall through to a+ // direct capture so a bridge without includeImages support still renders.+ function settleThenCapture(c, stateCmd) {+ var lastCount = -1, tries = 0;+ function capture() { return postVerb(stateCmd, { maxWidth: 900 }); }+ function poll() {+ tries++;+ return postVerb(stateCmd, { includeImages: false }).then(function (pr) {+ var d = (pr.j && (pr.j.state || pr.j)) || {};+ var shots = d.shots || d.windows || [];+ var modals = d.modalDialogs || [];+ var count = shots.length;+ var settled = modals.length === 0 && count > 0 && count === lastCount;+ lastCount = count;+ if (settled || tries >= 8) {+ if (!settled) clog(c, " (capturing after " + tries + " polls without full settle)");+ return capture(); }- });- }).catch(function (e) { setRes(c.res, "err", "blocked", String(e)); c.btn.disabled = false; });+ if (modals.length) clog(c, " waiting for a dialog to clear: " + modals.map(function (m) { return m.title || m; }).join(", "));+ return new Promise(function (r) { setTimeout(r, 1500); }).then(poll);+ }, capture);+ }+ return poll(); } // summaries@@ -552,41 +657,185 @@ for (const w of mine) { 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";+ // ---- The wiki part every surface button shows: Inolux IN-S42ATR ----+ // The part's page publishes the component artifacts (symbol, footprint, STEP,+ // .lbr); the buttons fetch them straight off the wiki (the files API sends+ // Access-Control-Allow-Origin: *), install them through the bridges' install+ // verbs, and only then open the surface. Electronics is a dependency ladder —+ // 3D board needs a routed board, needs a schematic, needs the library — so+ // each stage function climbs exactly as far as its surface requires, and each+ // is MEMOIZED (one staging per page load; every later button reuses it).+ var PART = {+ name: "IN-S42ATR",+ kicadLib: "Adom", // kicad_install_symbol installs into this library+ page: "https://wiki.adom.inc/aravk/in-s42atr",+ filesBase: "https://wiki.adom.inc/api/pages/aravk/in-s42atr/files/",+ dir: "~/Documents/adom-demo-parts/IN-S42ATR/" // staged on the PC via write_file (~ expands)+ };+ function fetchPartText(f) { return fetch(PART.filesBase + f).then(function (r) { if (!r.ok) throw new Error("wiki fetch " + f + ": HTTP " + r.status); return r.text(); }); }+ function fetchPartBytes(f) { return fetch(PART.filesBase + f).then(function (r) { if (!r.ok) throw new Error("wiki fetch " + f + ": HTTP " + r.status); return r.arrayBuffer(); }); }+ function b64Text(s) { return btoa(unescape(encodeURIComponent(s))); }+ function b64Bytes(buf) {+ var u8 = new Uint8Array(buf), out = "", CH = 0x8000;+ for (var i = 0; i < u8.length; i += CH) out += String.fromCharCode.apply(null, u8.subarray(i, i + CH));+ return btoa(out);+ }+ function stageFail(msg) { var e = new Error(msg); e.staged = true; return e; }+ // Write one part file onto the PC via ab's write_file; resolves the ABSOLUTE path.+ function stageFile(c, name, b64) {+ return postSelf("write_file", { path: PART.dir + name, content: b64, encoding: "base64" }).then(function (r) {+ if (!(r.s === 200 && r.j && r.j.success)) throw stageFail("write_file " + name + ": " + ((r.j && (r.j.error || r.j.errorCode)) || r.s));+ clog(c, " staged " + name + " -> " + r.j.path);+ return r.j.path;+ });+ }++ // KiCad component staging (the kicad-bridge's own recipe, adom-bridge-sdk#4+ // reply 1007/1032): fetch symbol+footprint+STEP from the wiki, write all three+ // onto the PC, then install by filePath. kicad_install_footprint's `model`+ // arg (0.9.108) installs the .step into 3dmodels/Adom and WIRES the+ // footprint's (model ...) reference itself — the page's old hand-splice is+ // retired; the bridge owns model wiring now.+ var _kicadPart = null;+ function stageKicadPart(c) {+ if (_kicadPart) { clog(c, "Part already staged this session (reusing)."); return _kicadPart; }+ clog(c, "Staging " + PART.name + " from " + PART.page + " ...");+ _kicadPart = Promise.all([+ fetchPartText(PART.name + ".kicad_sym"),+ fetchPartText(PART.name + ".kicad_mod"),+ fetchPartBytes(PART.name + ".step")+ ]).then(function (got) {+ clog(c, " fetched symbol (" + got[0].length + " B), footprint (" + got[1].length + " B), STEP (" + got[2].byteLength + " B) from the wiki");+ return Promise.all([+ stageFile(c, PART.name + ".kicad_sym", b64Text(got[0])),+ stageFile(c, PART.name + ".kicad_mod", b64Text(got[1])),+ stageFile(c, PART.name + ".step", b64Bytes(got[2]))+ ]);+ }).then(function (paths) {+ return postVerb("kicad_install_symbol", { filePath: paths[0], quietInstall: true }).then(function (r) {+ if (!(r.s === 200 && r.j && r.j.success)) throw stageFail("kicad_install_symbol: " + ((r.j && r.j.error) || r.s));+ if (r.j.libraryName) PART.kicadLib = r.j.libraryName;+ clog(c, " installed symbol into the '" + PART.kicadLib + "' library");+ return postVerb("kicad_install_footprint", { filePath: paths[1], model: { filePath: paths[2] }, quietInstall: true });+ }).then(function (r) {+ if (!(r.s === 200 && r.j && r.j.success)) throw stageFail("kicad_install_footprint: " + ((r.j && r.j.error) || r.s));+ clog(c, " installed footprint; the bridge wired the 3D model reference (0.9.108 model arg)");+ });+ });+ _kicadPart.catch(function () { _kicadPart = null; }); // a failed staging retries on the next click+ return _kicadPart;+ }++ // KiCad PROJECT staging — kicad_make_part_project (0.9.108+, closed #29):+ // generates a real project around the part and returns the file paths the+ // project/schematic/board buttons need. 0.9.112: SINGLE-COMPONENT by design+ // (John's ruling — just the part, no companion resistor), refdes from the+ // part's own Reference property, part-dominant tight edge cuts so an 0402+ // fills the 3D view instead of rendering as a speck.+ var _kicadProj = null;+ function stageKicadProject(c) {+ if (_kicadProj) return _kicadProj;+ _kicadProj = stageKicadPart(c).then(function () {+ clog(c, "Generating the part's demo project (kicad_make_part_project) ...");+ return postVerb("kicad_make_part_project", { partName: PART.name }).then(function (r) {+ if (r.s === 200 && r.j && r.j.success) {+ PART.kicadProject = {+ proPath: r.j.projectFile,+ schPath: r.j.schematicFile,+ pcbPath: r.j.boardFile+ };+ clog(c, " project generated (" + (r.j.reference || PART.name) + " on a part-dominant board): " + (r.j.projectFile || "?"));+ return;+ }+ throw stageFail("kicad_make_part_project: " + ((r.j && (r.j.error || r.j.errorCode)) || r.s));+ });+ });+ _kicadProj.catch(function () { _kicadProj = null; });+ return _kicadProj;+ }++ // ---- KiCad surfaces: the eight-surface ladder for the wiki part ----+ // ALL EIGHT open cold + background (the 3D warm-only limitation is obsolete:+ // kicad-bridge #26 closed via the Win32 menu tier in 0.9.99). [- { 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" }+ { label: "Symbol (" + PART.name + ")", cmd: "kicad_show_symbol", args: function () { return { symbolName: PART.name, libraryName: PART.kicadLib }; }, stage: stageKicadPart, pick: "symbol editor", note: "installs the part from its wiki page, then shows JUST the symbol" },+ { label: "Footprint (" + PART.name + ")", cmd: "kicad_show_footprint", args: function () { return { footprintName: PART.name, library: PART.kicadLib }; }, stage: stageKicadPart, pick: "footprint editor", note: "just the land pattern: pads + courtyard" },+ { label: "3D chip (" + PART.name + ")", cmd: "kicad_show_3d_chip", args: function () { return { footprintName: PART.name, library: PART.kicadLib }; }, stage: stageKicadPart, pick: "3d viewer", note: "the part's STEP model on its footprint" },+ { label: "Library (" + PART.name + " in '" + PART.kicadLib + "')", cmd: "kicad_show_library", args: function () { return { libraryName: PART.kicadLib }; }, stage: stageKicadPart, pick: "symbol editor", note: "the library holding the symbol + footprint + 3D model" },+ { label: "Schematic (just the " + PART.name + ")", cmd: "kicad_show_schematic", args: function () { return { filePath: (PART.kicadProject || {}).schPath }; }, stage: stageKicadProject, pick: "schematic editor", note: "the generated single-component schematic (kicad_make_part_project)" },+ { label: "2D board (part placed, tight edge cuts)", cmd: "kicad_show_2d_board", args: function () { return { filePath: (PART.kicadProject || {}).pcbPath }; }, stage: stageKicadProject, pick: "pcb editor", note: "the part-dominant board" },+ { label: "3D board (real 3D body on the board)", cmd: "kicad_show_3d_board", args: function () { return { filePath: (PART.kicadProject || {}).pcbPath }; }, stage: stageKicadProject, pick: "3d viewer", note: "the populated board in 3D — the whole ladder underneath it" },+ { label: "Project (top of the tree)", cmd: "kicad_show_project", args: function () { return { filePath: (PART.kicadProject || {}).proPath }; }, stage: stageKicadProject, pick: "kicad 10", note: "the Project Manager: schematic + board hang off it" } ].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 }); };+ cc.btn.onclick = function () { showSurface(cc, { showCmd: s.cmd, showArgs: s.args, stage: s.stage, pickTitle: s.pick, 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.+ // Fusion component staging: the SAME wiki part's .lbr + .step, written onto the+ // PC (the fusion_show_* verbs take a libraryPath / stepPath).+ var _fusionPart = null;+ function stageFusionPart(c) {+ if (_fusionPart) { clog(c, "Part already staged this session (reusing)."); return _fusionPart; }+ clog(c, "Staging " + PART.name + ".lbr + .step from " + PART.page + " ...");+ _fusionPart = Promise.all([+ fetchPartText(PART.name + ".lbr"),+ fetchPartBytes(PART.name + ".step")+ ]).then(function (got) {+ return Promise.all([+ stageFile(c, PART.name + ".lbr", b64Text(got[0])),+ stageFile(c, PART.name + ".step", b64Bytes(got[1]))+ ]).then(function (paths) { PART.lbrPath = paths[0]; PART.stepPath = paths[1]; });+ });+ _fusionPart.catch(function () { _fusionPart = null; });+ return _fusionPart;+ }+ // Fusion PROJECT staging — the fusion-bridge's own per-part flow (#30 reply,+ // v1.9.41): (1) fusion_bind_library_3d mints the part's cloud 3D urn and binds+ // it into the .lbr (signed-in Autodesk session required; best-effort — the+ // design still stages without it, just with a placeholder 3D body), then+ // (2) fusion_make_test_project generates a SINGLE-COMPONENT design (John's+ // ruling: just the part — one symbol, one element on a small board) with the+ // part's real refdes. After it, schematic / 2D / 3D board show the real design.+ var _fusionProj = null;+ function stageFusionProject(c) {+ if (_fusionProj) return _fusionProj;+ _fusionProj = stageFusionPart(c).then(function () {+ clog(c, "Binding the part's 3D body into the library (fusion_bind_library_3d; needs a signed-in Autodesk session) ...");+ return postVerb("fusion_bind_library_3d", { libraryPath: PART.lbrPath, stepPath: PART.stepPath }).then(function (r) {+ if (r.s === 200 && r.j && r.j.success) clog(c, " 3D body bound into the library");+ else clog(c, " 3D bind skipped (" + ((r.j && (r.j.error || r.j.errorCode)) || r.s) + ") - continuing; the 3D board may show a placeholder body");+ clog(c, "Generating the single-component design (fusion_make_test_project) ...");+ return postVerb("fusion_make_test_project", { libraryPath: PART.lbrPath });+ }).then(function (r) {+ if (r.s === 200 && r.j && r.j.success) { clog(c, " design generated + open (" + (r.j.reference || PART.name) + ")"); return; }+ throw stageFail("fusion_make_test_project: " + ((r.j && (r.j.error || r.j.errorCode)) || r.s));+ });+ });+ _fusionProj.catch(function () { _fusionProj = null; });+ return _fusionProj;+ }++ // ---- Fusion surfaces: same part, same ladder ----+ // All eight fusion_show_* verbs are live (v1.9.41) and open in the BACKGROUND;+ // fusion_state (fusion-bridge #28) is the render path, so pickTitle:"fusion"+ // pulls the surface's window out of the state array. [- { 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" }+ { label: "Symbol (" + PART.name + ")", cmd: "fusion_show_symbol", args: function () { return { name: PART.name, libraryPath: PART.lbrPath }; }, stage: stageFusionPart, note: "full pinout in the symbol editor" },+ { label: "Footprint (" + PART.name + ")", cmd: "fusion_show_footprint", args: function () { return { name: PART.name, libraryPath: PART.lbrPath }; }, stage: stageFusionPart, note: "pads + layers in the footprint editor" },+ { label: "3D package (" + PART.name + ")", cmd: "fusion_show_3d_package", args: function () { return { name: PART.name, libraryPath: PART.lbrPath, stepPath: PART.stepPath }; }, stage: stageFusionPart, note: "attaches the part's STEP to its footprint" },+ { label: "Library (" + PART.name + ".lbr)", cmd: "fusion_show_library", args: function () { return { libraryPath: PART.lbrPath }; }, stage: stageFusionPart, note: "the Electronics Library with symbol + footprint + 3D" },+ { label: "Schematic (just the " + PART.name + ")", cmd: "fusion_show_schematic", args: function () { return {}; }, stage: stageFusionProject, note: "the generated single-component design's schematic view" },+ { label: "2D board (part placed)", cmd: "fusion_show_2d_board", args: function () { return {}; }, stage: stageFusionProject, note: "the PCB editor view of the staged design" },+ { label: "3D board (real 3D body)", cmd: "fusion_show_3d_board", args: function () { return {}; }, stage: stageFusionProject, note: "populated board in 3D: the bound urn renders the real component" },+ { label: "Project (top of the design)", cmd: "fusion_show_project", args: function () { return {}; }, stage: stageFusionProject, note: "the generated project's top level" } ].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 }); };+ cc.btn.onclick = function () {+ // fallback stays a FUNCTION: its args reference paths the stage step produces.+ showSurface(cc, { showCmd: s.cmd, showArgs: s.args, stage: s.stage, fallback: s.fallback, pickTitle: "fusion", stateCmd: "fusion_state", startCmd: "fusion_start", windowInfoCmd: "fusion_window_info", note: s.note });+ }; }); // Single full-screen shot (keep the simple case too). var cShot = card("grp-do", "verb", "Take a screenshot (whole screen)");