← 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

John Lauer ·19ea59b337 ·3mo ago ·parent 4edaa7c
6 files changed +74−39
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.5.11\ No newline at end of file+1.5.12\ No newline at end of file
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" }\ No newline at end of file
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)