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.33.0: pad-clearance warns on fine pitch and errors only on true overlap (wiki #20)
7 files changed
+71−22
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-lbr"-version = "2.32.0"+version = "2.33.0" dependencies = [ "altium-codec", "anyhow",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.32.0"+version = "2.33.0" 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.32.0+2.33.0
bin/adom-lbr⋯ 1 unchanged line ⋯
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "2.32.0",+ "version": "2.33.0", "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+8−8@@ -1,8 +1,8 @@ { "slug": "adom-lbr",- "version": "2.32.0",+ "version": "2.33.0", "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.",+ "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": {}, "tags": [ "eda",@@ -61,15 +61,15 @@ "screenshots/**", "dockbar.json" ],- "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools — KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion — through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.",+ "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools \u2014 KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion \u2014 through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.", "sample_prompts": [ { "prompt": "Convert this KiCad symbol and footprint to an Altium SchLib and PcbLib",- "label": "KiCad → Altium"+ "label": "KiCad \u2192 Altium" }, { "prompt": "Import this Altium footprint into the adom-lbr JSON",- "label": "Altium → JSON"+ "label": "Altium \u2192 JSON" }, { "prompt": "Bundle this part into a single Altium IntLib",@@ -77,15 +77,15 @@ }, { "prompt": "Convert this Altium SchLib and PcbLib to a KiCad symbol and footprint",- "label": "Altium → KiCad"+ "label": "Altium \u2192 KiCad" }, { "prompt": "Export this adom-lbr JSON to a KiCad .kicad_sym and .kicad_mod",- "label": "JSON → KiCad"+ "label": "JSON \u2192 KiCad" }, { "prompt": "Open the adom-lbr Manager to browse wiki components and view the layer stackup", "label": "Browse parts" } ]-}+}
src/main.rs+59−10@@ -2483,7 +2483,8 @@ pub(crate) fn lint_lbr(content: &str) -> LintResult { struct P { name: String, x: f64, y: f64, hx: f64, hy: f64, round: bool } let num = |s: &str| s.parse::<f64>().unwrap_or(0.0); let rot90 = |r: Option<&str>| matches!(r, Some("R90") | Some("R270"));- let mut bad: Vec<String> = Vec::new();+ let mut overlaps: Vec<String> = Vec::new();+ let mut close: Vec<String> = Vec::new(); let pkg_re = regex_lite::Regex::new(r#"(?s)<package name="([^"]*)">(.*?)</package>"#).unwrap(); // Match the whole element, then read each attribute from it. An optional (?:rot=...)? // group followed by a greedy [^>]* silently matches EMPTY and lets the tail eat the@@ -2536,21 +2537,37 @@ pub(crate) fn lint_lbr(content: &str) -> LintResult { } else { gx.max(gy) };- if gap < 0.2 {- bad.push(if gap < 0.0 {- format!("{} pads {} and {} OVERLAP by {:.3} mm", pkg, a.name, b.name, -gap)- } else {- format!("{} pads {} and {} are {:.3} mm apart", pkg, a.name, b.name, gap)- });+ // True copper OVERLAP is a short and stays a hard error. A small+ // POSITIVE gap is what fine-pitch land patterns look like by+ // construction (a 0.5 mm-pitch SOT-563 has 0.150 mm between pads in+ // KiCad's own library), so it warns instead of failing: a flat 0.2 mm+ // ERROR rejected 559 of 1314 stock KiCad footprints (wiki #20), each+ // one a part silently dropped from the factory worklist. The epsilon+ // keeps a gap of exactly the threshold clean: 1.2 - 1.0 is+ // 0.19999999999999996 in floats, and a message that says "closer than+ // 0.2 mm" must not reject a pair that is "0.200 mm apart".+ const EPS: f64 = 1e-4;+ if gap < -EPS {+ overlaps.push(format!("{} pads {} and {} OVERLAP by {:.3} mm", pkg, a.name, b.name, -gap));+ } else if gap < 0.2 - EPS {+ close.push(format!("{} pads {} and {} are {:.3} mm apart", pkg, a.name, b.name, gap)); } } } }- if !bad.is_empty() {+ if !overlaps.is_empty() { errors.push(LintError { code: "pad-clearance".into(),- message: format!("copper closer than 0.2 mm: {}. A pad mapping that grows into its neighbour is a short.",- bad.join("; ")),+ message: format!("copper OVERLAPS: {}. A pad mapping that grows into its neighbour is a short.",+ overlaps.join("; ")),+ });+ }+ if !close.is_empty() {+ warnings.push(LintError {+ code: "pad-clearance".into(),+ message: format!("copper closer than 0.2 mm: {}. Fine-pitch land patterns sit here by construction; \+check the source footprint intends it rather than a mapping having grown a pad.",+ close.join("; ")), }); } }@@ -4051,6 +4068,38 @@ mod lint_tests { assert!(!lint_lbr(&vias).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,+ // and 225 of them sat at exactly 0.200 mm because of float representation.+ 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>"#);+ // a 0.5 mm-pitch SOT-563 row: 0.150 mm apart by construction. WARNING, never error.+ let fine = lib(r#"<smd name="1" x="0" y="0" dx="0.675" dy="0.35"/>+ <smd name="2" x="0" y="0.5" dx="0.675" dy="0.35"/>"#);+ let r = lint_lbr(&fine);+ assert!(r.errors.iter().all(|e| e.code != "pad-clearance"), "fine pitch must not FAIL: {:?}", r.errors);+ assert!(r.warnings.iter().any(|w| w.code == "pad-clearance"), "but it should still WARN: {:?}", r.warnings);+ // exactly 0.200 mm apart: 1.0 mm rect pads at x -0.6 and 0.6 compute a gap of+ // 0.19999999999999996 in floats. The epsilon keeps the rule's own wording true:+ // "closer than 0.2 mm" cannot reject "0.200 mm apart". Clean, not even a warning.+ let boundary = lib(r#"<smd name="1" x="-0.6" y="0" dx="1.0" dy="1.0"/>+ <smd name="2" x="0.6" y="0" dx="1.0" dy="1.0"/>"#);+ let rb = lint_lbr(&boundary);+ assert!(rb.errors.iter().all(|e| e.code != "pad-clearance"), "{:?}", rb.errors);+ assert!(rb.warnings.iter().all(|w| w.code != "pad-clearance"), "{:?}", rb.warnings);+ // negative control: genuinely overlapping copper is still a hard error.+ let overlap = lib(r#"<smd name="1" x="0" y="0" dx="1.2" dy="1.2"/>+ <smd name="2" x="1.0" y="0" dx="1.2" dy="1.2"/>"#);+ assert!(lint_lbr(&overlap).errors.iter().any(|e| e.code == "pad-clearance"));+ }+ #[test] fn roundrect_pads_carry_their_corner_radius_into_eagle() { use super::parse_mod_pads;