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.19: long-axis markings + sidecar manifest + multi-line
8 files changed
+116−19
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.18+1.6.19
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.6.19 - 2026-07-04++- **Markings run along the LONGEST axis of the top face** (auto-rotated 90 deg when the long axis is+ sketch-y) - maximum room for the text (John's rule). **Multi-line markings** work: newline-joined+ text (MPN + variant) with per-line auto-fit (h = min(boxH/(1.35*lines), boxW/(0.75*maxChars))).+- **Sidecar manifest for marking refreshes:** with `outputStep`, `<step>.manifest.json` records every+ setting picked + WHY + the calculated bboxes (chip bbox, top-5% band, face extents, text box,+ height, rotation). To refresh a marking (e.g. add a variant on line 2), re-call the verb with the+ manifest's `type` + `paramsCm` (`unitsCm:true`) + the new etch text - deterministic generation+ reproduces placement exactly. Manifest also returned inline; `_hint` teaches the whole workflow.+ ## 1.6.18 - 2026-07-04 - **Chip markings are now READABLE: raised WHITE text by default (John's spec).** Humans need
adom-bridge-fusion-manifest.json+5−5@@ -1,13 +1,13 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.18",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.18/adom-bridge-fusion-v1.6.18.zip",- "sha256": "a3df7c12b837287cf5f9702d770aa447751f631dc501f7a51f23b6a2e5d55b65",- "size": 310510,+ "version": "1.6.19",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.19/adom-bridge-fusion-v1.6.19.zip",+ "sha256": "6bb2970739a8a5733b7c6e557b9da4e0c111dc1f2cc6271b2a1e1ffd2fbf1692",+ "size": 312585, "verbPrefixes": [ "fusion_" ], "healthEndpoint": "/status",- "released_at": "2026-07-04T17:20:00Z"+ "released_at": "2026-07-04T18:00:00Z" }
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.18",+ "version": "1.6.19", "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−2@@ -80,8 +80,8 @@ _T = [ {"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_generate_package", "Generate an IPC-7351-compliant parametric 3D package via Fusion's BUILT-IN ElectronicsPackageGenerator (EPG) - ~50 package families (chip/soic/qfn/qfp/bga/sot23/dfn/melf/ecap/crystal/headers/...), each a few seconds, ZERO GUI. Optionally LASER-ETCH a marking (MPN / '103') into the body top as REAL cut geometry (survives STEP), and export STEP in the same call - the royalty-free, license-clean path to a whole component library (our own generated output, not vendor models). Proven live: 0603 + '103' etch + STEP in ~2s.",- {"type": "required str (EPG family, e.g. chip|soic|qfn|qfp|bga|sot23|dfn2|melf|ecap|crystal|header_straight - see supportedTypes in the error for the full list)", "params": "optional dict (generator dims in MM by default, e.g. chip: {D,E,A,L,L1}; soic: {A,A1,b,D,E,E1,e,L,DPins}; omitted keys use EPG defaults)", "unitsCm": "optional bool (params already EPG-native cm)", "etch": "optional str (marking text on the chip top: MPN / '103')", "etchStyle": "optional str raised|engraved (default raised = thin WHITE positive extrude, silkscreen-style contrast; engraved = sunken cut)", "etchDepthMm": "optional float (marking height/depth, default 0.03)", "etchHeightMm": "optional float (text height; default auto-fit: top-5%-band face, 10% margin, width-aware)", "outputStep": "optional str (WINDOWS path; exports STEP incl. the marking)"},- {"success": "bool", "bodies": "[{name,vol}]", "etched": "str|null", "stepPath": "str|null", "epgDir": "str"}, 120, _STATUS, True, {"type": "chip", "params": {"D": 1.6, "E": 0.8, "A": 0.45, "L": 0.3, "L1": 0.3}, "etch": "103", "outputStep": "C:/tmp/ADOM-R0603-103.step"}),+ {"type": "required str (EPG family, e.g. chip|soic|qfn|qfp|bga|sot23|dfn2|melf|ecap|crystal|header_straight - see supportedTypes in the error for the full list)", "params": "optional dict (generator dims in MM by default, e.g. chip: {D,E,A,L,L1}; soic: {A,A1,b,D,E,E1,e,L,DPins}; omitted keys use EPG defaults)", "unitsCm": "optional bool (params already EPG-native cm)", "etch": "optional str (marking text on the chip top: MPN / '103'; multi-line with newline, e.g. MPN+variant - laid along the LONGEST face axis)", "etchStyle": "optional str raised|engraved (default raised = thin WHITE positive extrude, silkscreen-style contrast; engraved = sunken cut)", "etchDepthMm": "optional float (marking height/depth, default 0.03)", "etchHeightMm": "optional float (text height; default auto-fit: top-5%-band face, 10% margin, width-aware)", "outputStep": "optional str (WINDOWS path; exports STEP incl. the marking)"},+ {"success": "bool", "bodies": "[{name,vol}]", "etched": "str|null", "stepPath": "str|null", "manifest": "obj (settings+why+bboxes; also written as <step>.manifest.json sidecar for marking refreshes)", "manifestPath": "str|null", "epgDir": "str"}, 120, _STATUS, True, {"type": "chip", "params": {"D": 1.6, "E": 0.8, "A": 0.45, "L": 0.3, "L1": 0.3}, "etch": "103", "outputStep": "C:/tmp/ADOM-R0603-103.step"}), ("fusion_make_3d_package", "Create a RENDERING component 3D package (FOOTPRINT + chip, aligned) for ONE part, fully programmatically with ZERO GUI dialogs. Opens the .lbr, runs Electron.Create3DPackage to load the package's footprint into a generator doc, imports the STEP onto it, orients it flat, saveAs an .f3d (skips the FINISH Save dialog + the two unbeatable 'Fusion360' CEF modals), uploads the f3d via APS, and returns an fs.file:vf wip_urn to hand-write into the library's <packages3d>. projectId/folderId default to the MAIN-project upload folder. Pass captureLabel to also get a BEFORE (footprint) + AFTER (chip placed) screenshot. PITFALL: a RAW STEP upload binds but renders NOTHING ('Thumbnail download failed') - this verb makes an f3d, which renders. Prefer fusion_build_library_3d for many parts.", {"lbrPath": "required str (.lbr WINDOWS path; its FIRST package's footprint is loaded)", "modelPath": "required str (.step WINDOWS path)", "projectId": "optional str (default MAIN-project)", "folderId": "optional str (default upload folder)", "fileName": "optional str", "captureLabel": "optional str (capture before/after)", "orient": "optional bool (default true)"}, {"success": "bool", "wip_urn": "str", "dims_mm": "[x,y,z]", "f3d": "str", "before": "str|null", "after": "str|null"}, 200, _STATUS, True, {"lbrPath": "C:/tmp/newlib/R_4k7.lbr", "modelPath": "C:/tmp/newlib/R_4k7.step", "captureLabel": "R_4k7"}),
page.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-desktop-fusion-bridge",- "version": "1.6.18",+ "version": "1.6.19", "type": "app", "description": "Drive Autodesk Fusion 360 from the cloud via Adom Desktop: 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 with browse/recent/versions, cloud download/upload and folder creation, and in-app parametric modeling. Never-charge: APS calls are capped to the free tier.", "dependencies": {},
server.py+88−9@@ -2016,6 +2016,7 @@ def _orchestrate_generate_package(args: dict) -> dict: etch_depth_cm = float(args.get("etchDepthMm") or 0.03) / 10.0 etch_height_mm = args.get("etchHeightMm") # None = auto-fit output_step = args.get("outputStep") or ""+ bridge_version = BRIDGE_VERSION script = f""" import sys, glob, os, importlib@@ -2073,20 +2074,58 @@ if {etch!r}: bb = top_face.boundingBox p_min = sk.modelToSketchSpace(bb.minPoint) p_max = sk.modelToSketchSpace(bb.maxPoint)- face_w = abs(p_max.x - p_min.x) # sketch-x = the text baseline axis- face_h = abs(p_max.y - p_min.y)- w = face_w * 0.80 # 10% margin each side- box_h_cap = face_h * 0.80- # width-aware auto-fit: a glyph is ~0.75*h wide in Fusion's default text font- n_chars = max(1, len({etch!r}))- h = ({etch_height_mm!r} / 10.0) if {etch_height_mm!r} else max(0.015, min(box_h_cap * 0.55, w / (0.75 * n_chars)))- c1 = adsk.core.Point3D.create(center.x - w/2, center.y - h*0.75, 0)- c2 = adsk.core.Point3D.create(center.x + w/2, center.y + h*0.75, 0)+ face_w = abs(p_max.x - p_min.x) # extent along sketch-x (the default text baseline)+ face_h = abs(p_max.y - p_min.y) # extent along sketch-y+ # ALWAYS run the text along the LONGEST axis of the top face (most room for+ # the marking - John's rule, 2026-07-04). If the long axis is sketch-y,+ # rotate the text 90deg rather than squeezing it onto the short axis.+ import math as _math+ rot_deg = 90 if face_h > face_w else 0+ long_len = max(face_w, face_h)+ short_len = min(face_w, face_h)+ w = long_len * 0.80 # 10% margin each side along the long axis+ box_h_cap = short_len * 0.80 # 10% margin each side across it+ # multi-line support (e.g. MPN + variant on line 2): fit per-line+ lines = {etch!r}.split(chr(10))+ n_lines = max(1, len(lines))+ max_chars = max(1, max(len(l) for l in lines))+ # width-aware auto-fit: glyph ~0.75*h wide; line block ~1.35*h per line+ h = ({etch_height_mm!r} / 10.0) if {etch_height_mm!r} else \+ max(0.015, min(box_h_cap / (1.35 * n_lines), w / (0.75 * max_chars)))+ if rot_deg:+ c1 = adsk.core.Point3D.create(center.x - box_h_cap/2, center.y - w/2, 0)+ c2 = adsk.core.Point3D.create(center.x + box_h_cap/2, center.y + w/2, 0)+ else:+ c1 = adsk.core.Point3D.create(center.x - w/2, center.y - box_h_cap/2, 0)+ c2 = adsk.core.Point3D.create(center.x + w/2, center.y + box_h_cap/2, 0) ti = sk.sketchTexts.createInput2({etch!r}, h) ti.setAsMultiLine(c1, c2, adsk.core.HorizontalAlignments.CenterHorizontalAlignment, adsk.core.VerticalAlignments.MiddleVerticalAlignment, 0)+ if rot_deg:+ try:+ ti.angle = _math.pi / 2.0+ except Exception:+ rot_deg = 0 # older API without angle - fall back to unrotated txt = sk.sketchTexts.add(ti)+ marking_geom = {{+ 'chipBBox': {{'min': [round(gmin,5), 0, 0], 'maxZ': round(gmax,5)}},+ 'chipBBoxZ': {{'min': round(gmin,5), 'max': round(gmax,5)}},+ 'topBandZ': round(band_z,5),+ 'topFace': {{'area': round(top_area,6), 'centroidZ': round(top_face.centroid.z,5),+ 'sketchExtents': {{'x': round(face_w,5), 'y': round(face_h,5)}},+ 'body': body.name}},+ 'longAxis': 'sketch-y' if face_h > face_w else 'sketch-x',+ 'rotatedDeg': rot_deg,+ 'textBox': {{'c1': [round(c1.x,5), round(c1.y,5)], 'c2': [round(c2.x,5), round(c2.y,5)],+ 'marginPct': 10}},+ 'textHeightMm': round(h*10,4), 'lines': n_lines, 'maxLineChars': max_chars,+ 'heightAuto': not bool({etch_height_mm!r}),+ 'why': {{'band': 'top 5% of whole-chip bbox excludes terminal tops that tie the body',+ 'largestArea': 'molded body top beats small terminal faces in the band',+ 'longAxis': 'text runs along the longest face axis for maximum room',+ 'fit': 'h = min(boxH/(1.35*lines), boxW/(0.75*maxChars)) - glyphs ~0.75h wide'}},+ }} if {etch_style!r} == 'engraved': # engraved (sunken) cut - the pre-2026-07-04 default ext_in = root.features.extrudeFeatures.createInput(txt, adsk.fusion.FeatureOperations.CutFeatureOperation)@@ -2115,6 +2154,36 @@ if {output_step!r}: em.execute(opts) step_path = {output_step!r} +# SIDECAR MANIFEST: every setting we picked + WHY + the bboxes we calculated,+# so a marking refresh (e.g. adding a variant name as a 2nd line) can rework the+# text without re-deriving anything. Written next to the STEP as <step>.manifest.json.+manifest = {{+ 'schema': 'adom-fusion-generate-package/1',+ 'bridgeVersion': {bridge_version!r},+ 'type': {pkg_type!r},+ 'paramsCm': {params!r},+ 'paramsNote': 'paramsCm are EPG-native cm (mm inputs were /10)',+ 'marking': ({{'text': {etch!r}, 'style': {etch_style!r},+ 'depthMm': {etch_depth_cm!r} * 10, **marking_geom}} if etched else None),+ 'bodies': inv,+ 'outputs': {{'step': step_path}},+ 'refresh': {{+ 'howTo': 'To re-mark (e.g. add a variant on line 2): call fusion_generate_package again '+ 'with the SAME type+paramsCm (unitsCm:true) and the new multi-line etch text '+ '(join lines with a newline). The generator is deterministic, so geometry and '+ 'placement reproduce exactly; this manifest carries the boxes/height the last '+ 'run chose for comparison.',+ 'example': {{'type': {pkg_type!r}, 'unitsCm': True, 'params': 'paramsCm from this file',+ 'etch': ({etch!r} + chr(10) + 'VARIANT') if etched else 'MPN' + chr(10) + 'VARIANT'}},+ }},+}}+manifest_path = None+if step_path:+ manifest_path = step_path + '.manifest.json'+ import json as _json+ with open(manifest_path, 'w', encoding='utf-8') as _f:+ _json.dump(manifest, _f, indent=2)+ vp = app.activeViewport cam = vp.camera cam.isSmoothTransition = False@@ -2126,6 +2195,7 @@ vp.camera = cam vp.refresh() result = {{'type': {pkg_type!r}, 'bodies': inv, 'etched': etched, 'stepPath': step_path,+ 'manifest': manifest, 'manifestPath': manifest_path, 'epgDir': epg_dir, 'doc': app.activeDocument.name}} """ @@ -2155,6 +2225,15 @@ result = {{'type': {pkg_type!r}, 'bodies': inv, 'etched': etched, 'stepPath': st "the text is laid out with a 10% margin inside that face, auto-fit width-aware " "(override with etchHeightMm). Default style is raised+white for CONTRAST (humans " "can't read a dark-on-dark emboss); pass etchStyle:'engraved' for a sunken laser cut. "+ "The text ALWAYS runs along the LONGEST axis of the top face (rotated 90deg when "+ "needed) for maximum room, and multi-line markings work - join lines with a newline "+ "(e.g. 'LM358' + newline + 'ADOM-A' for MPN + variant; per-line auto-fit). "+ "SIDECAR MANIFEST: when outputStep is set, <step>.manifest.json records every "+ "setting picked + WHY + the calculated bboxes (chip bbox, top band, face extents, "+ "text box, height, rotation) - to refresh a marking (add a variant line), re-call "+ "this verb with the manifest's type + paramsCm (unitsCm:true) + the new etch text; "+ "generation is deterministic so placement reproduces exactly. The manifest is also "+ "returned inline as 'manifest'. " "PITFALLS: params are mm by default (unitsCm:true for EPG-native cm); dimension names " "follow each EPG generator (chip: D/E/A/L/L1; soic: A/A1/b/D/E/E1/e/L/DPins); raised " "markings are separate 'Marking' bodies (white in Fusion + colored STEP)."),
skills/fusion-build-parts-library/SKILL.md+7@@ -96,3 +96,10 @@ adom-desktop fusion_generate_package '{"type":"soic","params":{"DPins":8,"D":4.9 Read the verb's `_hint` on every call - it carries the band/margin/contrast rules and the per-family param names (chip: D/E/A/L/L1; soic: A/A1/b/D/E/E1/e/L/DPins).++**Long-axis + manifest (v1.6.19).** The marking always runs along the LONGEST axis of the top face+(auto-rotated 90 deg when the long axis is sketch-y) - maximum room for text. Multi-line works: join+with a newline ("LM358\nADOM-A" = MPN + variant, per-line auto-fit). With `outputStep`, a sidecar+`<step>.manifest.json` records every setting + why + the calculated bboxes; to REFRESH a marking+(e.g. add a variant line) re-call the verb with the manifest's `type` + `paramsCm` (`unitsCm:true`)++ the new etch text - generation is deterministic, so placement reproduces exactly.