← Commit history
Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "adom-lbr"-version = "1.6.2"+version = "1.7.0" dependencies = [  "anyhow",  "clap",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "1.6.2"+version = "1.7.0" edition = "2021" description = "EAGLE .lbr generator, linter, and validator for Fusion 360 Electronics. Converts KiCad symbols + footprints to complete EAGLE libraries." 
SKILL.md+7−3
@@ -106,9 +106,13 @@ the real footprint, and the chip outline are identical across them: - **OrCAD** — shells the **adom-orcad** converter to emit EDIF 2.0.0 (`.edn`),   which Capture imports to a native `.olb`. If adom-orcad isn't installed it   writes an `ORCAD-README.md` with the exact command instead of failing.-- **Altium** — the `.lbr` (Altium's Import Wizard reads EAGLE directly) **plus**-  the staged KiCad sources + `ALTIUM-README.md` for the **Altium bridge**, which-  drives a real Altium to native `.SchLib/.PcbLib` (Altium has no headless mode).+- **Altium** — a bridge-ready import package: **`<part>.altium.json`** (manifest,+  schema `adom-lbr.altium/1` — part, BOM part numbers incl. `LCSC Part #`, staged+  sources, target `.SchLib/.PcbLib`, mode) — the Altium analog of `fusion3d.json`.+  The orchestrating AI hands it to the **Altium bridge** (`adom/adom-desktop-altium-bridge`+  / `altium-part-importer`) on the Altium machine to build native libraries.+  `--altium-lib <base>` / `--altium-mode create|append|reuse` set the target. The+  staged `.lbr` is also Altium-Import-Wizard-ready as a no-tooling fallback.  `--targets` limits the set (e.g. `--targets kicad,fusion`). This is still a pure exporter: it writes files and hands off; the orchestrating AI does any
VERSION+1−1
@@ -1 +1 @@-1.6.2\ No newline at end of file+1.7.0\ No newline at end of file
bin/adom-lbr
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-lbr",-  "version": "1.6.2",+  "version": "1.7.0",   "type": "app",   "description": "One KiCad part \u2192 every major EDA tool. `adom-lbr export` writes a folder per tool: native KiCad, Fusion 360 (.lbr + 3D handoff), OrCAD (EDIF via adom-orcad), and an Altium bridge hand-off \u2014 real-footprint accurate. Bidirectional KiCad converter + 8-check linter + a live library viewer. Includes a focused fusion-export sub-skill.",   "dependencies": {},
page.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-lbr",-  "version": "1.6.2",+  "version": "1.7.0",   "type": "app",   "description": "One KiCad part → every major EDA tool. `adom-lbr export` writes a folder per tool: native KiCad, Fusion 360 (.lbr + 3D handoff), OrCAD (EDIF via adom-orcad), and an Altium bridge hand-off — real-footprint accurate. Bidirectional KiCad converter + 8-check linter + a live library viewer. Includes a focused fusion-export sub-skill.",   "dependencies": {},
src/main.rs+107−10
@@ -179,6 +179,14 @@ enum Commands {         /// Any of: kicad,fusion,orcad,altium         #[arg(long)]         targets: Option<String>,+        /// Altium destination library base name → `<base>.SchLib`/`<base>.PcbLib`+        /// in the Altium import manifest (the bridge surveys existing libs if omitted).+        #[arg(long)]+        altium_lib: Option<String>,+        /// Altium import mode hint for the bridge: create | append | reuse+        /// (default: survey — let the importer `--list-libs` and decide).+        #[arg(long)]+        altium_mode: Option<String>,     },     /// Export a Fusion-style 3-column INTERACTIVE HTML package — symbol | footprint     /// | 3D, each pan/zoomable, with a pin→pad mapping table carrying the datasheet@@ -1806,6 +1814,8 @@ fn run_export(     mouser: Option<String>,     digikey: Option<String>,     lcsc: Option<String>,+    altium_lib: Option<String>,+    altium_mode: Option<String>, ) {     use std::process::Command; @@ -1958,14 +1968,23 @@ fn run_export(         if let Some(fc) = &fp_content {             let _ = fs::write(adir.join(format!("{part}.kicad_mod")), fc);         }+        let mut model_ext: Option<String> = None;         if let Some(mp) = &model {             if let Ok(b) = fs::read(mp) {-                let ext = mp.extension().and_then(|e| e.to_str()).unwrap_or("step");+                let ext = mp.extension().and_then(|e| e.to_str()).unwrap_or("step").to_string();                 let _ = fs::write(adir.join(format!("{part}.{ext}")), b);+                model_ext = Some(ext);             }         }+        // The bridge-ready import manifest (the Altium analog of fusion3d.json).+        let _ = write_altium_manifest(+            &adir.join(format!("{part}.altium.json")),+            &part, &parsed.description, &attrs,+            fp_content.is_some(), model_ext.as_deref(),+            altium_lib.as_deref(), altium_mode.as_deref(),+        );         let _ = fs::write(adir.join("ALTIUM-README.md"), altium_readme(&part, fp_content.is_some(), model.is_some()));-        status.push(("Altium".into(), "ok".into(), format!("{part}.lbr (direct import) + sources + ALTIUM-README.md")));+        status.push(("Altium".into(), "ok".into(), format!("{part}.altium.json (bridge manifest) + {part}.lbr (direct import) + sources")));     }      // ── index + summary ──@@ -1989,13 +2008,89 @@ fn run_export(     } } +// The Altium import manifest — the Altium analog of fusion3d.json. adom-lbr is a+// pure exporter, so it can't write binary .SchLib/.PcbLib itself; instead it+// emits this machine-readable package describing the part + BOM + target library+// so the **altium bridge** (altium-part-importer / adom-desktop-altium-bridge,+// on the machine running Altium) can drive Altium to create native libraries.+#[allow(clippy::too_many_arguments)]+fn write_altium_manifest(+    path: &std::path::Path,+    part: &str,+    desc: &str,+    attrs: &PartAttrs,+    has_fp: bool,+    model_ext: Option<&str>,+    lib: Option<&str>,+    mode: Option<&str>,+) -> std::io::Result<()> {+    // BOM parameters under Altium's own field names; the importer keys off+    // "LCSC Part #" so JLCPCB BOM + pick-and-place exports carry it.+    let mut params = serde_json::Map::new();+    let mut put = |k: &str, v: &Option<String>| {+        if let Some(s) = v { if !s.trim().is_empty() {+            params.insert(k.to_string(), serde_json::Value::String(s.trim().to_string()));+        } }+    };+    put("Manufacturer", &attrs.mfr);+    put("Manufacturer Part Number", &attrs.mpn);+    put("Mouser Part Number", &attrs.mouser);+    put("Digi-Key Part Number", &attrs.digikey);+    put("LCSC Part #", &attrs.lcsc);++    let model = model_ext.map(|e| serde_json::json!(format!("{part}.{e}"))).unwrap_or(serde_json::Value::Null);+    let footprint = if has_fp { serde_json::json!(format!("{part}.kicad_mod")) } else { serde_json::Value::Null };+    let (schlib, pcblib) = match lib {+        Some(b) => (+            serde_json::json!(format!("{b}.SchLib")),+            if has_fp { serde_json::json!(format!("{b}.PcbLib")) } else { serde_json::Value::Null },+        ),+        None => (serde_json::Value::Null, serde_json::Value::Null),+    };++    let manifest = serde_json::json!({+        "schema": "adom-lbr.altium/1",+        "generated_by": format!("adom-lbr {}", VERSION.trim()),+        "part": { "name": part, "value": part, "description": desc },+        // Everything the bridge needs as input — all relative to this manifest's dir.+        "sources": {+            "symbol_kicad_sym": format!("{part}.kicad_sym"),+            "footprint_kicad_mod": footprint,+            "eagle_lbr": format!("{part}.lbr"),+            "model_3d": model+        },+        "parameters": serde_json::Value::Object(params),+        "target": {+            "schlib": schlib,+            "pcblib": pcblib,+            "intlib": serde_json::Value::Null,+            "mode": mode.unwrap_or("survey")+        },+        "import": {+            "preferred": "bridge",+            "bridge": {+                "importer_skill": "barrett-land/altium-part-importer",+                "desktop_bridge": "adom/adom-desktop-altium-bridge",+                "verb_prefix": "altium_",+                "note": "Run on the machine with Altium + Adom Desktop. The importer surveys existing libraries (--list-libs) before creating, attaches the LCSC Part #, and builds symbol + footprint (+3D) natively."+            },+            "fallback_import_wizard": format!("Altium ▸ File ▸ Import Wizard ▸ EAGLE Projects and Libraries ▸ {part}.lbr")+        },+        "status": "pending-altium-bridge"+    });+    std::fs::write(path, serde_json::to_string_pretty(&manifest)?)+}+ fn altium_readme(part: &str, has_fp: bool, has_model: bool) -> String {     let mut s = String::new();-    s.push_str(&format!("# {part} → Altium\n\nTwo paths — pick one.\n\n"));-    s.push_str("## 1. Direct (nothing else needed)\n\n");-    s.push_str(&format!("Altium imports EAGLE libraries: **File ▸ Import Wizard ▸ EAGLE Projects and Libraries** ▸ select `{part}.lbr`. You get a schematic symbol + PCB footprint in an integrated library.\n\n"));-    s.push_str("## 2. Native .SchLib / .PcbLib (Altium bridge)\n\n");-    s.push_str("adom-lbr is a pure exporter — it does **not** drive Altium. For true native\nAltium libraries, the orchestrating AI runs the **Altium bridge** on the machine\nrunning Altium + Adom Desktop (Altium has no headless mode, so the bridge drives\na real Altium via DelphiScript). The KiCad sources are staged here for it:\n\n");+    s.push_str(&format!("# {part} → Altium\n\n"));+    s.push_str(&format!("**Primary input: `{part}.altium.json`** — a machine-readable import manifest\n(schema `adom-lbr.altium/1`) describing the part, its BOM part numbers, the staged\nsources, and the target library. Hand it to the Altium bridge (path 1). It's the\nAltium analog of the Fusion `fusion3d.json` sidecar.\n\nTwo paths to get a library:\n\n"));+    s.push_str("## 1. Native .SchLib / .PcbLib (Altium bridge) — recommended\n\n");+    s.push_str(&format!("adom-lbr is a pure exporter — it does **not** drive Altium. The orchestrating AI\nfeeds `{part}.altium.json` to the **Altium bridge** on the machine running Altium\n+ Adom Desktop (Altium has no headless mode, so the bridge drives a real Altium\nvia DelphiScript). The manifest points at the staged sources:\n\n"));+    s.push_str(&format!("- `{part}.kicad_sym` — schematic symbol\n"));+    if has_fp { s.push_str(&format!("- `{part}.kicad_mod` — PCB footprint\n")); }+    if has_model { s.push_str("- the 3D model (`*.step` / `*.glb`) — 3D body\n"); }+    s.push_str("\nThe shipped tools that consume it (install on the Altium machine):\n\n");     s.push_str(&format!("- `{part}.kicad_sym` — schematic symbol\n"));     if has_fp { s.push_str(&format!("- `{part}.kicad_mod` — PCB footprint\n")); }     if has_model { s.push_str("- the 3D model (`*.step` / `*.glb`) — 3D body\n"); }@@ -2003,6 +2098,8 @@ fn altium_readme(part: &str, has_fp: bool, has_model: bool) -> String {     s.push_str("- **`adom/adom-desktop-altium-bridge`** — the Adom Desktop bridge that drives\n  Altium via DelphiScript to add symbols + footprints (with 3D) to a file-based\n  library:\n  ```\n  adompkg install adom/adom-desktop-altium-bridge\n  ```\n");     s.push_str("- **`altium-part-importer`** — adds a catalog part to an Altium library;\n  CREATE a new `.IntLib`, APPEND to an existing one, or REUSE existing symbols:\n  ```\n  adompkg install altium-part-importer\n  ```\n");     s.push_str("- **`adom/adom-altium-library-importer`** — one-command install of pre-converted\n  `.SchLib` + `.PcbLib` (with linked 3D) into Altium.\n\n");+    s.push_str("## 2. Direct (nothing else needed)\n\n");+    s.push_str(&format!("No bridge handy? Altium imports EAGLE libraries directly: **File ▸ Import Wizard\n▸ EAGLE Projects and Libraries** ▸ select `{part}.lbr`. You get a schematic symbol\n+ PCB footprint. (The Import Wizard may re-place the designator at its default —\nthe bridge path preserves the exact placement.)\n\n"));     s.push_str("## Reverse (Altium → KiCad)\n\n");     s.push_str("Already have an Altium library? Convert it natively (no Altium, no KiCad install):\n\n");     s.push_str("```\nadompkg install adom/adom-sfconvert   # .SchLib/.PcbLib/.IntLib → .kicad_sym/.kicad_mod\n# parsers: adom/adom-altium-schlib (symbols), adom/altium-pcblib (footprints)\n```\n");@@ -2023,7 +2120,7 @@ fn export_index(part: &str, status: &[(String, String, String)], has_fp: bool, h     let fnote = if has_model { " Then the bridge attaches the 3D body (Create3DPackage → FINISH → Save)." } else { "" };     s.push_str(&format!("| **Fusion 360** | `fusion/{part}.lbr`{fsc} | Open the `.lbr` as an Electronics library.{fnote} |\n"));     s.push_str(&format!("| **OrCAD** | `orcad/{part}.edn` | Capture ▸ Accessories/Import ▸ EDIF 2.0.0 ▸ select the `.edn` → native `.olb`. (Falls back to a README if adom-orcad isn't installed.) |\n"));-    s.push_str(&format!("| **Altium** | `altium/{part}.lbr` + sources | Direct: Import Wizard (EAGLE). Native `.SchLib/.PcbLib`: see `altium/ALTIUM-README.md`. |\n\n"));+    s.push_str(&format!("| **Altium** | `altium/{part}.altium.json` + `.lbr` + sources | Hand `{part}.altium.json` (bridge manifest) to the Altium bridge for native `.SchLib/.PcbLib`; or Import Wizard the `.lbr` directly. See `altium/ALTIUM-README.md`. |\n\n"));     let _ = status;     s.push_str("adom-lbr is a pure exporter: it writes these files and hands off. The orchestrating AI does any app-driving (Fusion bridge, Altium bridge).\n");     s@@ -2155,8 +2252,8 @@ fn main() {             }         } -        Commands::Export { sym, fp, model, symbol_art, symbol_art_scale, name, out, targets, mfr, mpn, mouser, digikey, lcsc } => {-            run_export(sym, fp, model, symbol_art, symbol_art_scale, name, out, targets, mfr, mpn, mouser, digikey, lcsc);+        Commands::Export { sym, fp, model, symbol_art, symbol_art_scale, name, out, targets, mfr, mpn, mouser, digikey, lcsc, altium_lib, altium_mode } => {+            run_export(sym, fp, model, symbol_art, symbol_art_scale, name, out, targets, mfr, mpn, mouser, digikey, lcsc, altium_lib, altium_mode);         }          Commands::Bind3d { lbr, wip_urn, urn, lineage_urn, record_only, package, output, sidecar } => {