R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -0,0 +1,292 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# v3:短机版总装(风扇换 6010、分风腔 10、TopShell_v3 / BottomPlate_v3 / FilterFrame_v2)
|
||||
u"""**v2**:两件式 + 西端插拔式滤网(TopShell_v2 / BottomPlate_v2 / FilterFrame_v1)。
|
||||
(以下为 v1 说明)总装换成**两件式外壳**:TopShell_v1(一体顶盖)+ BottomPlate_v1(一整块底盖)。
|
||||
|
||||
删:Tray_v9、EndPlate_v13 ×2、SouthPanel_v4、NorthPanel_v3、TopPanel_v4(六件式全部)
|
||||
插:TopShell_v1 dz +74.4、BottomPlate_v1 dz −10(纯平移)
|
||||
不动:底板、5 片刀片、2 只风扇
|
||||
|
||||
判据:
|
||||
1. 两件装配 box;分风腔深度复测(西 15.01 / 东 15.51)
|
||||
2. 干涉 = 360 基线
|
||||
3. 顶盖墙底 ↔ 底盖顶面 **面贴合**(z=−10)
|
||||
4. 同轴:底盖四角 Ø3.4 过孔 ↔ 顶盖角柱 Ø4.2 螺母孔(轴 z)
|
||||
5. 同轴:顶盖端墙 Ø3.4 风扇孔 ↔ 风扇模型 R2.15 安装孔(轴 x)—— 六件式时代没核过
|
||||
6. 截图
|
||||
"""
|
||||
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
|
||||
|
||||
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
|
||||
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
|
||||
ASM = 'PhoneRail_5blades'
|
||||
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
|
||||
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
|
||||
OLD = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL', 'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
app = wc.GetActiveObject('SldWorks.Application')
|
||||
doc = None
|
||||
for d in app.GetDocuments or []:
|
||||
if ASM in (prop(d, 'GetTitle') or ''):
|
||||
doc = d
|
||||
break
|
||||
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
|
||||
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
|
||||
doc = app.ActiveDoc
|
||||
|
||||
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
|
||||
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
|
||||
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
|
||||
|
||||
|
||||
def setx(c, t):
|
||||
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
|
||||
IDENT + [t[0] / 1000.0, t[1] / 1000.0, t[2] / 1000.0, 1.0, 0.0, 0.0, 0.0])
|
||||
xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, v))
|
||||
try:
|
||||
c.SetTransformAndSolve2(xf)
|
||||
except Exception:
|
||||
c.Transform2 = xf
|
||||
doc.EditRebuild3
|
||||
return [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
|
||||
|
||||
|
||||
allok = True
|
||||
P(u'[1] 删六件式')
|
||||
for c in list(doc.GetComponents(True)):
|
||||
nm = prop(c, 'Name2')
|
||||
if any(k in nm.upper() for k in OLD):
|
||||
doc.ClearSelection2(True)
|
||||
doc.Extension.SelectByID2(nm, 'COMPONENT', 0, 0, 0, False, 0, EMPTY, 0)
|
||||
doc.Extension.DeleteSelection2(0)
|
||||
P(u' 删掉 %s' % nm)
|
||||
doc.EditRebuild3
|
||||
left = [prop(c, 'Name2') for c in doc.GetComponents(True)
|
||||
if any(k in prop(c, 'Name2').upper() for k in OLD)]
|
||||
if left:
|
||||
P(u' ⛔ 没删干净(EditDelete 式假成功?)%s' % left); sys.exit(2)
|
||||
|
||||
P(u'\n[2] 插两件')
|
||||
for fn, t, want in ((u'TopShell_v5.SLDPRT', (0, 0, 74.4), [-24.8, -4.055, -10.0, 115.9, 106.745, 74.4]),
|
||||
(u'BottomPlate_v4.SLDPRT', (0, 0, -10.0), [-24.3, -3.555, -13.6, 115.4, 106.245, 0.0])):
|
||||
c = doc.AddComponent4(DIR + fn, '', 0.0, 0.0, 0.0)
|
||||
if c is None:
|
||||
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
|
||||
b = setx(c, t)
|
||||
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
|
||||
allok &= ok
|
||||
P(u' %-22s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
|
||||
% (fn, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
|
||||
|
||||
P(u'\n[2b] 插滤网框(本地 x→y、y→z、z→x)')
|
||||
_fc = doc.AddComponent4(DIR + u'FilterFrame_v3.SLDPRT', '', 0.0, 0.0, 0.0)
|
||||
if _fc is None:
|
||||
P(u' ⛔ FilterFrame AddComponent4 → None'); sys.exit(3)
|
||||
_v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
|
||||
[0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, -0.0214, 0.016345, -0.010, 1.0, 0.0, 0.0, 0.0])
|
||||
_xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, _v))
|
||||
try:
|
||||
_fc.SetTransformAndSolve2(_xf)
|
||||
except Exception:
|
||||
_fc.Transform2 = _xf
|
||||
doc.EditRebuild3
|
||||
_b = [round(x * 1000.0, 3) for x in _fc.GetBox(False, False)]
|
||||
_w = [-22.4, 16.345, -10.0, -18.4, 86.345, 74.4]
|
||||
_ok = all(abs(_b[i] - _w[i]) < 0.05 for i in range(6))
|
||||
allok &= _ok
|
||||
P(u' 滤网框 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
|
||||
% (_b[0], _b[3], _b[1], _b[4], _b[2], _b[5], u'✅' if _ok else u'⛔ 期望 %s' % _w))
|
||||
P(u' 滑槽间隙:贴墙 %.2f(应 0)/ 唇边侧 %.2f(应 0.4)/ 南 %.2f、北 %.2f(应 0.3)/ 底 %.2f(落在底盖上,应 0)/ 露出顶盖 %.2f'
|
||||
% (-18.4 - _b[3], _b[0] - (-22.8), _b[1] - 16.045, 86.645 - _b[4], _b[2] - (-10.0), _b[5] - 74.4))
|
||||
|
||||
P(u'\n[1b] 风扇 60×60×25 → 60×60×10(按实物 10 mm:外侧面贴墙)')
|
||||
FAN10 = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.SLDPRT'
|
||||
_old = []
|
||||
for c in list(doc.GetComponents(True)):
|
||||
nm = prop(c, 'Name2')
|
||||
if 'OD60' in nm.upper():
|
||||
_old.append((list(prop(c, 'Transform2').ArrayData)[0:9], [x * 1000 for x in c.GetBox(False, False)]))
|
||||
doc.ClearSelection2(True)
|
||||
doc.Extension.SelectByID2(nm, 'COMPONENT', 0, 0, 0, False, 0, EMPTY, 0)
|
||||
doc.Extension.DeleteSelection2(0)
|
||||
P(u' 删掉 %s' % nm)
|
||||
doc.EditRebuild3
|
||||
if len(_old) != 2:
|
||||
P(u' ⛔ 旧风扇不是 2 只'); sys.exit(4)
|
||||
|
||||
|
||||
def _setrt(c, rot, t):
|
||||
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
|
||||
list(rot) + [t[0] / 1000.0, t[1] / 1000.0, t[2] / 1000.0, 1.0, 0.0, 0.0, 0.0])
|
||||
xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, v))
|
||||
try:
|
||||
c.SetTransformAndSolve2(xf)
|
||||
except Exception:
|
||||
c.Transform2 = xf
|
||||
doc.EditRebuild3
|
||||
return [x * 1000.0 for x in c.GetBox(False, False)]
|
||||
|
||||
|
||||
for rot, b_old in _old:
|
||||
west = b_old[0] < 50
|
||||
c = doc.AddComponent4(FAN10, '', 0.0, 0.0, 0.0)
|
||||
if c is None:
|
||||
P(u' ⛔ OD6010 AddComponent4 → None'); sys.exit(5)
|
||||
b0 = _setrt(c, rot, (0, 0, 0))
|
||||
dx = (-16.0 - b0[0]) if west else (113.5 - b0[3])
|
||||
dy = 51.345 - (b0[1] + b0[4]) / 2
|
||||
dz = 37.85 - (b0[2] + b0[5]) / 2
|
||||
b = _setrt(c, rot, (dx, dy, dz))
|
||||
P(u' %s风扇 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f(模型厚 %.2f,实物 10)'
|
||||
% (u'西' if west else u'东', b[0], b[3], b[1], b[4], b[2], b[5], b[3] - b[0]))
|
||||
|
||||
tops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}
|
||||
box = {n: [x * 1000 for x in c.GetBox(False, False)] for n, c in tops.items()}
|
||||
fans = sorted([b for n, b in box.items() if 'OD60' in n.upper()], key=lambda b: b[0])
|
||||
blades = [b for n, b in box.items() if 'BLADE' in n.upper()]
|
||||
pw = min(b[0] for b in blades) - fans[0][3]
|
||||
pe = fans[1][0] - max(b[3] for b in blades)
|
||||
P(u' 分风腔 西 %.2f / 东 %.2f %s' % (pw, pe, u'✅' if pw > 9 and pe > 9 else u'⛔'))
|
||||
allok &= pw > 9 and pe > 9
|
||||
|
||||
P(u'\n[3] 干涉(基线 360)')
|
||||
idm = doc.InterferenceDetectionManager
|
||||
idm.TreatCoincidenceAsInterference = False
|
||||
idm.IncludeMultibodyPartInterferences = False
|
||||
idm.TreatSubAssembliesAsComponents = True
|
||||
idm.IgnoreHiddenBodies = True
|
||||
idm.UseTransform = False
|
||||
try:
|
||||
n = idm.GetInterferenceCount
|
||||
except AttributeError:
|
||||
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
|
||||
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
|
||||
hits = []
|
||||
if n and n != 360:
|
||||
try:
|
||||
items = idm.GetInterferences
|
||||
except AttributeError:
|
||||
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
|
||||
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
|
||||
for it in items:
|
||||
o = it if hasattr(it, 'Volume') else wc.Dispatch(it)
|
||||
nms = [wc.Dispatch(x).Name2.split('/')[0] for x in o.Components]
|
||||
if any(k in y.upper() for y in nms for k in ('TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME', 'OD60')):
|
||||
hits.append((o.Volume * 1e9, nms))
|
||||
idm.Done()
|
||||
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
|
||||
for vv, nms in sorted(hits, reverse=True)[:8]:
|
||||
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
|
||||
allok &= n == 360
|
||||
|
||||
P(u'\n[4] 贴合:顶盖墙底 ↔ 底盖顶面')
|
||||
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
|
||||
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
|
||||
P(u' 顶盖 z 下沿 %.3f / 底盖板面 z −10(底盖 box 上沿 %.3f 是 PCB 立柱顶)'
|
||||
% (ts[2], bp[5]))
|
||||
allok &= abs(ts[2] + 10.0) < 0.05
|
||||
|
||||
|
||||
def world_holes(comp, radius, tol=0.01):
|
||||
xf = list(prop(comp, 'Transform2').ArrayData)
|
||||
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
|
||||
out = []
|
||||
for bd in comp.GetBodies2(0) or []:
|
||||
for f in prop(D(bd), 'GetFaces'):
|
||||
s = prop(D(f), 'GetSurface')
|
||||
if not prop(s, 'IsCylinder'):
|
||||
continue
|
||||
cp = list(prop(s, 'CylinderParams'))
|
||||
if abs(cp[6] * 1000 - radius) > tol:
|
||||
continue
|
||||
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
|
||||
a = cp[3:6]
|
||||
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
|
||||
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
|
||||
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
|
||||
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
|
||||
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
|
||||
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
|
||||
out.append((max(range(3), key=lambda i: abs(ga[i])), gp))
|
||||
return out
|
||||
|
||||
|
||||
def pair(tag, A, B, axis, want):
|
||||
A = [p for ax, p in A if ax == axis]
|
||||
B = [p for ax, p in B if ax == axis]
|
||||
o = [i for i in range(3) if i != axis]
|
||||
ok = 0
|
||||
for p in A:
|
||||
lat = min((max(abs(p[i] - q[i]) for i in o) for q in B), default=99)
|
||||
ok += lat < 0.05
|
||||
P(u' %s (%s=%7.2f, %s=%6.2f) 横偏 %.3f %s'
|
||||
% (tag, 'xyz'[o[0]], p[o[0]], 'xyz'[o[1]], p[o[1]], lat, u'✅' if lat < 0.05 else u'⛔'))
|
||||
P(u' %s 对上 %d / %d(期望 %d)' % (tag, ok, len(A), want))
|
||||
return ok == want and len(A) == want
|
||||
|
||||
|
||||
shell = [c for n, c in tops.items() if 'TOPSHELL' in n.upper()][0]
|
||||
plate = [c for n, c in tops.items() if 'BOTTOMPLATE' in n.upper()][0]
|
||||
fanc = [c for n, c in tops.items() if 'OD60' in n.upper()]
|
||||
|
||||
P(u'\n[5] 同轴:底盖四角过孔 ↔ 顶盖角柱螺母孔')
|
||||
thru = [h for h in world_holes(plate, 1.7)]
|
||||
nuts = [h for h in world_holes(shell, 2.1)]
|
||||
allok &= pair(u'底盖过孔', thru, nuts, 2, 4)
|
||||
|
||||
P(u'\n[6] 同轴:顶盖端墙风扇孔 ↔ 风扇安装孔(R2.15)')
|
||||
wall = world_holes(shell, 2.6)
|
||||
fan_h = []
|
||||
for fc in fanc:
|
||||
fan_h += world_holes(fc, 2.15)
|
||||
# Orion OD6010 模型的安装孔是**腰形孔**:每个孔由两个 R2.15 半圆柱组成(中心 ±1.075),
|
||||
# 模型孔距 50.0;实物(广诚 6010)孔距 50.2 ⇒ 墙孔按 ±25.1 开,与模型预期差 0.1。
|
||||
# ⇒ 先把离墙孔 1.5 以内的半圆柱轴点取平均得到腰形孔中心,再比偏差,容差放到 0.15。
|
||||
_ok = 0
|
||||
_ws = [p for ax, p in wall if ax == 0]
|
||||
for p in _ws:
|
||||
near = [q for ax, q in fan_h if ax == 0 and max(abs(p[1] - q[1]), abs(p[2] - q[2])) < 1.5]
|
||||
if not near:
|
||||
P(u' 墙孔 (y=%.2f, z=%.2f) 附近没有风扇孔 ⛔' % (p[1], p[2]))
|
||||
continue
|
||||
cy = sum(q[1] for q in near) / len(near)
|
||||
cz = sum(q[2] for q in near) / len(near)
|
||||
off = max(abs(p[1] - cy), abs(p[2] - cz))
|
||||
_ok += off <= 0.15
|
||||
P(u' 墙孔 (y=%.2f, z=%.2f) ↔ 模型腰孔中心 (%.2f, %.2f) 偏 %.3f %s'
|
||||
% (p[1], p[2], cy, cz, off, u'✅(模型 50.0 vs 实物 50.2)' if off <= 0.15 else u'⛔'))
|
||||
P(u' 风扇孔对上 %d / %d(期望 8)' % (_ok, len(_ws)))
|
||||
allok &= _ok == 8 and len(_ws) == 8
|
||||
|
||||
for nm2, vid in ((u'v5_iso', 7), (u'v5_east', 0), (u'v5_south', 6), (u'v5_bottom', 2), (u'v5_top', 1)):
|
||||
try:
|
||||
doc.ShowNamedView2('', vid)
|
||||
doc.ViewZoomtofit2
|
||||
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
|
||||
except Exception as ex:
|
||||
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
|
||||
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
|
||||
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
|
||||
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
|
||||
P(u'\n存盘 err=%s 顶层件 %d 总判 %s'
|
||||
% (e3.value, len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))
|
||||
在新工单中引用
屏蔽一个用户