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.1.2
9 files changed
+135−57
Cargo.lock+1−1@@ -4,7 +4,7 @@ version = 4 [[package]] name = "adom-lbr"-version = "1.1.1"+version = "1.1.2" dependencies = [ "anyhow", "clap",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "1.1.1"+version = "1.1.2" edition = "2021" description = "EAGLE .lbr generator, linter, and validator for Fusion 360 Electronics. Converts KiCad symbols + footprints to complete EAGLE libraries."
SKILL.md+14−8@@ -30,7 +30,7 @@ native symbol via `import`. ``` adom-lbr generate --sym <part.kicad_sym> [--fp <part.kicad_mod>] [--model <part.step>] [-o out.lbr] [--name <override>]-adom-lbr bind-3d --lbr <part.lbr> (--wip-urn <urn:adsk.wipprod:fs.file:vf.<id>?version=1> | --urn <urn:adsk.eagle:package:NNNN/V>) [--package <name>] [--sidecar <part.fusion3d.json>] [-o out.lbr]+adom-lbr bind-3d (--lbr <part.lbr> | --record-only) (--wip-urn <…fs.file:vf…?version=N> | --urn <…eagle:package…>) [--lineage-urn <…dm.lineage…>] [--package <name>] [--sidecar <part.fusion3d.json>] [-o out.lbr] adom-lbr import <part.lbr> [-o out.kicad_sym] adom-lbr lint <part.lbr> # JSON error codes adom-lbr validate <part.lbr> # human-readable@@ -71,13 +71,19 @@ a round-tripped `.flbr`): sidecar (schema `adom-lbr.fusion3d/2`): `model.path`, format, units, placement, a `binding` block, and `status: "pending-fusion-attach"`. No URN is pre-minted. 2. The bridge opens the `.lbr` (library active) → `Create3DPackage` → import- `model.path` → orient → **FINISH** → Save. This both binds and renders.-3. **Optional fast-path** — if the model is already cloud-synced:- `bind-3d --lbr part.lbr --wip-urn <urn:adsk.wipprod:fs.file:vf.<id>?version=1>`- pre-writes the binding (`urn=""`, `wip_urn=…`, `locally_modified="yes"`).- Use `--urn <urn:adsk.eagle:package:…>` only for managed/library.io parts.- Caveat: a pre-written `wip_urn` shows a gray placeholder until the model is- brought into a session — so this is not a substitute for the bridge attach.+ `model.path` → orient (your `placement` overrides) → **FINISH** → Save. This+ both binds and renders; the in-Fusion library is the runtime source of truth.+3. **Record the URN** the bridge reports (`fusion_aps_file_info {query:"<pkg>"}`+ after Save → `fs.file:vf…?version=N`):+ `bind-3d --record-only --sidecar part.fusion3d.json --wip-urn <that>` writes+ `binding.wip_urn` + `status: bound` (and `--lineage-urn` for optional+ provenance) WITHOUT touching the `.lbr`.+4. **Optional fast-path** — bake the binding into the `.lbr`:+ `bind-3d --lbr part.lbr --wip-urn <…fs.file:vf…?version=N>`+ (`urn=""`, `wip_urn=…`, `locally_modified="yes"`). `--urn <…eagle:package…>`+ is managed/library.io only. NB a pre-written `wip_urn` shows a gray placeholder+ until the model is brought into a session, and `dm.lineage` is refused for the+ `.lbr` (provenance only) — so this is not a substitute for the bridge attach. URN families: **self-hosted** model → `wip_urn` = versioned `fs.file:vf …?version=1` (NOT the `dm.lineage` URN); **managed** part → `urn:adsk.eagle:package:NNN/V`.
VERSION+1−1@@ -1 +1 @@-1.1.1+1.1.2
bin/adom-lbr⋯ 1 unchanged line ⋯
docs/fusion-export.md+36−15@@ -127,30 +127,48 @@ names `model.path`, `package`, and the placement: ### Step 2 — the bridge attaches the model (this both binds AND renders) The Fusion bridge: opens the `.lbr` (library **active**) → `Electron.Create3DPackage`-→ imports `model.path` → auto-orients → `Package3DStop` (**FINISH**) → **Save**.+→ imports `model.path` → auto-orients (your `placement` is an **override** for its+"rotate smallest bbox dim to Z" default) → `Package3DStop` (**FINISH**) → **Save**. The Content Manager tree then shows the 3D nested under the package. (Run [chipfit](../../adom-chipfit) before FINISH to confirm pin-1 / seat-Z.) -This is the whole job for the normal case — no `bind-3d` needed, because the-FINISH step is what makes the body actually render.+This is the whole job for the normal case — the FINISH step is what makes the body+actually render. The **in-Fusion library is the runtime source of truth.** -### Optional — pre-write the binding (fast-path)+### Step 3 — record the URN back (provenance) -If the model is **already cloud-synced** and you have its versioned file URN, you-can pre-write the binding so a downstream consumer gets a self-contained `.lbr`:+After Save, the bridge reports the saved version's URN —+`fusion_aps_file_info {query:"<package>"}` returns the tip+`fs.file:vf.<id>?version=N`. adom-lbr **owns the sidecar record** and writes it:++```bash+adom-lbr bind-3d --record-only --sidecar part.fusion3d.json \+ --wip-urn "urn:adsk.wipprod:fs.file:vf.<id>?version=N" \+ --lineage-urn "urn:adsk.wipprod:dm.lineage:<id>" # optional provenance+```++`--record-only` writes `binding.wip_urn` + `status: "bound"` (and the optional+`lineage_urn`) **without touching the `.lbr`** — bookkeeping for idempotency and+provenance. `dm.lineage` is kept here only as the stable cross-version id; it is+**never** written into the `.lbr` (Fusion pins the versioned `fs.file` URN).++### Optional — pre-write the binding into the `.lbr` (fast-path)++Once recorded/known-synced, you can bake the binding into the `.lbr` for a+self-contained library: ```bash adom-lbr bind-3d --lbr part.lbr \- --wip-urn "urn:adsk.wipprod:fs.file:vf.<id>?version=1" --sidecar part.fusion3d.json+ --wip-urn "urn:adsk.wipprod:fs.file:vf.<id>?version=N" --sidecar part.fusion3d.json # managed/library.io part instead? --urn "urn:adsk.eagle:package:NNN/V" ``` `bind-3d` inserts the `<packages3d>` block + the device `<package3dinstance>`,-re-lints, and (with `--sidecar`) flips the manifest to `status: "bound"`. It's-**idempotent**. **Caveat:** a pre-written `wip_urn` shows a **gray placeholder**-on a cold open — Fusion only renders the body once the model is brought into a-session via the FINISH step above. So `bind-3d` is for known-synced models, not a-substitute for the bridge attach.+re-lints, and updates the sidecar. It's **idempotent**, and refuses a `dm.lineage`+URN for the `.lbr`. **Caveat:** a pre-written `wip_urn` shows a **gray placeholder**+on a cold open — Fusion renders the body only once the model is brought into a+session via FINISH. So this is for known-synced models, not a substitute for the+bridge attach. ## End-to-end @@ -161,10 +179,13 @@ adom-lbr generate --sym part.kicad_sym --fp part.kicad_mod --model part.step -o # 2. Hand part.lbr + part.fusion3d.json + the STEP to the Fusion bridge. # It does Create3DPackage → import → orient → FINISH → Save (binds + renders). -# 3. (Optional) if the model is already synced, bake the binding in:-adom-lbr bind-3d --lbr part.lbr --wip-urn "urn:adsk.wipprod:fs.file:vf.<id>?version=1" --sidecar part.fusion3d.json+# 3. Record the URN the bridge reports (fusion_aps_file_info) back into the sidecar:+adom-lbr bind-3d --record-only --sidecar part.fusion3d.json --wip-urn "urn:adsk.wipprod:fs.file:vf.<id>?version=N"++# 4. (Optional) bake the binding into the .lbr for a self-contained library:+adom-lbr bind-3d --lbr part.lbr --wip-urn "urn:adsk.wipprod:fs.file:vf.<id>?version=N" --sidecar part.fusion3d.json -# 4. Verify the 2D library+# 5. Verify the 2D library adom-lbr validate part.lbr # complete deviceset, 0 errors ```
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "1.1.1",+ "version": "1.1.2", "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": {},
page.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "1.1.1",+ "version": "1.1.2", "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": {},
src/main.rs+80−29@@ -54,23 +54,31 @@ enum Commands { /// placeholder until the model is brought into a Fusion session. #[command(name = "bind-3d")] Bind3d {- /// Path to the .lbr to patch+ /// Path to the .lbr to patch (omit with --record-only) #[arg(long)]- lbr: PathBuf,+ lbr: Option<PathBuf>, /// Self-hosted model: the versioned file URN- /// (urn:adsk.wipprod:fs.file:vf.<id>?version=1). The usual case.+ /// (urn:adsk.wipprod:fs.file:vf.<id>?version=N). The usual case. #[arg(long, conflicts_with = "urn")] wip_urn: Option<String>, /// Managed (library.io) part: an urn:adsk.eagle:package:NNN/V URN. #[arg(long)] urn: Option<String>,+ /// Optional dm.lineage URN — recorded as sidecar provenance ONLY,+ /// never written into the .lbr (Fusion pins the versioned fs.file URN).+ #[arg(long)]+ lineage_urn: Option<String>,+ /// Record the URN into the sidecar only; don't touch the .lbr. Use after+ /// the bridge attaches the model (in-Fusion library is source of truth).+ #[arg(long)]+ record_only: bool, /// Package to attach to (default: the .lbr's first/device package) #[arg(long)] package: Option<String>, /// Output path (default: patch --lbr in place) #[arg(short, long)] output: Option<PathBuf>,- /// Also patch this sidecar manifest's binding + status to bound+ /// Sidecar manifest to record the binding + status into #[arg(long)] sidecar: Option<PathBuf>, },@@ -706,37 +714,73 @@ fn write_fusion3d_sidecar( "units": "mm" }, "placement": {- // .kicad_mod footprints are centred on the package origin (0,0),- // seat plane at Z=0. Identity unless chipfit reports an offset.+ // Orientation hint/override for the bridge's auto-orient (which+ // rotates the smallest bbox dim to Z). .kicad_mod footprints are+ // centred on the package origin (0,0), seat plane Z=0; set a known+ // pin-1 rotation/offset here when a part needs it. "translation_mm": [0.0, 0.0, 0.0], "rotation_deg": [0.0, 0.0, 0.0],- "note": "Footprint origin is the package centre; pin 1 per the .lbr <smd>. Run chipfit to confirm pin-1 alignment / seat Z before the FINISH step."+ "note": "Orientation hint/override. Footprint origin is the package centre; pin 1 per the .lbr <smd>. Run chipfit to confirm pin-1 alignment / seat Z before the FINISH step." },- // How the bridge should link the model once it's in Fusion. Self-hosted- // is the normal path; managed is only for library.io parts.+ // How the model is linked once the bridge has attached it. Self-hosted+ // is the normal path; managed is only for library.io parts. adom-lbr+ // OWNS this record (the in-Fusion library is the runtime source of+ // truth); it's filled in by `bind-3d` once the bridge reports the URN. "binding": { "scheme": "self-hosted",+ // The versioned file URN that goes in the .lbr. Recorded after the+ // attach (the bridge reports it via fusion_aps_file_info+ // {query:"<package>"} after Save). Always fs.file:vf …?version=N. "wip_urn": serde_json::Value::Null,+ // Optional provenance only — the stable cross-version id for+ // re-resolving the latest version. NEVER written into the .lbr.+ "lineage_urn": serde_json::Value::Null, "package3d_attrs": { "urn": "",- "wip_urn": "<urn:adsk.wipprod:fs.file:vf.<id>?version=1>",+ "wip_urn": "<urn:adsk.wipprod:fs.file:vf.<id>?version=N>", "locally_modified": "yes", "type": "model" },- "note": "Self-uploaded model → wip_urn (versioned fs.file:vf, ?version=1) with urn=\"\" empty. urn:adsk.eagle:package is MANAGED-only. A bare wip_urn does NOT auto-render on a cold open — the model still has to be brought into a session via the generator FINISH."+ "note": "Self-uploaded model → wip_urn = versioned fs.file:vf …?version=N (urn=\"\" empty). NEVER dm.lineage in the .lbr (keep lineage as sidecar provenance only). urn:adsk.eagle:package is MANAGED-only. A bare wip_urn does NOT auto-render on a cold open — the model must be brought into a session via the FINISH." }, "status": "pending-fusion-attach", "bridge_steps": [ "Open the .lbr in Fusion (library ACTIVE) — the 2D symbol + footprint are already valid.",- format!("Electron.Create3DPackage for package '{package}' → import model.path → auto-orient → Package3DStop (FINISH) → Save. This BOTH binds the package and renders the model."),+ format!("Electron.Create3DPackage for package '{package}' → import model.path → auto-orient (placement overrides if set) → Package3DStop (FINISH) → Save. This BOTH binds the package and renders the model."), "The sidecar needs no pre-minted URN; the FINISH step is what makes the body appear.",- format!("(Optional fast-path, only if the model is already cloud-synced) `adom-lbr bind-3d --lbr <lbr> --wip-urn <urn:adsk.wipprod:fs.file:vf.<id>?version=1>` pre-writes the binding for package '{package}'. Note: a pre-written wip_urn alone shows a gray placeholder until the model is brought into a session."),+ format!("After Save, report the URN: fusion_aps_file_info {{query:\"{package}\"}} returns the tip fs.file:vf.<id>?version=N. Record it: `adom-lbr bind-3d --record-only --sidecar <this.json> --wip-urn <that-urn>` (writes binding.wip_urn + status=bound; pass --lineage-urn for optional provenance)."),+ "(Optional fast-path) once recorded/known-synced, `adom-lbr bind-3d --lbr <lbr> --wip-urn <…fs.file:vf…?version=N>` pre-writes the binding into the .lbr. NB a pre-written wip_urn shows a gray placeholder until the model is brought into a session.", "Verify the 3D body seats on the footprint with pin 1 aligned." ] }); fs::write(side_path, serde_json::to_string_pretty(&manifest).unwrap() + "\n") } +/// Record a 3D binding into the `*.fusion3d.json` sidecar: the versioned URN+/// (`binding.wip_urn` for self-hosted, `binding.urn` for managed), an optional+/// `dm.lineage` provenance URN, and `status: "bound"`. adom-lbr owns this record;+/// the in-Fusion library is the runtime source of truth.+fn update_sidecar_binding(side: &std::path::Path, link: &str, managed: bool, lineage: Option<&str>) {+ match fs::read_to_string(side).ok().and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok()) {+ Some(mut v) => {+ if managed {+ v["binding"]["urn"] = serde_json::Value::String(link.to_string());+ v["binding"]["scheme"] = serde_json::Value::String("managed".into());+ } else {+ v["binding"]["wip_urn"] = serde_json::Value::String(link.to_string());+ v["binding"]["scheme"] = serde_json::Value::String("self-hosted".into());+ }+ if let Some(l) = lineage {+ v["binding"]["lineage_urn"] = serde_json::Value::String(l.to_string());+ }+ v["status"] = serde_json::Value::String("bound".into());+ let _ = fs::write(side, serde_json::to_string_pretty(&v).unwrap() + "\n");+ println!("OK: Recorded binding in sidecar {} (status -> bound)", side.display());+ }+ None => eprintln!("WARN: Could not update sidecar {}", side.display()),+ }+}+ /// Patch a 3D-model binding into a `.lbr`, making the reference self-contained. /// Adds (or replaces) the `<packages3d>` section after `</packages>` and inserts /// a `<package3dinstance>` into the device(s) using `package` (defaults to the@@ -1218,7 +1262,7 @@ fn main() { } } - Commands::Bind3d { lbr, wip_urn, urn, package, output, sidecar } => {+ Commands::Bind3d { lbr, wip_urn, urn, lineage_urn, record_only, package, output, sidecar } => { // Exactly one of --wip-urn (self-hosted) or --urn (managed). let (link, managed) = match (wip_urn.as_deref(), urn.as_deref()) { (Some(w), None) => (w.to_string(), false),@@ -1232,6 +1276,27 @@ fn main() { process::exit(1); } };+ // A dm.lineage URN must never land in the .lbr — Fusion pins the+ // versioned fs.file URN. Route lineage to provenance instead.+ if link.contains("dm.lineage") {+ eprintln!("ERROR: that's a dm.lineage URN — it must not go in the .lbr. Pass the versioned fs.file:vf…?version=N URN to --wip-urn, and the lineage URN to --lineage-urn (sidecar provenance only).");+ process::exit(1);+ }++ // Record-only: update the sidecar provenance, leave the .lbr alone.+ if record_only {+ let Some(side) = sidecar.as_ref() else {+ eprintln!("ERROR: --record-only needs --sidecar <manifest.json> to write into.");+ process::exit(1);+ };+ update_sidecar_binding(side, &link, managed, lineage_urn.as_deref());+ return;+ }++ let Some(lbr) = lbr else {+ eprintln!("ERROR: bind-3d needs --lbr <file.lbr> (or --record-only to update just the sidecar).");+ process::exit(1);+ }; let content = fs::read_to_string(&lbr).unwrap_or_else(|e| { eprintln!("ERROR: Cannot read {}: {}", lbr.display(), e); process::exit(1);@@ -1258,21 +1323,7 @@ fn main() { } if let Some(side) = &sidecar {- match fs::read_to_string(side).ok().and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok()) {- Some(mut v) => {- if managed {- v["binding"]["urn"] = serde_json::Value::String(link.clone());- v["binding"]["scheme"] = serde_json::Value::String("managed".into());- } else {- v["binding"]["wip_urn"] = serde_json::Value::String(link.clone());- v["binding"]["scheme"] = serde_json::Value::String("self-hosted".into());- }- v["status"] = serde_json::Value::String("bound".into());- let _ = fs::write(side, serde_json::to_string_pretty(&v).unwrap() + "\n");- println!("OK: Updated sidecar {} (status -> bound)", side.display());- }- None => eprintln!("WARN: Could not update sidecar {}", side.display()),- }+ update_sidecar_binding(side, &link, managed, lineage_urn.as_deref()); } if !lint.passed {