← Commit history

v1.6.18: raised white markings + bbox-band top-surface algorithm

John Lauer ·6346ca64c9 ·3mo ago ·parent 34effa5
8 files changed +101−31
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.6.17\ No newline at end of file+1.6.18\ 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.6.18 - 2026-07-04++- **Chip markings are now READABLE: raised WHITE text by default (John's spec).** Humans need+  contrast - a dark-on-dark emboss is invisible - so `fusion_generate_package` now marks chips with a+  thin POSITIVE extrude painted white (silkscreen-style, via EPG's own rgb-appearance utility; white+  survives into the colored STEP). `etchStyle:"engraved"` keeps the sunken laser cut.+- **Top-surface finding is now the bbox-band algorithm (in CODE, not just the skill):** whole-chip+  bounding box -> only upward planar faces in the TOP 5% height band -> largest area wins -> text+  laid out with a 10% margin inside that face. The verb's `_hint` teaches all of this + per-family+  param names on every call; the fusion-build-parts-library skill gained a "creating chips" section.+ ## 1.6.16 - 2026-07-04  - (1.6.17) Etch auto-fit measures the face in SKETCH space (model-bbox axes can be swapped vs the
adom-bridge-fusion-manifest.json+5−5
@@ -1,13 +1,13 @@ {   "manifest_version": 1,   "name": "fusion360",-  "version": "1.6.17",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.17/adom-bridge-fusion-v1.6.17.zip",-  "sha256": "ca6c178dcee2145cb6593958d5001fb86a5820c2a4d5a71130a1922acf47a6e3",-  "size": 309227,+  "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,   "verbPrefixes": [     "fusion_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-07-04T16:40:00Z"+  "released_at": "2026-07-04T17:20:00Z" }\ 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.6.17",+  "version": "1.6.18",   "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+1−1
@@ -80,7 +80,7 @@ _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 lasered into the body top)", "etchDepthMm": "optional float (default 0.03)", "etchHeightMm": "optional float (default auto-fit to the face)", "outputStep": "optional str (WINDOWS path; exports STEP incl. the etch)"},+     {"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"}),     ("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)"},
page.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-desktop-fusion-bridge",-  "version": "1.6.17",+  "version": "1.6.18",   "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+54−22
@@ -2012,6 +2012,7 @@ def _orchestrate_generate_package(args: dict) -> dict:             params[k] = v      etch = args.get("etch") or ""+    etch_style = (args.get("etchStyle") or "raised").strip().lower()  # raised (white, default) | engraved     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 ""@@ -2044,30 +2045,41 @@ root = design.rootComponent inv = [{{'name': b.name, 'vol': round(b.volume, 6)}} for b in root.bRepBodies] etched = None if {etch!r}:-    # Etch the LARGEST body (the molded body): terminals can TIE the body on-    # max-Z, so never pick the top face globally (learned live - first attempt-    # etched a terminal).-    body = max(root.bRepBodies, key=lambda b: b.volume)-    top_face, top_z = None, -1e9-    for f in body.faces:-        if isinstance(f.geometry, adsk.core.Plane) and f.geometry.normal.z > 0.9 and f.centroid.z > top_z:-            top_face, top_z = f, f.centroid.z+    # FIND THE TOP SURFACE the robust way (John's algorithm, 2026-07-04):+    # 1. bounding box of the WHOLE chip (all bodies);+    # 2. only faces whose centroid sits in the TOP 5% band of that bbox count;+    # 3. among those upward planar faces take the LARGEST AREA (the molded body+    #    top beats terminal tops that tie it on height);+    # 4. lay the text out with a 10% margin inside that face.+    gmin, gmax = 1e9, -1e9+    for b in root.bRepBodies:+        bb = b.boundingBox+        gmin = min(gmin, bb.minPoint.z)+        gmax = max(gmax, bb.maxPoint.z)+    band_z = gmax - 0.05 * (gmax - gmin)+    top_face, top_area = None, -1.0+    for b in root.bRepBodies:+        for f in b.faces:+            if isinstance(f.geometry, adsk.core.Plane) and f.geometry.normal.z > 0.9 \+                    and f.centroid.z >= band_z and f.area > top_area:+                top_face, top_area = f, f.area     if top_face is None:-        raise RuntimeError('ETCH_NO_TOP_FACE: largest body has no upward planar face')+        raise RuntimeError('ETCH_NO_TOP_FACE: no upward planar face in the top 5% of the chip bbox')+    body = top_face.body     sk = root.sketches.add(top_face)     center = sk.modelToSketchSpace(top_face.centroid)     # Measure the face in SKETCH space (model-space bbox axes can be SWAPPED vs-    # the sketch axes the text lays out in - learned live: 'LM358' still clipped-    # when sized from the model bbox). Project the model bbox corners.+    # the sketch axes the text lays out in). Project the model bbox corners.     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.85+    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(face_h * 0.45, w / (0.75 * n_chars)))+    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)     ti = sk.sketchTexts.createInput2({etch!r}, h)@@ -2075,10 +2087,25 @@ if {etch!r}:                       adsk.core.HorizontalAlignments.CenterHorizontalAlignment,                       adsk.core.VerticalAlignments.MiddleVerticalAlignment, 0)     txt = sk.sketchTexts.add(ti)-    ext_in = root.features.extrudeFeatures.createInput(txt, adsk.fusion.FeatureOperations.CutFeatureOperation)-    ext_in.setDistanceExtent(False, adsk.core.ValueInput.createByReal(-{etch_depth_cm!r}))-    ext_in.participantBodies = [body]-    root.features.extrudeFeatures.add(ext_in)+    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)+        ext_in.setDistanceExtent(False, adsk.core.ValueInput.createByReal(-{etch_depth_cm!r}))+        ext_in.participantBodies = [body]+        root.features.extrudeFeatures.add(ext_in)+    else:+        # RAISED WHITE marking (default): humans need CONTRAST - a thin positive+        # extrude painted white on the dark body reads like real silkscreen ink,+        # far better than a same-color emboss. Reuses EPG's own rgb-appearance+        # utility so the white survives Fusion rendering (+ colored STEP).+        ext_in = root.features.extrudeFeatures.createInput(txt, adsk.fusion.FeatureOperations.NewBodyFeatureOperation)+        ext_in.setDistanceExtent(False, adsk.core.ValueInput.createByReal({etch_depth_cm!r}))+        mark = root.features.extrudeFeatures.add(ext_in)+        from ElectronicsPackageGenerator.Utilities import addin_utility as _au+        for i in range(mark.bodies.count):+            mb = mark.bodies.item(i)+            mb.name = 'Marking' if i == 0 else 'Marking' + str(i + 1)+            _au.apply_rgb_appearance(app, design, mb, 255, 255, 255, 'AdomMarkingWhite')     etched = {etch!r}  step_path = None@@ -2120,12 +2147,17 @@ result = {{'type': {pkg_type!r}, 'bodies': inv, 'etched': etched, 'stepPath': st         "output": json.dumps(inner),         **inner,         "_hint": ("Generated an IPC-compliant parametric package via Fusion's built-in EPG"-                  + (f", laser-etched '{etch}' into the body top (real cut geometry - survives STEP)" if etch else "")+                  + (f", marked '{etch}' on the chip top as {'an engraved cut' if etch_style == 'engraved' else 'RAISED WHITE text (silkscreen-style - high contrast on the dark body)'}" if etch else "")                   + (f", exported STEP to {output_step}" if output_step else "")-                  + ". NEXT: fusion_screenshot_fusion for a visual check; pull_file the STEP for "-                    "KiCad. PITFALLS: params are mm by default (unitsCm:true to pass 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); "-                    "the etch targets the LARGEST body's top face (terminals tie the body on max-Z)."),+                  + ". NEXT: fusion_screenshot_fusion for a visual check; pull_file the STEP for KiCad. "+                    "HOW THE MARKING WORKS: the top surface is found from the WHOLE-chip bounding box - "+                    "only upward planar faces in the top 5% height band count, largest area wins - and "+                    "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. "+                    "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+27
@@ -69,3 +69,30 @@ should be the harmless `duplicate-pin-name` warnings). - Publish the `.lbr` + README (a table of all values) + screenshots + video to the wiki.  The whole thing is ~10 3D builds + a deterministic clone-and-assemble - not 138 separate builds.++## Creating chips from parameters (fusion_generate_package) + marking them readably++For GENERATED parts (no vendor STEP needed - royalty-free, license-clean), use+**`fusion_generate_package`**: it drives Fusion's built-in ElectronicsPackageGenerator (~50 IPC+families - chip/soic/qfn/qfp/bga/sot23/dfn/melf/ecap/crystal/headers...), marks the chip top, and+exports STEP in ONE call. Params are datasheet **mm** (auto-converted).++**How the marking finds the top surface (also baked into the verb - you get it for free):**+1. Take the bounding box of the WHOLE chip (all bodies, terminals included).+2. Only upward planar faces whose centroid sits in the **top 5% height band** of that bbox are+   candidates (terminals often TIE the molded body on height - a naive max-Z pick etches a terminal).+3. The **largest-area** face in the band is the top surface.+4. Lay the text out with a **10% margin** inside that face, width-aware auto-fit+   (glyph ~0.75h; override with `etchHeightMm`).++**Marking style - contrast matters.** Default `etchStyle:"raised"` puts the text on as a thin+**positive extrude painted WHITE** (silkscreen-style) - humans can't read a dark-on-dark emboss.+`etchStyle:"engraved"` gives the sunken laser cut when that's what a datasheet calls for. Raised+markings are separate `Marking` bodies (white in Fusion and in the colored STEP).++```bash+adom-desktop fusion_generate_package '{"type":"soic","params":{"DPins":8,"D":4.9,"E":6.0,"E1":3.9,"e":1.27,"A":1.6,"A1":0.1,"b":0.41,"L":0.84},"etch":"LM358","outputStep":"C:/tmp/ADOM-LM358-SOIC8.step"}'+```++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).