app
Altium - the Altium Bridge
Public Made by Adomby adom
Adom Bridge for Altium Designer: a resident DelphiScript agent gives the AI eval, the 1,100-command process launcher, all 8 EDA surfaces with screenshots, compile/ERC, DRC, OutJob outputs, native SchLib/PcbLib authoring with STEP 3D bodies, and dialog handling by PID.
← Commit history
v0.1.30: background proof capture (restore+bottom, never move the window)
7 files changed
+92−13
CHANGELOG.md+5@@ -1,5 +1,10 @@ # Changelog +## 0.1.30 — 2026-09-01+- **Proof captures work with Altium minimized, without ever moving the window.** Windows does not render a minimized window, so a capture of one returned a ~237x39 title-bar sliver — which the bridge handed back as "proof" of a successful import, and which the wiki front-end would have displayed as evidence. Now every capture first makes the window CAPTURABLE: restore it if minimized, push it to the BOTTOM of the z-order, and hand the user's foreground straight back. Measured end to end at ~0.6 s; the capture then reads the occluded window at full size.+- **The rejected alternative, recorded so nobody rebuilds it:** parking the window off-screen. John, 2026-09-01 — *"i hate the idea of parking an altium window off screen cuz if the user wants to manually foreground, you fuck them over."* Never move a window the user might reach for.+- **A capture narrower than 400 px is refused, not returned.** `_shot_of_main` reports `proofUnavailable` with the reason, and the surface verbs (`show_symbol` / `show_footprint` / `show_3d_chip` / …) return `success: false, errorCode: "proof_unavailable"` rather than a confident nothing. The frame also carries `prepared` — what the bridge had to do to the window — so a caller can say honestly whether the user's screen was touched.+ ## 0.1.29 — 2026-09-01 - **`import_part` no longer reports a silent half-import.** The `add_models` step recorded a bare boolean, so attaching ZERO footprint models looked identical to attaching one — and a symbol with no model places on a schematic with no footprint. It now surfaces the agent's own counts and FAILS with `failedAt: "add_models"` when the count is zero. - **It also sanitises the model spec the way `add_models` always did**: the agent splits models on `;` and fields on `|`, so an unescaped separator inside a DESCRIPTION silently truncated the spec. `import_part` passed the description raw.
adom-bridge-altium-manifest.json+4−4@@ -1,9 +1,9 @@ { "name": "altium",- "version": "0.1.29",- "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.29/adom-bridge-altium-v0.1.29.zip",- "sha256": "03d78b08a236eaf10333233f9fa58b5b8e52942b9c77c5895401fc119413133d",- "size": 128004,+ "version": "0.1.30",+ "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.30/adom-bridge-altium-v0.1.30.zip",+ "sha256": "5c4909ca9c72037891762c97941b2ef57aaf9703684444176350cfe884dbdea7",+ "size": 129499, "verbPrefixes": [ "altium_" ],
adom-bridge-altium-v0.1.30.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "altium", "displayName": "Altium Designer",- "version": "0.1.29",+ "version": "0.1.30", "description": "Drive Altium Designer from Adom: a resident DelphiScript agent (eval + the 1,100-command process launcher), the X2 -R command line, UIA over the VCL shell, dialogs by PID, screenshots of every Altium window, and Altium 365.", "homepage": "https://wiki.adom.inc/adom/altium-bridge", "docs": "https://wiki.adom.inc/adom/altium-bridge",
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "altium-bridge",- "version": "0.1.29",+ "version": "0.1.30", "type": "app", "description": "Skills for your container so your AI knows how to drive the Altium Designer bridge (altium_* verbs). The bridge runtime itself is the release zip; Adom Bridge loads that.", "tags": [
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.1.29+0.1.30
src/server.py+80−6@@ -405,14 +405,77 @@ DEMO_PCB = EXAMPLES_DIR + r"\Bluetooth Sentinel\Bluetooth_Sentinel.PcbDoc" DEMO_SCH = EXAMPLES_DIR + r"\Bluetooth Sentinel\Bluetooth_Sentinel.SchDoc" -def _shot_of_main() -> dict | None:++# ---- background capture (John, 2026-09-01) ----------------------------------------------------+#+# Windows will not render a MINIMIZED window, so a capture of one returns a ~237x39 sliver of the+# title bar — which this bridge used to hand back as "proof" of a successful import. The wiki+# front-end would have displayed that.+#+# The fix John asked for, and it is the right one: make sure the window is in the BACKGROUND but+# NOT minimized, then use ab's window capture (which reads an occluded window fine). The rejected+# alternative was parking the window off-screen: if the user then reaches for Altium manually, they+# find nothing on screen. Never move a window the user might go looking for.++MIN_PROOF_WIDTH = 400 # anything narrower is a minimized-window sliver, not a screenshot+++def _is_minimized(rect: dict) -> bool:+ if not rect:+ return False+ return (rect.get("left", 0) <= -30000 or rect.get("top", 0) <= -30000+ or (rect.get("width", 0) < MIN_PROOF_WIDTH and rect.get("height", 0) < 200))+++def _make_capturable(hwnd: int) -> dict:+ """Restore a minimized window and push it BEHIND the user's windows, then hand the foreground+ straight back. Measured end to end at ~0.6s. Returns what it had to do, so a caller can report+ honestly whether the user's screen was touched."""+ out = {"restored": False, "sentToBack": False, "foregroundReturned": False}+ try:+ before = (caller_identity.ad_callback("desktop_get_foreground", {}, on_behalf=False, timeout=8) or {}).get("hwnd")+ wins = caller_identity.ad_callback("desktop_enum_windows", {"pids": [altium_detect.main_window()["pid"]]},+ on_behalf=False, timeout=10) or {}+ rect = next((w.get("rect") for w in (wins.get("windows") or []) if w.get("hwnd") == hwnd), None)+ if isinstance(rect, list) and len(rect) == 4:+ rect = {"left": rect[0], "top": rect[1], "width": rect[2] - rect[0], "height": rect[3] - rect[1]}+ if _is_minimized(rect):+ caller_identity.ad_callback("desktop_set_window_state", {"hwnd": hwnd, "state": "restore"},+ on_behalf=False, timeout=10)+ out["restored"] = True+ r = caller_identity.ad_callback("desktop_set_window_state", {"hwnd": hwnd, "state": "bottom"},+ on_behalf=False, timeout=10) or {}+ out["sentToBack"] = True+ after = (caller_identity.ad_callback("desktop_get_foreground", {}, on_behalf=False, timeout=8) or {}).get("hwnd")+ if before and after != before:+ caller_identity.ad_callback("desktop_raise_window", {"hwnd": before}, on_behalf=False, timeout=10)+ out["foregroundReturned"] = True+ out["userForeground"] = before+ except Exception as e:+ out["error"] = str(e)+ return out+++def _shot_of_main(prepare: bool = True) -> dict | None:+ """A capture of Altium's main window, or None — never a minimized-window sliver.++ `prepare` restores the window into the BACKGROUND first if it is minimized (see+ `_make_capturable`). A returned frame carries `prepared` so the caller can say what was done.""" mw = altium_detect.main_window() if not mw or not ad_client.available(): return None+ prepared = _make_capturable(mw["hwnd"]) if prepare else None r = caller_identity.ad_callback("desktop_screenshot_base64", {"hwnd": mw["hwnd"]}) if isinstance(r, dict) and (r.get("image") or r.get("base64")):+ w, h = r.get("width"), r.get("height")+ if isinstance(w, int) and w < MIN_PROOF_WIDTH:+ # a sliver: Windows had nothing to render. Say so instead of shipping it as evidence.+ return {"title": mw["title"], "hwnd": mw["hwnd"], "proofUnavailable": True,+ "reason": f"the Altium window is minimized (captured {w}x{h}) — Windows does not "+ f"render a minimized window, so there is no image to prove anything",+ "prepared": prepared} return {"title": mw["title"], "hwnd": mw["hwnd"], "image": r.get("image") or r.get("base64"),- "width": r.get("width"), "height": r.get("height")}+ "width": w, "height": h, "prepared": prepared} return None @@ -495,10 +558,21 @@ def _lib_surface(args, kind: str, default_path: str, label: str, after: list[tup _attach_dialog_shots(dl) shot = _shot_of_main() mw = altium_detect.main_window() or {}- return {"success": not dl, "surface": label, "path": path, "selected": (sel.get("data") or {}).get("selected"),- "steps": steps, "title": mw.get("title"), "dialogsDetected": len(dl), "dialogs": dl, "shot": shot,- "windowEtiquette": "background", "elapsedSec": round(time.time() - t0, 1),- "_hint": f"{label}: {(sel.get('data') or {}).get('selected')} in {mw.get('document')}. shot.image is the frame."}+ # A surface verb exists to produce PROOF. If there is no image, saying success:true would hand+ # the caller (and the wiki front-end) a confident nothing.+ no_proof = bool(shot and shot.get("proofUnavailable"))+ out = {"success": (not dl) and not no_proof, "surface": label, "path": path,+ "selected": (sel.get("data") or {}).get("selected"),+ "steps": steps, "title": mw.get("title"), "dialogsDetected": len(dl), "dialogs": dl, "shot": shot,+ "windowEtiquette": "background", "elapsedSec": round(time.time() - t0, 1),+ "_hint": f"{label}: {(sel.get('data') or {}).get('selected')} in {mw.get('document')}. shot.image is the frame."}+ if no_proof:+ out["errorCode"] = "proof_unavailable"+ out["error"] = shot.get("reason")+ out["_hint"] = ("The surface was opened and selected correctly — only the CAPTURE failed, because "+ "the Altium window is minimized. The bridge restores it into the background "+ "automatically; if it is still minimized, something re-minimized it mid-call.")+ return out def h_show_library(args):