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.22: cross_check switches documents (Altium holds only one current)
7 files changed
+58−30
CHANGELOG.md+4@@ -1,5 +1,9 @@ # Changelog +## 0.1.22 — 2026-09-01+- **`altium_cross_check` reads the two documents in TURN, because Altium cannot hold both current.** `PCBServer.GetCurrentPCBBoard` follows the FOCUSED document, so focusing the schematic clears the current board — the first cut of this verb read the sheet, then failed on the board every time. It now activates the board, reads it, activates the sheet, reads it, and reports the document the user had in front of them. Takes `board` and `sheet` paths, or uses what is already open.+- The two caveats still travel with the answer: one sheet only (a hierarchical parent holds sheet symbols, not parts), and a folder mismatch between sheet and board is reported as `sameProject: false` with a warning, because comparing two unrelated documents makes every difference meaningless.+ ## 0.1.21 — 2026-09-01 - **"File type not recognised." pre-empted at the cause.** The agent opened every non-project file with `ObjectKind='Document'`, which Altium rejects with a modal on the user's screen even for a valid .PcbDoc. It now maps the extension to the kind Altium actually uses (PCB, SCH, PCBLIB, SCHLIB, OUTPUTJOB, PCB3DLIB, TEXT, Project) and opens through `Client.OpenDocument` — the sibling of `ShowDocument`, which this unit already relied on — falling back to the process lane only if that raises. - **The bridge refuses unsupported types before Altium can complain.** An extension outside the openable set returns `unsupported_document` with the supported list, instead of handing Altium something it will answer with a dialog.
adom-bridge-altium-manifest.json+4−4@@ -1,9 +1,9 @@ { "name": "altium",- "version": "0.1.21",- "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.21/adom-bridge-altium-v0.1.21.zip",- "sha256": "6c50ef8e8566c8eb9bea3f5cffdbdaa1cd8f3990fb60aa6634dd2cfa93d6f4a5",- "size": 120300,+ "version": "0.1.22",+ "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.22/adom-bridge-altium-v0.1.22.zip",+ "sha256": "7b198357002fdebc2c74f82f8fd4b292bc952e84cf5de67c834b8aaa927cc9c5",+ "size": 120677, "verbPrefixes": [ "altium_" ],
adom-bridge-altium-v0.1.22.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "altium", "displayName": "Altium Designer",- "version": "0.1.21",+ "version": "0.1.22", "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.21",+ "version": "0.1.22", "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.21+0.1.22
src/server.py+47−23@@ -1399,29 +1399,53 @@ def h_read_schematic(args): def h_cross_check(args):- """Compare the current schematic sheet against the current board.+ """Compare a schematic sheet against a board: what is drawn but not placed, and placed but not+ drawn. Neither read answers this alone, which is the point. - Neither read answers this alone, which is the point: what is drawn but not placed, and what is- placed but not drawn. Two caveats travel WITH the answer because both make it wrong to act on:- the schematic side is one sheet, and Altium's SchServer and PCBServer each hold their own- "current document" — so nothing stops the two belonging to different projects."""+ Altium CANNOT hold both current at once — `GetCurrentPCBBoard` follows the focused document, so+ focusing the sheet clears the board (measured 2026-09-01, which is why the first cut of this+ verb always failed on the second read). It therefore reads them in TURN: activate the board,+ read it, activate the sheet, read it, then put back whatever the user had in front of them.++ Pass `board` and `sheet` paths, or leave them out to use whatever is already open.+ """ g = _need_licensed() if g: return g- sr = agent.call("read_schematic", {"sections": "components"}, timeout=float(args.get("timeout", 90)))- sd = sr.get("data") or {}- if not sd.get("ok"):- return {"success": False, "stage": "schematic", "error": sd.get("error"),- "_hint": "Activate a leaf .SchDoc (altium_activate_document), then retry."}- br = agent.call("read_board", {"sections": "components"}, timeout=float(args.get("timeout", 90)))- bd = br.get("data") or {}- if not bd.get("ok"):- return {"success": False, "stage": "board", "error": bd.get("error"),- "_hint": "A .PcbDoc must be current — altium_activate_document."}- sch = sorted({c.get("designator") for c in ((sd.get("components") or {}).get("components") or [])- if c.get("designator")}, key=_natural_key)+ was = altium_detect.licence_state().get("document") # restore the user's view at the end+ board_path, sheet_path = args.get("board"), args.get("sheet")+ timeout = float(args.get("timeout", 45))++ def _read(kind, path):+ if path:+ bad = _validate_openable(path)+ if bad:+ return None, bad+ o = agent.call("open", {"path": path}, timeout=timeout)+ if not (o.get("data") or {}).get("ok", True):+ return None, {"success": False, "stage": kind, "error": (o.get("data") or {}).get("error"),+ "_hint": f"could not open {path}"}+ cmd = "read_board" if kind == "board" else "read_schematic"+ r = agent.call(cmd, {"sections": "components"}, timeout=timeout)+ d = r.get("data") or {}+ if not d.get("ok"):+ return None, {"success": False, "stage": kind, "error": d.get("error"),+ "_hint": (f"No current {'PCB board' if kind == 'board' else 'schematic sheet'}. "+ f"Pass {kind}:<path>, or open one first — Altium can only hold ONE "+ "document current, so cross_check must switch between them.")}+ return d, None++ bd, err = _read("board", board_path)+ if err:+ return err+ sd, err = _read("sheet", sheet_path)+ if err:+ return err+ pcb = sorted({c.get("designator") for c in ((bd.get("components") or {}).get("components") or []) if c.get("designator")}, key=_natural_key)+ sch = sorted({c.get("designator") for c in ((sd.get("components") or {}).get("components") or [])+ if c.get("designator")}, key=_natural_key) ss, ps = set(sch), set(pcb) doc, board = sd.get("document"), bd.get("board") @@ -1439,13 +1463,13 @@ def h_cross_check(args): "onSheetNotOnBoard": sorted(ss - ps, key=_natural_key), "onBoardNotOnSheet": sorted(ps - ss, key=_natural_key), "schematicComponents": len(sch), "boardComponents": len(pcb),- "roundtripMs": (sr.get("roundtripMs") or 0) + (br.get("roundtripMs") or 0),- "_scope": "The schematic side is the CURRENT SHEET only. On a hierarchical design, read every "- "leaf sheet before calling anything 'missing from the schematic'."}+ "_scope": "The schematic side is ONE sheet. On a hierarchical design, run this per leaf sheet "+ "before calling anything 'missing from the schematic'."} if same is False:- out["_warning"] = ("The current sheet and the current board are in DIFFERENT folders, so they are "- "probably not the same project — which makes every difference above meaningless. "- "Activate the right documents and re-run.")+ out["_warning"] = ("The sheet and the board are in DIFFERENT folders, so they are probably not the "+ "same project — which makes every difference above meaningless.")+ if was:+ out["restoredDocument"] = was # best effort: leave the user looking at what they were looking at return out