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.10.1: import-kicad honors pinmap.json / --pin-map and warns on dropped pins (adom-lbr#9)
9 files changed
+84−32
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-lbr"-version = "2.10.0"+version = "2.10.1" dependencies = [ "altium-codec", "anyhow",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.10.0"+version = "2.10.1" 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."
SKILL.md+2−2@@ -122,8 +122,8 @@ bottom-left, buses contiguous, every name horizontal. `keep` preserves the .kicad_sym placement verbatim; `two-column` forces the re-layout. Duplicate PAD names (KiCad same-net arrays: thermal vias, ground paddles,-shield tabs) are resolved to the EAGLE `@`-suffix convention — `17`, `17@2`,-`17@3`, ... — with one space-separated multi-pad `<connect>` binding the whole+shield tabs) are resolved to the EAGLE `@`-suffix convention (`17`, `17@2`,+`17@3`, ...) with one space-separated multi-pad `<connect>` binding the whole group to its pin, so the electrical meaning is preserved. Fusion refuses the entire library over a raw duplicate.
VERSION+1−1@@ -1 +1 @@-2.10.0+2.10.1
crates/altium-codec/src/kicad.rs+28−12@@ -471,26 +471,42 @@ fn import_footprint_graphics(root: &Sx) -> Vec<FpGraphic> { /// link is identity-mapped (pin number == pad number) where they coincide, and /// the symbol is given the footprint name so the Altium writer links them. pub fn import_kicad(sym_text: &str, mod_text: &str, mpn: &str) -> Result<AdomLbrPart> {+ import_kicad_with_pin_map(sym_text, mod_text, mpn, &Default::default()).map(|(part, _)| part)+}++/// Like [`import_kicad`], with an explicit pin-number → pad-name map for parts+/// where the KiCad convention (pin NUMBER binds pad NAME) does not hold, e.g. a+/// USB-C shield pin "S1" against a pad "SH". Also returns the pin numbers that+/// still matched no pad — the caller must surface those instead of letting a+/// pin vanish from `connect[]` silently (adom/adom-lbr#9: a shield that+/// connects to nothing only shows up in hardware).+pub fn import_kicad_with_pin_map(+ sym_text: &str,+ mod_text: &str,+ mpn: &str,+ pin_map: &std::collections::HashMap<String, String>,+) -> Result<(AdomLbrPart, Vec<String>)> { let mut symbol = import_symbol(sym_text, mpn)?; let footprint = import_footprint(mod_text)?; symbol.footprint = Some(footprint.name.clone()); // Center the symbol on the origin (KiCad sources are often drawn off-origin). symbol.center(); - // identity connect map over shared designators+ // identity connect map over shared designators, pin_map as the escape hatch let pad_nums: std::collections::HashSet<&str> = footprint.pads.iter().map(|p| p.number.as_str()).collect();- let connect = symbol- .pins- .iter()- .filter(|p| pad_nums.contains(p.number.as_str()))- .map(|p| ConnectEntry {- pin: p.number.clone(),- pad: p.number.clone(),- })- .collect();+ let mut connect = Vec::new();+ let mut dropped = Vec::new();+ for p in &symbol.pins {+ let pad = pin_map.get(&p.number).unwrap_or(&p.number);+ if pad_nums.contains(pad.as_str()) {+ connect.push(ConnectEntry { pin: p.number.clone(), pad: pad.clone() });+ } else {+ dropped.push(p.number.clone());+ }+ } - Ok(AdomLbrPart {+ Ok((AdomLbrPart { schema_version: 2, mpn: mpn.to_string(), manufacturer: String::new(),@@ -502,7 +518,7 @@ pub fn import_kicad(sym_text: &str, mod_text: &str, mpn: &str) -> Result<AdomLbr connect, model_3d: None, provenance: Some(serde_json::json!({"source":"kicad_import"})),- })+ }, dropped)) } #[cfg(test)]
crates/altium-codec/src/lib.rs+1−1@@ -18,7 +18,7 @@ pub mod sexpr; pub use altium_decode::{decode_altium, decode_intlib_part}; pub use intlib::{decode_intlib, encode_intlib};-pub use kicad::import_kicad;+pub use kicad::{import_kicad, import_kicad_with_pin_map}; pub use kicad_export::{export_kicad_footprint, export_kicad_symbol}; pub use model::{AdomLbrPart, BodyRect, ConnectEntry, Footprint, FpGraphic, FpLayer, Graphic, Pad, Parameter, Pin, Symbol}; pub use pcblib::{decode_pcblib, encode_pcblib};
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "2.10.0",+ "version": "2.10.1", "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+9−11@@ -1,8 +1,8 @@ { "slug": "adom-lbr",- "version": "2.10.0",+ "version": "2.10.1", "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",@@ -18,7 +18,7 @@ ], "hero": { "type": "image",- "path": "screenshots/hero.png"+ "path": "screenshots/hero-v2.png" }, "scripts": { "install": "./install.sh",@@ -52,23 +52,21 @@ ], "files": [ "bin/**",- "assets/**", "SKILL.md",- "skills/**", "README.md", "install.sh", "uninstall.sh", "screenshots/**" ],- "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",@@ -76,15 +74,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+40−2@@ -181,6 +181,11 @@ enum Commands { /// Output adom-lbr JSON path (default: <mpn>.adom-lbr.json) #[arg(long)] out: Option<PathBuf>,+ /// JSON map from SYMBOL PIN NUMBER to FOOTPRINT PAD NAME, for parts+ /// where the two disagree (a USB-C shield pin "S1" against a pad "SH").+ /// Defaults to `pinmap.json` beside the symbol when that file exists.+ #[arg(long)]+ pin_map: Option<PathBuf>, }, /// Convert an adom-lbr JSON into native Altium .SchLib + .PcbLib (no Altium needed). ExportAltium {@@ -2020,11 +2025,44 @@ fn main() { } } - Commands::ImportKicad { sym, fp, mpn, out } => {+ Commands::ImportKicad { sym, fp, mpn, out, pin_map } => { let sym_txt = read_or_exit(&sym); let fp_txt = read_or_exit(&fp);- let part = altium_codec::import_kicad(&sym_txt, &fp_txt, &mpn)+ // Same pin-map convention as generate: explicit --pin-map wins, else+ // a pinmap.json sidecar beside the symbol just works.+ let pin_map_path = pin_map.or_else(|| {+ sym.parent().map(|d| d.join("pinmap.json")).filter(|p| p.exists())+ });+ let pin_map: std::collections::HashMap<String, String> = match &pin_map_path {+ Some(path) => serde_json::from_str(&read_or_exit(path)).unwrap_or_else(|e| {+ eprintln!("ERROR: {} is not a JSON object of \"pin number\": \"pad name\": {}", path.display(), e);+ process::exit(1);+ }),+ None => Default::default(),+ };+ if !pin_map.is_empty() {+ println!("HINT: pin map applied ({} entries) from {}", pin_map.len(),+ pin_map_path.as_ref().map(|p| p.display().to_string()).unwrap_or_default());+ }+ let (part, dropped) = altium_codec::import_kicad_with_pin_map(&sym_txt, &fp_txt, &mpn, &pin_map) .unwrap_or_else(|e| { eprintln!("ERROR: {e:#}"); process::exit(1); });+ if !dropped.is_empty() {+ // A pin that matches no pad is a broken netlist in EVERY export+ // target — say so loudly instead of letting it vanish (adom-lbr#9).+ let referenced: std::collections::HashSet<&str> =+ part.connect.iter().map(|c| c.pad.as_str()).collect();+ let mut unref: Vec<&str> = part.footprint.pads.iter()+ .map(|p| p.number.as_str())+ .filter(|n| !n.is_empty() && !referenced.contains(n))+ .collect();+ unref.sort();+ unref.dedup();+ eprintln!("WARN: {} symbol pin(s) match no footprint pad and were left out of connect[]: {}",+ dropped.len(), dropped.join(", "));+ if !unref.is_empty() {+ eprintln!(" unreferenced pads in footprint: {} — if one of these is the intended target, supply --pin-map (or a pinmap.json sidecar)", unref.join(", "));+ }+ } let outp = out.unwrap_or_else(|| PathBuf::from(format!("{mpn}.adom-lbr.json"))); fs::write(&outp, serde_json::to_string_pretty(&part).unwrap()).ok(); println!("OK: wrote {} (adom-lbr JSON — the canonical hub)", outp.display());