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
Add fusion_attach_3d_package orchestrator verb (open -> Create3DPackage -> import+orient -> FINISH, returns the Save-click hint); correct the open_lbr 3D hints to point at it
2 files changed
+84−3
describe.py+3@@ -74,6 +74,9 @@ _T = [ ("fusion_open_lbr", "Import an EAGLE .lbr library into Fusion's Electronics LIBRARY editor (Content Manager / Electronics Library workspace), not a PCB design. PITFALL: filePath MUST be a WINDOWS-local path (C:/...). The chip-fetcher/adom-lbr pipeline runs in the cloud container, and there is NO container-to-Windows push verb, so STAGE the .lbr onto Windows first (serve it on a proxied port + Invoke-WebRequest via shell_execute), then pass the C:/ path. PITFALL: an adom-lbr .lbr is 2D ONLY - symbol + footprint + a PLACEHOLDER 3D package; the real 3D chip is NOT attached (that needs a Fusion cloud URN). To attach the 3D, see the 'fusion-libraries' skill. Pass verify:true to round-trip-confirm Fusion parsed the deviceset, then check fusion_get_app_state (activeWorkspace 'Electronics Library').", {"filePath": "required str (.lbr WINDOWS path C:/...; stage from container first)", "symbolName": "optional str", "verify": "optional bool"}, {"success": "bool", "postOpenScreenshot": "{savedTo}"}, 50, None, False, {"filePath": "C:/Users/<user>/adom-lib/nRF54L15.lbr"}),+ ("fusion_attach_3d_package", "Attach a real 3D chip model to a library package, end to end: opens the .lbr (library active), runs Electron.Create3DPackage to enter the Package3DEnvironment showing the footprint, imports the STEP + auto-orients it flat on the footprint, then executes Package3DStop (FINISH). This BINDS the 3D onto the deviceset (Content Manager shows the 3D nested under the package; the Packages 'Package' column flips Placeholder->part-name). ONE desktop-side step remains: Fusion pops a modal Save dialog (an OWNED popup) - the response _hint gives the exact desktop_ui_click to confirm it. AFTER: re-grab desktop_screenshot_window and check ownedPopupCount for errors; the 3D preview LAGS a beat before showing the real chip. Both paths (filePath/modelPath) are WINDOWS paths - stage from the container first.",+ {"filePath": "required str (.lbr WINDOWS path C:/...)", "modelPath": "required str (.step WINDOWS path C:/...)", "packageName": "optional str"},+ {"success": "bool", "savePending": "bool", "data": "{steps}", "_hint": "the Save-dialog click + verify steps"}, 240, _STATUS, True, {"filePath": "C:/Users/<user>/adom-lib/ADS8588SIPM.fusion.lbr", "modelPath": "C:/Users/<user>/adom-lib/ADS8588SIPM.step", "packageName": "ADS8588SIPM"}), ("fusion_run_modeling_script", "Run an adsk.fusion modeling script in the live session (sketches/extrudes/params). Free programmatic CAD.", {"script": "required str (Python using adsk; globals: adsk, app, ui, design)"}, {"result": "obj", "documentName": "str"}, 180, None, True, {"script": "import adsk.fusion\n# ...build geometry..."}),
server.py+81−3@@ -1009,8 +1009,8 @@ def _orchestrate_open_lbr(args: dict) -> dict: "_screenshots[] array (AD v1.8.177+ captures owned dialogs invisible to a plain capture); " "ownedPopupCount>0 means an error/confirm dialog is up. " "NOTE: an adom-lbr .lbr is 2D ONLY - symbol + footprint + a PLACEHOLDER 3D package. "- "The real 3D chip is NOT attached; that needs an adsk.eagle:package URN (a wipprod URN does NOT "- "work in package3d). See the 'fusion-libraries' skill / FUSION_3D_FINDINGS.md."+ "To attach the real 3D chip, use fusion_attach_3d_package (it runs the Package3D generator + "+ "FINISH, which binds the 3D onto the deviceset). See the 'fusion-libraries' skill / FUSION_3D_FINDINGS.md." ), } if symbol_name:@@ -1022,6 +1022,82 @@ def _orchestrate_open_lbr(args: dict) -> dict: return _post_open_screenshot(response) +def _orchestrate_attach_3d_package(args: dict) -> dict:+ """Attach a real 3D model to a library package, end to end.++ Opens the .lbr (library active), runs Electron.Create3DPackage to enter the+ Package3DEnvironment showing the footprint, imports the STEP model and+ auto-orients it flat on the footprint, then executes Package3DStop (FINISH).+ Fusion then shows a modal Save dialog (an OWNED popup) — that single click is+ the only desktop-side step; this returns the exact instruction for it.++ args: {filePath: Windows path to the .lbr, modelPath: Windows path to the+ STEP, packageName: optional str}+ """+ import time+ file_path = (args.get("filePath") or "").replace("\\", "/")+ model_path = (args.get("modelPath") or "").replace("\\", "/")+ package = args.get("packageName", "")+ if not file_path or not model_path:+ return {"success": False,+ "error": "filePath (.lbr) and modelPath (.step) are required (Windows paths, e.g. C:/...).",+ "_hint": "Stage both onto Windows first (no container->Windows push verb). See the fusion-libraries skill."}+ steps = []+ open_res = _orchestrate_open_lbr({"filePath": file_path})+ if not open_res.get("success"):+ return {"success": False, "error": "open_lbr failed: " + str(open_res.get("error")), "data": {"steps": steps}}+ steps.append("open_lbr: ok (library active)")+ time.sleep(1)+ cp = _proxy_to_addin("execute_text_command", {"command": f"Electron.Create3DPackage {file_path}"}, timeout=40)+ if not cp.get("success"):+ return {"success": False, "error": "Create3DPackage failed: " + str(cp.get("error")),+ "_hint": "The library document must be ACTIVE, and the .lbr must be adom-lbr-generated "+ "(a raw vendor EAGLE .lbr fails Fusion's XML parser).", "data": {"steps": steps}}+ steps.append("Create3DPackage: ok (Package3DEnvironment)")+ time.sleep(2)+ import_script = (+ "import adsk.core, adsk.fusion, math\n"+ "res={}\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"+ "oc=root.occurrences.item(root.occurrences.count-1); bb=oc.boundingBox\n"+ "dx=bb.maxPoint.x-bb.minPoint.x; dy=bb.maxPoint.y-bb.minPoint.y; dz=bb.maxPoint.z-bb.minPoint.z\n"+ "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(); rot=adsk.core.Matrix3D.create()\n"+ " rot.setToRotation(math.pi/2, adsk.core.Vector3D.create(*axis), adsk.core.Point3D.create(0,0,0))\n"+ " mat.transformBy(rot); oc.transform2=mat\n"+ " d.snapshots.add() if d.snapshots.hasPendingSnapshot else None\n"+ "res['dims_mm']=[round(dx*10,2),round(dy*10,2),round(dz*10,2)]\n"+ "result=res\n"+ )+ imp = _proxy_to_addin("run_modeling_script", {"script": import_script}, timeout=120)+ if not imp.get("success"):+ return {"success": False, "error": "import/orient failed: " + str(imp.get("error")), "data": {"steps": steps}}+ dims = (imp.get("data", {}) or {}).get("result", {})+ steps.append(f"import+orient: ok ({dims.get('dims_mm') if isinstance(dims, dict) else dims})")+ time.sleep(1)+ _proxy_to_addin("run_modeling_script",+ {"script": "import adsk.core\ncd=ui.commandDefinitions.itemById('Package3DStop')\nresult={'finished': bool(cd) and cd.execute()}\n"},+ timeout=30)+ steps.append("FINISH (Package3DStop): executed")+ return {+ "success": True,+ "output": f"3D model placed + oriented on the footprint and FINISH executed for '{package or file_path}'. A Save dialog is now up.",+ "data": {"steps": steps, "package": package},+ "savePending": True,+ "_hint": (+ "FINAL STEP (desktop-side, one click): a Fusion 'Save' dialog is now up to save the 3D package. "+ "Click it: desktop_find_window {titleContains:'Save'} -> desktop_ui_click "+ "{hwnd, automationId:'QTApplication.QTFrameWindow.standardActions.SaveButton'}. "+ "THEN VERIFY with desktop_screenshot_window on the Fusion hwnd: check ownedPopupCount (errors are owned "+ "popups), and the deviceset's Package column should flip Placeholder->part-name + the 3D preview becomes "+ "the real chip (the preview LAGS a beat - re-grab). Full flow: fusion-libraries skill / FUSION_3D_FINDINGS.md sect 11."+ ),+ }++ def _orchestrate_save_lbr(args: dict) -> dict: """Save the currently open Electronics library as a .flbr file. @@ -1091,7 +1167,7 @@ def dispatch_command(command: str, args: dict) -> dict: # The user should launch Fusion themselves; we just report the status. # Commands that need Fusion running (add-in commands + orchestrated commands) _OPEN_WITH_SCREENSHOT = {"open_schematic", "open_board", "show_3d_board", "show_2d_board", "show_schematic", "import_electronics"}- if command in ADDIN_COMMANDS or command in ("open_lbr", "save_lbr") or command in _OPEN_WITH_SCREENSHOT:+ if command in ADDIN_COMMANDS or command in ("open_lbr", "save_lbr", "attach_3d_package") or command in _OPEN_WITH_SCREENSHOT: if not fusion_info.get("installed"): return { "success": False,@@ -1193,6 +1269,8 @@ def dispatch_command(command: str, args: dict) -> dict: return _orchestrate_open_lbr(args) if command == "save_lbr": return _orchestrate_save_lbr(args)+ if command == "attach_3d_package":+ return _orchestrate_attach_3d_package(args) # Add-in proxy commands if command in ADDIN_COMMANDS: