Adom Desktop - Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Desktop: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
name: fusion-multipart-libraries description: "How to build ONE Fusion 360 .lbr library holding MANY parts where EACH package has its real 3D model attached and RENDERING - the SOLVED, fully programmatic, zero-GUI-dialog recipe. The method: for each part, import its STEP into a Design, orient it flat, saveAs an .f3d, aps_upload the .f3d, construct an fs.file:vf wip_urn from the upload lineage, and hand-write the package3d binding into the combined .lbr. Covers the exact binding markup, the orient, merging per-part .lbr files, the connect/pad pre-merge validation, and the dead ends to avoid (the GUI Package3D generator's unbeatable CEF dialogs; a raw STEP upload renders nothing). Read this when attaching 3D to more than one part in a single library, or building a parts-library sampler. Trigger words - multi part library, multiple 3d packages, attach 3d to many parts, combined lbr, merge lbr, basic parts library, parts sampler, per package 3d, package3d, f3d upload, fs.file vf urn, aps_upload 3d, programmatic 3d binding, render 3d package, thumbnail download failed, Create3DPackage dialogs, attach all 3d."
Building a many-part Fusion library with real, RENDERING 3D on every package
The multi-part extension of fusion-libraries (one part, end to end).
Getting a distinct, RENDERING 3D model onto each of many packages inside one .lbr is solved -
and the solution is fully programmatic, with zero GUI dialogs. Built on
fusion-cloud-save and fusion-driving.
Proven 2026-06-29: a 10-part Adom basic-parts library (R/C/L/LED/MCU), every package rendering its
real chip, flat on its footprint, built entirely this way.
⚡ FASTEST PATH - one call: fusion_build_library_3d (v1.6.0+)
For a whole many-part library, you no longer loop make_3d_package + hand-write bindings. ONE verb
does the entire pipeline - per-part footprint+chip f3d packages, all bindings injected in one
pass, the finished library opened once, plus a BEFORE/AFTER screenshot per part:
fusion_build_library_3d {
"lbrPath": "C:/Users/<user>/lib/Combined.lbr", // the merged 2D library to bind + open
"parts": [ // one entry per part
{"package":"R_4k7","lbrPath":"C:/.../R_4k7.lbr","modelPath":"C:/.../R_4k7.step"},
{"package":"ESP32-S3FN8","lbrPath":"C:/.../ESP32-S3FN8.lbr","modelPath":"C:/.../ESP32-S3FN8-named.step"}
// ...
],
"outLbrPath": "C:/Users/<user>/lib/Combined_bound.lbr", // optional (default: overwrite lbrPath)
"capture": true // before/after PNGs per part (default true)
}
- Each part's
lbrPathis its OWN per-part.lbr(whose FIRST package is that footprint - what Create3DPackage loads);modelPathis its STEP.packageis the package name in BOTH the per-part and the combined.lbr(they match - that's how the binding lands). projectId/folderIdare optional - they default to the MAIN-project upload folder, so you do not have tofusion_aps_browsefor them.- Returns
{partsBound, partsTotal, parts:[{package,success,wip_urn,dims_mm,error}], screenshots: [{package,stage,path}], boundLbr, opened}. The before/after PNGs land on the box (C:/tmp/conduit-screenshots) -desktop_pull_filethem for a demo video. - Each part is independent. If Fusion resets mid-run, the parts already done kept their f3d
uploads; just re-run the whole
parts[](re-making a few f3ds is cheap) - the injection rewrites all bindings in one pass, so a single final call always yields a complete library.
⚠️ Relay timeout - a many-part run "times out" but KEEPS GOING (don't panic)
The adom-desktop relay times out the request at ~60s, but fusion_build_library_3d for a dozen
parts takes minutes. So you will often get status: timeout / Request timed out while the build is
still running server-side and finishes fine. Do NOT treat that as failure. Instead:
- Wait, then verify by the artifacts, not the response: read the
boundLbrand count<package3d name=(should equal your part count), and list the before/after PNGs inC:/tmp/conduit-screenshots. - If a part is missing (the FIRST part of a run is the usual casualty, as Fusion settles from a prior
view), just re-run
fusion_build_library_3dfor the missing parts withoutLbrPathpointing at the SAME file. The binding injection is merge-aware + idempotent (v1.6.1+): it reads the bindings already in the file and accumulates, so re-running a subset NO LONGER wipes the parts that already succeeded. (Each part also self-retries once on a transient first-part failure.)
This is the fix for the "a re-run dropped my earlier bindings" trap - earlier the injection replaced
the whole <packages3d> block, so a subset re-run left you with only that subset.
🔍 Show the library views (what makes EEs trust it) - fusion_capture_library_views
The 3D before/after shots prove the chip renders, but they do NOT show the symbol, footprint, or device - the views developers actually want. After building, open the library and call:
fusion_capture_library_views { "packages": ["ESP32-S3FN8", "ATSAMD51J20A-AUT", "R_4k7"] }
It runs EDIT <deviceset>.sym / .pac / .dev + WINDOW FIT + a background screenshot per view and
returns the PNG paths (on the box; desktop_pull_file them). You get, per part: the SYMBOL (full
pinout), the FOOTPRINT (pad array + the Copper/Silkscreen/Mask layer stack), and the
COMPONENT view (Content Manager: the symbol + the package table with the footprint<->package
Mapped ✓ check + pin/pad counts). Put these in the demo video alongside the 3D - they are the warm
-and-fuzzy shots. (Manual equivalent: fusion_open_lbr then fusion_electron_run "EDIT X.sym" etc.)
🏭 Scaling to a WHOLE catalog - SHARE footprints (do NOT build 3D per value)
Building a big library (e.g. the entire 138-part Adom basic parts library) does NOT mean 138 3D
builds. A real EAGLE/Fusion library SHARES footprints: ONE R-0402 package is used by all 41 of the
0402 resistor values; ONE C-0603 by all 14 of those caps. The 3D model is per package, not per
value. So:
- Collapse the catalog to its unique
(type, package)combos. The whole Adom basic parts catalog (138 values) is just 10 footprints: R-0402, R-0603, C-0402, C-0603, C-0805, L, LED-0402, LED-0603, LED-2020, FB-0402. - Build ONE base part per combo (
adom-lbr generate→ a.lbrwith that footprint + symbol), thenfusion_build_library_3dover the ~10 bases → ~10 f3dpackage3dbindings. (10 builds, not 138.) - Clone the base deviceset per value. Take each bound base
<deviceset>(it already carries the shared<gate symbol=…>,<device package=…>, and<package3dinstances>3D binding) and emit one copy per catalog value, changing only the<deviceset name=…>(EAGLE-style, e.g.R-0402-4K7,C-0603-100N) and the<technology>attributes (VALUE/MPN/MF/MOUSER). All clones reference the SAME symbol + package + 3D urn. - Result: N packages + N symbols + N package3d + M devicesets (M ≫ N). The 138-part library = 10 packages + 138 devicesets, every one Mapped ✓ and rendering, built from 10 uploads.
Sourcing the ~10 footprints + 3D: passive land patterns are standard per package (IPC-7351), so a
0603 cap shares the 0603 resistor land, an 0805 cap shares the 0805 land, etc. Pull real CAD from the
per-MPN wiki pages (/api/v1/pages/<mpn-lowercased>/files/<MPN>.kicad_mod/.step - chip-fetcher's
output), the wiki static footprints library (static/footprints/<pkg>/), or generic KiCad models
(C_0603_1608Metric.step, etc.). One representative MPN per combo is enough.
Everything below is the MANUAL equivalent (what the verb does internally) - read it to understand or
debug, but reach for fusion_build_library_3d first.
✅ THE RECIPE - per part, then merge (no Create3DPackage, no dialogs)
A proper component 3D package contains the FOOTPRINT (pads + courtyard) AND the chip, merged and aligned, so the 3D viewer can verify the chip's pads land on the footprint pads. For each part:
Make the component f3d (footprint + chip) - one verb does it:
fusion_make_3d_package {lbrPath:"C:/.../sp_PART.lbr", modelPath:"C:/.../PART.step", projectId, folderId}-> returns awip_urn. It opens the lbr, runsElectron.Create3DPackageto load the package's FOOTPRINT into a generator doc, imports the chip onto it, orients it flat,saveAsan.f3d(footprint + chip), uploads it, and constructs the urn. (projectId/folderId fromfusion_aps_browse.) Then skip to step 4.Manual equivalent (what the verb does - the key is Create3DPackage loads the footprint, and you
saveAsinstead of FINISH to skip the CEF dialogs):# fusion_open_lbr sp_PART.lbr, then Electron.Create3DPackage <sp_PART.lbr> # -> a generator doc with the package's FOOTPRINT (pads/courtyard) loaded. THEN, in that doc: import adsk.core, adsk.fusion, math app = adsk.core.Application.get(); doc = app.activeDocument # the generator doc (HAS the footprint) d = adsk.fusion.Design.cast(app.activeProduct); root = d.rootComponent im = app.importManager im.importToTarget(im.createSTEPImportOptions("C:/.../PART.step"), root) # chip ONTO the footprint oc = root.occurrences.item(root.occurrences.count-1); bb = oc.boundingBox dx,dy,dz = bb.maxPoint.x-bb.minPoint.x, bb.maxPoint.y-bb.minPoint.y, bb.maxPoint.z-bb.minPoint.z axis = (1,0,0) if (dy<=dx and dy<=dz) else ((0,1,0) if (dx<=dy and dx<=dz) else None) # smallest -> flat in Z if axis: mat = oc.transform2.copy(); r = adsk.core.Matrix3D.create() r.setToRotation(math.pi/2, adsk.core.Vector3D.create(*axis), adsk.core.Point3D.create(0,0,0)) mat.transformBy(r); oc.transform2 = mat doc.saveAs("PART_fp3d", app.data.activeProject.rootFolder, "fp+chip", "") # saveAs, NOT Package3DStop/FINISH result = {"path": doc.dataFile.id} # local .f3d cache path (footprint + chip)⛔ Do NOT import into an EMPTY Design - that yields a chip with NO footprint (an incomplete package: it renders the chip but the 3D viewer can't show the pads aligning).
Upload the f3d (the verb does this):
fusion_aps_upload {projectId, folderId, localPath:<the .f3d>, fileName:"PART_3d.f3d"}->{ "itemUrn": "urn:adsk.wipprod:dm.lineage:<id>", ... }.Construct the binding urn:
urn:adsk.wipprod:fs.file:vf.<id>?version=1- the lineage<id>doubles as the version id for these single-version assets.Hand-write the binding into the combined
.lbr(markup below). Repeat per part, then open the merged library: it RESOLVES and RENDERS every component (footprint + chip), and saves to cloud.
The binding markup (exactly what Fusion writes)
- Library-level, inserted between
</packages>and<symbols>(one<package3d>per part):<packages3d> <package3d name="PKG" urn="" wip_urn="urn:adsk.wipprod:fs.file:vf.<id>?version=1" locally_modified="yes" type="model"> <description>PKG</description> <packageinstances><packageinstance name="PKG"/></packageinstances> </package3d> ...one per part... </packages3d> - Inside each device, right after its
</connects>:<package3dinstances><package3dinstance package3d_urn="urn:adsk.wipprod:fs.file:vf.<id>?version=1"/></package3dinstances>
Verify success: open the merged .lbr, fusion_check_dialogs returns 0 (no broken-ref), and each
device's lower-right 3D preview shows the real chip (NOT "Thumbnail download failed").
Speedups + what to expect (so you go fast)
- Batch it. Call
fusion_make_3d_packageonce per part, collect the urns, hand-merge ALL bindings into the combined.lbrin ONE pass, thenfusion_open_lbrONCE. Do NOT open/save the library per part - it is slow and Fusion can reset the session between calls. - What success looks like:
fusion_check_dialogs== 0, every device's 3D preview shows footprint + chip, and opening an f3d shows the chip's pads landing on the footprint pads. - Fusion resets between calls on long runs (and after a
bridge_installrespawn it can briefly report "not running" -fusion_startthen retry). So: save the finished library to the cloud, and reopen it from the cloud (fusion_aps_open) for a clean Electronics Library view + screenshots - the in-session view often gets an empty "Untitled" shoved in front of it. - It is entirely dialog-free - no Save dialog, no CEF modals; capture only hwnd-targeted background screenshots (never fullscreen - see fusion-driving).
⛔ Why an f3d and not a raw STEP (the key gotcha)
A raw .step uploaded via fusion_aps_upload gives a urn that resolves (no broken-ref) and saves
- BUT the library 3D preview shows "Thumbnail download failed" and does NOT render. A raw STEP is
not a renderable Fusion 3D asset. An f3d (a real Fusion Design with the STEP imported) IS, and
binds + renders identically to a GUI-minted package. So always: STEP -> import into a Design ->
saveAsf3d -> upload the f3d. (Proven contrast: a STEP-bound nRF showed "Thumbnail download failed"; the f3d-bound nRF renders a clean black QFN.)
⛔ Do NOT use the GUI Package3D generator for this (it is dialog-trapped)
Electron.Create3DPackage + import + Package3DStop (FINISH) is the manual/GUI way and it is a trap:
- FINISH pops a native Save dialog AND two opaque "Fusion360" CEF modals that are unreadable by
Win32, by UIA (
desktop_find_controlfinds nothing), and by PrintWindow (captures solid black), and they ignore programmatic escape/WM_CLOSE. Create3DPackagealso binds to the first package of the OPEN library, not the file you pass it, so looping reordered files against one open library binds only 1/N. The f3d recipe above sidesteps ALL of this. Use it; do not fight the generator.
Merging per-part .lbr files into one combined library
An EAGLE .lbr is a library of many devicesets, so combine the per-part files: keep ONE file's
<layers> skeleton, then concatenate every part's <package>, <symbol>, <deviceset> (MPN-unique
names, no collisions), then add the <packages3d> + per-device <package3dinstances> from the recipe.
adom-lbr validate combined.lbr.
⚠️ VALIDATE connect/pad consistency BEFORE merging (one bad part poisons everything)
Fusion rejects the ENTIRE merged .lbr ("has errors and cannot be opened") if ANY device has a
<connect pad="N"> referencing a pad its package does not define. A single-device .lbr may still
open, masking it until merged. Before merging, for every device confirm every <connect pad="N">
references an existing <smd name="N">/<pad name="N">. Passives are the usual offenders (symbol pin
count vs footprint pad count). adom-lbr lint did NOT catch this - validate it yourself.
Capture the money shots (for a demo video)
Per part, hwnd-targeted (never fullscreen - see fusion-driving): the footprint alone, then the device view with the real rendered chip on its footprint (the money shot), reopening the cloud library so the 3D preview refreshes. See CREATING_BASIC_PARTS_LIBRARIES.md for the full tool-chain tutorial + montage recipe.
---
name: fusion-multipart-libraries
description: "How to build ONE Fusion 360 .lbr library holding MANY parts where EACH package has its real 3D model attached and RENDERING - the SOLVED, fully programmatic, zero-GUI-dialog recipe. The method: for each part, import its STEP into a Design, orient it flat, saveAs an .f3d, aps_upload the .f3d, construct an fs.file:vf wip_urn from the upload lineage, and hand-write the package3d binding into the combined .lbr. Covers the exact binding markup, the orient, merging per-part .lbr files, the connect/pad pre-merge validation, and the dead ends to avoid (the GUI Package3D generator's unbeatable CEF dialogs; a raw STEP upload renders nothing). Read this when attaching 3D to more than one part in a single library, or building a parts-library sampler. Trigger words - multi part library, multiple 3d packages, attach 3d to many parts, combined lbr, merge lbr, basic parts library, parts sampler, per package 3d, package3d, f3d upload, fs.file vf urn, aps_upload 3d, programmatic 3d binding, render 3d package, thumbnail download failed, Create3DPackage dialogs, attach all 3d."
---
# Building a many-part Fusion library with real, RENDERING 3D on every package
The multi-part extension of [fusion-libraries](../fusion-libraries/SKILL.md) (one part, end to end).
Getting a distinct, RENDERING 3D model onto each of many packages inside one `.lbr` is **solved** -
and the solution is fully programmatic, with **zero GUI dialogs**. Built on
[fusion-cloud-save](../fusion-cloud-save/SKILL.md) and [fusion-driving](../fusion-driving/SKILL.md).
Proven 2026-06-29: a 10-part Adom basic-parts library (R/C/L/LED/MCU), every package rendering its
real chip, flat on its footprint, built entirely this way.
## ⚡ FASTEST PATH - one call: `fusion_build_library_3d` (v1.6.0+)
For a whole many-part library, you no longer loop `make_3d_package` + hand-write bindings. ONE verb
does the entire pipeline - per-part footprint+chip f3d packages, **all** bindings injected in one
pass, the finished library opened once, plus a BEFORE/AFTER screenshot per part:
```jsonc
fusion_build_library_3d {
"lbrPath": "C:/Users/<user>/lib/Combined.lbr", // the merged 2D library to bind + open
"parts": [ // one entry per part
{"package":"R_4k7","lbrPath":"C:/.../R_4k7.lbr","modelPath":"C:/.../R_4k7.step"},
{"package":"ESP32-S3FN8","lbrPath":"C:/.../ESP32-S3FN8.lbr","modelPath":"C:/.../ESP32-S3FN8-named.step"}
// ...
],
"outLbrPath": "C:/Users/<user>/lib/Combined_bound.lbr", // optional (default: overwrite lbrPath)
"capture": true // before/after PNGs per part (default true)
}
```
- Each part's `lbrPath` is its OWN per-part `.lbr` (whose FIRST package is that footprint - what
Create3DPackage loads); `modelPath` is its STEP. `package` is the package name in BOTH the per-part
and the combined `.lbr` (they match - that's how the binding lands).
- `projectId`/`folderId` are **optional** - they default to the MAIN-project upload folder, so you do
not have to `fusion_aps_browse` for them.
- Returns `{partsBound, partsTotal, parts:[{package,success,wip_urn,dims_mm,error}], screenshots:
[{package,stage,path}], boundLbr, opened}`. The before/after PNGs land on the box
(`C:/tmp/conduit-screenshots`) - `desktop_pull_file` them for a demo video.
- **Each part is independent.** If Fusion resets mid-run, the parts already done kept their f3d
uploads; just re-run the whole `parts[]` (re-making a few f3ds is cheap) - the injection rewrites
all bindings in one pass, so a single final call always yields a complete library.
### ⚠️ Relay timeout - a many-part run "times out" but KEEPS GOING (don't panic)
The `adom-desktop` relay times out the *request* at ~60s, but `fusion_build_library_3d` for a dozen
parts takes minutes. So you will often get **`status: timeout / Request timed out` while the build is
still running server-side and finishes fine.** Do NOT treat that as failure. Instead:
1. Wait, then **verify by the artifacts, not the response**: read the `boundLbr` and count
`<package3d name=` (should equal your part count), and list the before/after PNGs in
`C:/tmp/conduit-screenshots`.
2. If a part is missing (the FIRST part of a run is the usual casualty, as Fusion settles from a prior
view), **just re-run `fusion_build_library_3d` for the missing parts with `outLbrPath` pointing at
the SAME file.** The binding injection is **merge-aware + idempotent** (v1.6.1+): it reads the
bindings already in the file and accumulates, so re-running a subset NO LONGER wipes the parts that
already succeeded. (Each part also self-retries once on a transient first-part failure.)
This is the fix for the "a re-run dropped my earlier bindings" trap - earlier the injection replaced
the whole `<packages3d>` block, so a subset re-run left you with only that subset.
### 🔍 Show the library views (what makes EEs trust it) - `fusion_capture_library_views`
The 3D before/after shots prove the chip renders, but they do NOT show the **symbol, footprint, or
device** - the views developers actually want. After building, open the library and call:
```jsonc
fusion_capture_library_views { "packages": ["ESP32-S3FN8", "ATSAMD51J20A-AUT", "R_4k7"] }
```
It runs `EDIT <deviceset>.sym / .pac / .dev` + `WINDOW FIT` + a background screenshot per view and
returns the PNG paths (on the box; `desktop_pull_file` them). You get, per part: the **SYMBOL** (full
pinout), the **FOOTPRINT** (pad array + the Copper/Silkscreen/Mask layer stack), and the
**COMPONENT** view (Content Manager: the symbol + the package table with the footprint<->package
**Mapped ✓** check + pin/pad counts). Put these in the demo video alongside the 3D - they are the warm
-and-fuzzy shots. (Manual equivalent: `fusion_open_lbr` then `fusion_electron_run "EDIT X.sym"` etc.)
### 🏭 Scaling to a WHOLE catalog - SHARE footprints (do NOT build 3D per value)
Building a big library (e.g. the entire **138-part Adom basic parts library**) does NOT mean 138 3D
builds. A real EAGLE/Fusion library SHARES footprints: ONE `R-0402` package is used by all 41 of the
0402 resistor *values*; ONE `C-0603` by all 14 of those caps. The 3D model is per *package*, not per
value. So:
1. **Collapse the catalog to its unique `(type, package)` combos.** The whole Adom basic parts catalog
(138 values) is just **10 footprints**: R-0402, R-0603, C-0402, C-0603, C-0805, L, LED-0402,
LED-0603, LED-2020, FB-0402.
2. **Build ONE base part per combo** (`adom-lbr generate` → a `.lbr` with that footprint + symbol),
then `fusion_build_library_3d` over the ~10 bases → ~10 f3d `package3d` bindings. (10 builds, not 138.)
3. **Clone the base deviceset per value.** Take each bound base `<deviceset>` (it already carries the
shared `<gate symbol=…>`, `<device package=…>`, and `<package3dinstances>` 3D binding) and emit one
copy per catalog value, changing only the `<deviceset name=…>` (EAGLE-style, e.g. `R-0402-4K7`,
`C-0603-100N`) and the `<technology>` attributes (`VALUE`/`MPN`/`MF`/`MOUSER`). All clones reference
the SAME symbol + package + 3D urn.
4. Result: **N packages + N symbols + N package3d + M devicesets** (M ≫ N). The 138-part library =
10 packages + 138 devicesets, every one Mapped ✓ and rendering, built from 10 uploads.
**Sourcing the ~10 footprints + 3D:** passive land patterns are standard per package (IPC-7351), so a
0603 cap shares the 0603 *resistor* land, an 0805 cap shares the 0805 land, etc. Pull real CAD from the
per-MPN wiki pages (`/api/v1/pages/<mpn-lowercased>/files/<MPN>.kicad_mod`/`.step` - chip-fetcher's
output), the wiki **static footprints library** (`static/footprints/<pkg>/`), or generic KiCad models
(`C_0603_1608Metric.step`, etc.). One representative MPN per combo is enough.
Everything below is the MANUAL equivalent (what the verb does internally) - read it to understand or
debug, but reach for `fusion_build_library_3d` first.
## ✅ THE RECIPE - per part, then merge (no Create3DPackage, no dialogs)
A proper component 3D package contains the **FOOTPRINT (pads + courtyard) AND the chip**, merged and
aligned, so the 3D viewer can verify the chip's pads land on the footprint pads. For each part:
1. **Make the component f3d (footprint + chip) - one verb does it:**
`fusion_make_3d_package {lbrPath:"C:/.../sp_PART.lbr", modelPath:"C:/.../PART.step", projectId, folderId}`
-> returns a `wip_urn`. It opens the lbr, runs `Electron.Create3DPackage` to load the package's
FOOTPRINT into a generator doc, imports the chip onto it, orients it flat, `saveAs` an `.f3d`
(footprint + chip), uploads it, and constructs the urn. (projectId/folderId from `fusion_aps_browse`.)
Then **skip to step 4.**
Manual equivalent (what the verb does - the key is Create3DPackage loads the footprint, and you
`saveAs` instead of FINISH to skip the CEF dialogs):
```python
# fusion_open_lbr sp_PART.lbr, then Electron.Create3DPackage <sp_PART.lbr>
# -> a generator doc with the package's FOOTPRINT (pads/courtyard) loaded. THEN, in that doc:
import adsk.core, adsk.fusion, math
app = adsk.core.Application.get(); doc = app.activeDocument # the generator doc (HAS the footprint)
d = adsk.fusion.Design.cast(app.activeProduct); root = d.rootComponent
im = app.importManager
im.importToTarget(im.createSTEPImportOptions("C:/.../PART.step"), root) # chip ONTO the footprint
oc = root.occurrences.item(root.occurrences.count-1); bb = oc.boundingBox
dx,dy,dz = bb.maxPoint.x-bb.minPoint.x, bb.maxPoint.y-bb.minPoint.y, bb.maxPoint.z-bb.minPoint.z
axis = (1,0,0) if (dy<=dx and dy<=dz) else ((0,1,0) if (dx<=dy and dx<=dz) else None) # smallest -> flat in Z
if axis:
mat = oc.transform2.copy(); r = adsk.core.Matrix3D.create()
r.setToRotation(math.pi/2, adsk.core.Vector3D.create(*axis), adsk.core.Point3D.create(0,0,0))
mat.transformBy(r); oc.transform2 = mat
doc.saveAs("PART_fp3d", app.data.activeProject.rootFolder, "fp+chip", "") # saveAs, NOT Package3DStop/FINISH
result = {"path": doc.dataFile.id} # local .f3d cache path (footprint + chip)
```
⛔ Do NOT import into an EMPTY Design - that yields a chip with **NO footprint** (an incomplete
package: it renders the chip but the 3D viewer can't show the pads aligning).
2. **Upload the f3d** (the verb does this): `fusion_aps_upload {projectId, folderId, localPath:<the .f3d>,
fileName:"PART_3d.f3d"}` -> `{ "itemUrn": "urn:adsk.wipprod:dm.lineage:<id>", ... }`.
3. **Construct the binding urn:** `urn:adsk.wipprod:fs.file:vf.<id>?version=1` - the lineage `<id>`
doubles as the version id for these single-version assets.
4. **Hand-write the binding** into the combined `.lbr` (markup below). Repeat per part, then open the
merged library: it RESOLVES and **RENDERS** every component (footprint + chip), and saves to cloud.
### The binding markup (exactly what Fusion writes)
- Library-level, inserted between `</packages>` and `<symbols>` (one `<package3d>` per part):
```xml
<packages3d>
<package3d name="PKG" urn="" wip_urn="urn:adsk.wipprod:fs.file:vf.<id>?version=1" locally_modified="yes" type="model">
<description>PKG</description>
<packageinstances><packageinstance name="PKG"/></packageinstances>
</package3d>
...one per part...
</packages3d>
```
- Inside each device, right after its `</connects>`:
```xml
<package3dinstances><package3dinstance package3d_urn="urn:adsk.wipprod:fs.file:vf.<id>?version=1"/></package3dinstances>
```
Verify success: open the merged `.lbr`, `fusion_check_dialogs` returns 0 (no broken-ref), and each
device's lower-right 3D preview shows the real chip (NOT "Thumbnail download failed").
## Speedups + what to expect (so you go fast)
- **Batch it.** Call `fusion_make_3d_package` once per part, collect the urns, hand-merge ALL bindings
into the combined `.lbr` in ONE pass, then `fusion_open_lbr` ONCE. Do NOT open/save the library per
part - it is slow and Fusion can reset the session between calls.
- **What success looks like:** `fusion_check_dialogs` == 0, every device's 3D preview shows footprint +
chip, and opening an f3d shows the chip's pads landing on the footprint pads.
- **Fusion resets between calls** on long runs (and after a `bridge_install` respawn it can briefly
report "not running" - `fusion_start` then retry). So: save the finished library to the cloud, and
**reopen it from the cloud** (`fusion_aps_open`) for a clean Electronics Library view + screenshots -
the in-session view often gets an empty "Untitled" shoved in front of it.
- **It is entirely dialog-free** - no Save dialog, no CEF modals; capture only hwnd-targeted background
screenshots (never fullscreen - see fusion-driving).
## ⛔ Why an f3d and not a raw STEP (the key gotcha)
A raw `.step` uploaded via `fusion_aps_upload` gives a urn that **resolves** (no broken-ref) and saves
- BUT the library 3D preview shows **"Thumbnail download failed"** and does NOT render. A raw STEP is
not a renderable Fusion 3D asset. An **f3d** (a real Fusion Design with the STEP imported) IS, and
binds + renders identically to a GUI-minted package. So always: STEP -> import into a Design ->
`saveAs` f3d -> upload the **f3d**. (Proven contrast: a STEP-bound nRF showed "Thumbnail download
failed"; the f3d-bound nRF renders a clean black QFN.)
## ⛔ Do NOT use the GUI Package3D generator for this (it is dialog-trapped)
`Electron.Create3DPackage` + import + `Package3DStop` (FINISH) is the manual/GUI way and it is a trap:
- FINISH pops a native Save dialog AND **two opaque "Fusion360" CEF modals** that are unreadable by
Win32, by UIA (`desktop_find_control` finds nothing), and by PrintWindow (captures solid black), and
they ignore programmatic escape/WM_CLOSE.
- `Create3DPackage` also binds to the **first package of the OPEN library**, not the file you pass it,
so looping reordered files against one open library binds only 1/N.
The f3d recipe above sidesteps ALL of this. Use it; do not fight the generator.
## Merging per-part `.lbr` files into one combined library
An EAGLE `.lbr` is a library of many devicesets, so combine the per-part files: keep ONE file's
`<layers>` skeleton, then concatenate every part's `<package>`, `<symbol>`, `<deviceset>` (MPN-unique
names, no collisions), then add the `<packages3d>` + per-device `<package3dinstances>` from the recipe.
`adom-lbr validate combined.lbr`.
## ⚠️ VALIDATE connect/pad consistency BEFORE merging (one bad part poisons everything)
Fusion rejects the ENTIRE merged `.lbr` ("has errors and cannot be opened") if ANY device has a
`<connect pad="N">` referencing a pad its package does not define. A single-device `.lbr` may still
open, masking it until merged. Before merging, for every device confirm every `<connect pad="N">`
references an existing `<smd name="N">`/`<pad name="N">`. Passives are the usual offenders (symbol pin
count vs footprint pad count). `adom-lbr` lint did NOT catch this - validate it yourself.
## Capture the money shots (for a demo video)
Per part, hwnd-targeted (never fullscreen - see fusion-driving): the footprint alone, then the device
view with the real rendered chip on its footprint (the money shot), reopening the cloud library so the
3D preview refreshes. See [CREATING_BASIC_PARTS_LIBRARIES.md](../../CREATING_BASIC_PARTS_LIBRARIES.md)
for the full tool-chain tutorial + montage recipe.