← Commit history

standard 0.4.3, engine 0.1.2: a clearance rule as a value with its unit (Fusion's form), same-net rules left out (fusion-lane #12); regenerated with fusion360-1.12.6, which answers its five checks from the engine: Fusion 118 of 236

Colby Knox ·f8584aeeb2 ·14d ago ·parent dd7f0a6
8 files changed +71−25
README.md+4−2
@@ -59,10 +59,10 @@ verb by verb: [docs/coverage.md](docs/coverage.md). |---|---|---|---|---| | **core** | 93 | 86 | 89 | 53 | | **native** | 46 | 38 | 39 | 20 |-| **checks** | 37 | 33 | 11 | 4 |+| **checks** | 37 | 33 | 11 | 9 | | **app** | 60 | 56 | 25 | 36 | | **platform** | 19 | 19 | 0 | 0 |-| standard (all but platform) | 236 | 213 | 164 | 113 |+| standard (all but platform) | 236 | 213 | 164 | 118 | <!-- coverage:end -->  The **checks** tier is where the other bridges are furthest behind, and it is the cheapest to close: those verbs are@@ -155,6 +155,8 @@ Decisions so far (proposals from the KiCad and Fusion bridges, 2026-10-27; issue  ## Status +Version 0.4.3: the engine (0.1.2) reads a clearance rule written as a `value` with its unit (`0.1mm`, Fusion's form) and+leaves same-net spacing rules out; Altium and Fusion now both answer their checks from the engine. Version 0.4.2: the engine (0.1.1) names a stack's copper layers by the stack entry's `layer`, or by place (`In<n>.Cu`), planes included, as 0.3.1 settled; from altium-lane, which runs the engine inside the Altium bridge now. Version 0.4.1: coverage counts only what works: a bridge marks a verb `partial` (with a note) or `unsupported` in its
docs/coverage.md+8−8
@@ -1,6 +1,6 @@ # Coverage: every standard verb on each bridge -Altium bridge 0.2.76 (the reference), KiCad bridge 1.0.65, Fusion 360 bridge 1.12.4. 54 verbs came from other bridges' proposals (tools/proposed/); they are marked *proposed*.+Altium bridge 0.2.76 (the reference), KiCad bridge 1.0.65, Fusion 360 bridge 1.12.6. 54 verbs came from other bridges' proposals (tools/proposed/); they are marked *proposed*. `exact` is the same job under the same or an equivalent name; `partial` does part of it or does it differently (`verbs/coverage.json` names what in `note`); `unsupported` answers `not_supported` for the whole job and is not counted; blank is missing. Generated by `tools/generate.py` from `verbs/coverage.json`; do not edit by hand. @@ -8,10 +8,10 @@ Generated by `tools/generate.py` from `verbs/coverage.json`; do not edit by hand |---|---|---|---|---| | **core** | 93 | 86 | 89 | 53 | | **native** | 46 | 38 | 39 | 20 |-| **checks** | 37 | 33 | 11 | 4 |+| **checks** | 37 | 33 | 11 | 9 | | **app** | 60 | 56 | 25 | 36 | | **platform** | 19 | 19 | 0 | 0 |-| standard (all but platform) | 236 | 213 | 164 | 113 |+| standard (all but platform) | 236 | 213 | 164 | 118 |  ## core @@ -182,15 +182,15 @@ Engineering computed from the board model, not by the tool: one shared engine, s | `build_schematic` | Draw a design's schematic from its netlist with the project library's symbols, inside a new project (.PrjPcb with the board and libraries), compile it, and... | `altium_build_schematic` |  | `fusion_schematic_plan / fusion_schematic_run` (partial) | | `check_clearance` *proposed* | Measure the gaps between parts, courtyard to courtyard or pad copper to pad copper, for the listed parts or the whole board, and report every pair under a... | `altium_check_clearance` | `kicad_check_clearance` |  | | `check_orientation` | Advisory: 2-pad parts whose rotation could be better. Each orthogonal rotation is scored in place by airline length (a routable net's MST with the pad... | `altium_check_orientation` |  |  |-| `copper_balance` | How much of each copper layer is copper (tracks, pads, vias and what Altium actually poured, inside the outline; a plane counts full), and how far apart... | `altium_copper_balance` |  |  |+| `copper_balance` | How much of each copper layer is copper (tracks, pads, vias and what Altium actually poured, inside the outline; a plane counts full), and how far apart... | `altium_copper_balance` |  | `fusion_copper_balance` | | `cross_check` | Compare one schematic sheet's designators with a board's. Altium holds ONE current document, so the verb activates the board, reads it, then activates the... | `altium_cross_check` | `kicad_analyze_connections` (partial) |  |-| `current_capacity` | Will this trace carry the current? For each power net and its current: the narrowest track on every layer it is routed on against the width IPC-2221 needs... | `altium_current_capacity` |  |  |+| `current_capacity` | Will this trace carry the current? For each power net and its current: the narrowest track on every layer it is routed on against the width IPC-2221 needs... | `altium_current_capacity` |  | `fusion_current_capacity` | | `estimate_cost` *proposed* | The board's numbers a fab quote needs (size, layers, thickness, smallest track, gap and drill, holes, SMD and through-hole parts and joints, unique parts)... |  | `kicad_estimate_cost` |  |-| `fab_check` | Before ordering: the board's fab-relevant minimums measured from the live board (narrowest track, smallest via drill, diameter and annular ring, smallest... | `altium_fab_check` | `kicad_fab_check` |  |+| `fab_check` | Before ordering: the board's fab-relevant minimums measured from the live board (narrowest track, smallest via drill, diameter and annular ring, smallest... | `altium_fab_check` | `kicad_fab_check` | `fusion_fab_check` | | `find_space` | Free spots on the board for a part near another (clear of every other part on that side, of that side's tracks and vias, and inside the outline), nearest... | `altium_find_space` |  |  | | `footprint_from_land_pattern` | Build a complete footprint from the land pattern a datasheet prints: pads numbered the standard way (chip, dual SOIC/TSSOP/DFN, SOT, quad QFP/QFN, BGA), an... | `altium_footprint_from_land_pattern` |  |  | | `height_check` | Will it fit the enclosure? Each part's height from its 3D bodies (top and bottom side), the parts over a height limit board-wide or under a lid feature in a... | `altium_height_check` |  |  |-| `impedance` | Controlled impedance from the board's own stack-up: the track width each copper layer needs for a single-ended target (50 ohm default) and the width and gap... | `altium_impedance` |  |  |+| `impedance` | Controlled impedance from the board's own stack-up: the track width each copper layer needs for a single-ended target (50 ohm default) and the width and gap... | `altium_impedance` |  | `fusion_impedance` | | `import_part` | One call imports a whole part: footprint spec + STEP + symbol modes + models + params + save. | `altium_import_part` |  |  | | `jlcpcb_order` | A JLCPCB order in one call: the board checked against JLCPCB's fab limits, the Gerbers and drill zipped the way JLCPCB's order page takes them, the JLCPCB... | `altium_jlcpcb_order` |  |  | | `length_tune` | Lengthen a routed net to a target length, or to match another net (a pair's skew, a DDR byte lane): a serpentine in its longest straight track that has room... | `altium_length_tune` |  |  |@@ -201,7 +201,7 @@ Engineering computed from the board model, not by the tool: one shared engine, s | `plan_fanout` *proposed* | Plan the fanout of a BGA or QFN/QFP: the pad pitch, dogbone or via-in-pad per pad, the escape layer, surface stubs for the outer ring and a via grid for an... |  | `kicad_plan_fanout` |  | | `plan_route` *proposed* | Plan a path for one net between two pads without changing the board: a grid search around the existing copper that keeps the clearance rules, on the allowed... |  |  | `fusion_plan_route` (partial) | | `read_sheet_file` | Read a schematic sheet straight from its .SchDoc file, without Altium (it need not be installed or running): every part with its designator, library... | `altium_read_sheet_file` |  | `fusion_read_sheet_file` |-| `return_path` | Where signal tracks lose their reference: for every signal net, the length of track on each layer that runs over no pour or plane copper on either... | `altium_return_path` |  |  |+| `return_path` | Where signal tracks lose their reference: for every signal net, the length of track on each layer that runs over no pour or plane copper on either... | `altium_return_path` |  | `fusion_return_path` | | `review_board` | Design-review checks (14) over one board read; schematic checks with includeSchematic. | `altium_review_board` | `kicad_lint_board` (partial) |  | | `route_pair` | Route a differential pair as a pair: one centreline from the source pads' midpoint through waypoints to the destination pads' midpoint, each net offset to... | `altium_route_pair` | `kicad_route_pair` |  | | `schematic_review` | Review the schematic before Update PCB: every placed pin (read from the sheet files, with its electrical type) against Altium's compiled netlist. Errors:... | `altium_schematic_review` | `kicad_sch_check / kicad_design_review / kicad_lint_schematic` (partial) |  |
engine/Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "adom-eda-checks"-version = "0.1.1"+version = "0.1.2" dependencies = [  "serde_json", ]
engine/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-eda-checks"-version = "0.1.1"+version = "0.1.2" edition = "2021" description = "The EDA Bridge Standard's checks, computed from the board model JSON: no bridge calls, no tool." license = "MIT"
engine/src/fab.rs+32−3
@@ -118,14 +118,32 @@ pub fn compare(meas: &Map<String, Value>, limits: &Map<String, Value>, clearance     out } +/// A length written with its unit ("0.1mm", "4 mil", "0.004in", "100um") in mm; None without a known unit.+fn length_mm(text: &str) -> Option<f64> {+    let t = text.trim();+    let num: String = t.chars().take_while(|c| c.is_ascii_digit() || *c == '.').collect();+    let v: f64 = num.parse().ok()?;+    match t[num.len()..].trim().to_ascii_lowercase().as_str() {+        "mm" => Some(v),+        "mil" | "mils" | "th" => Some(v * 0.0254),+        "in" | "inch" => Some(v * 25.4),+        "um" | "\u{b5}m" => Some(v / 1000.0),+        _ => None,+    }+}+ /// The smallest clearance among the board's enabled clearance rules, in mm. A rule gives it as a number-/// (`clearanceMm`, `gapMm` or `minMm`) or in a descriptor the tool wrote ("Gap=10mil", "Gap=0.2mm": Altium's form).+/// (`clearanceMm`, `gapMm` or `minMm`), as a `value` with its unit ("0.1mm": Fusion's form), or in a descriptor the tool+/// wrote ("Gap=10mil", "Gap=0.2mm": Altium's form). Same-net spacing rules are not clearance between nets: left out. pub fn min_clearance(rules: &[Value]) -> Option<f64> {-    rules.iter().filter(|r| r["kind"].as_i64() == Some(0) || r["kind"].as_str().map(|k| k.eq_ignore_ascii_case("clearance")).unwrap_or(false)-            || r["kindName"].as_str() == Some("Clearance") || r["name"].as_str().map(|n| n.to_lowercase().contains("clearance")).unwrap_or(false))+    let text = |r: &Value, k: &str| r[k].as_str().map(|s| s.to_lowercase()).unwrap_or_default();+    rules.iter().filter(|r| r["kind"].as_i64() == Some(0) || text(r, "kind").contains("clearance")+            || r["kindName"].as_str() == Some("Clearance") || text(r, "name").contains("clearance"))+        .filter(|r| !["kind", "name", "kindName"].iter().any(|k| { let t = text(r, k); t.contains("same net") || t.contains("samenet") || t.contains("same-net") }))         .filter(|r| r["enabled"].as_bool() != Some(false))         .filter_map(|r| {             if let Some(v) = ["clearanceMm", "gapMm", "minMm"].iter().find_map(|k| r[*k].as_f64()) { return Some(v); }+            if let Some(v) = r["value"].as_str().and_then(length_mm) { return Some(v); }             let d = r["descriptor"].as_str()?;             let i = d.find("Gap=")? + 4;             let num: String = d[i..].chars().take_while(|c| c.is_ascii_digit() || *c == '.').collect();@@ -291,4 +309,15 @@ mod tests {     fn clearance_from_a_standard_rule() {         assert_eq!(min_clearance(&[json!({"kind": "clearance", "clearanceMm": 0.127}), json!({"kind": "clearance", "gapMm": 0.2})]), Some(0.127));     }++    #[test]+    fn clearance_from_a_value_with_its_unit_and_never_same_net() {+        // Fusion writes rules as a kind name and a value with its unit+        let rules = vec![json!({"kind": "Copper Clearance", "value": "0.15mm"}), json!({"kind": "Copper Clearance", "value": "5mil"}),+                         json!({"kind": "Same Net Clearance", "value": "0.05mm"}), json!({"kind": "Copper Clearance", "value": "0.2"})];+        assert_eq!(min_clearance(&rules), Some(0.127));+        assert_eq!(length_mm("0.004in"), Some(0.1016));+        assert_eq!(length_mm("100um"), Some(0.1));+        assert_eq!(length_mm("0.2"), None, "a bare number has no unit: never guessed");+    } }
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "eda-bridge-standard",-  "version": "0.4.2",+  "version": "0.4.3",   "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.",
verbs/coverage.json+22−7
@@ -1,5 +1,5 @@ {- "standard": "0.4.2",+ "standard": "0.4.3",  "reference": {   "bridge": "altium",   "version": "0.2.76"@@ -7,7 +7,7 @@  "proposed": 54,  "compared": {   "kicad": "1.0.65",-  "fusion360": "1.12.4"+  "fusion360": "1.12.6"  },  "tiers": {   "core": "Read and edit the design. Every bridge provides these: they are how an AI sees and changes a board.",@@ -2248,7 +2248,10 @@     "match": "exact"    },    "kicad": null,-   "fusion360": null+   "fusion360": {+    "verb": "fusion_copper_balance",+    "match": "exact"+   }   },   {    "name": "cross_check",@@ -2273,7 +2276,10 @@     "match": "exact"    },    "kicad": null,-   "fusion360": null+   "fusion360": {+    "verb": "fusion_current_capacity",+    "match": "exact"+   }   },   {    "name": "estimate_cost",@@ -2298,7 +2304,10 @@     "verb": "kicad_fab_check",     "match": "exact"    },-   "fusion360": null+   "fusion360": {+    "verb": "fusion_fab_check",+    "match": "exact"+   }   },   {    "name": "find_space",@@ -2342,7 +2351,10 @@     "match": "exact"    },    "kicad": null,-   "fusion360": null+   "fusion360": {+    "verb": "fusion_impedance",+    "match": "exact"+   }   },   {    "name": "import_part",@@ -2470,7 +2482,10 @@     "match": "exact"    },    "kicad": null,-   "fusion360": null+   "fusion360": {+    "verb": "fusion_return_path",+    "match": "exact"+   }   },   {    "name": "review_board",
verbs/standard.json+2−2
@@ -1,5 +1,5 @@ {- "standard": "0.4.2",+ "standard": "0.4.3",  "reference": {   "bridge": "altium",   "version": "0.2.76"@@ -7,7 +7,7 @@  "proposed": 54,  "compared": {   "kicad": "1.0.65",-  "fusion360": "1.12.4"+  "fusion360": "1.12.6"  },  "tiers": {   "core": "Read and edit the design. Every bridge provides these: they are how an AI sees and changes a board.",