# -*- coding: utf-8 -*- u"""给外壳件加**外轮廓圆角**(用户 2026-09-13 要求)。v2 重写。 ⛔ v1 失败的真因(不是 API 坏了): 最小实验(纯长方体 12 条边)倒 4 条竖边 → 成功、直方图 `{3.0: 4}`。 但真实零件上:托盘 60 条边里选了 12 条、端板 34 条里选了 8 条, `FeatureFillet3` 一律返回 **None**。 共同点是**选中的边里混进了做不出来的**: · 托盘:USB-C 缺口的内角边 —— 缺口只有 4.2 深,R3 在窄槽内角会自相交; · 端板:L 脚与板面的交线 —— 同理超出可行范围。 SolidWorks 对这种"整批里有一条做不出"的情况**整批放弃**,还不报错。 ✅ v2 的三条对策: 1. **只倒外轮廓的角**:按实体包围盒筛,边的两个端点必须都落在包围盒的角上, 这样自动排除所有内部缺口/开口/台阶产生的边。 2. **逐条单独倒**:一条失败不拖垮其余,且能精确报出是哪条边做不出来。 3. **失败自动降半径**:R3 → R2 → R1.5 → R1 依次重试。 判据仍是**圆柱面半径直方图**(包围盒对圆角没有分辨力)。 用法:python add_fillets.py <零件文件名> [半径] [轴] """ import io import os import sys sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True) import pythoncom import win32com.client as wc DIR = u'D:\\DCDC\\新建文件夹\\5blades\\' EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None) fname = sys.argv[1] if len(sys.argv) > 1 else 'Tray_v7.SLDPRT' RAD0 = float(sys.argv[2]) if len(sys.argv) > 2 else 3.0 AXIS = (sys.argv[3] if len(sys.argv) > 3 else 'z').lower() AI = {'x': 0, 'y': 1, 'z': 2}[AXIS] PATH = DIR + fname RETRY = [RAD0, 2.0, 1.5, 1.0] def prop(o, n): v = getattr(o, n) return v() if type(v).__name__ in ('method', 'function', 'builtin_function_or_method') else v def D(x): return x if hasattr(x, '_oleobj_') else wc.Dispatch(x) def P(*a): print(*a) def M(v): return v / 1000.0 app = wc.GetActiveObject('SldWorks.Application') if not os.path.exists(PATH): P(u'⛔ 文件不存在 %s' % PATH); sys.exit(2) part = None for d in app.GetDocuments or []: try: if (prop(d, 'GetPathName') or '').lower() == PATH.lower(): part = d break except Exception: pass if part is None: err = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0) warn = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0) part = app.OpenDoc6(PATH, 1, 0, '', err, warn) P(u'打开 %s err=%s' % (fname, err.value)) if part is None: P(u'⛔ 打不开'); sys.exit(3) e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0) app.ActivateDoc3(prop(part, 'GetTitle'), False, 0, e0) part = app.ActiveDoc fm = part.FeatureManager P(u'零件 %s,目标 R%.1f,沿 %s 轴' % (prop(part, 'GetTitle'), RAD0, AXIS)) def body(): b = part.GetBodies2(0, True) or [] return D(b[0]) if b else None def cyl_hist(): bd = body() if bd is None: return {} rs = {} for f in prop(bd, 'GetFaces'): s = prop(D(f), 'GetSurface') if prop(s, 'IsCylinder'): r = round(list(prop(s, 'CylinderParams'))[6] * 1000, 2) rs[r] = rs.get(r, 0) + 1 return dict(sorted(rs.items())) before = cyl_hist() bx = [round(v * 1000, 3) for v in prop(body(), 'GetBodyBox')] P(u'包围盒 %s' % bx) P(u'倒角前 %s' % before) # 另外两根轴(外轮廓角要在这两根轴上都处于极值) oth = [i for i in range(3) if i != AI] def at_corner(p): u"""该点在另外两根轴上是否都落在包围盒边界(即外轮廓的角)""" for i in oth: if abs(p[i] - bx[i]) > 0.01 and abs(p[i] - bx[i + 3]) > 0.01: return False return True cand = [] for e in prop(body(), 'GetEdges'): E = D(e) try: p0 = [round(v * 1000, 3) for v in prop(D(prop(E, 'GetStartVertex')), 'GetPoint')] p1 = [round(v * 1000, 3) for v in prop(D(prop(E, 'GetEndVertex')), 'GetPoint')] except Exception: continue same = [i for i in range(3) if abs(p0[i] - p1[i]) < 1e-6] if len(same) != 2 or AI in same: continue if abs(p0[AI] - p1[AI]) < 1e-6: continue if not (at_corner(p0) and at_corner(p1)): continue # ⇐ 关键:排除缺口/台阶产生的内部边 cand.append((E, p0)) P(u'外轮廓上沿 %s 的直边 = %d 条(v1 不加这一层筛选时是 12 / 8 条,含做不出的内角)' % (AXIS, len(cand))) for _, p0 in cand: P(u' 角 (%.2f, %.2f)' % (p0[oth[0]], p0[oth[1]])) if not cand: P(u'⛔ 没有可倒的外轮廓边'); sys.exit(4) done, failed = 0, [] for idx, (E, p0) in enumerate(cand): ok = False for r in RETRY: part.ClearSelection2(True) E.Select4(False, EMPTY) if part.SelectionManager.GetSelectedObjectCount2(-1) != 1: break try: feat = fm.FeatureFillet3(195, M(r), 0, 0, 0, 0, 0, None, None, None, None, None, None, None) except Exception as ex: feat = None if feat: P(u' 边%d 角(%.2f, %.2f) → R%.1f ✅' % (idx, p0[oth[0]], p0[oth[1]], r)) done += 1 ok = True break if not ok: P(u' 边%d 角(%.2f, %.2f) → 四档半径都做不出 ⛔' % (idx, p0[oth[0]], p0[oth[1]])) failed.append(idx) after = cyl_hist() P(u'\n倒角后 %s' % after) P(u'成功 %d / %d 条%s' % (done, len(cand), (u',失败 %s' % failed) if failed else u'')) if done: e1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0) w1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0) part.Extension.SaveAs3(PATH, 0, 1, EMPTY, EMPTY, e1, w1) P(u'存盘 err=%s %.0f KB' % (e1.value, os.path.getsize(PATH) / 1024.0)) else: P(u'⛔ 一条都没倒成,不存盘') P(u'⚠ 零件保持打开')