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.6.74: fusion_board_stackup verb + pcb-stackup skill (physical fab stack, exploded view, z-fighting, publish-everything) + AI hints
7 files changed
+246−8
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.73+1.6.74
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.73",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.73/adom-bridge-fusion-v1.6.73.zip",- "sha256": "720325557ad11533caf7b618588255533b37fcff50924051be229d6d9862a4a2",- "size": 373207,+ "version": "1.6.74",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.74/adom-bridge-fusion-v1.6.74.zip",+ "sha256": "d4fded77e6f08362c413cb6acc359a5e827dbb6d0adaf447897b4cf57dd640a2",+ "size": 379830, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.73",+ "version": "1.6.74", "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://wiki.adom.inc/adom/adom-desktop-fusion-bridge", "author": "Adom Inc.",
describe.py+2−1@@ -132,8 +132,9 @@ _T = [ ("fusion_export_stl", "Export to STL mesh.", {"outputPath": "required str"}, {"format": "str"}, 300, None, True, {"outputPath": "C:/out/part.stl"}), ("fusion_export_3mf", "Export to 3MF mesh.", {"outputPath": "required str"}, {"format": "str"}, 300, None, True, {"outputPath": "C:/out/part.3mf"}), ("fusion_export_usdz", "Export to USDZ (AR / Hydrogen 3D).", {"outputPath": "required str"}, {"format": "str"}, 300, None, True, {"outputPath": "C:/out/part.usdz"}),- ("fusion_export_optimized_glb", "Fusion board -> wiki-grade optimized GLB. Exports STEP (+ optional silkscreen) and submits it to service-step2glb molecule mode (anchor to MP pins + silk bake + gold pins + join/weld/prune + Draco): ~465KB/~31 draw calls vs a raw ~16MB/~25000 that halts the viewer. Bounded-waits `wait`s (default 90); small boards return complete, big boards return {pending, jobId} - finish with fusion_fetch_optimized_glb. Use for a component page's component.parts.model_3d.", {"outputPath": "required str (.glb)", "silkscreen": "bool default true", "pin": "str medium|large default medium", "wait": "int seconds default 15 (keep under the ~60s relay timeout; re-call fetch until complete)"}, {"glbPath": "str", "jobId": "str", "pending": "bool", "meshesAfter": "int", "sizeBytes": "int", "moleculeAnchored": "bool", "silkscreenApplied": "bool"}, 200, None, True, {"outputPath": "C:/out/board.glb"}),+ ("fusion_export_optimized_glb", "Fusion board -> wiki-grade optimized GLB. Exports STEP (+ optional silkscreen) and submits it to service-step2glb molecule mode (anchor to MP pins + silk bake + gold pins + join/weld/prune + Draco): ~465KB/~31 draw calls vs a raw ~16MB/~25000 that halts the viewer. Bounded-waits `wait`s (default 90); small boards return complete, big boards return {pending, jobId} - finish with fusion_fetch_optimized_glb. Use for a component page's component.parts.model_3d. STACKUP RULE (John 2026-07-15, see pcb-stackup skill): board bbox = the FR4 body, NEVER the whole assembly (components overhang edges + tower above); each overlay goes at ITS layer's z (copper at FR4-top, silk above mask); copper traces are REAL 35um 3D bodies colored like the FR4 - recolor them + translucent mask in the RAW GLB before optimizing to make routing visible; put the measured stackup table on the board's wiki page. PUBLISH EVERYTHING: a board's wiki page repo must carry EVERY exportable design file (.f3d/.step/.usdz/EAGLE .brd+.sch/Fusion .fbrd+.fsch/gerbers/BOM/CPL/GLBs/renders), not just the 3D - the page exists to share the design.", {"outputPath": "required str (.glb)", "silkscreen": "bool default true", "pin": "str medium|large default medium", "wait": "int seconds default 15 (keep under the ~60s relay timeout; re-call fetch until complete)"}, {"glbPath": "str", "jobId": "str", "pending": "bool", "meshesAfter": "int", "sizeBytes": "int", "moleculeAnchored": "bool", "silkscreenApplied": "bool"}, 200, None, True, {"outputPath": "C:/out/board.glb"}), ("fusion_fetch_optimized_glb", "Finish a fusion_export_optimized_glb job: bounded-poll the optimizer by jobId and write the GLB to outputPath when complete. Re-call until status=complete (big boards tessellate a few minutes). No Fusion needed.", {"jobId": "required str", "outputPath": "str (.glb)", "wait": "int seconds default 15 (keep under the ~60s relay timeout; re-call fetch until complete)"}, {"glbPath": "str", "status": "str", "pending": "bool", "sizeBytes": "int"}, 150, None, False, {"jobId": "abc123", "outputPath": "C:/out/board.glb"}),+ ("fusion_board_stackup", "Read a board's PHYSICAL fabrication stackup (see the pcb-stackup skill): copper layer count + copper/dielectric thicknesses from the EAGLE design rules (layerSetup/mtCopper/mtIsolate) plus the measured FR4 extent. A PCB is Cu/prepreg/Cu/core/.../Cu - the FR4 is NOT one slab. Use to build the stackup table + real-thickness exploded 3D view, and put a stackup table on every exported board's wiki page.", {}, {"layerSetup": "str", "copperLayers": "list", "copperCount": "int", "copperThickness_mm": "list", "dielectricBonds": "list (prepreg/core order)", "fr4": "{x_mm,y_mm,dielectric_mm}"}, 200, None, True, {}), ("fusion_export_obj", "Export to OBJ mesh.", {"outputPath": "required str"}, {"format": "str"}, 300, None, True, {"outputPath": "C:/out/part.obj"}), ("fusion_export_dxf", "Export 2D DXF.", {"outputPath": "required str"}, {"format": "str"}, 120, None, True, {"outputPath": "C:/out/sketch.dxf"}), ("fusion_export_dwg", "Export 2D DWG.", {"outputPath": "required str"}, {"format": "str"}, 120, None, True, {"outputPath": "C:/out/sketch.dwg"}),
server.py+76−1@@ -3482,6 +3482,79 @@ def _orchestrate_fetch_optimized_glb(args: dict) -> dict: "message": "Job %s still %s; re-call fusion_fetch_optimized_glb to finish." % (job_id, res.get("status") or "processing")} +def _orchestrate_board_stackup(args: dict) -> dict:+ """Read a board's PHYSICAL fabrication stackup (see the pcb-stackup skill). A PCB is a+ stack of copper + dielectric: Cu(L1) / prepreg / Cu(L2) / core / ... / Cu(bottom). This+ reads the copper count + copper/dielectric thicknesses from the EAGLE design rules+ (layerSetup / mtCopper / mtIsolate in the .brd) and the measured FR4 extent from the 3D+ body, and returns the ordered layer list (name, thickness, z) so a caller can build the+ stackup table + the real-thickness exploded 3D view."""+ import re as _re+ import tempfile as _tf+ # 1) design rules from the EAGLE .brd (board editor active)+ _proxy_to_addin("show_2d_board", {}, timeout=60)+ brd = os.path.join(_tf.gettempdir(), "adom_stackup.brd")+ r = _proxy_to_addin("export_eagle_source", {"outputPath": brd}, timeout=120)+ if not r.get("success"):+ return {"success": False, "error": "could not export .brd for stackup: %s" % (r.get("error") or r.get("message")),+ "_hint": "Open the PROJECT and switch to the board (fusion_show_2d_board) first."}+ try:+ txt = open(brd, encoding="latin1", errors="replace").read()+ except Exception as e:+ return {"success": False, "error": "could not read .brd: %s" % e}+ def _p(name):+ m = _re.search(r'<param name="%s" value="([^"]*)"' % name, txt)+ return m.group(1) if m else None+ layer_setup = _p("layerSetup") or ""+ mt_copper = [x for x in (_p("mtCopper") or "").split()]+ mt_isolate = [x for x in (_p("mtIsolate") or "").split()]+ copper_layers = _re.findall(r"\d+", layer_setup) # e.g. ['1','2','15','16']+ ncu = len(copper_layers)+ # bonds between copper layers, in order: '+' prepreg, '*' core+ bonds = [("core" if c == "*" else "prepreg") for c in layer_setup if c in "+*"]+ # 2) measure the FR4 Board body (3D)+ _proxy_to_addin("show_3d_board", {}, timeout=60)+ mscript = (+ "import adsk.core, adsk.fusion\n"+ "app=adsk.core.Application.get(); des=adsk.fusion.Design.cast(app.activeProduct)\n"+ "res='none'\n"+ "for occ in des.rootComponent.allOccurrences:\n"+ " for b in occ.component.bRepBodies:\n"+ " if b.name.lower()=='board':\n"+ " bb=b.boundingBox; mn=bb.minPoint; mx=bb.maxPoint\n"+ " res='%.4f %.4f %.4f'%((mx.x-mn.x)*10,(mx.y-mn.y)*10,(mx.z-mn.z)*10)\n"+ "print('FR4 '+res)")+ mm = _proxy_to_addin("run_modeling_script", {"script": mscript}, timeout=60)+ fr4 = None+ try:+ msg = ""+ if isinstance(mm, dict):+ o = mm.get("output")+ msg = (json.loads(o).get("message") if isinstance(o, str) and o.startswith("{") else (mm.get("message") or o)) or ""+ m = _re.search(r"FR4\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)", str(msg))+ if m:+ fr4 = {"x_mm": float(m.group(1)), "y_mm": float(m.group(2)), "dielectric_mm": float(m.group(3))}+ except Exception:+ pass+ def _num(s):+ try: return float(str(s).replace("mm", ""))+ except Exception: return None+ return {+ "success": True,+ "layerSetup": layer_setup,+ "copperLayers": copper_layers,+ "copperCount": ncu,+ "copperThickness_mm": [_num(t) for t in mt_copper[:ncu]] if mt_copper else None,+ "dielectricBonds": bonds, # e.g. ['prepreg','core','prepreg']+ "dielectricThickness_raw": mt_isolate, # design-rule list (may hold 2-layer defaults)+ "fr4": fr4,+ "brdPath": brd,+ "_hint": ("Physical stack (see pcb-stackup skill): the FR4 is NOT one slab - it is "+ "prepreg/core/prepreg with copper between. Build the table + exploded view per that skill. "+ "If mtIsolate looks like 2-layer defaults, fit a symmetric split to fr4.dielectric_mm."),+ }++ def dispatch_command(command: str, args: dict) -> dict: """Dispatch a command to the appropriate handler.""" # Direct handlers (don't need the add-in)@@ -3494,7 +3567,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", "attach_3d_package", "make_3d_package", "build_library_3d", "capture_library_views", "cleanup_cloud_files", "generate_package", "export_optimized_glb") or command in _OPEN_WITH_SCREENSHOT:+ if command in ADDIN_COMMANDS or command in ("open_lbr", "save_lbr", "attach_3d_package", "make_3d_package", "build_library_3d", "capture_library_views", "cleanup_cloud_files", "generate_package", "export_optimized_glb", "board_stackup") or command in _OPEN_WITH_SCREENSHOT: if not fusion_info.get("installed"): return { "success": False,@@ -3612,6 +3685,8 @@ def dispatch_command(command: str, args: dict) -> dict: return _orchestrate_export_optimized_glb(args) if command == "fetch_optimized_glb": return _orchestrate_fetch_optimized_glb(args)+ if command == "board_stackup":+ return _orchestrate_board_stackup(args) # Add-in proxy commands if command in ADDIN_COMMANDS:
skills/fusion-molecule-export/SKILL.md+22@@ -123,6 +123,28 @@ preserves the Fusion material colors). `adom-wiki pkg init <slug> --type compone `repo push` the page.json + GLB + images (publish only ships the package layer, NOT the page repo). The native `/viewer/3d/component/<slug>` iframe renders the GLB in Babylon. Verified live. ++## PUBLISH EVERYTHING (John, hard rule, 2026-07-15)++A board/molecule wiki page is about SHARING. Push absolutely every design file you can produce into+the page repo - never just the GLB + images. The full set for a Fusion board:++| File | How |+|---|---|+| `.f3d` (Fusion archive) | `fusion_export_f3d` (3D view active) |+| `.step` | `fusion_export_step` |+| `.usdz` | `fusion_export_usdz` |+| EAGLE `.brd` / `.sch` | `fusion_export_eagle_source` (board / schematic editor active) |+| Fusion `.fbrd` / `.fsch` | `fusion_export_source` (board / schematic editor active) |+| gerbers ZIP | `fusion_export_gerbers` |+| BOM `.csv` | `fusion_export_bom` (board editor) |+| CPL (pick and place) `.csv` | `fusion_export_cpl` (board editor) |+| GLB (assembled + exploded) | the optimized-GLB pipeline in this skill |+| renders (iso/top), schematic PNG | viewport captures |++If a file type exists for the design, it belongs in the repo. List them all in the README Files+section so humans see what is available.+ ## Related - `fusion-electronics` / `fusion-eagle-commands` - open the PROJECT and drive the editors first. - `adom-molecule` (user skill) - the CLI reference (note its auth gap above).
skills/pcb-stackup/SKILL.mdadded+140@@ -0,0 +1,140 @@+---+name: pcb-stackup+description: >+ Think about a PCB as its STACKUP - the physical layer sandwich - before doing ANY 3D work with a+ board: placing overlays (silkscreen/copper textures), computing the board bounding box, picking+ z-heights, making traces visible, or building a wiki 3D model. Records John's teaching (2026-07-15)+ after an overlay was placed at component height instead of the copper layer. Trigger words: pcb+ stackup, board layers, fr4 bounding box, board bbox wrong, silkscreen z height, copper layer z,+ traces not visible, overlay floating above board, layer heights, soldermask z, board thickness.+---++# PCB stackup - how to think about a board's layers (John, 2026-07-15)++A PCB is not "a green slab with parts". It is a **stack**, and every 3D/overlay/bbox decision must+be made per-layer, at that layer's z-height. From bottom to top (typical 1.6mm board):++```+components (top side) <- pick-and-place, then solder reflow welds them on+top silkscreen (ink) <- printed ON the soldermask+top soldermask ~65 um <- the green; OPENS over pads+top copper (traces) ~35 um <- layer 1; the routing+FR4 core + inner Cu ~1.5 mm <- inner layers (e.g. 2, 15) live inside+bottom copper ~35 um <- layer 16+bottom soldermask ~65 um+bottom silkscreen+components (bottom side)+```++Measured live on Adonis's BQ25792 board (from the RAW GLB bodies): FR4 core z 0.000-1.516, top+copper 1.516-1.551, top mask 1.516-1.582, bottom copper -0.035-0.000, bottom mask -0.065-0.000.++## The rules this implies (each one was violated once and looked wrong)++1. **The board bbox = the FR4 body, never the whole assembly.** Components overhang edges (a switch+ hung off the BQ board and inflated Y 26.4 -> 28.2mm) and tower above (a quad placed at+ whole-assembly z-max floated ON TOP of the components). Measure the substrate body itself.+2. **Each overlay goes at ITS layer's z.** Copper texture -> copper layer z (FR4 top + eps).+ Silkscreen -> above the mask top. Never "board bbox top + eps".+3. **Copper traces ARE 3D geometry in a Fusion export** - real bodies, 35um thick, so they are NOT+ heavy. They are invisible for two reasons only: Fusion colors them IDENTICAL to the FR4+ appearance, and the soldermask exports opaque. Fix = recolor the copper bodies to a real copper+ material + make the mask translucent (alpha ~0.6 BLEND). No texture overlay needed.+4. **Measure bodies in the RAW (pre-optimize) GLB.** After join/flatten (gltf-transform optimize or+ the service molecule mode), meshes merge across bodies and per-layer semantics are DESTROYED+ (the "Board" node becomes a 10mm-thick mega-mesh). Identify FR4/copper/mask bodies FIRST (raw GLB+ node names: `Board`, `1-copper`, `16-copper`, `1-soldermask`, `16-soldermask`), retarget their+ materials, THEN optimize - join preserves distinct materials.+5. **The stackup is queryable, don't guess.** Two sources:+ - EAGLE `.brd` design rules (`fusion_export_eagle_source`): `layerSetup` (e.g. `(1+2*15+16)` =+ 4-layer), `mtCopper` (copper thicknesses), `mtIsolate` (dielectric thicknesses).+ - The raw exported 3D bodies' z-ranges (as measured above). Fusion also shows a stackup dialog;+ the .brd rules are its programmatic equivalent.+6. **Put the stackup table on the wiki page** of every exported board (John wants this standard):+ a layer/Z/thickness table like the BQ25792 page's "Board stackup" section.++## Recipe: visible REAL traces in an optimized GLB (proven on the BQ board)++1. `fusion_export_step` -> raw STEP -> raw GLB (adom-step2glb convert). Bodies stay distinct.+2. In the raw GLB, retarget the `*-copper` nodes' primitives to a new metallic copper material+ (baseColor ~[0.85,0.42,0.12], metallic 1, roughness 0.35) and the `*-soldermask` nodes to a+ translucent green (alpha ~0.62, BLEND, doubleSided).+3. `gltf-transform optimize --compress draco` -> ~800KB with the copper as its OWN mesh.+ Result: real 3D routing visible under the mask, stackup-correct. 19MB -> 822KB on the BQ board.+++## Z-fighting: coplanar faces are the enemy (learned live, 2026-07-15)++At true exported heights the stack has PERFECTLY coplanar faces: FR4 top = copper bottom = mask+bottom (one plane, e.g. z=1.516). Renderers z-fight (shimmer) on them. Fix: micro z-offsets that+break coplanarity but are invisible at view scale - mask +/-5um away from the board, copper +/-2um.+Apply to the RAW GLB nodes before optimizing (catch nested sub-assembly copies of copper/mask too).++## Inner layers do NOT exist as 3D bodies++A 4-layer board (layerSetup (1+2*15+16)) exports only OUTER copper as bodies (1-copper, 16-copper).+Inner layers (2, 15) live only in the GERBERS (Fusion CAM names them copper_l2.G1, copper_l3.G2).+To show them in 3D: render those gerbers to copper-colored transparent PNGs framed EXACTLY to the+Profile outline, and add them as textured quads at their stackup z (inside the FR4 core).++## Exploded stackup view (a standard option users may ask for)++Shift each stack group by slot*gap (gap 2mm default, 5mm if crowded), FR4 = slot 0:+ components +5 | top silk +4 | top mask +3 | top copper +2 | inner L2 +1 quad+ [FR4 core 0]+ inner L15 -1 quad | bottom copper -2 | bottom mask -3 | bottom silk -4+Real bodies move by node translation (top-level groups: Board, 1-copper, 16-copper, 1-soldermask,+16-soldermask, Packages=all components); silk + inner layers are gerber quads. Keep the WHOLE+stackup present and in correct order. Do this on the raw GLB, then optimize (join keeps materials).++## Wiki page gotcha: the Overview README is the PACKAGE layer++`adom-wiki repo push README.md` updates the Files tab + assets the viewers fetch (GLB changes show+instantly) but does NOT refresh the page Overview README - that renders from the last `pkg publish`.+Bump the version in package.json (publish reads package.json, NOT page.json) and republish.+++## THE PHYSICAL FABRICATION STACK (golden, John 2026-07-15) - do not model FR4 as one slab++A multilayer board is NOT one FR4 slab with copper on it. It is built up in the fab as alternating+copper and dielectric, and the 3D view must show EACH dielectric sublayer at its REAL thickness:++```+4-layer: Cu(L1) | prepreg | Cu(L2) | CORE | Cu(L3) | prepreg | Cu(L4)+6-layer: Cu | pp | Cu | core | Cu | pp | Cu | core | Cu | pp | Cu (extend the pattern)+8-layer: ... keep extending: copper, dielectric, copper, dielectric ...+```++Fab reality: a thin CORE (rigid FR4) is etched with the two inner copper layers on its faces; then+PREPREG (bonding sheet) is epoxied on top and bottom, and the outer copper foil is pressed onto that.+So the dielectric between copper layers alternates prepreg (outer, thinner) / core (center, thicker).++The stack is in the EAGLE/Fusion design rules (this IS Fusion's stackup dialog, programmatically):+- `layerSetup` e.g. `(1+2*15+16)` - copper layer numbers, with `+` = prepreg bond, `*` = core bond.+ `(1+2*15+16)` = L1 [prepreg] L2 [CORE] L15 [prepreg] L16 (4-layer, symmetric).+- `mtCopper` - copper thickness per layer (e.g. 0.035 mm = 1 oz).+- `mtIsolate` - dielectric thicknesses (prepreg + core). NOTE: these can be left at 2-layer defaults+ (a leading 1.6mm is the default board thickness, not a real prepreg); if they don't reconcile with+ the measured board, fit a symmetric stack to the MEASURED FR4 total instead, keeping copper 0.035.++BQ25792 (measured FR4 dielectric = 1.516mm, fitted symmetric): top/bottom prepreg 0.200 each,+core 1.046, four copper 0.035. Real z (mm): L16 -0.035..0.000 | bot prepreg 0.000..0.200 |+L15 0.200..0.235 | core 0.235..1.281 | L2 1.281..1.316 | top prepreg 1.316..1.516 | L1 1.516..1.551.++## Building the exploded view with REAL thicknesses (the breakthrough view - almost no EDA tool does this)++Every element is its OWN slab at its EXACT thickness, separated by a uniform slot gap (5-6 mm). The+FR4 must be split into its sublayers (prepreg/core/prepreg) - clone the exported Board body 3x, each+z-scaled to that sublayer's thickness (scale_z = thickness / measured_FR4_thickness) and translated+to its slot center; make the sublabs slightly translucent so inner copper reads through. Order:+components / top silk / top mask / L1 copper / top prepreg / L2 copper / CORE / L15 copper /+bottom prepreg / L16 copper / bottom mask / bottom silk. Outer copper (L1/L16) are the real recolored+bodies; inner copper (L2/L15) are gerber-rendered quads (they aren't exported as bodies). Keep the+ORIGINAL Board body but recolor it alpha=0 (hidden) since the 3 slabs replace it. This makes Adom a+breakthrough: you can SEE the inner layers, which almost no EDA tool shows in 3D.++## Related+- `fusion-molecule-export` - the export/optimize pipeline these rules feed into.+- Issue adom/step2glb#158 - the service's `_board_bbox` still uses the whole-assembly XY+ ("nothing overhangs a PCB edge" is false) and a stack z that merged meshes pollute; fix is to key+ on the FR4/substrate body per rules 1-2.