← 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)

Ray ·4c133db817 ·27d ago ·parent ba8e754
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,