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.23: review_board - five design-review checks over one board read
7 files changed
+175−8
CHANGELOG.md+10@@ -1,5 +1,15 @@ # Changelog +## 0.1.23 — 2026-09-01+- **`altium_review_board`: design review over ONE board read.** Phase 2 of the plan, from the lint catalog. Five checks, all pure functions over data the bridge already reads — so adding them cost no new Altium API surface, which is the whole point: a wrong identifier in the agent halts the script engine behind a modal on the user's screen.+ - `placeholder_values` (error) — a component still carrying Altium's default Comment. It reaches the BOM as "Comment".+ - `duplicate_designators` (error) — the same designator twice on a sheet or board.+ - `single_pin_nets` (warning) — a net with one pad connects to nothing; usually a typo'd label.+ - `unused_mechanical_layers` (info) — an enabled mechanical layer carrying nothing. It travels into fab outputs and is invisible in the editor precisely because it is empty.+ - `off_grid_components` (info) — a placed part off the grid (configurable; a metric board legitimately fails a 5 mil test).+- **A clean result is not claimed to be a complete one.** The reply carries `notImplemented` naming the checks this review cannot see yet and why — board outline, ratsnest, silkscreen geometry, via topology, decoupling. Silence is not a pass.+- Findings name the exact designator, net or layer and say what is wrong; never "verify X". Sorted error → warning → info, with counts.+ ## 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.
adom-bridge-altium-manifest.json+4−4@@ -1,9 +1,9 @@ { "name": "altium",- "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,+ "version": "0.1.23",+ "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.23/adom-bridge-altium-v0.1.23.zip",+ "sha256": "45c55098e23334a1b3b98407c2835396a0c26d21db52a0a4a9ff98b87fc28dd7",+ "size": 122949, "verbPrefixes": [ "altium_" ],
adom-bridge-altium-v0.1.23.zipadded⋯ 1 unchanged line ⋯
bridge.json+2−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "altium", "displayName": "Altium Designer",- "version": "0.1.22",+ "version": "0.1.23", "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",@@ -99,6 +99,7 @@ "altium_remove_symbol", "altium_rename_footprint", "altium_rename_symbol",+ "altium_review_board", "altium_run_command", "altium_run_drc", "altium_run_erc",
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "altium-bridge",- "version": "0.1.22",+ "version": "0.1.23", "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.22+0.1.23
src/server.py+157−1@@ -1473,6 +1473,161 @@ def h_cross_check(args): return out ++# ---- design review: checks over ONE board read (John's lint catalog, phase 2) -----------------+#+# Every check below is a PURE FUNCTION over data the bridge already reads, so adding one costs no+# new Altium API surface — which matters because a wrong identifier in the agent halts the script+# engine behind a modal on the user's screen. Checks that need geometry Altium has not been asked+# for yet (board outline, silkscreen overlap, ratsnest) are deliberately absent rather than guessed;+# `notImplemented` in the reply names them so nobody mistakes silence for a clean board.++PLACEHOLDER_VALUES = {"comment", "value", "*", "?", "n/a", "na", "tbd", "xxx", "component",+ "designator", "part", "unknown"}+++def _chk_placeholder_values(board, sheet):+ """error — a component still carrying Altium's default text instead of a real value/MPN.++ Ships in real designs surprisingly often: the part is placed, nobody filled the Comment, and it+ reaches the BOM as "Comment"."""+ out = []+ for c in (board.get("components") or {}).get("components") or []:+ com = (c.get("comment") or "").strip()+ if com.lower() in PLACEHOLDER_VALUES:+ out.append({"check": "placeholder_values", "severity": "error",+ "designator": c.get("designator"),+ "message": f"{c.get('designator')} has placeholder Comment {com!r} — it will reach the BOM as-is."})+ return out+++def _chk_single_pin_nets(board, sheet):+ """warning — a net with exactly one pad connects to nothing. Usually a typo'd net label or a+ wire that never landed."""+ out = []+ for n in (board.get("nets") or {}).get("nets") or []:+ pads = n.get("pads")+ if isinstance(pads, int) and pads == 1:+ out.append({"check": "single_pin_nets", "severity": "warning", "net": n.get("name"),+ "message": f"Net {n.get('name')!r} has a single pad — it connects to nothing."})+ return out+++def _chk_unused_mechanical_layers(board, sheet):+ """info — a mechanical layer that is enabled but carries nothing.++ Empty enabled layers travel into fabrication outputs and confuse the manufacturer, and they are+ invisible in the editor precisely BECAUSE they are empty."""+ occupied = {l.get("layerId") for l in (board.get("occupancy") or {}).get("occupancy") or []}+ out = []+ for l in (board.get("layers") or {}).get("layers") or []:+ if l.get("kind") != "mechanical" or not l.get("displayed"):+ continue+ if l.get("id") not in occupied:+ out.append({"check": "unused_mechanical_layers", "severity": "info",+ "layer": l.get("name"),+ "message": f"Mechanical layer {l.get('name')!r} is enabled but carries no primitives."})+ return out+++def _chk_off_grid_components(board, sheet, grid_mil=5):+ """info — a placed component whose origin is off the placement grid.++ Off-grid parts are usually an accidental drag; they make later alignment and panelisation+ fiddly. Grid is configurable because a metric board legitimately fails a 5 mil test."""+ out = []+ for c in (board.get("components") or {}).get("components") or []:+ x, y = c.get("xMil"), c.get("yMil")+ if not isinstance(x, (int, float)) or not isinstance(y, (int, float)):+ continue+ if (round(x) % grid_mil) or (round(y) % grid_mil):+ out.append({"check": "off_grid_components", "severity": "info",+ "designator": c.get("designator"),+ "message": f"{c.get('designator')} sits at ({x}, {y}) mil — off the {grid_mil} mil grid."})+ return out+++def _chk_duplicate_designators(board, sheet):+ """error — the same designator twice on one sheet. Altium's compiler catches collisions across+ a project; this catches the within-sheet case from data already read."""+ out = []+ for src, label in ((sheet, "sheet"), (board, "board")):+ if not src:+ continue+ seen = {}+ comps = (src.get("components") or {}).get("components") or []+ for c in comps:+ d = c.get("designator")+ if d:+ seen[d] = seen.get(d, 0) + 1+ for d, n in sorted(seen.items()):+ if n > 1:+ out.append({"check": "duplicate_designators", "severity": "error", "designator": d,+ "message": f"Designator {d} occurs {n} times on the {label}."})+ return out+++BOARD_CHECKS = [_chk_placeholder_values, _chk_single_pin_nets, _chk_unused_mechanical_layers,+ _chk_off_grid_components, _chk_duplicate_designators]++# Named so a clean result is not mistaken for a complete one.+REVIEW_NOT_IMPLEMENTED = {+ "components_outside_board_outline": "needs the board outline, which read_board does not carry yet",+ "pads_near_board_edge": "needs the board outline",+ "unrouted_connections": "needs the ratsnest",+ "silkscreen_over_pad": "needs per-layer primitive geometry",+ "via_antennas": "needs via/track topology",+ "missing_decoupling": "needs net-to-part topology",+}+++def h_review_board(args):+ """Run the design-review checks over ONE board read (plus the current sheet, if asked).++ One agent round trip, all the analysis in the bridge. Findings name the exact designator, net or+ layer and say what is wrong — never 'verify X'. `severity` is error | warning | info.+ """+ g = _need_licensed()+ if g:+ return g+ r = agent.call("read_board", {"sections": "components,nets,layers,copper,occupancy",+ "limit": args.get("limit", 2000)},+ timeout=float(args.get("timeout", 90)))+ board = r.get("data") or {}+ if not board.get("ok"):+ return r+ sheet = None+ if args.get("includeSchematic"):+ sr = agent.call("read_schematic", {"sections": "components"}, timeout=float(args.get("timeout", 90)))+ sd = sr.get("data") or {}+ sheet = sd if sd.get("ok") else None+ wanted = args.get("checks")+ findings = []+ ran = []+ for fn in BOARD_CHECKS:+ name = fn.__name__.replace("_chk_", "")+ if wanted and name not in wanted:+ continue+ ran.append(name)+ try:+ findings.extend(fn(board, sheet))+ except Exception as e: # a broken check must not lose the other checks' findings+ findings.append({"check": name, "severity": "info",+ "message": f"check {name} failed to run: {e}"})+ order = {"error": 0, "warning": 1, "info": 2}+ findings.sort(key=lambda f: (order.get(f.get("severity"), 9), f.get("check"), str(f.get("designator") or f.get("net") or f.get("layer") or "")))+ counts = {}+ for f in findings:+ counts[f["severity"]] = counts.get(f["severity"], 0) + 1+ return {"success": True, "board": board.get("board"),+ "findings": findings, "counts": counts, "checksRun": ran,+ "notImplemented": REVIEW_NOT_IMPLEMENTED,+ "roundtripMs": r.get("roundtripMs"),+ "_hint": ("A clean result means the checks that RAN found nothing — see notImplemented for "+ "what this review cannot see yet (board outline, ratsnest, silkscreen geometry). "+ "Add includeSchematic:true to also read the current sheet.")}++ # ---- licence lifecycle: status, ensure-licensed, trial setup (John 2026-08-31) ------------ def h_license_status(args):@@ -2040,6 +2195,7 @@ _VERB_CATALOG = { "close": "WM_CLOSE the main window; reports a save prompt instead of answering it; force:true taskkills.", "activate_document": "Make an ALREADY-OPEN document current by invoking its document-bar tab through UIA — no cursor, no foreground.", "layers_in_use": "How many layers the board ACTUALLY has: copper layers carrying primitives, densest first (a floor, not the stack).",+ "review_board": "Design review over one board read: placeholder values, single-pin nets, unused mechanical layers, off-grid parts, duplicate designators.", "read_schematic": "Components + pins of the CURRENT schematic sheet (one round trip).", "cross_check": "Schematic sheet vs board: what is drawn but not placed, and placed but not drawn.", "instances": "Every running Altium (Designer, Designer Develop, ...) with the pid/key to address it. Other verbs take instance:<pid|key|word>.",@@ -2106,7 +2262,7 @@ COMMAND_HANDLERS = { "window_info": h_window_info, "state": h_state, "check_dialogs": h_check_dialogs, "dismiss_dialogs": h_dismiss_dialogs, "launch": h_launch, "close": h_close, "activate_document": h_activate_document, "layers_in_use": h_layers_in_use,- "instances": h_instances, "read_schematic": h_read_schematic, "cross_check": h_cross_check,+ "instances": h_instances, "review_board": h_review_board, "read_schematic": h_read_schematic, "cross_check": h_cross_check, "agent_start": h_agent_start, "agent_stop": h_agent_stop, "agent_status": h_agent_status, "agent_recover": h_agent_recover, "eval": h_eval, "run_command": h_run_command, "run_script": h_run_script,