178 行
5.9 KiB
Python
178 行
5.9 KiB
Python
# -*- 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'⚠ 零件保持打开')
|