app
Adom Library
Public Made by Adomby adom
adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.
← Commit history
2.47.4: lint treats overlapping pads on the same pin as one net (Ray, GH PR #3), order-safe across devices; GH ba5f9d1
8 files changed
+69−5
CHANGELOG.md+3@@ -1,5 +1,8 @@ # Changelog +## 2.47.4 (2026-10-08)+- Lint (`pad-clearance`, so `validate` too): overlapping pads wired to the same pin are one net, not a short. KiCad's USB-C receptacles stack paired pads on purpose (A1 over B12, both GND; A4 over B9, both VBUS), and EAGLE names the paired pins `GND@1`, `GND@2`; those footprints failed validation as four shorts. Overlapping pads on different pins still fail, and a pair counts as one net only if every device that uses the package wires it that way. (Ray)+ ## 2.47.3 (2026-10-07) - A round KiCad SMD pad (`circle`) is round in Fusion: `roundness="100"`. It used to be written with no roundness, so EAGLE drew it square; a 0.5 mm probe test point came out as a square pad. - An SMD pad with no paste layer gets `cream="no"`, and one with no mask layer gets `stop="no"`, so a test point or a fiducial is not printed with solder paste in Fusion.
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-lbr"-version = "2.47.3"+version = "2.47.4" dependencies = [ "altium-codec", "anyhow",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.47.3"+version = "2.47.4" edition = "2021" description = "adom-lbr generator / EDA translator: the canonical adom-lbr JSON is the hub, with native converters to Altium (.SchLib/.PcbLib/.IntLib), EAGLE/Fusion (.lbr), and KiCad. Altium output is written natively — no Altium needed at conversion time."
VERSION+1−1@@ -1 +1 @@-2.47.3+2.47.4
bin/adom-lbr⋯ 1 unchanged line ⋯
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "2.47.3",+ "version": "2.47.4", "type": "app", "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.", "dependencies": {},
page.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "2.47.3",+ "version": "2.47.4", "type": "app", "description": "adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.", "dependencies": {},
src/main.rs+61@@ -2678,6 +2678,28 @@ pub(crate) fn lint_lbr(content: &str) -> LintResult { // had just been used to fix. let smd_re = regex_lite::Regex::new(r#"<smd [^>]*/>"#).unwrap(); let pad_re = regex_lite::Regex::new(r#"<pad [^>]*/>"#).unwrap();+ // Which pin each pad wires to, per package, from every device that uses it. Two pads that wire to the SAME+ // pin name are one net, so their copper may overlap by design: KiCad's USB-C receptacles stack the paired+ // A1/B12 (GND) and A4/B9 (VBUS) pads, and EAGLE names the duplicate pins GND@1, GND@2. Without this the+ // HRO TYPE-C-31-M-12 footprint failed as a short (adom/type-c-31-m-12, 2026-10-08).+ let pin_base = |n: &str| n.split('@').next().unwrap_or(n).to_string();+ let dev_re = regex_lite::Regex::new(r#"(?s)<device [^>]*package="([^"]*)"[^>]*>(.*?)</device>"#).unwrap();+ let con_re = regex_lite::Regex::new(r#"<connect [^>]*/>"#).unwrap();+ // Every pin a pad wires to across the devices that use the package: two devices may wire the same package+ // differently, and the pair is one net only if it is one net in all of them.+ let mut pad_net: std::collections::HashMap<(String, String), std::collections::BTreeSet<String>> = std::collections::HashMap::new();+ for d in dev_re.captures_iter(content) {+ for c in con_re.find_iter(&d[2]) {+ let el = c.as_str();+ let get = |k: &str| regex_lite::Regex::new(&format!(r#"\b{}="([^"]*)""#, k)).ok()+ .and_then(|re| re.captures(el).map(|m| m[1].to_string()));+ if let (Some(pin), Some(pads)) = (get("pin"), get("pad")) {+ for pad in pads.split_whitespace() {+ pad_net.entry((d[1].to_string(), pad.to_string())).or_default().insert(pin_base(&pin));+ }+ }+ }+ } let attr = |el: &str, k: &str| -> Option<String> { regex_lite::Regex::new(&format!(r#"{}="([^"]*)""#, k)).ok()? .captures(el).map(|c| c[1].to_string())@@ -2716,6 +2738,10 @@ pub(crate) fn lint_lbr(content: &str) -> LintResult { // NAME, NAME@2, NAME@3. Comparing those would fail every such footprint. let base = |n: &str| n.split('@').next().unwrap_or(n).to_string(); if base(&a.name) == base(&b.name) { continue; }+ // pads wired to the same pin (one net): overlapping copper is a merged pad, not a short+ if let (Some(na), Some(nb)) = (pad_net.get(&(pkg.clone(), a.name.clone())), pad_net.get(&(pkg.clone(), b.name.clone()))) {+ if na.len() == 1 && na == nb { continue; }+ } let gx = (a.x - b.x).abs() - (a.hx + b.hx); let gy = (a.y - b.y).abs() - (a.hy + b.hy); let gap = if a.round && b.round {@@ -4806,6 +4832,41 @@ mod lint_tests { assert!(!lint_lbr(&vias).errors.iter().any(|e| e.code == "pad-clearance")); } + #[test]+ fn overlapping_pads_on_the_same_pin_are_one_net_but_different_pins_still_short() {+ // KiCad's USB_C_Receptacle_HRO_TYPE-C-31-M-12 stacks A1 over B12 (both GND): EAGLE pins GND@1 and GND@2.+ use super::lint_lbr;+ let lib = |pins: (&str, &str)| format!(r#"<eagle><drawing><layers></layers><library>+ <packages><package name="P"><smd name="A1" x="0" y="0" dx="0.6" dy="1.2"/><smd name="B12" x="0.3" y="0" dx="0.6" dy="1.2"/></package></packages>+ <symbols><symbol name="S"><pin name="{a}" x="0" y="0" length="short"/><pin name="{b}" x="0" y="2" length="short"/></symbol></symbols>+ <devicesets><deviceset name="D"><gates><gate name="G$1" symbol="S" x="0" y="0"/></gates>+ <devices><device name="" package="P"><connects><connect gate="G$1" pin="{a}" pad="A1"/><connect gate="G$1" pin="{b}" pad="B12"/></connects>+ <technologies><technology name=""/></technologies></device></devices></deviceset></devicesets>+ </library></drawing></eagle>"#, a = pins.0, b = pins.1);+ let same = lint_lbr(&lib(("GND@1", "GND@2")));+ assert!(!same.errors.iter().any(|e| e.code == "pad-clearance"), "same net must pass: {:?}", same.errors);+ let diff = lint_lbr(&lib(("GND", "VBUS")));+ assert!(diff.errors.iter().any(|e| e.code == "pad-clearance"), "different nets must still fail: {:?}", diff.errors);+ }++ #[test]+ fn a_short_in_one_device_is_not_hidden_by_another_device_on_the_same_package() {+ // Two devices share package P: one wires A1/B12 to GND@1/GND@2 (one net), the other to GND/VBUS (a short).+ // The short must be reported whichever device comes last.+ use super::lint_lbr;+ let dev = |name: &str, b: &str| format!(r#"<device name="{name}" package="P"><connects><connect gate="G$1" pin="GND@1" pad="A1"/><connect gate="G$1" pin="{b}" pad="B12"/></connects><technologies><technology name=""/></technologies></device>"#);+ let lib = |first: String, second: String| format!(r#"<eagle><drawing><layers></layers><library>+ <packages><package name="P"><smd name="A1" x="0" y="0" dx="0.6" dy="1.2"/><smd name="B12" x="0.3" y="0" dx="0.6" dy="1.2"/></package></packages>+ <symbols><symbol name="S"><pin name="GND@1" x="0" y="0" length="short"/><pin name="GND@2" x="0" y="2" length="short"/><pin name="VBUS" x="0" y="4" length="short"/></symbol></symbols>+ <devicesets><deviceset name="D"><gates><gate name="G$1" symbol="S" x="0" y="0"/></gates>+ <devices>{first}{second}</devices></deviceset></devicesets>+ </library></drawing></eagle>"#);+ for (a, b) in [(dev("A", "GND@2"), dev("B", "VBUS")), (dev("B", "VBUS"), dev("A", "GND@2"))] {+ let r = lint_lbr(&lib(a, b));+ assert!(r.errors.iter().any(|e| e.code == "pad-clearance"), "the GND/VBUS short must fail in either order: {:?}", r.errors);+ }+ }+ #[test] fn a_round_pad_off_a_square_pads_corner_is_measured_as_copper_not_boxes() { // KiCad's stock Package_TO_SOT_THT:TO-92 (staggered): square pad 1 at (0,0), round pad