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.26: vendor part number house rule as a check, schematic params
8 files changed
+127−12
CHANGELOG.md+7@@ -1,5 +1,12 @@ # Changelog +## 0.1.26 — 2026-09-01+- **The Adom house rule is now a check.** `vendor_part_numbers` (warning) flags any purchased component missing Mouser / DigiKey / LCSC part numbers — Mouser named first because it is the preferred vendor, so if only one gets filled in, that is the one. Test points, fiducials and mounting holes are exempt: they are not purchased parts, and flagging them would train people to ignore the check.+- `missing_datasheets` (info) and `schematic_placeholder_values` (error) — the latter catches default Comments at the SOURCE, where the BOM is actually generated from, rather than only on placed parts.+- **The schematic read now carries component parameters** (`params: "1"`), using the same `MkSet(eParameter)` iterator the parameter WRITER has always used — so three checks arrived with no new Altium API surface. Capped at 40 parameters per component.+- The three schematic checks return nothing unless `includeSchematic: true`, since without a sheet read they have nothing to judge.+- 10 checks.+ ## 0.1.25 — 2026-09-01 - **`checks:[...]` now filters on the ID you actually see.** The selector was derived from the Python function name (`_chk_components_outside_board`) while findings report `components_outside_board_outline`, so filtering by the visible name silently ran ZERO checks and returned a clean review. Each check now declares its own ID, the reply lists `availableChecks`, and an unrecognised name comes back as `unknownChecks` instead of being ignored. - **New verb `altium_read_board`** — the raw read (components, nets, layers, copper, rules, occupancy, outline) in one round trip. It existed only in the Rust bridge, which meant nobody could see what a check saw; a review's silence had to be taken on trust. Now it can be inspected directly.
adom-bridge-altium-manifest.json+4−4@@ -1,9 +1,9 @@ { "name": "altium",- "version": "0.1.25",- "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.25/adom-bridge-altium-v0.1.25.zip",- "sha256": "480d7fea5b48f0b5423096278512b2a4f597f04674806a095de31e6a464904b1",- "size": 124534,+ "version": "0.1.26",+ "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.26/adom-bridge-altium-v0.1.26.zip",+ "sha256": "4d39499ee8bbf1af001b29043920415852e95cc8d9820393595f49fed75558b7",+ "size": 125669, "verbPrefixes": [ "altium_" ],
adom-bridge-altium-v0.1.26.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "altium", "displayName": "Altium Designer",- "version": "0.1.25",+ "version": "0.1.26", "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.25",+ "version": "0.1.26", "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.25+0.1.26
src/agent/AdomAgent.pas.tmpl+23−4@@ -3327,15 +3327,18 @@ Var PIt : ISch_Iterator; Comp : ISch_Component; Pin : ISch_Pin;- Want, S, PS : String;- N, Limit : Integer;- WantPins : Boolean;+ Prm : ISch_Parameter;+ QIt : ISch_Iterator;+ Want, S, PS, MS : String;+ N, Limit, PN : Integer;+ WantPins, WantParams : Boolean; Begin Doc := SchServer.GetCurrentSchDocument; If Doc = Nil Then Begin Result := '{' + JRaw('ok','false') + ',' + JStr('error','no current schematic document (open a .SchDoc first)') + '}'; Exit; End; Want := Param(Lines, 'designator'); Limit := StrToIntDef(Param(Lines, 'limit'), 500); WantPins := Param(Lines, 'pins') <> '0';+ WantParams := Param(Lines, 'params') = '1'; S := ''; N := 0; Iter := Doc.SchIterator_Create; Iter.AddFilter_ObjectSet(MkSet(eSchComponent));@@ -3359,8 +3362,24 @@ Begin End; Comp.SchIterator_Destroy(PIt); End;+ MS := '';+ If WantParams Then+ Begin+ PN := 0;+ QIt := Comp.SchIterator_Create;+ QIt.AddFilter_ObjectSet(MkSet(eParameter));+ Prm := QIt.FirstSchObject;+ While (Prm <> Nil) And (PN < 40) Do+ Begin+ If MS <> '' Then MS := MS + ',';+ MS := MS + '{' + JStr('name', Prm.Name) + ',' + JStr('value', Prm.Text) + '}';+ PN := PN + 1;+ Prm := QIt.NextSchObject;+ End;+ Comp.SchIterator_Destroy(QIt);+ End; If S <> '' Then S := S + ',';- S := S + '{' + JStr('designator', Comp.Designator.Text) + ',' ++ S := S + '{' + JRaw('params', '[' + MS + ']') + ',' + JStr('designator', Comp.Designator.Text) + ',' + JRaw('partId', IntToStr(Comp.CurrentPartID)) + ',' + JStr('libReference', Comp.LibReference) + ',' + JStr('comment', Comp.Comment.Text) + ',' +
src/server.py+90−1@@ -1386,6 +1386,7 @@ def h_read_schematic(args): if g: return g r = agent.call("read_schematic", {"sections": args.get("sections") or "components",+ "params": "1" if args.get("params", True) else "0", "limit": args.get("limit", 500)}, timeout=float(args.get("timeout", 90))) data = r.get("data") or {}@@ -1630,6 +1631,89 @@ def _chk_components_near_board_edge(board, sheet, margin_mil=20): return out ++# Adom house rule (John, standing): Mouser is the PREFERRED vendor, and every component carries+# Mouser + DigiKey + LCSC part numbers. Encoded as a check so a board can be held to it, rather+# than the rule living only in people's heads.+VENDOR_PATTERNS = {+ "Mouser": ("mouser",),+ "DigiKey": ("digikey", "digi-key", "digi key"),+ "LCSC": ("lcsc", "jlcpcb"),+}+# Designator prefixes that legitimately carry no vendor part number.+NON_PURCHASED_PREFIXES = ("TP", "FID", "MH", "H", "LOGO", "NET", "PWR", "GND")+++def _params_of(comp):+ return {(p.get("name") or "").strip().lower(): (p.get("value") or "").strip()+ for p in (comp.get("params") or []) if p.get("name")}+++def _is_purchased(designator):+ d = (designator or "").upper()+ prefix = "".join(ch for ch in d if ch.isalpha())+ return prefix not in NON_PURCHASED_PREFIXES+++def _chk_vendor_part_numbers(board, sheet):+ """warning — a purchased component missing Mouser / DigiKey / LCSC part numbers.++ Mouser first in the message because it is the preferred vendor: if only one is going to be+ filled in, that is the one. Test points, fiducials and mounting holes are exempt — they are not+ purchased parts and flagging them would train people to ignore the check."""+ if not sheet:+ return []+ out = []+ for c in (sheet.get("components") or {}).get("components") or []:+ des = c.get("designator")+ if not _is_purchased(des):+ continue+ params = _params_of(c)+ blob = " ".join(f"{k} {v}" for k, v in params.items())+ missing = [vendor for vendor, keys in VENDOR_PATTERNS.items()+ if not any(k in blob for k in keys)]+ if missing:+ out.append({"check": "vendor_part_numbers", "severity": "warning", "designator": des,+ "missing": missing,+ "message": f"{des} has no {' / '.join(missing)} part number "+ f"(house rule: Mouser preferred, all three required)."})+ return out+++def _chk_missing_datasheets(board, sheet):+ """info — a purchased component with no datasheet link."""+ if not sheet:+ return []+ out = []+ for c in (sheet.get("components") or {}).get("components") or []:+ des = c.get("designator")+ if not _is_purchased(des):+ continue+ params = _params_of(c)+ if not any(k in ("datasheet", "helpurl", "datasheet link", "datasheeturl") or "datasheet" in k+ for k in params):+ out.append({"check": "missing_datasheets", "severity": "info", "designator": des,+ "message": f"{des} has no Datasheet parameter."})+ return out+++def _chk_schematic_placeholders(board, sheet):+ """error — a schematic component still carrying default text in Comment.++ The board-side check only sees placed parts; this catches it at the source, where it is+ cheaper to fix and where the BOM is actually generated from."""+ if not sheet:+ return []+ out = []+ for c in (sheet.get("components") or {}).get("components") or []:+ com = (c.get("comment") or "").strip()+ if com.lower() in PLACEHOLDER_VALUES:+ out.append({"check": "schematic_placeholder_values", "severity": "error",+ "designator": c.get("designator"),+ "message": f"{c.get('designator')} on the sheet has placeholder Comment {com!r}."})+ return out++ # (id, fn). The ID is what appears in a finding's `check` field, so `checks:["..."]` filters on the # name the caller actually sees. Deriving it from the Python function name instead was a trap: the # function is `_chk_components_outside_board`, the finding says `components_outside_board_outline`,@@ -1642,6 +1726,10 @@ BOARD_CHECKS = [ ("duplicate_designators", _chk_duplicate_designators), ("components_outside_board_outline", _chk_components_outside_board), ("components_near_board_edge", _chk_components_near_board_edge),+ # schematic-side: need includeSchematic:true, and quietly return nothing without it+ ("vendor_part_numbers", _chk_vendor_part_numbers),+ ("missing_datasheets", _chk_missing_datasheets),+ ("schematic_placeholder_values", _chk_schematic_placeholders), ] # Named so a clean result is not mistaken for a complete one.@@ -1674,7 +1762,8 @@ def h_review_board(args): return r sheet = None if args.get("includeSchematic"):- sr = agent.call("read_schematic", {"sections": "components"}, timeout=float(args.get("timeout", 90)))+ sr = agent.call("read_schematic", {"sections": "components", "params": "1"},+ timeout=float(args.get("timeout", 90))) sd = sr.get("data") or {} sheet = sd if sd.get("ok") else None wanted = args.get("checks")