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.
master
Colby Knox
generate_body pin counts per generator, exposed pad, STEP folder (unreleased, 1.13.4)
0e7aa24
14d ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
[
{
"args": {
"type": "chip",
"params": {
"D": 1.6,
"E": 0.8,
"A": 0.45,
"L": 0.3,
"L1": 0.2
},
"etch": "103",
"outputStep": "C:/tmp/r0603.step"
},
"script": "\nimport sys, glob, os, importlib\nimport adsk.core, adsk.fusion\n\n# Locate EPG across install conventions + versions (NEVER hardcode the webdeploy hash).\n_bases = []\nfor env in ('ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432', 'LOCALAPPDATA'):\n root = os.environ.get(env)\n if root:\n _bases += glob.glob(os.path.join(root, 'Autodesk', 'webdeploy', 'production', '*',\n 'Api', 'InternalAddins', 'ElectronicsPackageGenerator'))\nif not _bases:\n raise RuntimeError('EPG_NOT_FOUND: ElectronicsPackageGenerator not present in this Fusion install')\nepg_dir = max(_bases, key=os.path.getmtime)\nparent = os.path.dirname(epg_dir)\nif parent not in sys.path:\n sys.path.insert(0, parent)\n\nmod = importlib.import_module('ElectronicsPackageGenerator.Scripts3d.chip')\n\ndoc = app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)\ndesign = adsk.fusion.Design.cast(app.activeProduct)\nmod.runWithInput({'D': 0.16, 'E': 0.08, 'A': 0.045, 'L': 0.03, 'L1': 0.02}, design)\nroot = design.rootComponent\n\ninv = [{'name': b.name, 'vol': round(b.volume, 6)} for b in root.bRepBodies]\netched = None\nif '103':\n # FIND THE TOP SURFACE the robust way (John's algorithm, 2026-07-04):\n # 1. bounding box of the WHOLE chip (all bodies);\n # 2. only faces whose centroid sits in the TOP 5% band of that bbox count;\n # 3. among those upward planar faces take the LARGEST AREA (the molded body\n # top beats terminal tops that tie it on height);\n # 4. lay the text out with a 10% margin inside that face.\n gmin, gmax = 1e9, -1e9\n for b in root.bRepBodies:\n bb = b.boundingBox\n gmin = min(gmin, bb.minPoint.z)\n gmax = max(gmax, bb.maxPoint.z)\n band_z = gmax - 0.05 * (gmax - gmin)\n top_face, top_area = None, -1.0\n for b in root.bRepBodies:\n for f in b.faces:\n 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:\n top_face, top_area = f, f.area\n if top_face is None:\n raise RuntimeError('ETCH_NO_TOP_FACE: no upward planar face in the top 5% of the chip bbox')\n body = top_face.body\n sk = root.sketches.add(top_face)\n center = sk.modelToSketchSpace(top_face.centroid)\n # Measure the face in SKETCH space (model-space bbox axes can be SWAPPED vs\n # the sketch axes the text lays out in). Project the model bbox corners.\n bb = top_face.boundingBox\n p_min = sk.modelToSketchSpace(bb.minPoint)\n p_max = sk.modelToSketchSpace(bb.maxPoint)\n face_w = abs(p_max.x - p_min.x) # extent along sketch-x (the default text baseline)\n face_h = abs(p_max.y - p_min.y) # extent along sketch-y\n # ALWAYS run the text along the LONGEST axis of the top face (most room for\n # the marking - John's rule, 2026-07-04). If the long axis is sketch-y,\n # rotate the text 90deg rather than squeezing it onto the short axis.\n import math as _math\n rot_deg = 90 if face_h > face_w else 0\n long_len = max(face_w, face_h)\n short_len = min(face_w, face_h)\n w = long_len * 0.80 # 10% margin each side along the long axis\n box_h_cap = short_len * 0.80 # 10% margin each side across it\n # multi-line support (e.g. MPN + variant on line 2): fit per-line\n lines = '103'.split(chr(10))\n n_lines = max(1, len(lines))\n max_chars = max(1, max(len(l) for l in lines))\n # width-aware auto-fit: glyph ~0.75*h wide; line block ~1.35*h per line\n h = (None / 10.0) if None else max(0.015, min(box_h_cap / (1.35 * n_lines), w / (0.75 * max_chars)))\n if rot_deg:\n c1 = adsk.core.Point3D.create(center.x - box_h_cap/2, center.y - w/2, 0)\n c2 = adsk.core.Point3D.create(center.x + box_h_cap/2, center.y + w/2, 0)\n else:\n c1 = adsk.core.Point3D.create(center.x - w/2, center.y - box_h_cap/2, 0)\n c2 = adsk.core.Point3D.create(center.x + w/2, center.y + box_h_cap/2, 0)\n ti = sk.sketchTexts.createInput2('103', h)\n ti.setAsMultiLine(c1, c2,\n adsk.core.HorizontalAlignments.CenterHorizontalAlignment,\n adsk.core.VerticalAlignments.MiddleVerticalAlignment, 0)\n if rot_deg:\n try:\n ti.angle = _math.pi / 2.0\n except Exception:\n rot_deg = 0 # older API without angle - fall back to unrotated\n txt = sk.sketchTexts.add(ti)\n # MEASURE-AND-CORRECT (the margin is a CONTRACT): font metrics vary, so the\n # 0.75h glyph estimate can under-shoot ('ADOM-A' rendered edge-to-edge). After\n # placing, measure the text's real bbox; if it busts the 10%-margin box,\n # recreate at the scaled-down height. Never trust an estimate you can measure.\n fit_iterations = 0\n measured_w = None\n for _pass in range(2):\n tb = txt.boundingBox\n t_ext_x = abs(tb.maxPoint.x - tb.minPoint.x)\n t_ext_y = abs(tb.maxPoint.y - tb.minPoint.y)\n measured_w = max(t_ext_x, t_ext_y) # the baseline-axis extent\n measured_c = min(t_ext_x, t_ext_y) # the cross-axis extent (line stack)\n if measured_w <= w and measured_c <= box_h_cap:\n break\n scale = min(w / max(measured_w, 1e-6), box_h_cap / max(measured_c, 1e-6)) * 0.95\n h = max(0.008, h * scale)\n txt.deleteMe()\n ti = sk.sketchTexts.createInput2('103', h)\n ti.setAsMultiLine(c1, c2,\n adsk.core.HorizontalAlignments.CenterHorizontalAlignment,\n adsk.core.VerticalAlignments.MiddleVerticalAlignment, 0)\n if rot_deg:\n try:\n ti.angle = _math.pi / 2.0\n except Exception:\n pass\n txt = sk.sketchTexts.add(ti)\n fit_iterations += 1\n marking_geom = {\n 'chipBBox': {'min': [round(gmin,5), 0, 0], 'maxZ': round(gmax,5)},\n 'chipBBoxZ': {'min': round(gmin,5), 'max': round(gmax,5)},\n 'topBandZ': round(band_z,5),\n 'topFace': {'area': round(top_area,6), 'centroidZ': round(top_face.centroid.z,5),\n 'sketchExtents': {'x': round(face_w,5), 'y': round(face_h,5)},\n 'body': body.name},\n 'longAxis': 'sketch-y' if face_h > face_w else 'sketch-x',\n 'rotatedDeg': rot_deg,\n 'textBox': {'c1': [round(c1.x,5), round(c1.y,5)], 'c2': [round(c2.x,5), round(c2.y,5)],\n 'marginPct': 10},\n 'textHeightMm': round(h*10,4), 'lines': n_lines, 'maxLineChars': max_chars,\n 'heightAuto': not bool(None),\n 'textWidthMeasuredMm': round(measured_w*10,4) if measured_w else None,\n 'fitIterations': fit_iterations,\n 'why': {'band': 'top 5% of whole-chip bbox excludes terminal tops that tie the body',\n 'largestArea': 'molded body top beats small terminal faces in the band',\n 'longAxis': 'text runs along the longest face axis for maximum room',\n 'fit': 'h = min(boxH/(1.35*lines), boxW/(0.75*maxChars)) - glyphs ~0.75h wide'},\n }\n if 'raised' == 'engraved':\n # engraved (sunken) cut - the pre-2026-07-04 default\n ext_in = root.features.extrudeFeatures.createInput(txt, adsk.fusion.FeatureOperations.CutFeatureOperation)\n ext_in.setDistanceExtent(False, adsk.core.ValueInput.createByReal(-0.003))\n ext_in.participantBodies = [body]\n root.features.extrudeFeatures.add(ext_in)\n else:\n # RAISED WHITE marking (default): humans need CONTRAST - a thin positive\n # extrude painted white on the dark body reads like real silkscreen ink,\n # far better than a same-color emboss. Reuses EPG's own rgb-appearance\n # utility so the white survives Fusion rendering (+ colored STEP).\n ext_in = root.features.extrudeFeatures.createInput(txt, adsk.fusion.FeatureOperations.NewBodyFeatureOperation)\n ext_in.setDistanceExtent(False, adsk.core.ValueInput.createByReal(0.003))\n mark = root.features.extrudeFeatures.add(ext_in)\n from ElectronicsPackageGenerator.Utilities import addin_utility as _au\n for i in range(mark.bodies.count):\n mb = mark.bodies.item(i)\n mb.name = 'Marking' if i == 0 else 'Marking' + str(i + 1)\n _au.apply_rgb_appearance(app, design, mb, 255, 255, 255, 'AdomMarkingWhite')\n etched = '103'\n\nstep_path = None\nif 'C:/tmp/r0603.step':\n # Fusion's STEP export writes nothing into a folder that does not exist: make it first\n os.makedirs(os.path.dirname(os.path.abspath('C:/tmp/r0603.step')), exist_ok=True)\n em = design.exportManager\n opts = em.createSTEPExportOptions('C:/tmp/r0603.step')\n em.execute(opts)\n if not os.path.isfile('C:/tmp/r0603.step'):\n raise RuntimeError('Fusion did not write the STEP file %s' % 'C:/tmp/r0603.step')\n step_path = 'C:/tmp/r0603.step'\n\n# SIDECAR MANIFEST: every setting we picked + WHY + the bboxes we calculated,\n# so a marking refresh (e.g. adding a variant name as a 2nd line) can rework the\n# text without re-deriving anything. Written next to the STEP as <step>.manifest.json.\nmanifest = {\n 'schema': 'adom-fusion-generate-package/1',\n 'bridgeVersion': 'TESTVER',\n 'type': 'chip',\n 'paramsCm': {'D': 0.16, 'E': 0.08, 'A': 0.045, 'L': 0.03, 'L1': 0.02},\n 'paramsNote': 'paramsCm are EPG-native cm (mm inputs were /10)',\n 'marking': ({'text': '103', 'style': 'raised',\n 'depthMm': 0.003 * 10, **marking_geom} if etched else None),\n 'bodies': inv,\n 'outputs': {'step': step_path},\n 'refresh': {\n 'howTo': 'To re-mark (e.g. add a variant on line 2): call fusion_generate_package again '\n 'with the SAME type+paramsCm (unitsCm:true) and the new multi-line etch text '\n '(join lines with a newline). The generator is deterministic, so geometry and '\n 'placement reproduce exactly; this manifest carries the boxes/height the last '\n 'run chose for comparison.',\n 'example': {'type': 'chip', 'unitsCm': True, 'params': 'paramsCm from this file',\n 'etch': ('103' + chr(10) + 'VARIANT') if etched else 'MPN' + chr(10) + 'VARIANT'},\n },\n}\nmanifest_path = None\nif step_path:\n manifest_path = step_path + '.manifest.json'\n import json as _json\n with open(manifest_path, 'w', encoding='utf-8') as _f:\n _json.dump(manifest, _f, indent=2)\n\nvp = app.activeViewport\ncam = vp.camera\ncam.isSmoothTransition = False\ncam.target = adsk.core.Point3D.create(0, 0, 0)\ncam.eye = adsk.core.Point3D.create(0.25, -0.35, 0.45)\ncam.upVector = adsk.core.Vector3D.create(0, 0, 1)\ncam.isFitView = True\nvp.camera = cam\nvp.refresh()\n\nresult = {'type': 'chip', 'bodies': inv, 'etched': etched, 'stepPath': step_path,\n 'manifest': manifest, 'manifestPath': manifest_path,\n 'epgDir': epg_dir, 'doc': app.activeDocument.name}\n",
"errorCode": null
},
{
"args": {
"type": "soic",
"params": {
"A": 1.75,
"A1": 0.1,
"b": 0.41,
"D": 4.9,
"E": 6.0,
"E1": 3.9,
"e": 1.27,
"L": 0.8,
"DPins": 8
},
"unitsCm": false,
"etch": "LM358\nADOM-A",
"etchStyle": "engraved",
"etchDepthMm": 0.05,
"etchHeightMm": 0.5
},
"script": "\nimport sys, glob, os, importlib\nimport adsk.core, adsk.fusion\n\n# Locate EPG across install conventions + versions (NEVER hardcode the webdeploy hash).\n_bases = []\nfor env in ('ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432', 'LOCALAPPDATA'):\n root = os.environ.get(env)\n if root:\n _bases += glob.glob(os.path.join(root, 'Autodesk', 'webdeploy', 'production', '*',\n 'Api', 'InternalAddins', 'ElectronicsPackageGenerator'))\nif not _bases:\n raise RuntimeError('EPG_NOT_FOUND: ElectronicsPackageGenerator not present in this Fusion install')\nepg_dir = max(_bases, key=os.path.getmtime)\nparent = os.path.dirname(epg_dir)\nif parent not in sys.path:\n sys.path.insert(0, parent)\n\nmod = importlib.import_module('ElectronicsPackageGenerator.Scripts3d.soic')\n\ndoc = app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)\ndesign = adsk.fusion.Design.cast(app.activeProduct)\nmod.runWithInput({'A': 0.175, 'A1': 0.01, 'b': 0.040999999999999995, 'D': 0.49000000000000005, 'E': 0.6, 'E1': 0.39, 'e': 0.127, 'L': 0.08, 'DPins': 8}, design)\nroot = design.rootComponent\n\ninv = [{'name': b.name, 'vol': round(b.volume, 6)} for b in root.bRepBodies]\netched = None\nif 'LM358\\nADOM-A':\n # FIND THE TOP SURFACE the robust way (John's algorithm, 2026-07-04):\n # 1. bounding box of the WHOLE chip (all bodies);\n # 2. only faces whose centroid sits in the TOP 5% band of that bbox count;\n # 3. among those upward planar faces take the LARGEST AREA (the molded body\n # top beats terminal tops that tie it on height);\n # 4. lay the text out with a 10% margin inside that face.\n gmin, gmax = 1e9, -1e9\n for b in root.bRepBodies:\n bb = b.boundingBox\n gmin = min(gmin, bb.minPoint.z)\n gmax = max(gmax, bb.maxPoint.z)\n band_z = gmax - 0.05 * (gmax - gmin)\n top_face, top_area = None, -1.0\n for b in root.bRepBodies:\n for f in b.faces:\n 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:\n top_face, top_area = f, f.area\n if top_face is None:\n raise RuntimeError('ETCH_NO_TOP_FACE: no upward planar face in the top 5% of the chip bbox')\n body = top_face.body\n sk = root.sketches.add(top_face)\n center = sk.modelToSketchSpace(top_face.centroid)\n # Measure the face in SKETCH space (model-space bbox axes can be SWAPPED vs\n # the sketch axes the text lays out in). Project the model bbox corners.\n bb = top_face.boundingBox\n p_min = sk.modelToSketchSpace(bb.minPoint)\n p_max = sk.modelToSketchSpace(bb.maxPoint)\n face_w = abs(p_max.x - p_min.x) # extent along sketch-x (the default text baseline)\n face_h = abs(p_max.y - p_min.y) # extent along sketch-y\n # ALWAYS run the text along the LONGEST axis of the top face (most room for\n # the marking - John's rule, 2026-07-04). If the long axis is sketch-y,\n # rotate the text 90deg rather than squeezing it onto the short axis.\n import math as _math\n rot_deg = 90 if face_h > face_w else 0\n long_len = max(face_w, face_h)\n short_len = min(face_w, face_h)\n w = long_len * 0.80 # 10% margin each side along the long axis\n box_h_cap = short_len * 0.80 # 10% margin each side across it\n # multi-line support (e.g. MPN + variant on line 2): fit per-line\n lines = 'LM358\\nADOM-A'.split(chr(10))\n n_lines = max(1, len(lines))\n max_chars = max(1, max(len(l) for l in lines))\n # width-aware auto-fit: glyph ~0.75*h wide; line block ~1.35*h per line\n h = (0.5 / 10.0) if 0.5 else max(0.015, min(box_h_cap / (1.35 * n_lines), w / (0.75 * max_chars)))\n if rot_deg:\n c1 = adsk.core.Point3D.create(center.x - box_h_cap/2, center.y - w/2, 0)\n c2 = adsk.core.Point3D.create(center.x + box_h_cap/2, center.y + w/2, 0)\n else:\n c1 = adsk.core.Point3D.create(center.x - w/2, center.y - box_h_cap/2, 0)\n c2 = adsk.core.Point3D.create(center.x + w/2, center.y + box_h_cap/2, 0)\n ti = sk.sketchTexts.createInput2('LM358\\nADOM-A', h)\n ti.setAsMultiLine(c1, c2,\n adsk.core.HorizontalAlignments.CenterHorizontalAlignment,\n adsk.core.VerticalAlignments.MiddleVerticalAlignment, 0)\n if rot_deg:\n try:\n ti.angle = _math.pi / 2.0\n except Exception:\n rot_deg = 0 # older API without angle - fall back to unrotated\n txt = sk.sketchTexts.add(ti)\n # MEASURE-AND-CORRECT (the margin is a CONTRACT): font metrics vary, so the\n # 0.75h glyph estimate can under-shoot ('ADOM-A' rendered edge-to-edge). After\n # placing, measure the text's real bbox; if it busts the 10%-margin box,\n # recreate at the scaled-down height. Never trust an estimate you can measure.\n fit_iterations = 0\n measured_w = None\n for _pass in range(2):\n tb = txt.boundingBox\n t_ext_x = abs(tb.maxPoint.x - tb.minPoint.x)\n t_ext_y = abs(tb.maxPoint.y - tb.minPoint.y)\n measured_w = max(t_ext_x, t_ext_y) # the baseline-axis extent\n measured_c = min(t_ext_x, t_ext_y) # the cross-axis extent (line stack)\n if measured_w <= w and measured_c <= box_h_cap:\n break\n scale = min(w / max(measured_w, 1e-6), box_h_cap / max(measured_c, 1e-6)) * 0.95\n h = max(0.008, h * scale)\n txt.deleteMe()\n ti = sk.sketchTexts.createInput2('LM358\\nADOM-A', h)\n ti.setAsMultiLine(c1, c2,\n adsk.core.HorizontalAlignments.CenterHorizontalAlignment,\n adsk.core.VerticalAlignments.MiddleVerticalAlignment, 0)\n if rot_deg:\n try:\n ti.angle = _math.pi / 2.0\n except Exception:\n pass\n txt = sk.sketchTexts.add(ti)\n fit_iterations += 1\n marking_geom = {\n 'chipBBox': {'min': [round(gmin,5), 0, 0], 'maxZ': round(gmax,5)},\n 'chipBBoxZ': {'min': round(gmin,5), 'max': round(gmax,5)},\n 'topBandZ': round(band_z,5),\n 'topFace': {'area': round(top_area,6), 'centroidZ': round(top_face.centroid.z,5),\n 'sketchExtents': {'x': round(face_w,5), 'y': round(face_h,5)},\n 'body': body.name},\n 'longAxis': 'sketch-y' if face_h > face_w else 'sketch-x',\n 'rotatedDeg': rot_deg,\n 'textBox': {'c1': [round(c1.x,5), round(c1.y,5)], 'c2': [round(c2.x,5), round(c2.y,5)],\n 'marginPct': 10},\n 'textHeightMm': round(h*10,4), 'lines': n_lines, 'maxLineChars': max_chars,\n 'heightAuto': not bool(0.5),\n 'textWidthMeasuredMm': round(measured_w*10,4) if measured_w else None,\n 'fitIterations': fit_iterations,\n 'why': {'band': 'top 5% of whole-chip bbox excludes terminal tops that tie the body',\n 'largestArea': 'molded body top beats small terminal faces in the band',\n 'longAxis': 'text runs along the longest face axis for maximum room',\n 'fit': 'h = min(boxH/(1.35*lines), boxW/(0.75*maxChars)) - glyphs ~0.75h wide'},\n }\n if 'engraved' == 'engraved':\n # engraved (sunken) cut - the pre-2026-07-04 default\n ext_in = root.features.extrudeFeatures.createInput(txt, adsk.fusion.FeatureOperations.CutFeatureOperation)\n ext_in.setDistanceExtent(False, adsk.core.ValueInput.createByReal(-0.005))\n ext_in.participantBodies = [body]\n root.features.extrudeFeatures.add(ext_in)\n else:\n # RAISED WHITE marking (default): humans need CONTRAST - a thin positive\n # extrude painted white on the dark body reads like real silkscreen ink,\n # far better than a same-color emboss. Reuses EPG's own rgb-appearance\n # utility so the white survives Fusion rendering (+ colored STEP).\n ext_in = root.features.extrudeFeatures.createInput(txt, adsk.fusion.FeatureOperations.NewBodyFeatureOperation)\n ext_in.setDistanceExtent(False, adsk.core.ValueInput.createByReal(0.005))\n mark = root.features.extrudeFeatures.add(ext_in)\n from ElectronicsPackageGenerator.Utilities import addin_utility as _au\n for i in range(mark.bodies.count):\n mb = mark.bodies.item(i)\n mb.name = 'Marking' if i == 0 else 'Marking' + str(i + 1)\n _au.apply_rgb_appearance(app, design, mb, 255, 255, 255, 'AdomMarkingWhite')\n etched = 'LM358\\nADOM-A'\n\nstep_path = None\nif '':\n # Fusion's STEP export writes nothing into a folder that does not exist: make it first\n os.makedirs(os.path.dirname(os.path.abspath('')), exist_ok=True)\n em = design.exportManager\n opts = em.createSTEPExportOptions('')\n em.execute(opts)\n if not os.path.isfile(''):\n raise RuntimeError('Fusion did not write the STEP file %s' % '')\n step_path = ''\n\n# SIDECAR MANIFEST: every setting we picked + WHY + the bboxes we calculated,\n# so a marking refresh (e.g. adding a variant name as a 2nd line) can rework the\n# text without re-deriving anything. Written next to the STEP as <step>.manifest.json.\nmanifest = {\n 'schema': 'adom-fusion-generate-package/1',\n 'bridgeVersion': 'TESTVER',\n 'type': 'soic',\n 'paramsCm': {'A': 0.175, 'A1': 0.01, 'b': 0.040999999999999995, 'D': 0.49000000000000005, 'E': 0.6, 'E1': 0.39, 'e': 0.127, 'L': 0.08, 'DPins': 8},\n 'paramsNote': 'paramsCm are EPG-native cm (mm inputs were /10)',\n 'marking': ({'text': 'LM358\\nADOM-A', 'style': 'engraved',\n 'depthMm': 0.005 * 10, **marking_geom} if etched else None),\n 'bodies': inv,\n 'outputs': {'step': step_path},\n 'refresh': {\n 'howTo': 'To re-mark (e.g. add a variant on line 2): call fusion_generate_package again '\n 'with the SAME type+paramsCm (unitsCm:true) and the new multi-line etch text '\n '(join lines with a newline). The generator is deterministic, so geometry and '\n 'placement reproduce exactly; this manifest carries the boxes/height the last '\n 'run chose for comparison.',\n 'example': {'type': 'soic', 'unitsCm': True, 'params': 'paramsCm from this file',\n 'etch': ('LM358\\nADOM-A' + chr(10) + 'VARIANT') if etched else 'MPN' + chr(10) + 'VARIANT'},\n },\n}\nmanifest_path = None\nif step_path:\n manifest_path = step_path + '.manifest.json'\n import json as _json\n with open(manifest_path, 'w', encoding='utf-8') as _f:\n _json.dump(manifest, _f, indent=2)\n\nvp = app.activeViewport\ncam = vp.camera\ncam.isSmoothTransition = False\ncam.target = adsk.core.Point3D.create(0, 0, 0)\ncam.eye = adsk.core.Point3D.create(0.25, -0.35, 0.45)\ncam.upVector = adsk.core.Vector3D.create(0, 0, 1)\ncam.isFitView = True\nvp.camera = cam\nvp.refresh()\n\nresult = {'type': 'soic', 'bodies': inv, 'etched': etched, 'stepPath': step_path,\n 'manifest': manifest, 'manifestPath': manifest_path,\n 'epgDir': epg_dir, 'doc': app.activeDocument.name}\n",
"errorCode": null
},
{
"args": {
"type": "qfn",
"params": {
"D": 0.5,
"E": 0.5,
"DPins": 4,
"EPins": 4,
"color_r": 30
},
"unitsCm": true,
"etch": "it's \"q\"",
"etchHeightMm": 1
},
"script": "\nimport sys, glob, os, importlib\nimport adsk.core, adsk.fusion\n\n# Locate EPG across install conventions + versions (NEVER hardcode the webdeploy hash).\n_bases = []\nfor env in ('ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432', 'LOCALAPPDATA'):\n root = os.environ.get(env)\n if root:\n _bases += glob.glob(os.path.join(root, 'Autodesk', 'webdeploy', 'production', '*',\n 'Api', 'InternalAddins', 'ElectronicsPackageGenerator'))\nif not _bases:\n raise RuntimeError('EPG_NOT_FOUND: ElectronicsPackageGenerator not present in this Fusion install')\nepg_dir = max(_bases, key=os.path.getmtime)\nparent = os.path.dirname(epg_dir)\nif parent not in sys.path:\n sys.path.insert(0, parent)\n\nmod = importlib.import_module('ElectronicsPackageGenerator.Scripts3d.qfn')\n\ndoc = app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)\ndesign = adsk.fusion.Design.cast(app.activeProduct)\nmod.runWithInput({'D': 0.5, 'E': 0.5, 'DPins': 4, 'EPins': 4, 'color_r': 30}, design)\nroot = design.rootComponent\n\ninv = [{'name': b.name, 'vol': round(b.volume, 6)} for b in root.bRepBodies]\netched = None\nif 'it\\'s \"q\"':\n # FIND THE TOP SURFACE the robust way (John's algorithm, 2026-07-04):\n # 1. bounding box of the WHOLE chip (all bodies);\n # 2. only faces whose centroid sits in the TOP 5% band of that bbox count;\n # 3. among those upward planar faces take the LARGEST AREA (the molded body\n # top beats terminal tops that tie it on height);\n # 4. lay the text out with a 10% margin inside that face.\n gmin, gmax = 1e9, -1e9\n for b in root.bRepBodies:\n bb = b.boundingBox\n gmin = min(gmin, bb.minPoint.z)\n gmax = max(gmax, bb.maxPoint.z)\n band_z = gmax - 0.05 * (gmax - gmin)\n top_face, top_area = None, -1.0\n for b in root.bRepBodies:\n for f in b.faces:\n 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:\n top_face, top_area = f, f.area\n if top_face is None:\n raise RuntimeError('ETCH_NO_TOP_FACE: no upward planar face in the top 5% of the chip bbox')\n body = top_face.body\n sk = root.sketches.add(top_face)\n center = sk.modelToSketchSpace(top_face.centroid)\n # Measure the face in SKETCH space (model-space bbox axes can be SWAPPED vs\n # the sketch axes the text lays out in). Project the model bbox corners.\n bb = top_face.boundingBox\n p_min = sk.modelToSketchSpace(bb.minPoint)\n p_max = sk.modelToSketchSpace(bb.maxPoint)\n face_w = abs(p_max.x - p_min.x) # extent along sketch-x (the default text baseline)\n face_h = abs(p_max.y - p_min.y) # extent along sketch-y\n # ALWAYS run the text along the LONGEST axis of the top face (most room for\n # the marking - John's rule, 2026-07-04). If the long axis is sketch-y,\n # rotate the text 90deg rather than squeezing it onto the short axis.\n import math as _math\n rot_deg = 90 if face_h > face_w else 0\n long_len = max(face_w, face_h)\n short_len = min(face_w, face_h)\n w = long_len * 0.80 # 10% margin each side along the long axis\n box_h_cap = short_len * 0.80 # 10% margin each side across it\n # multi-line support (e.g. MPN + variant on line 2): fit per-line\n lines = 'it\\'s \"q\"'.split(chr(10))\n n_lines = max(1, len(lines))\n max_chars = max(1, max(len(l) for l in lines))\n # width-aware auto-fit: glyph ~0.75*h wide; line block ~1.35*h per line\n h = (1 / 10.0) if 1 else max(0.015, min(box_h_cap / (1.35 * n_lines), w / (0.75 * max_chars)))\n if rot_deg:\n c1 = adsk.core.Point3D.create(center.x - box_h_cap/2, center.y - w/2, 0)\n c2 = adsk.core.Point3D.create(center.x + box_h_cap/2, center.y + w/2, 0)\n else:\n c1 = adsk.core.Point3D.create(center.x - w/2, center.y - box_h_cap/2, 0)\n c2 = adsk.core.Point3D.create(center.x + w/2, center.y + box_h_cap/2, 0)\n ti = sk.sketchTexts.createInput2('it\\'s \"q\"', h)\n ti.setAsMultiLine(c1, c2,\n adsk.core.HorizontalAlignments.CenterHorizontalAlignment,\n adsk.core.VerticalAlignments.MiddleVerticalAlignment, 0)\n if rot_deg:\n try:\n ti.angle = _math.pi / 2.0\n except Exception:\n rot_deg = 0 # older API without angle - fall back to unrotated\n txt = sk.sketchTexts.add(ti)\n # MEASURE-AND-CORRECT (the margin is a CONTRACT): font metrics vary, so the\n # 0.75h glyph estimate can under-shoot ('ADOM-A' rendered edge-to-edge). After\n # placing, measure the text's real bbox; if it busts the 10%-margin box,\n # recreate at the scaled-down height. Never trust an estimate you can measure.\n fit_iterations = 0\n measured_w = None\n for _pass in range(2):\n tb = txt.boundingBox\n t_ext_x = abs(tb.maxPoint.x - tb.minPoint.x)\n t_ext_y = abs(tb.maxPoint.y - tb.minPoint.y)\n measured_w = max(t_ext_x, t_ext_y) # the baseline-axis extent\n measured_c = min(t_ext_x, t_ext_y) # the cross-axis extent (line stack)\n if measured_w <= w and measured_c <= box_h_cap:\n break\n scale = min(w / max(measured_w, 1e-6), box_h_cap / max(measured_c, 1e-6)) * 0.95\n h = max(0.008, h * scale)\n txt.deleteMe()\n ti = sk.sketchTexts.createInput2('it\\'s \"q\"', h)\n ti.setAsMultiLine(c1, c2,\n adsk.core.HorizontalAlignments.CenterHorizontalAlignment,\n adsk.core.VerticalAlignments.MiddleVerticalAlignment, 0)\n if rot_deg:\n try:\n ti.angle = _math.pi / 2.0\n except Exception:\n pass\n txt = sk.sketchTexts.add(ti)\n fit_iterations += 1\n marking_geom = {\n 'chipBBox': {'min': [round(gmin,5), 0, 0], 'maxZ': round(gmax,5)},\n 'chipBBoxZ': {'min': round(gmin,5), 'max': round(gmax,5)},\n 'topBandZ': round(band_z,5),\n 'topFace': {'area': round(top_area,6), 'centroidZ': round(top_face.centroid.z,5),\n 'sketchExtents': {'x': round(face_w,5), 'y': round(face_h,5)},\n 'body': body.name},\n 'longAxis': 'sketch-y' if face_h > face_w else 'sketch-x',\n 'rotatedDeg': rot_deg,\n 'textBox': {'c1': [round(c1.x,5), round(c1.y,5)], 'c2': [round(c2.x,5), round(c2.y,5)],\n 'marginPct': 10},\n 'textHeightMm': round(h*10,4), 'lines': n_lines, 'maxLineChars': max_chars,\n 'heightAuto': not bool(1),\n 'textWidthMeasuredMm': round(measured_w*10,4) if measured_w else None,\n 'fitIterations': fit_iterations,\n 'why': {'band': 'top 5% of whole-chip bbox excludes terminal tops that tie the body',\n 'largestArea': 'molded body top beats small terminal faces in the band',\n 'longAxis': 'text runs along the longest face axis for maximum room',\n 'fit': 'h = min(boxH/(1.35*lines), boxW/(0.75*maxChars)) - glyphs ~0.75h wide'},\n }\n if 'raised' == 'engraved':\n # engraved (sunken) cut - the pre-2026-07-04 default\n ext_in = root.features.extrudeFeatures.createInput(txt, adsk.fusion.FeatureOperations.CutFeatureOperation)\n ext_in.setDistanceExtent(False, adsk.core.ValueInput.createByReal(-0.003))\n ext_in.participantBodies = [body]\n root.features.extrudeFeatures.add(ext_in)\n else:\n # RAISED WHITE marking (default): humans need CONTRAST - a thin positive\n # extrude painted white on the dark body reads like real silkscreen ink,\n # far better than a same-color emboss. Reuses EPG's own rgb-appearance\n # utility so the white survives Fusion rendering (+ colored STEP).\n ext_in = root.features.extrudeFeatures.createInput(txt, adsk.fusion.FeatureOperations.NewBodyFeatureOperation)\n ext_in.setDistanceExtent(False, adsk.core.ValueInput.createByReal(0.003))\n mark = root.features.extrudeFeatures.add(ext_in)\n from ElectronicsPackageGenerator.Utilities import addin_utility as _au\n for i in range(mark.bodies.count):\n mb = mark.bodies.item(i)\n mb.name = 'Marking' if i == 0 else 'Marking' + str(i + 1)\n _au.apply_rgb_appearance(app, design, mb, 255, 255, 255, 'AdomMarkingWhite')\n etched = 'it\\'s \"q\"'\n\nstep_path = None\nif '':\n # Fusion's STEP export writes nothing into a folder that does not exist: make it first\n os.makedirs(os.path.dirname(os.path.abspath('')), exist_ok=True)\n em = design.exportManager\n opts = em.createSTEPExportOptions('')\n em.execute(opts)\n if not os.path.isfile(''):\n raise RuntimeError('Fusion did not write the STEP file %s' % '')\n step_path = ''\n\n# SIDECAR MANIFEST: every setting we picked + WHY + the bboxes we calculated,\n# so a marking refresh (e.g. adding a variant name as a 2nd line) can rework the\n# text without re-deriving anything. Written next to the STEP as <step>.manifest.json.\nmanifest = {\n 'schema': 'adom-fusion-generate-package/1',\n 'bridgeVersion': 'TESTVER',\n 'type': 'qfn',\n 'paramsCm': {'D': 0.5, 'E': 0.5, 'DPins': 4, 'EPins': 4, 'color_r': 30},\n 'paramsNote': 'paramsCm are EPG-native cm (mm inputs were /10)',\n 'marking': ({'text': 'it\\'s \"q\"', 'style': 'raised',\n 'depthMm': 0.003 * 10, **marking_geom} if etched else None),\n 'bodies': inv,\n 'outputs': {'step': step_path},\n 'refresh': {\n 'howTo': 'To re-mark (e.g. add a variant on line 2): call fusion_generate_package again '\n 'with the SAME type+paramsCm (unitsCm:true) and the new multi-line etch text '\n '(join lines with a newline). The generator is deterministic, so geometry and '\n 'placement reproduce exactly; this manifest carries the boxes/height the last '\n 'run chose for comparison.',\n 'example': {'type': 'qfn', 'unitsCm': True, 'params': 'paramsCm from this file',\n 'etch': ('it\\'s \"q\"' + chr(10) + 'VARIANT') if etched else 'MPN' + chr(10) + 'VARIANT'},\n },\n}\nmanifest_path = None\nif step_path:\n manifest_path = step_path + '.manifest.json'\n import json as _json\n with open(manifest_path, 'w', encoding='utf-8') as _f:\n _json.dump(manifest, _f, indent=2)\n\nvp = app.activeViewport\ncam = vp.camera\ncam.isSmoothTransition = False\ncam.target = adsk.core.Point3D.create(0, 0, 0)\ncam.eye = adsk.core.Point3D.create(0.25, -0.35, 0.45)\ncam.upVector = adsk.core.Vector3D.create(0, 0, 1)\ncam.isFitView = True\nvp.camera = cam\nvp.refresh()\n\nresult = {'type': 'qfn', 'bodies': inv, 'etched': etched, 'stepPath': step_path,\n 'manifest': manifest, 'manifestPath': manifest_path,\n 'epgDir': epg_dir, 'doc': app.activeDocument.name}\n",
"errorCode": null
},
{
"args": {
"type": "chip",
"params": {
"D": 3,
"E": 1e-05,
"A": 1.2345678901234568e+16
},
"etch": ""
},
"script": "\nimport sys, glob, os, importlib\nimport adsk.core, adsk.fusion\n\n# Locate EPG across install conventions + versions (NEVER hardcode the webdeploy hash).\n_bases = []\nfor env in ('ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432', 'LOCALAPPDATA'):\n root = os.environ.get(env)\n if root:\n _bases += glob.glob(os.path.join(root, 'Autodesk', 'webdeploy', 'production', '*',\n 'Api', 'InternalAddins', 'ElectronicsPackageGenerator'))\nif not _bases:\n raise RuntimeError('EPG_NOT_FOUND: ElectronicsPackageGenerator not present in this Fusion install')\nepg_dir = max(_bases, key=os.path.getmtime)\nparent = os.path.dirname(epg_dir)\nif parent not in sys.path:\n sys.path.insert(0, parent)\n\nmod = importlib.import_module('ElectronicsPackageGenerator.Scripts3d.chip')\n\ndoc = app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)\ndesign = adsk.fusion.Design.cast(app.activeProduct)\nmod.runWithInput({'D': 0.3, 'E': 1.0000000000000002e-06, 'A': 1234567890123456.8}, design)\nroot = design.rootComponent\n\ninv = [{'name': b.name, 'vol': round(b.volume, 6)} for b in root.bRepBodies]\netched = None\nif '':\n # FIND THE TOP SURFACE the robust way (John's algorithm, 2026-07-04):\n # 1. bounding box of the WHOLE chip (all bodies);\n # 2. only faces whose centroid sits in the TOP 5% band of that bbox count;\n # 3. among those upward planar faces take the LARGEST AREA (the molded body\n # top beats terminal tops that tie it on height);\n # 4. lay the text out with a 10% margin inside that face.\n gmin, gmax = 1e9, -1e9\n for b in root.bRepBodies:\n bb = b.boundingBox\n gmin = min(gmin, bb.minPoint.z)\n gmax = max(gmax, bb.maxPoint.z)\n band_z = gmax - 0.05 * (gmax - gmin)\n top_face, top_area = None, -1.0\n for b in root.bRepBodies:\n for f in b.faces:\n 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:\n top_face, top_area = f, f.area\n if top_face is None:\n raise RuntimeError('ETCH_NO_TOP_FACE: no upward planar face in the top 5% of the chip bbox')\n body = top_face.body\n sk = root.sketches.add(top_face)\n center = sk.modelToSketchSpace(top_face.centroid)\n # Measure the face in SKETCH space (model-space bbox axes can be SWAPPED vs\n # the sketch axes the text lays out in). Project the model bbox corners.\n bb = top_face.boundingBox\n p_min = sk.modelToSketchSpace(bb.minPoint)\n p_max = sk.modelToSketchSpace(bb.maxPoint)\n face_w = abs(p_max.x - p_min.x) # extent along sketch-x (the default text baseline)\n face_h = abs(p_max.y - p_min.y) # extent along sketch-y\n # ALWAYS run the text along the LONGEST axis of the top face (most room for\n # the marking - John's rule, 2026-07-04). If the long axis is sketch-y,\n # rotate the text 90deg rather than squeezing it onto the short axis.\n import math as _math\n rot_deg = 90 if face_h > face_w else 0\n long_len = max(face_w, face_h)\n short_len = min(face_w, face_h)\n w = long_len * 0.80 # 10% margin each side along the long axis\n box_h_cap = short_len * 0.80 # 10% margin each side across it\n # multi-line support (e.g. MPN + variant on line 2): fit per-line\n lines = ''.split(chr(10))\n n_lines = max(1, len(lines))\n max_chars = max(1, max(len(l) for l in lines))\n # width-aware auto-fit: glyph ~0.75*h wide; line block ~1.35*h per line\n h = (None / 10.0) if None else max(0.015, min(box_h_cap / (1.35 * n_lines), w / (0.75 * max_chars)))\n if rot_deg:\n c1 = adsk.core.Point3D.create(center.x - box_h_cap/2, center.y - w/2, 0)\n c2 = adsk.core.Point3D.create(center.x + box_h_cap/2, center.y + w/2, 0)\n else:\n c1 = adsk.core.Point3D.create(center.x - w/2, center.y - box_h_cap/2, 0)\n c2 = adsk.core.Point3D.create(center.x + w/2, center.y + box_h_cap/2, 0)\n ti = sk.sketchTexts.createInput2('', h)\n ti.setAsMultiLine(c1, c2,\n adsk.core.HorizontalAlignments.CenterHorizontalAlignment,\n adsk.core.VerticalAlignments.MiddleVerticalAlignment, 0)\n if rot_deg:\n try:\n ti.angle = _math.pi / 2.0\n except Exception:\n rot_deg = 0 # older API without angle - fall back to unrotated\n txt = sk.sketchTexts.add(ti)\n # MEASURE-AND-CORRECT (the margin is a CONTRACT): font metrics vary, so the\n # 0.75h glyph estimate can under-shoot ('ADOM-A' rendered edge-to-edge). After\n # placing, measure the text's real bbox; if it busts the 10%-margin box,\n # recreate at the scaled-down height. Never trust an estimate you can measure.\n fit_iterations = 0\n measured_w = None\n for _pass in range(2):\n tb = txt.boundingBox\n t_ext_x = abs(tb.maxPoint.x - tb.minPoint.x)\n t_ext_y = abs(tb.maxPoint.y - tb.minPoint.y)\n measured_w = max(t_ext_x, t_ext_y) # the baseline-axis extent\n measured_c = min(t_ext_x, t_ext_y) # the cross-axis extent (line stack)\n if measured_w <= w and measured_c <= box_h_cap:\n break\n scale = min(w / max(measured_w, 1e-6), box_h_cap / max(measured_c, 1e-6)) * 0.95\n h = max(0.008, h * scale)\n txt.deleteMe()\n ti = sk.sketchTexts.createInput2('', h)\n ti.setAsMultiLine(c1, c2,\n adsk.core.HorizontalAlignments.CenterHorizontalAlignment,\n adsk.core.VerticalAlignments.MiddleVerticalAlignment, 0)\n if rot_deg:\n try:\n ti.angle = _math.pi / 2.0\n except Exception:\n pass\n txt = sk.sketchTexts.add(ti)\n fit_iterations += 1\n marking_geom = {\n 'chipBBox': {'min': [round(gmin,5), 0, 0], 'maxZ': round(gmax,5)},\n 'chipBBoxZ': {'min': round(gmin,5), 'max': round(gmax,5)},\n 'topBandZ': round(band_z,5),\n 'topFace': {'area': round(top_area,6), 'centroidZ': round(top_face.centroid.z,5),\n 'sketchExtents': {'x': round(face_w,5), 'y': round(face_h,5)},\n 'body': body.name},\n 'longAxis': 'sketch-y' if face_h > face_w else 'sketch-x',\n 'rotatedDeg': rot_deg,\n 'textBox': {'c1': [round(c1.x,5), round(c1.y,5)], 'c2': [round(c2.x,5), round(c2.y,5)],\n 'marginPct': 10},\n 'textHeightMm': round(h*10,4), 'lines': n_lines, 'maxLineChars': max_chars,\n 'heightAuto': not bool(None),\n 'textWidthMeasuredMm': round(measured_w*10,4) if measured_w else None,\n 'fitIterations': fit_iterations,\n 'why': {'band': 'top 5% of whole-chip bbox excludes terminal tops that tie the body',\n 'largestArea': 'molded body top beats small terminal faces in the band',\n 'longAxis': 'text runs along the longest face axis for maximum room',\n 'fit': 'h = min(boxH/(1.35*lines), boxW/(0.75*maxChars)) - glyphs ~0.75h wide'},\n }\n if 'raised' == 'engraved':\n # engraved (sunken) cut - the pre-2026-07-04 default\n ext_in = root.features.extrudeFeatures.createInput(txt, adsk.fusion.FeatureOperations.CutFeatureOperation)\n ext_in.setDistanceExtent(False, adsk.core.ValueInput.createByReal(-0.003))\n ext_in.participantBodies = [body]\n root.features.extrudeFeatures.add(ext_in)\n else:\n # RAISED WHITE marking (default): humans need CONTRAST - a thin positive\n # extrude painted white on the dark body reads like real silkscreen ink,\n # far better than a same-color emboss. Reuses EPG's own rgb-appearance\n # utility so the white survives Fusion rendering (+ colored STEP).\n ext_in = root.features.extrudeFeatures.createInput(txt, adsk.fusion.FeatureOperations.NewBodyFeatureOperation)\n ext_in.setDistanceExtent(False, adsk.core.ValueInput.createByReal(0.003))\n mark = root.features.extrudeFeatures.add(ext_in)\n from ElectronicsPackageGenerator.Utilities import addin_utility as _au\n for i in range(mark.bodies.count):\n mb = mark.bodies.item(i)\n mb.name = 'Marking' if i == 0 else 'Marking' + str(i + 1)\n _au.apply_rgb_appearance(app, design, mb, 255, 255, 255, 'AdomMarkingWhite')\n etched = ''\n\nstep_path = None\nif '':\n # Fusion's STEP export writes nothing into a folder that does not exist: make it first\n os.makedirs(os.path.dirname(os.path.abspath('')), exist_ok=True)\n em = design.exportManager\n opts = em.createSTEPExportOptions('')\n em.execute(opts)\n if not os.path.isfile(''):\n raise RuntimeError('Fusion did not write the STEP file %s' % '')\n step_path = ''\n\n# SIDECAR MANIFEST: every setting we picked + WHY + the bboxes we calculated,\n# so a marking refresh (e.g. adding a variant name as a 2nd line) can rework the\n# text without re-deriving anything. Written next to the STEP as <step>.manifest.json.\nmanifest = {\n 'schema': 'adom-fusion-generate-package/1',\n 'bridgeVersion': 'TESTVER',\n 'type': 'chip',\n 'paramsCm': {'D': 0.3, 'E': 1.0000000000000002e-06, 'A': 1234567890123456.8},\n 'paramsNote': 'paramsCm are EPG-native cm (mm inputs were /10)',\n 'marking': ({'text': '', 'style': 'raised',\n 'depthMm': 0.003 * 10, **marking_geom} if etched else None),\n 'bodies': inv,\n 'outputs': {'step': step_path},\n 'refresh': {\n 'howTo': 'To re-mark (e.g. add a variant on line 2): call fusion_generate_package again '\n 'with the SAME type+paramsCm (unitsCm:true) and the new multi-line etch text '\n '(join lines with a newline). The generator is deterministic, so geometry and '\n 'placement reproduce exactly; this manifest carries the boxes/height the last '\n 'run chose for comparison.',\n 'example': {'type': 'chip', 'unitsCm': True, 'params': 'paramsCm from this file',\n 'etch': ('' + chr(10) + 'VARIANT') if etched else 'MPN' + chr(10) + 'VARIANT'},\n },\n}\nmanifest_path = None\nif step_path:\n manifest_path = step_path + '.manifest.json'\n import json as _json\n with open(manifest_path, 'w', encoding='utf-8') as _f:\n _json.dump(manifest, _f, indent=2)\n\nvp = app.activeViewport\ncam = vp.camera\ncam.isSmoothTransition = False\ncam.target = adsk.core.Point3D.create(0, 0, 0)\ncam.eye = adsk.core.Point3D.create(0.25, -0.35, 0.45)\ncam.upVector = adsk.core.Vector3D.create(0, 0, 1)\ncam.isFitView = True\nvp.camera = cam\nvp.refresh()\n\nresult = {'type': 'chip', 'bodies': inv, 'etched': etched, 'stepPath': step_path,\n 'manifest': manifest, 'manifestPath': manifest_path,\n 'epgDir': epg_dir, 'doc': app.activeDocument.name}\n",
"errorCode": null
},
{
"args": {
"type": "bogus",
"params": {}
},
"script": null,
"errorCode": "unknown_package_type"
}
]