R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -0,0 +1,177 @@
|
||||
# -*- 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'⚠ 零件保持打开')
|
||||
在新工单中引用
屏蔽一个用户