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
Publish 1.0.4
8 files changed
+92−229
README.md+49−123@@ -1,161 +1,87 @@ # adom-lbr -EAGLE .lbr generator, linter, validator, and KiCad converter. Converts between KiCad `.kicad_sym` and EAGLE `.lbr` formats so both Fusion 360 and KiCad users can share component libraries.+**One part → every major EDA tool. A library generator, linter, and converter that takes KiCad sources and delivers to KiCad, Fusion 360, Altium and OrCAD — standalone Rust, no local EAGLE.** -+adom-lbr turns a KiCad `.kicad_sym` (and optional `.kicad_mod`) into a true EAGLE `.lbr` with a complete deviceset — symbol + package + pin→pad connects. Because EAGLE libraries are the universal interchange format, that one `.lbr` imports cleanly into **Fusion 360 Electronics, Altium, and OrCAD**; and for KiCad it round-trips back to a native `.kicad_sym`. It can also synthesize a placeable symbol from a footprint's pads when there's no schematic symbol, and verify a library through `service-kicad`. Standalone Rust, no Node, no gallia. -+## Formats & targets -Standalone Rust — no Node, no gallia, no local EAGLE/Fusion install. Ships a-live, AI-drivable lint-report app (`adom-lbr serve`) alongside the CLI.+| Direction | Format | Used by |+|---|---|---|+| **Generate** | EAGLE `.lbr` (symbol + package + connects) | the source of truth adom-lbr builds |+| **Deliver → Fusion 360** | `.lbr` imported directly | Autodesk Fusion 360 Electronics |+| **Deliver → Altium** | `.lbr` import | Altium Designer |+| **Deliver → OrCAD** | `.lbr` import | Cadence OrCAD / Allegro |+| **Deliver → KiCad** | converted to native `.kicad_sym` | KiCad 6+ |+| **Import (reverse)** | EAGLE `.lbr` → `.kicad_sym` | bring EAGLE parts into KiCad | -## Why this exists+The **Send to** bar in the live app pushes the loaded library to whichever of those tools you're running (via the Adom Desktop bridge); the Altium/OrCAD bridges are an open contribution slot. -Fusion 360 Electronics shows "No Components" when an `.lbr` file has an empty `<packages>` section or empty `<connects/>`. The old JS generator produced these broken files. adom-lbr generates a **complete** deviceset: symbol + placeholder SMD package + full pin-to-pad connect mappings.+## The 3-column library viewer — and an interactive embed -KiCad users can import any `.lbr` back to `.kicad_sym` with `adom-lbr import`, validated locally (Rust parser).+ -## Install+▶ **[Open the live ADS1115 3-column viewer](https://wiki.adom.inc/api/v1/pages/john/adom-lbr/files/demo/ads1115-viewer.html)** — the exact self-contained file `adom-lbr embed` produces, served straight from this page (pan/zoom each panel; the pin→pad table is fully populated from the datasheet). It is built to sit in an `<iframe>` once the wiki enables component-page iframe embedding. -```-adompkg install adom-lbr-``` -## Quick start+`adom-lbr embed` exports a **Fusion 360-style three-column viewer** as one self-contained `.html` file: **symbol** in column 1, **footprint** in column 2, the **3D chip** in column 3 — each independently pan/zoomable — with the full **pin → pad mapping table** below, carrying each pin's **type and datasheet description**. It's built to drop straight into an `<iframe>` on a **wiki component page**: a single portable file, no server, no external assets. ```bash-# Fusion user: KiCad symbol to EAGLE .lbr-adom-lbr generate --sym RP2040.kicad_sym -o RP2040.lbr--# KiCad user: EAGLE .lbr to KiCad symbol-adom-lbr import RP2040.lbr -o RP2040.kicad_sym-adom-lbr check RP2040.kicad_sym--# Lint any .lbr for Fusion compatibility-adom-lbr lint suspicious.lbr--# Run the live lint-report app (AI-drivable: /state, /load, /eval, /console)-adom-lbr serve --port 8784 --file RP2040.lbr+adom-lbr embed --dir <chip-dir> # → <mpn>-lbr-embed.html ```--## Screenshots--### RP2040 in Fusion 360 Electronics--The generated `.lbr` opens in Fusion with all 57 pins, complete deviceset, and the symbol rendering correctly. The device table shows the package and connect mappings that make Fusion recognize the component.--### Linting the old broken .lbr--The linter catches the exact bugs that cause Fusion to show "No Components":--```json-{- "passed": false,- "errors": [- {"code": "empty-packages", "message": "Packages section is empty"},- {"code": "empty-connects", "message": "Device has no pin-to-pad connect mappings"},- {"code": "device-no-package", "message": "Device has no package attribute"}- ]-}+```html+<iframe src="…/<mpn>-lbr-embed.html" width="100%" height="640" style="border:0"></iframe> ``` -### Round-trip into KiCad 10--The same `.lbr` imported back to `.kicad_sym`, validated locally, and installed as a KiCad 10 symbol library. Renders with all pins, body rectangle, and description.--## Dependencies+It assembles the three renders, the pin→pad connects, and the datasheet pin descriptions from a chip directory — exactly the part data adom-symbol, adom-footprint, ds2sf and chip-thumbnailer already produce. -### adompkg (auto-installed)+## The library at a glance -None — standalone Rust binary.+ -### System (manual)+Load an `.lbr` and the viewer breaks it down at a glance — **symbols, packages, devicesets, pins, pads** — and re-lints it server-side. A green **Lint passed** badge means the library is ready to import into Fusion 360 Electronics with no surprises. The top bar carries the **Send to** targets (KiCad / Fusion / Altium / OrCAD). -| Dependency | Required? | Purpose |-|---|---|---|-| service-kicad | Optional | Remote validation of `.kicad_sym` via SVG export (no local KiCad install needed) |-| adom-desktop | Optional | Used by adom-symbol's Send to for Fusion 360 delivery (not yet on adompkg) |+## The linter catches what Fusion rejects -**Upstream**: adom-symbol creates the `.kicad_sym` input. chip-fetcher downloads `.lbr` files from manufacturer sites.+ -**Downstream**: generated `.lbr` opens directly in Fusion 360 Electronics. Imported `.kicad_sym` installs into KiCad via `kicad_install_library`.+This is the whole point. Fusion will silently show "No Components" or refuse to place a part when a library is subtly broken — adom-lbr catches it first. Here the deviceset has **no pin-to-pad connect mappings**, so the linter flags `empty-connects` with a plain-English reason: *"Fusion cannot use this component."* It also catches missing packages, devices with no package attribute, malformed XML, and invisible symbol bodies — each with a code and a fix. -## Features+## Generate from the command line -- Bidirectional conversion: `.kicad_sym` to `.lbr` and `.lbr` to `.kicad_sym`-- Generates complete EAGLE `.lbr` with symbol + placeholder SMD package + connect mappings-- Handles duplicate pin names (IOVDD x6, DVDD x2) with EAGLE `@N` suffix convention-- 7-check linter: empty-packages, empty-connects, device-no-package, duplicate-pin-name, connect-pad-missing, malformed-xml, no-symbol-layer-- JSON lint output for programmatic use-- `check` validates `.kicad_sym` locally (Rust parser)-- Shell completions (bash/zsh/fish)+ -## Talk to the AI+`generate` builds the whole library in one shot from a `.kicad_sym` (+ optional `.kicad_mod`), lint-checks it, and writes a native `.lbr`. The same engine powers the **Send to → Fusion/Altium/OrCAD** flow in adom-symbol and adom-footprint, so whatever you see in those apps is exactly what lands in your library. -**Generate:**-- "Generate an lbr from RP2040.kicad_sym"-- "Convert this KiCad symbol to EAGLE format"-- "Make a Fusion 360 library from this symbol"+## What it does -**Import:**-- "Import this .lbr to KiCad format"-- "Convert EAGLE library to .kicad_sym"+- **`generate`** — `.kicad_sym` (+ `.kicad_mod`) → native `.lbr`, lint-checked for Fusion compatibility.+- **`lint` / `validate`** — catch the things Fusion rejects: missing connects, incomplete devicesets, bad pad maps.+- **`import`** — EAGLE `.lbr` → KiCad `.kicad_sym`.+- **Live app** — a viewer that lints the loaded `.lbr` and shows symbols / packages / devicesets / pins / pads at a glance. -**Lint:**-- "Lint this .lbr file"-- "Check if this library will work in Fusion"-- "Why does Fusion show No Components?"+## Send to -## Skills+Fusion 360 (open the `.lbr` directly), Altium / OrCAD (download the `.lbr` + import steps; native exporters on the roadmap), KiCad (converted back to `.kicad_sym`). -| Skill | Purpose |-|---|---|-| adom-lbr | Generate, import, lint, validate, check EAGLE .lbr / KiCad .kicad_sym files |+## Open source — check it out, vibe-code it, send it back -## CLI reference+adom-lbr is MIT and **fully open**. The complete Rust source is in this page's **Files** tab — browse it, or grab `src/` + `Cargo.toml`. -| Command | Description |-|---|---|-| `generate --sym <file> [-o out] [--fp <file>] [--name <name>]` | KiCad `.kicad_sym` to EAGLE `.lbr` |-| `import <file> [-o out]` | EAGLE `.lbr` to KiCad `.kicad_sym` |-| `lint <file>` | Lint `.lbr`, output JSON with error codes |-| `validate <file>` | Validate `.lbr`, human-readable output |-| `check <file>` | Validate `.kicad_sym` locally (structure, pins, properties) |-| `serve [--port 8784] [--file <lbr>]` | Run the live AI-drivable lint-report app |-| `health` | Print version |-| `install` | Install Claude Code skill + shell completions |-| `completions <shell>` | Generate bash/zsh/fish completions |+1. **Build it.** `cargo build --release` drops a standalone binary (no Node, no gallia).+2. **Point your install at your checkout.** `adompkg link john/adom-lbr ./adom-lbr` so the `adom-lbr` you run is your local copy.+3. **Vibe-code a change.** Open the folder in Claude Code and describe what you want — a new lint rule, another export target, a smarter symbol synthesizer. The lint rules live in one readable list, easy to extend.+4. **Send it back.** Open a **pull request** on this page (the wiki reviews and merges per-repo PRs), or start a discussion thread. Good additions get folded into the shipped app. -The live app (`serve`) follows the Adom app-creator contract: `GET /state`,-`POST /load` (push raw `.lbr` XML — it's re-linted server-side), `POST /eval` +-`GET /eval/pending` + `POST /eval/:id/result`, `GET/POST /console`,-`POST /shutdown`, `GET /favicon.svg`, and best-effort HD port registration.+## Part of the Adom EDA family -## Build from source+Used by **chip-fetcher** and the Send-to flow across **adom-symbol** and **adom-footprint**; pairs with **ds2sf**. -The full source ships on the wiki app page — anyone in the community can build-it locally:+## Run it ```bash-adompkg source adom-lbr # downloads + unpacks the repo-cargo build --release # standalone — only needs Rust + cargo-cp target/release/adom-lbr ~/.local/bin/+adom-lbr generate --sym part.kicad_sym --fp part.kicad_mod --name PART --output part.lbr+adom-lbr serve --port 8784 # live linter app (AI-drivable)+adom-lbr embed --dir <chip-dir> # Fusion-style 3-column viewer as one .html ```--No runtime dependencies — `generate`, `import`, `lint`, `validate`, and `check`-all run fully locally in Rust.--## Contribute back via pull request--adom-lbr is public, source included. To propose a change:--1. `adompkg source adom-lbr` to get the repo.-2. Make your change and `cargo build --release` to confirm it compiles.-3. Open a pull request against the wiki page for `adom-lbr`.--> **Note:** real PR support on wiki.adom.inc is in progress. Until it lands,-> send patches or a branch link to the maintainers and they will apply it.--## Wiki--https://wiki.adom.inc/apps/adom-lbr+MIT, contributions welcome.
bin/adom-lbr⋯ 1 unchanged line ⋯
install.sh+2−12@@ -1,14 +1,4 @@ #!/bin/bash-# install.sh for adom-lbr (app).-# Runs from the extracted module directory (~/project/adom_modules/adom-lbr/). set -euo pipefail--DIR="$(cd "$(dirname "$0")" && pwd)"-cd "$DIR"--# App install: link the binary onto PATH.-BIN_DIR="${HOME}/.local/bin"-mkdir -p "$BIN_DIR"-install -m 0755 "$DIR/bin/adom-lbr" "$BIN_DIR/adom-lbr"-echo "Installed adom-lbr -> $BIN_DIR/adom-lbr"-+DIR="$(cd "$(dirname "$0")" && pwd)"; BIN="${HOME}/.local/bin"; mkdir -p "$BIN"+install -m 0755 "$DIR/bin/adom-lbr" "$BIN/adom-lbr"; echo "Installed adom-lbr"
package.json+17−2@@ -1,9 +1,24 @@ { "slug": "adom-lbr",- "version": "0.1.4",+ "version": "1.0.4", "type": "app",- "description": "EAGLE .lbr generator, 7-check linter, and bidirectional KiCad converter",+ "description": "Symbol, footprint and 3D side by side with the pin to pad map and datasheet descriptions, exportable as one embeddable file.", "dependencies": {},+ "tags": [+ "eda",+ "kicad",+ "eagle",+ "lbr",+ "fusion360",+ "viewer"+ ],+ "hero": {+ "type": "billboard",+ "headline": "See the part, three ways.",+ "subhead": "Symbol, footprint and 3D side by side with the pin to pad map and datasheet descriptions, exportable as one embeddable file.",+ "screenshot": "screenshots/shot.png",+ "accent": "#00b8b1"+ }, "scripts": { "install": "./install.sh", "uninstall": "./uninstall.sh"
page.json+9−5@@ -1,20 +1,23 @@ { "slug": "adom-lbr",- "version": "1.0.3",+ "version": "1.0.4", "type": "app", "description": "Symbol, footprint and 3D side by side with the pin to pad map and datasheet descriptions, exportable as one embeddable file.", "dependencies": {}, "tags": [ "eda",- "library",+ "kicad", "eagle", "lbr", "fusion360", "viewer" ], "hero": {- "type": "image",- "path": "adom-lbr.png"+ "type": "billboard",+ "headline": "See the part, three ways.",+ "subhead": "Symbol, footprint and 3D side by side with the pin to pad map and datasheet descriptions, exportable as one embeddable file.",+ "screenshot": "screenshots/shot.png",+ "accent": "#00b8b1" }, "scripts": { "install": "./install.sh",@@ -26,5 +29,6 @@ "name": "John Lauer", "email": "[email protected]" },- "title": "Adom Library"+ "title": "Adom Library",+ "org": "adom" }
src/main.rs+13−67@@ -1,4 +1,3 @@-mod provenance; mod deliver; mod server; @@ -990,17 +989,6 @@ fn main() { process::exit(1); } }- // Build the 3-column (symbol|footprint|3D) viewer from the part dir- // (the .lbr's parent), so GET / shows the full studio, not just lint.- if let Some(dir) = f.parent() {- match build_embed_html(dir) {- Ok((html, pins)) => {- eprintln!("[adom-lbr] 3-column viewer ready ({pins} pins) from {}", dir.display());- server::set_embed(html);- }- Err(e) => eprintln!("[adom-lbr] no 3-column viewer ({e}); serving lint card"),- }- } } if let Err(e) = server::run(port) { eprintln!("ERROR: {e:#}");@@ -1074,59 +1062,34 @@ fn read_first(dir: &std::path::Path, names: &[String]) -> Option<String> { } 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();- let (html, pin_count) = build_embed_html(dir)?;- let out = out.unwrap_or_else(|| dir.join(format!("{mpn}-lbr-embed.html")));- fs::write(&out, &html).map_err(|e| format!("writing {}: {e}", out.display()))?;- println!("OK: 3-column interactive embed ({pin_count} pins) → {}", out.display());- Ok(())-}--/// Build the 3-column (symbol | footprint | 3D) interactive HTML for a part dir.-/// Reads pre-rendered assets, tolerating both `<mpn>-` prefixed and generic-/// (`symbol.svg`, `assets/footprint.svg`, …) names so the same renders that-/// drive the wiki page also drive the live app and the `embed` export.-pub fn build_embed_html(dir: &std::path::Path) -> Result<(String, usize), 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"), "symbol.svg".into(), "assets/symbol.svg".into()])+ let sym_svg = read_first(dir, &[format!("{mpn}-symbol.svg")]) .map(|s| s) .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"), "footprint.svg".into(), "assets/footprint.svg".into()])+ let fp_svg = read_first(dir, &[format!("{mpn}-footprint.authoritative.svg"), format!("{mpn}-footprint.svg")]) .unwrap_or_else(|| "<div class=\"ph\">no footprint render in this directory</div>".into()); let threed = {- let names = [- format!("{mpn}-3d-iso-named.png"), format!("{mpn}-3d-iso.png"), format!("{mpn}-3d-iso-lg.png"),- "3d-iso.png".into(), "assets/3d-iso.png".into(),- ];- let cands = names.iter().map(|s| dir.join(s)).find(|p| p.exists());+ 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()); 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(), } };- // optional pin→pad connects from a .lbr (tolerate generic name)+ // optional pin→pad connects from a .lbr let mut pin_to_pad: std::collections::HashMap<String, String> = std::collections::HashMap::new();- let mut connect_order: Vec<(String, String)> = Vec::new();- let lbr_text = read_first(dir, &[format!("{mpn}.lbr"), "library.lbr".into()])- .or_else(|| {- // any *.lbr in the dir- fs::read_dir(dir).ok().and_then(|rd| rd.filter_map(|e| e.ok())- .find(|e| e.path().extension().map(|x| x == "lbr").unwrap_or(false))- .and_then(|e| fs::read_to_string(e.path()).ok()))- });- if let Some(lbr) = &lbr_text {+ if let Some(lbr) = read_first(dir, &[format!("{mpn}.lbr")]) { if let Ok(re) = regex_lite::Regex::new(r#"<connect gate="[^"]*" pin="([^"]+)" pad="([^"]+)"/>"#) {- for cap in re.captures_iter(lbr) {+ for cap in re.captures_iter(&lbr) { pin_to_pad.insert(cap[1].to_string(), cap[2].to_string());- connect_order.push((cap[1].to_string(), cap[2].to_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"), "symbol.extracted.json".into(), "assets/symbol.extracted.json".into()]) {+ if let Some(ext) = read_first(dir, &[format!("{mpn}-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| {@@ -1149,36 +1112,19 @@ pub fn build_embed_html(dir: &std::path::Path) -> Result<(String, usize), String } } }- // fallback: no extracted JSON — build the pin→pad rows straight from the .lbr connects- if pins_json == "[]" && !connect_order.is_empty() {- let rows: Vec<serde_json::Value> = connect_order.iter().map(|(pin, pad)| {- serde_json::json!({ "number": pad, "name": pin, "pad": pad, "type": "Signal", "color": "#80cbc4", "desc": "" })- }).collect();- pad_count = pad_count.max(rows.len());- pins_json = serde_json::to_string(&rows).unwrap_or_else(|_| "[]".into());- } let pin_count = serde_json::from_str::<serde_json::Value>(&pins_json).ok() .and_then(|v| v.as_array().map(|a| a.len())).unwrap_or(0); - // Display name: prefer the .lbr's deviceset/symbol name over the dir name.- let display_name = lbr_text.as_deref()- .and_then(|l| ["deviceset", "symbol", "package"].iter().find_map(|t| {- let n = format!("<{t} ");- let i = l.find(&n)? + n.len();- let r = &l[i..];- let k = r.find("name=\"")? + 6;- let j = r[k..].find('"')? + k;- Some(r[k..j].to_string())- }))- .filter(|s| !s.is_empty())- .unwrap_or_else(|| mpn.clone()); let html = include_str!("embed.html")- .replace("__NAME__", &esc_html(&display_name))+ .replace("__NAME__", &esc_html(&mpn)) .replace("__SYM__", &sym_svg) .replace("__FP__", &fp_svg) .replace("__THREED__", &threed) .replace("__PINS__", &pins_json) .replace("__PADN__", &pad_count.to_string()) .replace("__PINN__", &pin_count.to_string());- Ok((html, pin_count))+ let out = out.unwrap_or_else(|| dir.join(format!("{mpn}-lbr-embed.html")));+ fs::write(&out, &html).map_err(|e| format!("writing {}: {e}", out.display()))?;+ println!("OK: 3-column interactive embed ({pin_count} pins) → {}", out.display());+ Ok(()) }
src/server.rs+1−15@@ -28,8 +28,6 @@ pub struct AppState { } static STATE: Mutex<Option<AppState>> = Mutex::new(None);-/// Filled 3-column (symbol | footprint | 3D) HTML, served at `/` when present.-static EMBED_HTML: Mutex<Option<String>> = Mutex::new(None); static EVAL_PENDING: Mutex<VecDeque<(u64, String)>> = Mutex::new(VecDeque::new()); static EVAL_RESULTS: Mutex<Option<HashMap<u64, Value>>> = Mutex::new(None); static CONSOLE: Mutex<Option<Vec<String>>> = Mutex::new(None);@@ -42,11 +40,6 @@ pub fn set_state(s: AppState) { *STATE.lock().unwrap() = Some(s); } -/// Install the 3-column viewer HTML so `GET /` shows symbol|footprint|3D live.-pub fn set_embed(html: String) {- *EMBED_HTML.lock().unwrap() = Some(html);-}- /// Parse + lint an `.lbr` (EAGLE XML) into the displayable app state. pub fn analyze_lbr(content: &str) -> AppState { let lint = crate::lint_lbr(content);@@ -160,14 +153,7 @@ fn hd_post(body: &str) { fn route(method: &Method, url: &str, body: &str) -> Response<std::io::Cursor<Vec<u8>>> { match (method, url) {- (Method::Get, "/") | (Method::Get, "/index.html") => {- // 3-column viewer when render assets are loaded; lint card otherwise.- match &*EMBED_HTML.lock().unwrap() {- Some(h) => html(h),- None => html(APP_HTML),- }- }- (Method::Get, "/lint") => html(APP_HTML),+ (Method::Get, "/") | (Method::Get, "/index.html") => html(APP_HTML), (Method::Get, "/favicon.svg") => svg(FAVICON), (Method::Get, "/state") => json_resp(&state_json()), (Method::Post, "/load") => {
uninstall.sh+1−5@@ -1,7 +1,3 @@ #!/bin/bash-# uninstall.sh for adom-lbr (app). set -euo pipefail--rm -f "${HOME}/.local/bin/adom-lbr"-echo "Uninstalled adom-lbr"-+rm -f "${HOME}/.local/bin/adom-lbr"; echo "Uninstalled adom-lbr"