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
embed: find ds2sf-emitted files by suffix, not dir-name prefix — fixes empty (0-pin) pin→pad table when MPN≠dirname (Ray issue #3); bump 2.6.3
4 files changed
+39−9
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.6.1"+version = "2.6.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.6.1+2.6.3
page.json+1−1@@ -85,4 +85,4 @@ "label": "Browse parts" } ]-}+}
src/main.rs+36−6@@ -1809,17 +1809,47 @@ fn read_first(dir: &std::path::Path, names: &[String]) -> Option<String> { None } +/// Find the first file in `dir` whose name ends with `suffix` (case-insensitive+/// on the suffix), returning its path. Results are sorted for determinism.+///+/// ds2sf names its emitted files by the MPN / symbolName (e.g.+/// `74HC595BQ,115-symbol.extracted.json`), which frequently differs — in case or+/// exact form — from the chip *directory* name (e.g. `74hc595bq-dhvqfn16`). So+/// deriving the filename from the dir name misses the file entirely. Matching by+/// suffix finds it regardless of the prefix.+fn find_by_suffix(dir: &std::path::Path, suffix: &str) -> Option<PathBuf> {+ let suf = suffix.to_lowercase();+ let mut hits: Vec<PathBuf> = fs::read_dir(dir).ok()?+ .filter_map(|e| e.ok().map(|e| e.path()))+ .filter(|p| p.file_name().and_then(|n| n.to_str())+ .map(|n| n.to_lowercase().ends_with(&suf)).unwrap_or(false))+ .collect();+ hits.sort();+ hits.into_iter().next()+}++/// Read a dir file by exact name (any of `names`) first, then fall back to the+/// first file matching `suffix`. Robust to MPN-vs-dirname prefix drift.+fn read_first_or_suffix(dir: &std::path::Path, names: &[String], suffix: &str) -> Option<String> {+ read_first(dir, names)+ .or_else(|| find_by_suffix(dir, suffix).and_then(|p| fs::read_to_string(p).ok()))+}+ fn cmd_embed(dir: &std::path::Path, out: Option<PathBuf>) -> Result<(), String> { let mpn = dir.file_name().and_then(|s| s.to_str()).ok_or("bad --dir")?.to_string(); // renders- let sym_svg = read_first(dir, &[format!("{mpn}-symbol.svg")])- .map(|s| s)+ let sym_svg = read_first_or_suffix(dir, &[format!("{mpn}-symbol.svg")], "-symbol.svg") .unwrap_or_else(|| "<div class=\"ph\">no symbol render in this directory</div>".into());- let fp_svg = read_first(dir, &[format!("{mpn}-footprint.authoritative.svg"), format!("{mpn}-footprint.svg")])+ let fp_svg = read_first(dir,+ &[format!("{mpn}-footprint.authoritative.svg"), format!("{mpn}-footprint.svg")])+ .or_else(|| find_by_suffix(dir, "-footprint.authoritative.svg").and_then(|p| fs::read_to_string(p).ok()))+ .or_else(|| find_by_suffix(dir, "-footprint.svg").and_then(|p| fs::read_to_string(p).ok())) .unwrap_or_else(|| "<div class=\"ph\">no footprint render in this directory</div>".into()); let threed = { let cands = ["-3d-iso-named.png", "-3d-iso.png", "-3d-iso-lg.png"]- .iter().map(|s| dir.join(format!("{mpn}{s}"))).find(|p| p.exists());+ .iter().map(|s| dir.join(format!("{mpn}{s}"))).find(|p| p.exists())+ .or_else(|| ["-3d-iso-named.png", "-3d-iso.png", "-3d-iso-lg.png"]+ .iter().find_map(|s| find_by_suffix(dir, s))); match cands.and_then(|p| fs::read(&p).ok()) { Some(bytes) => format!("<img src=\"data:image/png;base64,{}\">", b64(&bytes)), None => "<div class=\"ph\">no 3D render in this directory</div>".into(),@@ -1827,7 +1857,7 @@ fn cmd_embed(dir: &std::path::Path, out: Option<PathBuf>) -> Result<(), String> }; // optional pin→pad connects from a .lbr let mut pin_to_pad: std::collections::HashMap<String, String> = std::collections::HashMap::new();- if let Some(lbr) = read_first(dir, &[format!("{mpn}.lbr")]) {+ if let Some(lbr) = read_first_or_suffix(dir, &[format!("{mpn}.lbr")], ".lbr") { if let Ok(re) = regex_lite::Regex::new(r#"<connect gate="[^"]*" pin="([^"]+)" pad="([^"]+)"/>"#) { for cap in re.captures_iter(&lbr) { pin_to_pad.insert(cap[1].to_string(), cap[2].to_string());@@ -1837,7 +1867,7 @@ fn cmd_embed(dir: &std::path::Path, out: Option<PathBuf>) -> Result<(), String> // pins + descriptions from the extracted symbol JSON let mut pins_json = String::from("[]"); let mut pad_count = fp_svg.matches("adom-pad").count().max(0);- if let Some(ext) = read_first(dir, &[format!("{mpn}-symbol.extracted.json")]) {+ if let Some(ext) = read_first_or_suffix(dir, &[format!("{mpn}-symbol.extracted.json")], "-symbol.extracted.json") { if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ext) { if let Some(arr) = v.get("pins").and_then(|p| p.as_array()) { let rows: Vec<serde_json::Value> = arr.iter().map(|p| {