← Commit history

0.1.1: settle #1 and #2: the y-down frame, isolatedCopper (pour islands not counted), fractured fills, custom pads with copperPoints, pin types by name, replies in mm (unitsNote on the reference's mil verbs), references sorted by version

Colby Knox ·70f72bb960 ·15d ago ·parent be85e82
10 files changed +512−73
README.md+7−3
@@ -80,7 +80,11 @@ under another name. When the reference bridge gains a verb, give it a tier and r  ## Status -Version 0.1: the verbs, the board model and the conventions, with Altium bridge 0.2.75 as the reference. Next: the-checks tier as a shared library and command-line tool every bridge can call, and fixture boards (the same small board-as `.PcbDoc`, `.kicad_pcb` and Fusion) with the expected board model and check results, so a bridge can prove it+Version 0.1.1: the verbs, the board model and the conventions, with Altium bridge 0.2.75 as the reference. 0.1.1+settles the questions the KiCad and Fusion bridges raised as they adopted the board model (the y-down frame, pour+islands, fractured fills, custom pads, reply units): the answers are in [docs/board-model.md](docs/board-model.md#settled-questions).++Next: the checks tier as a shared Rust crate and command-line tool every bridge can call. It takes the board model and+its companions as plain JSON and calls no bridge, so a bridge can link it or run it. Then fixture boards (the same small+board as `.PcbDoc`, `.kicad_pcb` and Fusion) with the expected board model and check results, so a bridge can prove it conforms.
SKILL.md+7−2
@@ -47,8 +47,13 @@ with its own core verbs (`route_net`, `place_vias`, `apply_silkscreen`).  ## Rules that bite -- Millimetres, the tool's frame, y up, absolute: what `routing_state` reports is what `route_net` takes.+- Millimetres, absolute, the tool's origin with y up (a y-down tool negates y, nothing else): what `routing_state`+  reports is what `route_net` takes. - Copper layers as `F.Cu`, `In<n>.Cu`, `B.Cu`, `*.Cu` on every tool.-- A custom-shape pad is reported as its bounding rectangle, never 0 x 0.+- A custom-shape pad is reported as its bounding rectangle, never 0 x 0, with its real outline in `copperPoints` when+  the bridge can read it.+- `unconnected` is computed from the copper; poured copper that reaches nothing else of its net goes in+  `isolatedCopper`, not the count. A stray track or via counts.+- Replies in mm, unit-named fields as `xMm`. - Net-less copper (a hatched pour's pieces) stays in the model with an empty net. - Fab limits come from adom/pcb-design-rules, with the vendor page and date in the reply.
docs/board-model.md+36−11
@@ -26,29 +26,54 @@ layer names.  ## Rules a bridge must keep -- **Millimetres, the tool's frame, y up, absolute.** No offset from the board origin: the checks compare against the-  outline the same reply gives.+- **Millimetres, absolute, in the standard frame: the tool's origin and x axis, y up.** A tool whose y points down+  (KiCad) negates y and nothing else: same origin, same x, and angles stay counterclockwise as seen on screen, so the+  numbers need no change. Altium and Fusion are y up already. No offset from the board origin: the checks compare+  against the outline the same reply gives. A bridge whose existing callers read its native frame may keep that as its+  default and answer the standard model on request (KiCad: `model: "standard"`), as long as its edit verbs take the+  standard model's coordinates back (KiCad: `frame: "standard"`). - **Copper layers by KiCad's names** (`F.Cu`, `In<n>.Cu`, `B.Cu`, `*.Cu`). Non-copper layers may use the tool's own   name or id (Altium sends `L57` for Mechanical 1).-- **A custom-shape pad is its bounding rectangle** (`shape: "rect"`, rotation 0). Altium's API gives such a pad no-  size; reported as 0 x 0, every clearance check walked straight through the Bluetooth Sentinel's 5 mm coin cell contacts.+- **Pad shapes:** `round` (a circle when `sizeX` = `sizeY`, otherwise a stadium with semicircle ends on the long axis;+  a tool's oval or obround is `round`), `circle`, `rect`, `roundrect`, `octagonal`, `rotated-rect`, `other`.+- **A custom-shape pad is its bounding rectangle** (`shape: "rect"`, rotation 0), with its real outline in+  `copperPoints` when the bridge can read it (Fusion can). Checks use `copperPoints` when present. Altium's API gives+  such a pad no size; reported as 0 x 0, every clearance check walked straight through the Bluetooth Sentinel's 5 mm+  coin cell contacts. - **Net-less copper is reported with an empty net**, not dropped: a hatched pour's pieces arrive that way from Altium,   and the copper checks count them. - **`unconnected` is computed from the copper**, not taken from the tool's ratsnest (Altium's ratsnest skips parts-  outside the board). A bridge may add the tool's own count under its own key (`altiumRatsnest`).-- **`revision` changes when copper, parts or the outline change**, and only then: it is the edit verbs' guard.+  outside the board; Fusion's draws airwires to track ends 5 to 7.5 um off a pad's anchor but over its copper). Per+  net, pads, tracks, vias and poured copper join where they touch on a shared layer; every group beyond the first is a+  connection still to make. The tool's own count may ride along under its own key (`altiumRatsnest`,+  `fusionAirwires`).+- **Poured copper that reaches nothing else of its net is `isolatedCopper`**, listed and not counted: an island a pour+  left behind, or a pour that touches no pad, track or via of its net. KiCad's DRC treats it the same way (a warning,+  not an unconnected item). A stray track or via still counts: copper that reaches nothing is a connection not made.+- **`revision` changes when copper, parts or the outline change**, and only then: it is the edit verbs' guard. It must+  be stable for an unchanged board; independent of item order is better (KiCad's is). - **The outline as edges, arcs kept.** Measuring against a curved edge needs the arc; sample it every 2 degrees (16   points per arc put copper 0.04 mm outside the Sentinel's round edge).-- **`polygons` is the pour as drawn.** The copper actually poured (regions minus holes) is the pours model-  (`pour_state`): checks that need real copper read that.+- **`polygons` is the pour as drawn**, `kind: "zone"` (the default) or `"graphic"` for a filled copper shape. The copper+  actually poured is the pours model (`pour_state`): checks that need real copper read that.  ## The companion models  | Model | Verb | What reads it | |---|---|---| | Stack-up | `layer_stack` | impedance, current capacity, fab check (copper weight, thickness), copper balance, return paths (planes) |-| Poured copper | `pour_state` | copper balance, return paths and their fix plans |-| 3D bodies | `bodies` (Altium: an agent call behind `height_check`) | height check, placement check |-| Sheet parts with pin types | `read_sheet_file` (Altium reads the .SchDoc file itself) | schematic review, pin-to-pad check |+| Poured copper | `pour_state` | copper balance, return paths and their fix plans. Island by island, outline and holes, filled by the even-odd rule: a fractured fill (KiCad: holes joined to the outline by zero-width cuts, `holes: []`, `fractured: true`) is the same copper and needs no separating |+| 3D bodies | `bodies` (Altium: an agent call behind `height_check`) | height check, placement check. One box per body (Altium) or per part (Fusion); z from the surface the part sits on |+| Sheet parts with pin types | `read_sheet_file` (Altium reads the .SchDoc file itself) | schematic review, pin-to-pad check. Pin types by name (`input`, `io`, `output`, `power`, `supply`, `not_connected`...); Altium's codes are accepted too | | Netlist | `netlist` | schematic review | | Silkscreen | `silk_state` | designator placement, fab check (text height, line width) |++## Settled questions++| Question | Answer | Where |+|---|---|---|+| What does "y up" mean on a y-down tool? | Negate y; keep the origin, x and the angles | [#1](https://wiki.adom.inc/adom/eda-bridge-standard/issues/1) |+| Do pour islands count as unconnected? | No: `isolatedCopper`, listed, not counted. Strays still count | [#1](https://wiki.adom.inc/adom/eda-bridge-standard/issues/1), [#2](https://wiki.adom.inc/adom/eda-bridge-standard/issues/2) |+| Must fractured fills be split into outline and holes? | No: even-odd filling gives the same copper | [#1](https://wiki.adom.inc/adom/eda-bridge-standard/issues/1) |+| Custom pads? | Bounding rectangle, plus `copperPoints` when known | [#2](https://wiki.adom.inc/adom/eda-bridge-standard/issues/2) |+| Units in replies? | mm, and a unit-named field says mm (`xMm`); see conventions | [#2](https://wiki.adom.inc/adom/eda-bridge-standard/issues/2) |
docs/conventions.md+5−1
@@ -16,7 +16,11 @@ the same way, and lets the shared checks run on any of them.  - Millimetres everywhere in replies and arguments; degrees for angles, counterclockwise from +x. An argument in other   units says so in its name (`copperUm`, `wMil`).-- Coordinates are the tool's own board frame, y up, absolute: what `routing_state` reports is what `route_net` takes.+- Coordinates are absolute in the standard frame: the tool's origin and x axis, y up (a y-down tool negates y and+  nothing else). What `routing_state` reports is what `route_net` takes. See [board-model.md](board-model.md).+- A reply field that carries its unit in its name says mm: `xMm`, `widthMm`, `areaMm2`. Some of the reference bridge's+  older verbs still answer in mils (`xMil`, marked `unitsNote` in `verbs/standard.json`); that is the reference's debt,+  not the standard. A new bridge answers those verbs in mm under `Mm` names. - Copper layers carry KiCad's names on every tool: `F.Cu`, `In1.Cu` to `In30.Cu`, `B.Cu`, and `*.Cu` for a pad on every   copper layer. A layer argument also takes the tool's own id. - Nets are named as the design names them; matching is case-insensitive.
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "eda-bridge-standard",-  "version": "0.1.0",+  "version": "0.1.1",   "type": "skill",   "title": "EDA Bridge Standard: one way for an AI to drive every PCB tool",   "description": "The standard every Adom EDA bridge follows so one AI can drive Altium, KiCad, Fusion 360 and any future PCB tool the same way: shared verb names in tiers, the board model JSON, reply and refusal conventions, and a coverage table of each bridge against the Altium reference.",
page.json+1−1
@@ -4,7 +4,7 @@   "type": "skill",   "title": "EDA Bridge Standard: one way for an AI to drive every PCB tool",   "brief": "The standard every Adom EDA bridge follows so one AI can drive Altium, KiCad, Fusion 360 and any future PCB tool the same way: shared verb names in tiers, the board model JSON, reply and refusal conventions, and a coverage table of each bridge against the Altium reference.",-  "version": "0.1.0",+  "version": "0.1.1",   "hero": {     "type": "billboard",     "headline": "One standard for every EDA bridge",
schema/board.schema.json+408−32
@@ -2,41 +2,417 @@   "$schema": "https://json-schema.org/draft/2020-12/schema",   "$id": "https://wiki.adom.inc/adom/eda-bridge-standard/schema/board.schema.json",   "title": "Board model (routing_state)",-  "description": "One PCB as every bridge's <prefix>_routing_state returns it. Millimetres, the tool's own frame (y up), absolute board coordinates. Copper layers carry KiCad's names whatever the tool: F.Cu, In1.Cu..In30.Cu, B.Cu, and *.Cu for a pad on every copper layer. Reference: altium-bridge 0.2.75.",+  "description": "One PCB as every bridge's <prefix>_routing_state returns it. Millimetres, absolute board coordinates in the standard frame: the tool's origin and x axis, y up (a y-down tool such as KiCad negates y), angles counterclockwise as seen. Copper layers carry KiCad's names whatever the tool: F.Cu, In1.Cu..In30.Cu, B.Cu, and *.Cu for a pad on every copper layer. Reference: altium-bridge 0.2.75; settled points: docs/board-model.md.",   "type": "object",-  "required": ["units", "revision", "nets", "tracks", "arcs", "vias", "pads", "components", "polygons", "outline", "outlineEdges", "unconnected"],+  "required": [+    "units",+    "revision",+    "nets",+    "tracks",+    "arcs",+    "vias",+    "pads",+    "components",+    "polygons",+    "outline",+    "outlineEdges",+    "unconnected"+  ],   "$defs": {-    "xy": {"type": "object", "required": ["x", "y"], "properties": {"x": {"type": "number"}, "y": {"type": "number"}}},-    "pt": {"type": "array", "items": {"type": "number"}, "minItems": 2, "maxItems": 2, "description": "[x, y] mm"},-    "layer": {"type": "string", "description": "F.Cu | In<n>.Cu | B.Cu | *.Cu for copper; a non-copper layer is the tool's own name or id (L57 on Altium is Mechanical 1)"},-    "net": {"type": "string", "description": "the net's name; empty for no net (a hatched pour's pieces, free copper)"}+    "xy": {+      "type": "object",+      "required": [+        "x",+        "y"+      ],+      "properties": {+        "x": {+          "type": "number"+        },+        "y": {+          "type": "number"+        }+      }+    },+    "pt": {+      "type": "array",+      "items": {+        "type": "number"+      },+      "minItems": 2,+      "maxItems": 2,+      "description": "[x, y] mm"+    },+    "layer": {+      "type": "string",+      "description": "F.Cu | In<n>.Cu | B.Cu | *.Cu for copper; a non-copper layer is the tool's own name or id (L57 on Altium is Mechanical 1)"+    },+    "net": {+      "type": "string",+      "description": "the net's name; empty for no net (a hatched pour's pieces, free copper)"+    }   },   "properties": {-    "units": {"const": "mm"},-    "file": {"type": ["string", "null"], "description": "the board file's path"},-    "boardId": {"type": ["string", "null"], "description": "identifies the board; the file path where there is one"},-    "revision": {"type": "string", "description": "a hash of copper, parts and outline: stable for an unchanged board. Edit verbs take it back as expectedRevision and refuse with stale_board when the board moved on"},-    "counts": {"type": "object", "description": "tracks, arcs, vias, pads, components, polygons, nets"},-    "unconnected": {"type": "integer", "description": "connections still to make, computed from the copper itself (pads, tracks, vias and pours joined where they touch on a shared layer)"},-    "splitNets": {"type": "array", "description": "nets whose copper is in more than one piece"},-    "nets": {"type": "array", "items": {"type": "string"}},-    "tracks": {"type": "array", "items": {"type": "object", "required": ["start", "end", "width", "layer", "net"],-      "properties": {"start": {"$ref": "#/$defs/xy"}, "end": {"$ref": "#/$defs/xy"}, "width": {"type": "number"}, "layer": {"$ref": "#/$defs/layer"}, "layerId": {"description": "the tool's own layer id (optional)"}, "net": {"$ref": "#/$defs/net"}}}},-    "arcs": {"type": "array", "items": {"type": "object", "required": ["center", "radius", "startAngle", "endAngle", "width", "layer", "net"],-      "properties": {"center": {"$ref": "#/$defs/xy"}, "radius": {"type": "number"}, "startAngle": {"type": "number", "description": "degrees, counterclockwise from +x"}, "endAngle": {"type": "number"}, "width": {"type": "number"}, "layer": {"$ref": "#/$defs/layer"}, "net": {"$ref": "#/$defs/net"}}}},-    "vias": {"type": "array", "items": {"type": "object", "required": ["position", "size", "drill", "layers", "net"],-      "properties": {"position": {"$ref": "#/$defs/xy"}, "size": {"type": "number", "description": "diameter mm"}, "drill": {"type": "number"}, "layers": {"type": "array", "items": {"$ref": "#/$defs/layer"}, "description": "[from, to]"}, "net": {"$ref": "#/$defs/net"}}}},-    "pads": {"type": "array", "items": {"type": "object", "required": ["ref", "name", "x", "y", "sizeX", "sizeY", "rotation", "layer", "drill", "net"],-      "properties": {"ref": {"type": "string", "description": "the part's designator; empty for a free pad (a mounting hole, a fiducial)"}, "name": {"type": "string", "description": "the pad's number/name"},-        "x": {"type": "number"}, "y": {"type": "number"}, "sizeX": {"type": "number"}, "sizeY": {"type": "number"}, "rotation": {"type": "number", "description": "degrees"},-        "layer": {"$ref": "#/$defs/layer"}, "drill": {"type": "number", "description": "0 for SMD"}, "net": {"$ref": "#/$defs/net"},-        "shape": {"type": "string", "description": "round | rect | roundrect | octagonal | other. A custom-shape pad (copper drawn as regions) is reported as its bounding rectangle: rect, rotation 0"}}}},-    "components": {"type": "array", "items": {"type": "object", "required": ["ref", "x", "y", "rotation", "side"],-      "properties": {"ref": {"type": "string"}, "x": {"type": "number"}, "y": {"type": "number"}, "rotation": {"type": "number"}, "side": {"enum": ["top", "bottom"]}, "pattern": {"type": "string", "description": "the footprint's name"}, "has3d": {"type": ["boolean", "null"]}}}},-    "polygons": {"type": "array", "description": "pours as drawn (their boundary). What was actually poured is the pours model (pour_state)", "items": {"type": "object", "required": ["net", "layer", "points"],-      "properties": {"net": {"$ref": "#/$defs/net"}, "layer": {"$ref": "#/$defs/layer"}, "points": {"type": "array", "items": {"$ref": "#/$defs/pt"}}}}},-    "outline": {"type": "array", "items": {"$ref": "#/$defs/pt"}, "description": "the board outline's vertices in order"},-    "outlineEdges": {"type": "array", "description": "the outline as edges: line, or arc (center, radius, angle1, angle2 in degrees). Sample arcs finely (every 2 degrees) before measuring against them", "items": {"type": "object", "required": ["kind", "start"],-      "properties": {"kind": {"enum": ["line", "arc"]}, "start": {"$ref": "#/$defs/pt"}, "end": {"$ref": "#/$defs/pt"}, "center": {"$ref": "#/$defs/pt"}, "radius": {"type": "number"}, "angle1": {"type": "number"}, "angle2": {"type": "number"}}}}+    "units": {+      "const": "mm"+    },+    "file": {+      "type": [+        "string",+        "null"+      ],+      "description": "the board file's path"+    },+    "boardId": {+      "type": [+        "string",+        "null"+      ],+      "description": "identifies the board; the file path where there is one"+    },+    "revision": {+      "type": "string",+      "description": "a hash of copper, parts and outline: it changes when they change and only then. Edit verbs take it back as expectedRevision and refuse with stale_board when the board moved on. Independent of item order is better (KiCad's is); stable for an unchanged board is required"+    },+    "counts": {+      "type": "object",+      "description": "tracks, arcs, vias, pads, components, polygons, nets"+    },+    "unconnected": {+      "type": "integer",+      "description": "connections still to make, computed from the copper itself: per net, pads, tracks, vias and poured copper joined where they touch on a shared layer, and every group beyond the first is one connection. A group that is only poured copper (an island, or a pour that reaches no pad) is not counted: it goes in isolatedCopper. A stray track or via is counted. The tool's own count may ride along under its own key (altiumRatsnest, fusionAirwires)"+    },+    "splitNets": {+      "type": "array",+      "description": "nets whose copper is in more than one piece"+    },+    "isolatedCopper": {+      "type": "array",+      "description": "poured copper that touches nothing else of its net: listed, not counted in unconnected (KiCad's DRC calls it isolated copper, a warning)",+      "items": {+        "type": "object",+        "required": [+          "net",+          "layer"+        ],+        "properties": {+          "net": {+            "$ref": "#/$defs/net"+          },+          "layer": {+            "$ref": "#/$defs/layer"+          },+          "at": {+            "$ref": "#/$defs/xy",+            "description": "a point on or in it"+          },+          "areaMm2": {+            "type": "number"+          }+        }+      }+    },+    "nets": {+      "type": "array",+      "items": {+        "type": "string"+      }+    },+    "tracks": {+      "type": "array",+      "items": {+        "type": "object",+        "required": [+          "start",+          "end",+          "width",+          "layer",+          "net"+        ],+        "properties": {+          "start": {+            "$ref": "#/$defs/xy"+          },+          "end": {+            "$ref": "#/$defs/xy"+          },+          "width": {+            "type": "number"+          },+          "layer": {+            "$ref": "#/$defs/layer"+          },+          "layerId": {+            "description": "the tool's own layer id (optional)"+          },+          "net": {+            "$ref": "#/$defs/net"+          }+        }+      }+    },+    "arcs": {+      "type": "array",+      "items": {+        "type": "object",+        "required": [+          "center",+          "radius",+          "startAngle",+          "endAngle",+          "width",+          "layer",+          "net"+        ],+        "properties": {+          "center": {+            "$ref": "#/$defs/xy"+          },+          "radius": {+            "type": "number"+          },+          "startAngle": {+            "type": "number",+            "description": "degrees, counterclockwise from +x"+          },+          "endAngle": {+            "type": "number"+          },+          "width": {+            "type": "number"+          },+          "layer": {+            "$ref": "#/$defs/layer"+          },+          "net": {+            "$ref": "#/$defs/net"+          }+        }+      }+    },+    "vias": {+      "type": "array",+      "items": {+        "type": "object",+        "required": [+          "position",+          "size",+          "drill",+          "layers",+          "net"+        ],+        "properties": {+          "position": {+            "$ref": "#/$defs/xy"+          },+          "size": {+            "type": "number",+            "description": "diameter mm"+          },+          "drill": {+            "type": "number"+          },+          "layers": {+            "type": "array",+            "items": {+              "$ref": "#/$defs/layer"+            },+            "description": "[from, to]"+          },+          "net": {+            "$ref": "#/$defs/net"+          }+        }+      }+    },+    "pads": {+      "type": "array",+      "items": {+        "type": "object",+        "required": [+          "ref",+          "name",+          "x",+          "y",+          "sizeX",+          "sizeY",+          "rotation",+          "layer",+          "drill",+          "net"+        ],+        "properties": {+          "ref": {+            "type": "string",+            "description": "the part's designator; empty for a free pad (a mounting hole, a fiducial)"+          },+          "name": {+            "type": "string",+            "description": "the pad's number/name"+          },+          "x": {+            "type": "number"+          },+          "y": {+            "type": "number"+          },+          "sizeX": {+            "type": "number"+          },+          "sizeY": {+            "type": "number"+          },+          "rotation": {+            "type": "number",+            "description": "degrees, counterclockwise as seen"+          },+          "layer": {+            "$ref": "#/$defs/layer"+          },+          "drill": {+            "type": "number",+            "description": "0 for SMD"+          },+          "net": {+            "$ref": "#/$defs/net"+          },+          "shape": {+            "type": "string",+            "description": "round (a circle when sizeX = sizeY, otherwise a stadium: semicircle ends on the long axis) | circle | rect | roundrect | octagonal | rotated-rect | other. A tool's oval or obround is round. A custom-shape pad (copper drawn as regions) is its bounding rectangle: rect, rotation 0, with its real outline in copperPoints when the bridge has it"+          },+          "customShape": {+            "type": "boolean",+            "description": "the pad's copper is a drawn shape, not a primitive"+          },+          "copperPoints": {+            "type": "array",+            "items": {+              "$ref": "#/$defs/pt"+            },+            "description": "a custom pad's real copper outline, absolute mm. Checks use it when present and the bounding rectangle otherwise. One drawn polygon can join several pads (a power FET's drain)"+          }+        }+      }+    },+    "components": {+      "type": "array",+      "items": {+        "type": "object",+        "required": [+          "ref",+          "x",+          "y",+          "rotation",+          "side"+        ],+        "properties": {+          "ref": {+            "type": "string"+          },+          "x": {+            "type": "number"+          },+          "y": {+            "type": "number"+          },+          "rotation": {+            "type": "number"+          },+          "side": {+            "enum": [+              "top",+              "bottom"+            ]+          },+          "pattern": {+            "type": "string",+            "description": "the footprint's name"+          },+          "has3d": {+            "type": [+              "boolean",+              "null"+            ]+          }+        }+      }+    },+    "polygons": {+      "type": "array",+      "description": "pours as drawn (their boundary). What was actually poured is the pours model (pour_state)",+      "items": {+        "type": "object",+        "required": [+          "net",+          "layer",+          "points"+        ],+        "properties": {+          "net": {+            "$ref": "#/$defs/net"+          },+          "layer": {+            "$ref": "#/$defs/layer"+          },+          "points": {+            "type": "array",+            "items": {+              "$ref": "#/$defs/pt"+            }+          },+          "kind": {+            "enum": [+              "zone",+              "graphic"+            ],+            "description": "zone (a pour; the default when absent) or graphic (a filled copper shape drawn on the layer)"+          }+        }+      }+    },+    "outline": {+      "type": "array",+      "items": {+        "$ref": "#/$defs/pt"+      },+      "description": "the board outline's vertices in order"+    },+    "outlineEdges": {+      "type": "array",+      "description": "the outline as edges: line, or arc (center, radius, angle1, angle2 in degrees). Sample arcs finely (every 2 degrees) before measuring against them",+      "items": {+        "type": "object",+        "required": [+          "kind",+          "start"+        ],+        "properties": {+          "kind": {+            "enum": [+              "line",+              "arc"+            ]+          },+          "start": {+            "$ref": "#/$defs/pt"+          },+          "end": {+            "$ref": "#/$defs/pt"+          },+          "center": {+            "$ref": "#/$defs/pt"+          },+          "radius": {+            "type": "number"+          },+          "angle1": {+            "type": "number"+          },+          "angle2": {+            "type": "number"+          }+        }+      }+    }   } }
schema/companions.schema.json+3−3
@@ -22,12 +22,12 @@       "type": "object",       "properties": {"zones": {"type": "array", "items": {"type": "object", "required": ["net", "layer", "fills"],         "properties": {"net": {"type": "string"}, "name": {"type": "string"}, "layer": {"type": "string"}, "outline": {"type": "array"}, "filledMm2": {"type": "number"}, "filled": {"type": "boolean"},-          "fills": {"type": "array", "description": "the copper actually poured: each region's outline and its holes", "items": {"type": "object", "properties": {"outline": {"type": "array"}, "holes": {"type": "array"}}}}}}}}+          "fills": {"type": "array", "description": "the copper actually poured, island by island: each one's outline and its holes. Filled by the even-odd rule, so a fractured fill (holes joined to the outline by zero-width cuts, holes empty, as KiCad stores it) is the same copper", "items": {"type": "object", "required": ["outline"], "properties": {"outline": {"type": "array"}, "holes": {"type": "array"}, "fractured": {"type": "boolean", "description": "the holes are cut into the outline"}}}}}}}}     },     "bodies": {       "title": "3D bodies",       "type": "array",-      "description": "Each part's 3D bodies as boxes: footprint rectangle and height range above the surface it sits on (a part with several bodies has several). Altium's agent sends rows [part, layer, overall, standoff, l, b, r, t] in internal units; the standard form is this, in mm.",+      "description": "Each part's 3D bodies as boxes: footprint rectangle and height range above the surface it sits on (a bottom part measures down from the bottom). One box per body (Altium) or one per part (Fusion) are both valid; checks take the union per part. Altium's agent sends rows [part, layer, overall, standoff, l, b, r, t] in internal units; the standard form is this, in mm.",       "items": {"type": "object", "required": ["ref", "side", "x0", "y0", "x1", "y1", "z0", "z1"],         "properties": {"ref": {"type": "string"}, "side": {"enum": ["top", "bottom"]}, "x0": {"type": "number"}, "y0": {"type": "number"}, "x1": {"type": "number"}, "y1": {"type": "number"},           "z0": {"type": "number", "description": "the body's bottom above the surface (standoff)"}, "z1": {"type": "number", "description": "its top"}}}@@ -39,7 +39,7 @@         "properties": {"designator": {"type": "string"}, "libReference": {"type": "string"}, "comment": {"type": "string"}, "uniqueId": {"type": "string"},           "pins": {"type": "array", "items": {"type": "object", "required": ["number", "name", "electrical"],             "properties": {"number": {"type": "string"}, "name": {"type": "string"},-              "electrical": {"description": "Altium's ELECTRICAL code (0 input, 1 io, 2 output, 3 open_collector, 4 passive, 5 hiz, 6 open_emitter, 7 power) or the name", "type": ["integer", "string"]}}}}}}+              "electrical": {"description": "the pin's type by name: input, io, output, open_collector, open_emitter, passive, hiz, power (a part's supply input), supply (a supply symbol's pin, which drives its net), not_connected, unknown. Altium's ELECTRICAL code (0 input, 1 io, 2 output, 3 open_collector, 4 passive, 5 hiz, 6 open_emitter, 7 power) is accepted too", "type": ["integer", "string"]}}}}}}     },     "netlist": {       "title": "The compiled netlist (netlist nets)",
tools/generate.py+8−1
@@ -31,7 +31,11 @@ def load(name):   def latest(bridge):-    files = sorted(glob.glob(os.path.join(ROOT, "reference", f"{bridge}-*.json")))+    # by version number, not by text: 1.0.10 is newer than 1.0.9+    def key(f):+        v = os.path.basename(f)[len(bridge) + 1:-len(".json")]+        return [int(x) if x.isdigit() else x for x in re.split(r"[.-]", v)]+    files = sorted(glob.glob(os.path.join(ROOT, "reference", f"{bridge}-*.json")), key=key)     return load(os.path.relpath(files[-1], ROOT))  @@ -56,6 +60,9 @@ def main():             "pitfalls": v.get("pitfalls", []), "related": [strip(r, "altium_") for r in v.get("related", [])],             "example": v.get("example", {}), "longRunning": v.get("longRunning", False), "timeoutSeconds": v.get("timeoutSeconds"),         })+        # the reference still answers some verbs in mils; the standard is mm (docs/conventions.md)+        if re.search(r"Mils?\b", json.dumps([v.get("input", {}), v.get("output", {})])):+            standard[-1]["unitsNote"] = "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."      coverage = []     for s in standard:
verbs/standard.json+36−18
@@ -58,7 +58,8 @@     "step": "C:/lib/VL53L5CX.step"    },    "longRunning": false,-   "timeoutSeconds": 90+   "timeoutSeconds": 90,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "add_models",@@ -138,7 +139,8 @@     "symbolName": "VL53L5CX"    },    "longRunning": false,-   "timeoutSeconds": 180+   "timeoutSeconds": 180,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "apply_silkscreen",@@ -254,7 +256,8 @@     "wMm": 39    },    "longRunning": false,-   "timeoutSeconds": 120+   "timeoutSeconds": 120,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "board_summary",@@ -343,7 +346,8 @@     "designator": "U1"    },    "longRunning": false,-   "timeoutSeconds": 90+   "timeoutSeconds": 90,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "component_pins",@@ -370,7 +374,8 @@     "designator": "U1"    },    "longRunning": false,-   "timeoutSeconds": 90+   "timeoutSeconds": 90,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "copper_layers",@@ -499,7 +504,8 @@     "pins": "1,GND,left,100;2,TXD,left,200;16,VCC,right,100"    },    "longRunning": false,-   "timeoutSeconds": 90+   "timeoutSeconds": 90,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "doc_params",@@ -796,7 +802,8 @@     "net": "GND"    },    "longRunning": false,-   "timeoutSeconds": 120+   "timeoutSeconds": 120,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "normalize_bodies",@@ -1447,7 +1454,8 @@     "sheet": "C:/proj/Main.SchDoc"    },    "longRunning": false,-   "timeoutSeconds": 90+   "timeoutSeconds": 90,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "sch_edit",@@ -1536,7 +1544,8 @@     "y": 4000    },    "longRunning": false,-   "timeoutSeconds": 120+   "timeoutSeconds": 120,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "sch_sheet_symbol",@@ -1595,7 +1604,8 @@     "sheet": "C:\\work\\Top.SchDoc"    },    "longRunning": false,-   "timeoutSeconds": 240+   "timeoutSeconds": 240,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "sch_swap_part",@@ -1667,7 +1677,8 @@     "designator": "U1"    },    "longRunning": false,-   "timeoutSeconds": 120+   "timeoutSeconds": 120,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "set_models",@@ -1963,7 +1974,8 @@    ],    "example": {},    "longRunning": false,-   "timeoutSeconds": 60+   "timeoutSeconds": 60,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "xcopy_footprint",@@ -2848,7 +2860,8 @@     "path": "C:/lib/Adom_ToF.PcbLib"    },    "longRunning": false,-   "timeoutSeconds": 60+   "timeoutSeconds": 60,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "pour_state",@@ -3162,7 +3175,8 @@     "filePath": "C:\\\\work\\\\Board.PcbDoc"    },    "longRunning": false,-   "timeoutSeconds": 600+   "timeoutSeconds": 600,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "symbol_mode",@@ -4274,7 +4288,8 @@     "path": "C:\\\\work\\\\Power.SchDoc"    },    "longRunning": false,-   "timeoutSeconds": 30+   "timeoutSeconds": 30,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "return_path",@@ -5609,7 +5624,8 @@     "sheet": "C:/proj/Main.SchDoc"    },    "longRunning": false,-   "timeoutSeconds": 60+   "timeoutSeconds": 60,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "select_net",@@ -5666,7 +5682,8 @@    ],    "example": {},    "longRunning": false,-   "timeoutSeconds": 60+   "timeoutSeconds": 60,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "selftest_script_error",@@ -6746,7 +6763,8 @@     "y": 2000    },    "longRunning": false,-   "timeoutSeconds": 300+   "timeoutSeconds": 300,+   "unitsNote": "The reference bridge answers this verb in mils (xMil). A bridge following the standard answers in mm under Mm names (xMm)."   },   {    "name": "workspace_search",