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
Update 10 file(s)
7 files changed
+84−16
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.128+1.9.129
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.128",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.144/adom-bridge-fusion-v1.9.128.zip",- "sha256": "cf3902dc227eaeaddb8d393b0d6a178f4b31f0cbd67506b9ac252ac549ddd539",- "size": 463809,+ "version": "1.9.129",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.145/adom-bridge-fusion-v1.9.129.zip",+ "sha256": "8185b0cdaf6412ee50200edad0e7971a140341cf5aa59abe1bbcdbfc4ce34ce2",+ "size": 464119, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.128",+ "version": "1.9.129", "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/fusion-bridge", "author": "Adom Inc.",
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.9.144",+ "version": "1.9.145", "type": "app", "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.", "dependencies": {
page.json+10−7@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.9.144",+ "version": "1.9.145", "type": "app", "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.", "dependencies": {@@ -11,6 +11,10 @@ "uninstall": "./uninstall.sh" }, "tags": [+ "ab",+ "bridge",+ "fusion",+ "desktop-app", "fusion360", "autodesk", "cad",@@ -70,7 +74,10 @@ "build a basic parts library", "add a 3d model to a component", "make a fusion 360 library with 3d",- "set up the fusion bridge"+ "set up the fusion bridge",+ "fusion dashboard",+ "fusion status page",+ "open the fusion launch page" ], "discovery_pitch": "Use the Adom Bridge Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Bridge. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).", "brief": "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.",@@ -99,9 +106,5 @@ "label": "Part volume", "prompt": "How much material does each printed part in this assembly use?" }- ],- "author": {- "name": "John Lauer",- "email": "[email protected]"- }+ ] }
server.py+11−2@@ -3005,16 +3005,21 @@ def _orchestrate_open_lbr(args: dict) -> dict: # its blocking "has errors and cannot be opened" Error modal ON EVERY RETRY and left # an empty Untitled design behind each time. The file's validity is knowable offline # in milliseconds - an unparseable library must NEVER reach Fusion's parser.+ # NOTE the _ospre alias: this function has a LOCAL `import os` further down, which+ # makes `os` local to the WHOLE scope - the 1.9.128 guard referenced bare `os` here,+ # crashed with UnboundLocalError on EVERY open, and reported its own crash as "not a+ # parseable EAGLE library" (caught by the matrix re-run within the hour, issue #47).+ import os as _ospre try: _pf = file_path.replace("\\", "/")- if _pf and os.path.exists(_pf):+ if _pf and _ospre.path.exists(_pf): import xml.etree.ElementTree as _ETpre _rootpre = _ETpre.parse(_pf).getroot() if _rootpre.tag != "eagle": raise ValueError("root element is <%s>, not <eagle>" % _rootpre.tag) except Exception as _epre: try:- _sz = os.path.getsize(_pf)+ _sz = _ospre.path.getsize(_pf) _head = open(_pf, "rb").read(48) except Exception: _sz, _head = None, b""@@ -10200,6 +10205,10 @@ def dispatch_command(command: str, args: dict, caller_identity: dict = None) -> def _dispatch_command_inner(command: str, args: dict, caller_identity: dict = None) -> dict:+ import time as _time # MUST be first: a later local `import time as _time` makes the+ # name local to this whole scope, so three earlier uses raised UnboundLocalError -+ # found by tools/shadow_check.py minutes after the same class shipped as the 1.9.128+ # open_lbr regression (issue #47). The gate now runs at every release. """Dispatch a command to the appropriate handler. caller_identity: {'thread','container','reason'} from the X-Adom-Caller-* headers AD stamped on
tools/shadow_check.pyadded+56@@ -0,0 +1,56 @@+"""Ship gate: catch local imports that shadow a module name USED EARLIER in the function.++The 1.9.128 regression in one sentence: a guard at the top of _orchestrate_open_lbr used+`os.path.exists`, a `import os` 150 lines later made `os` local to the whole scope, and+every open crashed with UnboundLocalError - reported as the caller's file being corrupt.+The matrix lost a full run to it within the hour (issue #47).++Run over any file(s); exits 1 with findings, 0 clean. Wired next to the AST parse in the+release ritual - same class of static check, same moment.+"""+import ast+import sys+++def check(path: str) -> list:+ tree = ast.parse(open(path, encoding="utf-8").read())+ findings = []+ for fn in ast.walk(tree):+ if not isinstance(fn, (ast.FunctionDef, ast.AsyncFunctionDef)):+ continue+ # names bound by LOCAL imports inside this function (not nested functions)+ local_imports = {} # name -> lineno of the import+ for node in ast.walk(fn):+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node is not fn:+ continue+ if isinstance(node, ast.Import):+ for a in node.names:+ k = a.asname or a.name.split(".")[0]+ local_imports[k] = min(local_imports.get(k, node.lineno), node.lineno)+ elif isinstance(node, ast.ImportFrom):+ for a in node.names:+ k = a.asname or a.name+ local_imports[k] = min(local_imports.get(k, node.lineno), node.lineno)+ if not local_imports:+ continue+ # any LOAD of one of those names BEFORE its import line = UnboundLocalError at runtime+ for node in ast.walk(fn):+ if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Load):+ imp_line = local_imports.get(node.id)+ if imp_line and node.lineno < imp_line:+ findings.append(+ "%s:%d: `%s` used in %s() before its local import on line %d "+ "- the local import makes it local to the WHOLE function, so "+ "this line raises UnboundLocalError" % (+ path, node.lineno, node.id, fn.name, imp_line))+ return findings+++if __name__ == "__main__":+ all_findings = []+ for p in sys.argv[1:] or ["server.py"]:+ all_findings += check(p)+ for f in all_findings:+ print(f)+ print("%d shadowing finding(s)" % len(all_findings))+ sys.exit(1 if all_findings else 0)