app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
← Commit history
v1.5.12: fusion_make_3d_package now footprint-aware (Create3DPackage loads footprint -> chip merged -> saveAs f3d); proper component packages w/ pad alignment
6 files changed
+74−39
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.5.11+1.5.12
CHANGELOG.md+11@@ -3,6 +3,17 @@ All notable changes to the Adom Desktop Fusion 360 bridge, newest first. Canonical: https://wiki.adom.inc/adom/adom-desktop-fusion-bridge · GitHub backup: `adom-inc/adom-desktop-fusion-bridge` +## 1.5.12 - 2026-06-29++- **`fusion_make_3d_package` now produces a PROPER component package (footprint + chip).** It opens the+ lbr and runs `Electron.Create3DPackage` to load the package's FOOTPRINT into the generator, imports+ the chip ONTO it, orients flat, and `saveAs` an f3d - so the 3D model contains the footprint+ (pads + courtyard) AND the chip, merged and **aligned** (the chip's pads land on the footprint pads+ in the 3D viewer, which is the whole point of that viewer). The 1.5.11 verb imported into an empty+ Design, giving a chip with NO footprint. New required arg: **`lbrPath`**. Proven on the full 10-part+ library (`AdomBasicParts3D_v2`).+- `fusion-multipart-libraries` skill updated to the footprint-aware recipe.+ ## 1.5.11 - 2026-06-29 - **NEW `fusion_make_3d_package` verb - a RENDERING per-package 3D model with ZERO GUI dialogs.** It
adom-bridge-fusion-manifest.json+5−5@@ -1,13 +1,13 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.5.11",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.5.11/adom-bridge-fusion-v1.5.11.zip",- "sha256": "33b39c974667e8f31d4f28a48dad6ad71586c6b8a31eb437583eddbcccf854b4",- "size": 270300,+ "version": "1.5.12",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.5.12/adom-bridge-fusion-v1.5.12.zip",+ "sha256": "387a80c684ebe345f3ad10175a45eeba733a1db7a092809e21a1150d7147cfcf",+ "size": 271269, "verbPrefixes": [ "fusion_" ], "healthEndpoint": "/status",- "released_at": "2026-06-29T14:30:05Z"+ "released_at": "2026-06-29T14:54:10Z" }
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.5.11",+ "version": "1.5.12", "description": "Drive Autodesk Fusion 360 from the cloud: launch Fusion, electronics board layout, design rules, exports (STEP/IGES/STL/3MF/USDZ/OBJ/DXF/DWG/Gerbers/BOM/CPL), fast APS server-indexed cloud search plus browse/recent/file-info/versions, cloud file download/upload and folder creation, and in-app parametric modeling (fusion_run_modeling_script). Never-charge: APS calls are capped to the free tier.", "homepage": "https://github.com/adom-inc/adom-desktop-fusion-bridge", "author": "Adom Inc.",
server.py+34−16@@ -1217,40 +1217,58 @@ def _orchestrate_attach_3d_package(args: dict) -> dict: def _orchestrate_make_3d_package(args: dict) -> dict:- """Create a RENDERING 3D-package urn from a STEP, fully programmatically - NO GUI dialogs.+ """Create a RENDERING component 3D-PACKAGE urn (footprint + chip), fully programmatically - NO GUI+ dialogs. A proper component 3D model 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. - This is the dialog-free replacement for the Create3DPackage generator (whose FINISH is trapped- behind two unbeatable "Fusion360" CEF modals). It imports the STEP into a throwaway Design,- orients it flat, saveAs an .f3d, aps_uploads the .f3d, and returns the fs.file:vf wip_urn to- hand-write into a library's <packages3d>. KEY: a raw STEP upload does NOT render ("Thumbnail- download failed"); an f3d does. See the fusion-multipart-libraries skill.+ So: open the library, run Electron.Create3DPackage to load the package's FOOTPRINT into a+ generator doc, import the STEP onto it, orient it flat, then saveAs an .f3d (footprint + chip) -+ which skips the FINISH Save dialog + the two unbeatable "Fusion360" CEF modals entirely -+ aps_upload the .f3d, and return the fs.file:vf wip_urn to hand-write into the library's+ <packages3d>. - args: {modelPath: Windows .step path, projectId, folderId (both from fusion_aps_browse),- fileName?: str, orient?: bool (default true)}+ Two gotchas this avoids: (1) importing the STEP into an EMPTY design gives a chip with NO+ footprint (an incomplete package); (2) a raw STEP upload does not render at all ("Thumbnail+ download failed"). See the fusion-multipart-libraries skill.++ args: {lbrPath: Windows .lbr whose FIRST package is the footprint, modelPath: Windows .step,+ projectId, folderId (both from fusion_aps_browse), fileName?: str, orient?: bool} """- import os as _os+ import os as _os, time as _time+ lbr_path = (args.get("lbrPath") or args.get("filePath") or "").replace("\\", "/") model_path = (args.get("modelPath") or "").replace("\\", "/") project_id = args.get("projectId"); folder_id = args.get("folderId")- if not model_path:- return {"success": False, "error": "modelPath (.step Windows path) is required."}+ if not lbr_path or not model_path:+ return {"success": False,+ "error": "lbrPath (.lbr with the footprint) and modelPath (.step) are required (Windows paths)."} if not project_id or not folder_id: return {"success": False, "error": "projectId + folderId are required.", "_hint": "Get them from fusion_aps_browse {} (hub->projects) then fusion_aps_browse {projectId} (folders)."} orient = args.get("orient", True) base = _os.path.basename(model_path).rsplit(".", 1)[0]- name = (args.get("fileName") or (base + "_3d")).replace("'", "")- f3d_name = name if name.endswith(".f3d") else name + ".f3d"+ name = (args.get("fileName") or (base + "_3d")).replace("'", "").replace(".f3d", "")+ f3d_name = name + ".f3d"+ # 1. open the library so the footprint exists; 2. Create3DPackage -> generator WITH the footprint loaded+ op = _orchestrate_open_lbr({"filePath": lbr_path})+ if not op.get("success"):+ return {"success": False, "error": "open_lbr failed: " + str(op.get("error"))}+ _time.sleep(1)+ cp = _proxy_to_addin("execute_text_command", {"command": f"Electron.Create3DPackage {lbr_path}"}, timeout=40)+ if not cp.get("success"):+ return {"success": False, "error": "Create3DPackage failed: " + str(cp.get("error")),+ "_hint": "The .lbr must be adom-lbr-generated; its FIRST package's footprint is loaded into the generator."}+ _time.sleep(2) orient_block = ( "axis=(1,0,0) if (dy<=dx and dy<=dz) else ((0,1,0) if (dx<=dy and dx<=dz) else None)\n" "if axis:\n mat=oc.transform2.copy(); r=adsk.core.Matrix3D.create()\n" " r.setToRotation(math.pi/2, adsk.core.Vector3D.create(*axis), adsk.core.Point3D.create(0,0,0))\n" " mat.transformBy(r); oc.transform2=mat\n" ) if orient else ""+ # 3. import the chip ONTO the footprint in the generator doc, orient, saveAs as f3d (footprint+chip) script = ( "import adsk.core, adsk.fusion, math\n"- "app=adsk.core.Application.get()\n"- "doc=app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)\n"+ "app=adsk.core.Application.get(); doc=app.activeDocument\n" "d=adsk.fusion.Design.cast(app.activeProduct); root=d.rootComponent\n" "im=app.importManager\n" f"im.importToTarget(im.createSTEPImportOptions('{model_path}'), root)\n"@@ -1259,7 +1277,7 @@ def _orchestrate_make_3d_package(args: dict) -> dict: + orient_block + "res={}\n" "try:\n"- f" doc.saveAs('{name}', app.data.activeProject.rootFolder, '3d package', '')\n"+ f" doc.saveAs('{name}', app.data.activeProject.rootFolder, '3d package (footprint+chip)', '')\n" " res['path']=doc.dataFile.id if doc.dataFile else None\n" " res['dims']=[round(dx*10,2),round(dy*10,2),round(dz*10,2)]\n" "except Exception as e: res['err']=str(e)[:80]\n"
skills/fusion-multipart-libraries/SKILL.md+22−16@@ -14,18 +14,26 @@ real chip, flat on its footprint, built entirely this way. ## ✅ THE RECIPE - per part, then merge (no Create3DPackage, no dialogs) -For each part you have a STEP for:+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 a renderable f3d.** Via `fusion_run_modeling_script`: add a new Design doc, import the STEP,- ORIENT it flat (below), then `saveAs` it. Read back `doc.dataFile.id` - it is the **local `.f3d`- cache path**.+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.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)+ 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)+ 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@@ -33,19 +41,17 @@ For each part you have a STEP for: 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_f3d", app.data.activeProject.rootFolder, "3d", "")- result = {"path": doc.dataFile.id} # local .f3d cache path- # then close the doc: app.activeDocument.close(False)+ 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) ```-2. **Upload the f3d to the cloud:**- `fusion_aps_upload {projectId, folderId, localPath:<the .f3d>, fileName:"PART_3d.f3d"}`- -> `{ "itemUrn": "urn:adsk.wipprod:dm.lineage:<id>", ... }`. (Find projectId/folderId once via- `fusion_aps_browse {}` then `fusion_aps_browse {projectId}`.)+ ⛔ 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 chip, flat on its footprint, and saves to the- cloud.+ merged library: it RESOLVES and **RENDERS** every component (footprint + chip), and saves to cloud. ### The binding markup (exactly what Fusion writes)