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.28: design-rule hygiene checks
7 files changed
+112−8
CHANGELOG.md+9@@ -1,5 +1,14 @@ # Changelog +## 0.1.28 — 2026-09-01+- **Four design-rule hygiene checks**, all from the rules the board read already carries:+ - `disabled_design_rules` — a rule that exists but is switched OFF. Worse than a missing rule: the board LOOKS ruled, DRC passes, and the constraint was never applied. Raised to error for Clearance / Width / HoleSize / ShortCircuit.+ - `missing_critical_rules` (error) — no ENABLED rule of a kind a fabricator relies on.+ - `duplicate_rule_priority` (info) — two enabled rules of one kind sharing a priority. Altium resolves the tie internally, so which one wins is not readable from the board; usually a copy-paste never re-prioritised.+ - `components_without_footprint` (warning) — a placed part with an empty footprint pattern name, so it cannot be matched back to a library or checked for updates.+- Rule kinds are named from the ordinal table read off a live default rule set; an unknown ordinal renders as `kind <n>` rather than being invented.+- 14 checks.+ ## 0.1.27 — 2026-09-01 - **`review_board` switches documents and returns PARTIAL results.** Altium holds one current document, so a board read and a sheet read cannot both be "now" — with a schematic focused, the previous version failed the whole review because `read_board` had nothing current. It now takes optional `board` and `sheet` paths and activates each in turn (like `cross_check`), and when one document cannot be read it still runs the checks that CAN run. - **`skipped` names every check that did not run and why** — "no board read", "no schematic read — pass includeSchematic:true". A review that silently drops half its checks reads exactly like a clean board, which is the failure mode worth engineering against.
adom-bridge-altium-manifest.json+4−4@@ -1,9 +1,9 @@ { "name": "altium",- "version": "0.1.27",- "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.27/adom-bridge-altium-v0.1.27.zip",- "sha256": "14e69c2e7d8543c9ae4a87ffc415d422ed124e4f32c8457cc558ece41ae89e93",- "size": 126211,+ "version": "0.1.28",+ "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.28/adom-bridge-altium-v0.1.28.zip",+ "sha256": "8cf6cda116e08da60816540c274409aeb752f5125b9135d7ff4bbfb21db09051",+ "size": 127694, "verbPrefixes": [ "altium_" ],
adom-bridge-altium-v0.1.28.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "altium", "displayName": "Altium Designer",- "version": "0.1.27",+ "version": "0.1.28", "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.27",+ "version": "0.1.28", "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.27+0.1.28
src/server.py+96−1@@ -1714,6 +1714,96 @@ def _chk_schematic_placeholders(board, sheet): return out ++# Altium's TRuleKind ordinals, read off a live AD26 default rule set (each default rule is named+# after its own kind, which makes the mapping observable rather than guessed). Unknown ordinals are+# rendered as "kind <n>" — never invented.+RULE_KIND_NAMES = {+ 0: "Clearance", 2: "Width", 6: "PlaneConnect", 7: "RoutingTopology", 8: "RoutingPriority",+ 9: "RoutingLayers", 10: "RoutingCorners", 11: "RoutingVias", 12: "PlaneClearance",+ 13: "SolderMaskExpansion", 14: "PasteMaskExpansion", 15: "ShortCircuit", 16: "UnRoutedNet",+ 20: "PolygonConnect", 24: "ComponentClearance", 42: "HoleSize", 43: "FabricationTestpoint",+ 44: "FabricationTestPointUsage", 48: "LayerPairs", 49: "Fanout", 50: "Height",+ 51: "DiffPairsRouting", 52: "HoleToHoleClearance", 53: "MinimumSolderMaskSliver",+ 54: "SilkToSolderMaskClearance", 55: "SilkToSilkClearance", 56: "NetAntennae",+ 57: "AssemblyTestpoint", 58: "AssemblyTestPointUsage", 59: "SilkToBoardRegionClearance",+ 62: "UnpouredPolygon",+}+# Rules whose absence a fabricator will notice before you do.+CRITICAL_RULE_KINDS = {0: "Clearance", 2: "Width", 42: "HoleSize", 15: "ShortCircuit"}+++def _rule_kind_name(k):+ return RULE_KIND_NAMES.get(k, f"kind {k}")+++def _chk_disabled_design_rules(board, sheet):+ """warning — a design rule that exists but is switched OFF.++ A disabled Clearance or ShortCircuit rule is worse than a missing one: the board LOOKS ruled,+ DRC passes, and the constraint was never applied."""+ out = []+ for r in (board.get("rules") or {}).get("rules") or []:+ if r.get("enabled") is False:+ kind = _rule_kind_name(r.get("kind"))+ sev = "error" if r.get("kind") in CRITICAL_RULE_KINDS else "warning"+ out.append({"check": "disabled_design_rules", "severity": sev, "rule": r.get("name"),+ "message": f"Design rule {r.get('name')!r} ({kind}) is DISABLED — DRC will pass "+ f"without ever applying it."})+ return out+++def _chk_missing_critical_rules(board, sheet):+ """error — no ENABLED rule of a kind a fabricator relies on."""+ rules = (board.get("rules") or {}).get("rules") or []+ if not rules:+ return [] # no rule data at all is not the same as no rules+ have = {r.get("kind") for r in rules if r.get("enabled") is not False}+ out = []+ for kind, name in sorted(CRITICAL_RULE_KINDS.items()):+ if kind not in have:+ out.append({"check": "missing_critical_rules", "severity": "error", "rule": name,+ "message": f"No enabled {name} rule on this board."})+ return out+++def _chk_duplicate_rule_priority(board, sheet):+ """info — two enabled rules of the same kind sharing a priority.++ Altium resolves ties by internal order, so which one wins is not something you can read off the+ board. Usually a copy-paste that was never re-prioritised."""+ seen = {}+ for r in (board.get("rules") or {}).get("rules") or []:+ if r.get("enabled") is False:+ continue+ key = (r.get("kind"), r.get("priority"))+ seen.setdefault(key, []).append(r.get("name"))+ out = []+ for (kind, prio), names in sorted(seen.items(), key=lambda kv: (str(kv[0][0]), str(kv[0][1]))):+ if len(names) > 1:+ out.append({"check": "duplicate_rule_priority", "severity": "info",+ "rule": ", ".join(sorted(names)),+ "message": f"{len(names)} enabled {_rule_kind_name(kind)} rules share priority "+ f"{prio} ({', '.join(sorted(names))}) — which one wins is not readable "+ f"from the board."})+ return out+++def _chk_components_without_footprint(board, sheet):+ """warning — a placed component whose footprint PATTERN is empty.++ A placed part always has geometry, but an empty pattern name means the footprint has no identity:+ it cannot be matched back to a library, compared across a project, or checked for updates."""+ out = []+ for c in (board.get("components") or {}).get("components") or []:+ if not (c.get("pattern") or "").strip():+ out.append({"check": "components_without_footprint", "severity": "warning",+ "designator": c.get("designator"),+ "message": f"{c.get('designator')} has no footprint pattern name — it cannot be "+ f"matched back to a library."})+ 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`,@@ -1730,6 +1820,11 @@ BOARD_CHECKS = [ ("vendor_part_numbers", _chk_vendor_part_numbers), ("missing_datasheets", _chk_missing_datasheets), ("schematic_placeholder_values", _chk_schematic_placeholders),+ # design-rule hygiene, from the rules the board read already carries+ ("disabled_design_rules", _chk_disabled_design_rules),+ ("missing_critical_rules", _chk_missing_critical_rules),+ ("duplicate_rule_priority", _chk_duplicate_rule_priority),+ ("components_without_footprint", _chk_components_without_footprint), ] # Named so a clean result is not mistaken for a complete one.@@ -1774,7 +1869,7 @@ def h_review_board(args): e = _open(args["board"]) if e: return {"success": False, "stage": "board", **e}- r = agent.call("read_board", {"sections": "components,nets,layers,copper,occupancy,outline",+ r = agent.call("read_board", {"sections": "components,nets,layers,copper,rules,occupancy,outline", "limit": args.get("limit", 2000)}, timeout=timeout) bd = r.get("data") or {} if bd.get("ok"):