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
Merge PR #173: 2.46.3: TO-92 pad clearance (copper, not boxes) and rect thru pads on the short side (fixes #27)
6 files changed
+79−5
CHANGELOG.md+4@@ -1,5 +1,9 @@ # Changelog +## 2.46.3 (2026-09-23)+- Pad clearance measures a round pad against a rectangular one as copper: off a corner it is the circle-to-corner distance, not the gap between bounding boxes. KiCad's stock staggered `TO-92` (square pad 1 at (0,0), round pad 2 at (1.27,-1.27), 1.3 mm) has 0.227 mm of real clearance but overlapping boxes, so every TO-92 part failed `pads 1 and 2 OVERLAP` and dropped out of the component factory. Real overlaps still fail.+- A rectangular thru pad is no longer drawn as a square on its LONG side. KiCad's `TO-92_Inline` pad 1 (1.05 x 1.5 mm at 1.27 mm pitch) became a 1.5 mm square whose copper reached into pad 2, a real short the clearance lint rightly rejected. An elongated rect is now drawn `long` on its short side along its length (with a warning that the square pin-1 corner is lost), a squarish one square on its short side, the same rule roundrect already used. Of KiCad's 11 `TO-92*` footprints, 8 failed the lint on 2.46.2 and none do on 2.46.3.+ ## 2.46.1 (2026-09-20) - Drilled pads in the native PcbLib are written as plated Multi-Layer pads: the pad record carries layer 74 plus the two bytes Altium sets when it plates a pad (114 = 15, 116 = 3, diffed against a library Altium saved after `altium_plate_vias`). Before this the heatsink vias loaded on the Top Layer with a hole. `altium_plate_vias {libraryPath}` (altium-bridge 0.2.20) repairs an older library in place.
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-lbr"-version = "2.46.2"+version = "2.46.3" dependencies = [ "altium-codec", "anyhow",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.46.2"+version = "2.46.3" 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.46.2+2.46.3
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "2.46.2",+ "version": "2.46.3", "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": {},
src/main.rs+71−1@@ -1263,7 +1263,25 @@ fn tht_pad_geometry(kicad_shape: &str, dx: f64, dy: f64) -> (String, f64, f64, O let rot = if along_y { 90.0 } else { 0.0 }; match kicad_shape { "circle" => ("round".into(), dx, 0.0, None),- "rect" => ("square".into(), long, 0.0, None),+ "rect" => {+ // EAGLE has no rectangular thru pad. A square on the LONG side grew KiCad's stock+ // TO-92_Inline pad 1 (1.05 x 1.5 mm at 1.27 mm pitch) into a 1.5 mm square whose+ // copper reached 0.005 mm into pad 2: a real short in the .lbr, which the clearance+ // lint rightly rejected (21 TO-92 parts dropped from the factory, 2026-09-23). Same+ // rule as roundrect below: an elongated pad is drawn `long` on its SHORT side along+ // its length; a squarish one stays square on its SHORT side. Both stay inside the+ // KiCad copper's width, never outside it.+ if aspect > 1.2 {+ let drawn = 2.0 * short;+ ("long".into(), short, rot,+ Some(format!("rect thru pad {short:.2} x {long:.2} mm has no EAGLE equivalent; drawn as a long pad {short:.2} x {drawn:.2} mm (the square pin-1 corner is lost; mark pin 1 in silk)")))+ } else {+ let warn = if long - short > 0.05 {+ Some(format!("rect thru pad {short:.2} x {long:.2} mm drawn square at {short:.2} mm; the long side has no EAGLE equivalent on a thru pad"))+ } else { None };+ ("square".into(), short, 0.0, warn)+ }+ } "oval" => { // EAGLE's 2:1 is fixed, so the drawn length is 2 * short whatever the source // said. Worth stating when that overshoots by a visible margin rather than@@ -2647,6 +2665,18 @@ pub(crate) fn lint_lbr(content: &str) -> LintResult { let gy = (a.y - b.y).abs() - (a.hy + b.hy); let gap = if a.round && b.round { ((a.x - b.x).powi(2) + (a.y - b.y).powi(2)).sqrt() - (a.hx + b.hx)+ } else if a.round != b.round {+ // A round pad against a rectangular one: when the circle sits off the+ // rectangle's CORNER, the copper gap is the circle-to-corner distance, not+ // the gap between the two bounding boxes. KiCad's stock staggered TO-92 (a+ // 1.3 mm square pad 1 at (0,0), a 1.3 mm round pad 2 at (1.27,-1.27)) has+ // 0.227 mm of real clearance, but its boxes overlap, so every TO-92 part+ // failed "pads 1 and 2 OVERLAP" (21 factory pages on 2026-09-23). Beside an+ // edge, or inside, the box gap is already exact.+ let (c, r) = if a.round { (a, b) } else { (b, a) };+ let dx = ((c.x - r.x).abs() - r.hx).max(0.0);+ let dy = ((c.y - r.y).abs() - r.hy).max(0.0);+ if dx > 0.0 && dy > 0.0 { (dx * dx + dy * dy).sqrt() - c.hx } else { gx.max(gy) } } else { gx.max(gy) };@@ -4541,6 +4571,13 @@ mod lint_tests { assert!(warn.is_some(), "silently rounding away the long side is what this fixes"); // an ordinary circle is untouched assert_eq!(tht_pad_geometry("circle", 1.2, 1.2).0, "round");+ // rect: never square on the LONG side (that grew TO-92_Inline pad 1 into pad 2).+ let (shape, dia, rot, warn) = tht_pad_geometry("rect", 1.05, 1.5);+ assert_eq!((shape.as_str(), dia, rot), ("long", 1.05, 90.0));+ assert!(warn.is_some());+ let (shape, dia, _, warn) = tht_pad_geometry("rect", 1.3, 1.3);+ assert_eq!((shape.as_str(), dia), ("square", 1.3));+ assert!(warn.is_none(), "a truly square pad converts silently"); } #[test]@@ -4571,6 +4608,39 @@ mod lint_tests { assert!(!lint_lbr(&vias).errors.iter().any(|e| e.code == "pad-clearance")); } + #[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+ // 2 at (1.27,-1.27), both 1.3 mm. Real clearance 0.227 mm; the bounding boxes overlap,+ // and the box test failed every TO-92 part as "pads 1 and 2 OVERLAP".+ use super::lint_lbr;+ let lib = |pads: &str| format!(r#"<eagle><drawing><layers></layers><library>+ <packages><package name="P">{pads}</package></packages>+ <symbols><symbol name="S"><pin name="A" x="0" y="0" 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="1"/></connects>+ <technologies><technology name=""/></technologies></device></devices></deviceset></devicesets>+ </library></drawing></eagle>"#);+ let to92 = lib(r#"<pad name="1" x="0" y="0" drill="0.75" diameter="1.3" shape="square"/>+ <pad name="2" x="1.27" y="-1.27" drill="0.75" diameter="1.3" shape="round"/>+ <pad name="3" x="2.54" y="0" drill="0.75" diameter="1.3" shape="round"/>"#);+ let r = lint_lbr(&to92);+ assert!(!r.errors.iter().any(|e| e.code == "pad-clearance"), "{:?}", r.errors);+ // KiCad's TO-92_Inline, converted with the rect rule: pad 1 long on its 1.05 short side+ let inline = lib(r#"<pad name="1" x="0" y="0" drill="0.75" diameter="1.05" shape="long" rot="R90"/>+ <pad name="2" x="1.27" y="0" drill="0.75" diameter="1.05" shape="long" rot="R90"/>+ <pad name="3" x="2.54" y="0" drill="0.75" diameter="1.05" shape="long" rot="R90"/>"#);+ assert!(!lint_lbr(&inline).errors.iter().any(|e| e.code == "pad-clearance"));+ // a round pad that really does reach into the square pad's corner still fails+ let corner = lib(r#"<pad name="1" x="0" y="0" drill="0.75" diameter="1.3" shape="square"/>+ <pad name="2" x="0.9" y="-0.9" drill="0.5" diameter="1.3" shape="round"/>"#);+ assert!(lint_lbr(&corner).errors.iter().any(|e| e.code == "pad-clearance"));+ // and beside an edge the gap is the plain axis gap: side by side, overlapping, fails+ let side = lib(r#"<pad name="1" x="0" y="0" drill="0.75" diameter="1.3" shape="square"/>+ <pad name="2" x="1.2" y="0" drill="0.75" diameter="1.3" shape="round"/>"#);+ assert!(lint_lbr(&side).errors.iter().any(|e| e.code == "pad-clearance"));+ }+ #[test] fn fine_pitch_gaps_warn_instead_of_failing_and_the_exact_boundary_passes_clean() { // wiki #20: a flat 0.2 mm ERROR rejected 559 of 1314 stock KiCad footprints,