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.9.3 source: BOM collapse transparency (collapsedUnits + possibleUnderCollapse), add-in 1.0.5
5 files changed
+53−9
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.2+1.9.3
addin/AdomBridge/AdomBridge.manifest+1−1@@ -6,7 +6,7 @@ "description": { "": "Adom Desktop Conduit bridge add-in. Exposes a localhost HTTP API for external automation of Fusion 360." },- "version": "1.0.4",+ "version": "1.0.5", "runOnStartup": true, "supportedOS": "windows" }
addin/AdomBridge/commands/assembly_bom.py+46−2@@ -56,13 +56,27 @@ def handle_assembly_bom(app: adsk.core.Application, args: dict) -> dict: exclude = tuple(args.get("exclude") or []) include_pp = bool(args.get("includePhysicalProperties", False)) - def is_leaf(comp):+ def kit_rule(comp): n = comp.name.lower()- if any(k in n for k in kit):+ for k in kit:+ if k in n:+ return k+ return None++ def is_leaf(comp):+ if kit_rule(comp) is not None: return True return comp.bRepBodies.count > 0 # a physical part is a leaf parts = {}+ # Transparency (issue #25, Oliver + John's call): every heuristic collapse is REPORTED so a+ # name-match on someone's own "bearing test jig" is a visible, correctable decision instead of+ # silently vanishing its internals from the BOM. collapsed = {unitName: {rule, quantity}}.+ collapsed = {}+ # Attribution for the inverse advisory: which DESCENDED subassembly each leaf came from, so a+ # purchased-looking unit that was exploded (240 identical bearing balls) can be flagged.+ sub_stats = {} # subassembly name -> {part name -> instances}+ sub_stack = [] def walk(occs): for i in range(occs.count):@@ -74,6 +88,13 @@ def handle_assembly_bom(app: adsk.core.Application, args: dict) -> dict: children = occ.childOccurrences has_children = children is not None and children.count > 0 if is_leaf(comp):+ rule = kit_rule(comp)+ if rule is not None and has_children:+ c = collapsed.setdefault(name, {"unit": name, "matchedRule": rule, "quantity": 0})+ c["quantity"] += 1+ if sub_stack and rule is None:+ stats = sub_stats.setdefault(sub_stack[-1], {})+ stats[name] = stats.get(name, 0) + 1 row = parts.get(name) if row is None: row = {@@ -97,7 +118,9 @@ def handle_assembly_bom(app: adsk.core.Application, args: dict) -> dict: else: row["quantity"] += 1 elif has_children:+ sub_stack.append(name) walk(children)+ sub_stack.pop() walk(design.rootComponent.occurrences) rows = list(parts.values())@@ -109,14 +132,35 @@ def handle_assembly_bom(app: adsk.core.Application, args: dict) -> dict: except Exception: pass + # Inverse advisory (Oliver, PR #38 intent): a DESCENDED subassembly dominated by many+ # identical small parts walks and quacks like a purchased unit that should be one BOM line.+ UNDER_COLLAPSE_MIN = 12+ possible_under = []+ for sub, stats in sub_stats.items():+ part, n = max(stats.items(), key=lambda kv: kv[1])+ if n >= UNDER_COLLAPSE_MIN:+ possible_under.append({+ "subassembly": sub, "dominantPart": part, "instances": n,+ "why": "descended subassembly dominated by %d identical parts; if it is a purchased unit, add a matching substring to treatAsUnit and it becomes one line" % n})+ out = { "success": True, "design": doc_name, "partCount": len(rows), "totalInstances": sum(r["quantity"] for r in rows), "treatAsUnit": kit,+ "collapsedUnits": sorted(collapsed.values(), key=lambda c: -c["quantity"]),+ "possibleUnderCollapse": sorted(possible_under, key=lambda a: -a["instances"]), "parts": rows, }+ if collapsed or possible_under:+ bits = []+ if collapsed:+ bits.append("%d subassembly type(s) were COLLAPSED to one line each by treatAsUnit name-matching (see collapsedUnits); if any is one of YOUR designs rather than a purchased unit, re-run with an explicit treatAsUnit list (or []) to explode it" % len(collapsed))+ if possible_under:+ bits.append("%d descended subassembly(ies) look like purchased units (see possibleUnderCollapse); add a matching substring to treatAsUnit to collapse them" % len(possible_under))+ out["_hint"] = ". ".join(bits) + "."+ outpath = args.get("outputPath") if outpath:
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.2",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.2/adom-bridge-fusion-v1.9.2.zip",- "sha256": "6c4b942a9856ccd9aa5be4c63c3a144b551582014d17b952e36844b9f3688813",- "size": 300217,+ "version": "1.9.3",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.3/adom-bridge-fusion-v1.9.3.zip",+ "sha256": "6a1b1ec071e8eb9c347f3cfc3bd386e370f73098cce9a0c7ec7e4df0ecebe93e",+ "size": 301185, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.2",+ "version": "1.9.3", "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.",