文件

285 行
13 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""螺丝建模 + 装进总装复验(2026-09-30 用户:原配螺丝太短)。
三种螺丝(长度 L = 头下长度;轴沿 z,头下支承面在 z=0、头朝 +z、杆朝 −z):
A 底盖 → 顶盖角柱 M3 薄头(平头)内六角 DIN 7984 头 Ø5.5×2.0、内六角 2.0 L=10 ×4
头埋进底盖沉孔 Ø6.5 深 2.0,头底面与墙底 z −13.6 齐平;穿 1.6 板后进角柱 8.4(柱孔 Ø4.2 盲孔深 82)
B PCB → 底盖立柱 同 A 头型 L=8 ×4
头压在 PCB 顶面;穿 1.6 板后进立柱 6.4(立柱孔 Ø4.2 盲孔深 10,余 3.6)
C 风扇 → 端墙 风扇自攻螺丝 螺纹 Ø5.0、平头 Ø7.0×2.0 L=12 ×8
头贴端墙外表面;穿 2.4 墙后进风扇 9.6(风扇实物厚 10,螺尖不进分风腔)
螺纹不建(只建外径圆柱);所以 C 与风扇安装孔(Ø4.3)的干涉是**预期的**(自攻就是靠这个咬住)。
判据:
1. 每个零件包围盒 = 设计值
2. 16 颗装进总装后逐颗包围盒在位(A 头底面 = −13.6、B 头底面 = PCB 顶面、C 头底面 = 墙外表面)
3. 干涉 = 基线 360 + 只允许「C ↔ 风扇」这一类;A / B 一颗都不许有
改长度:只动下面 SPECS 里的 L,重跑即可(零件与总装里的螺丝都会先删再建)。"""
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\\'
FAN10 = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.SLDPRT'
ASM = u'PhoneRail_5blades.SLDASM'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
# name, 头径, 头高, 杆径, L, 内六角对边(0 = 不做)
SPECS = {
'A': (u'Screw_M3x10_DIN7984', 5.5, 2.0, 3.0, 10.0, 2.0),
'B': (u'Screw_M3x8_DIN7984', 5.5, 2.0, 3.0, 8.0, 2.0),
'C': (u'FanScrew_5x12', 7.0, 2.0, 5.0, 12.0, 0.0),
}
PCB_TOP = 1.68 # 底板 PCB 底面 z 0(立柱顶);STEP 里 Board 实测厚 1.670x(1.6 基材 + 铜 / 阻焊),取 1.68 避开 0.001 mm³ 的数值接触
PLATE_BOT, CB_D = -13.6, 2.0 # 底盖底面、沉孔深
CORNER_HOLES = [(-11.0, 3.345), (-11.0, 99.345), (108.5, 3.345), (108.5, 99.345)]
PCB_HOLES = [(4.0, 4.0), (94.0, 4.0), (4.0, 94.0), (94.0, 94.0)]
FAN_HOLES = [(26.24, 12.75), (76.44, 12.75), (26.24, 62.95), (76.44, 62.95)] # (y, z),端墙孔 Ø5.2、孔距 50.2
WALL_W, WALL_E = -18.4, 115.9 # 端墙外表面 x
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')
app.CloseAllDocuments(True) # 总装开着会占住旧螺丝零件 ⇒ 另存 err=1
allok = True
# ── [1] 建三个零件 ─────────────────────────────────────────────────────────
def build(key):
name, dk, k, d, L, s = SPECS[key]
for dd in list(app.GetDocuments or []):
t = prop(dd, 'GetTitle') or ''
if name.upper() in t.upper():
app.CloseDoc(t)
app.NewDocument(app.GetUserPreferenceStringValue(8), 0, 0.0, 0.0)
part = app.ActiveDoc
sm, fm = part.SketchManager, part.FeatureManager
def last_sketch():
nm, f = None, prop(part, 'FirstFeature')
while f:
if prop(f, 'GetTypeName2') == 'ProfileFeature':
nm = prop(f, 'Name')
f = prop(f, 'GetNextFeature')
return nm
def pick_sketch():
part.ClearSelection2(True)
part.Extension.SelectByID2(last_sketch(), 'SKETCH', 0, 0, 0, False, 0, EMPTY, 0)
return part.SelectionManager.GetSelectedObjectCount2(-1) == 1
def circle_on_front(r):
part.ClearSelection2(True)
part.Extension.SelectByID2(u'前视基准面', 'PLANE', 0, 0, 0, False, 0, EMPTY, 0)
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCircleByRadius(0.0, 0.0, 0.0, M(r))
sm.AddToDB = False
sm.InsertSketch(True)
def extrude(depth, rev, merge=True):
if not pick_sketch():
raise RuntimeError(u'草图没选中')
return fm.FeatureExtrusion3(True, False, rev, 0, 0, M(depth), 0.0,
False, False, False, False, 0.0, 0.0,
False, False, False, False, merge, True, True, 0, 0.0, False)
circle_on_front(dk / 2)
f1 = extrude(k, False) # 头:z 0 → +k
circle_on_front(d / 2)
f2 = extrude(L, True) # 杆:z 0 → −L
nb = len(part.GetBodies2(0, True) or [])
hexok = True
if s > 0: # 内六角:头顶面(z=k)上画六边形,切深 0.6k
top = None
for bd in part.GetBodies2(0, True) or []:
for f in prop(D(bd), 'GetFaces'):
fc = D(f)
sf = prop(fc, 'GetSurface')
if prop(sf, 'IsPlane'):
fb = [v * 1000 for v in prop(fc, 'GetBox')]
if abs(fb[2] - k) < 0.01 and abs(fb[5] - k) < 0.01:
top = fc
part.ClearSelection2(True)
top.Select4(False, EMPTY)
sm.InsertSketch(True)
sm.AddToDB = True
# 外接圆半径 = 对边/√3(顶点在草图 x 轴上)
sm.CreatePolygon(0.0, 0.0, 0.0, M(s / 3 ** 0.5), 0.0, 0.0, 6, False)
sm.AddToDB = False
sm.InsertSketch(True)
fa0 = len(prop(D(part.GetBodies2(0, True)[0]), 'GetFaces'))
if pick_sketch():
fm.FeatureCut4(True, False, False, 0, 0, M(0.6 * k), 0.0,
False, False, False, False, 0.0, 0.0,
False, False, False, False, False,
True, True, False, False, False, 0, 0.0, False, False)
fa1 = len(prop(D(part.GetBodies2(0, True)[0]), 'GetFaces'))
hexok = fa1 - fa0 == 7 # 6 侧面 + 1 底面
bds = part.GetBodies2(0, True) or []
bx = [round(v * 1000, 3) for v in prop(D(bds[0]), 'GetBodyBox')]
want = [-dk / 2, -dk / 2, -L, dk / 2, dk / 2, k]
ok = len(bds) == 1 and all(abs(bx[i] - want[i]) < 0.02 for i in range(6)) and hexok
dst = DIR + name + u'.SLDPRT'
e1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
part.Extension.SaveAs3(dst, 0, 1, EMPTY, EMPTY, e1, w1)
P(u' %s %s 实体 %d box %s %s 内六角 %s 另存 err=%s'
% (key, name, len(bds), bx, u'✅' if ok else u'⛔ 期望 %s' % want,
(u'✅' if hexok else u'⛔') if s else u'—', e1.value))
return ok and e1.value == 0
P(u'[1] 建零件(L = 头下长度)')
for key in 'ABC':
allok &= build(key)
if not allok:
P(u'⛔ 零件没建对,停手'); sys.exit(2)
# ── [2] 打开总装(零件必须已在内存里,AddComponent4 才认)──────────────────
for fn, t in ((DIR + u'TopShell_v11.SLDPRT', 1), (DIR + u'BottomPlate_v6.SLDPRT', 1), (FAN10, 1), (DIR + ASM, 2)):
e = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.OpenDoc6(fn, t, 1, u'', e, w)
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(ASM, False, 0, e0)
doc = app.ActiveDoc
P(u'\n[2] 总装 %s' % prop(doc, 'GetTitle'))
names = [SPECS[k][0].upper() for k in 'ABC']
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if any(nm.upper().startswith(n) for n in names):
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
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
# 零件轴 +z(头)映射到:
ROT = {
'up': [1, 0, 0, 0, 1, 0, 0, 0, 1], # 头朝 +z(PCB 螺丝)
'down': [1, 0, 0, 0, -1, 0, 0, 0, -1], # 头朝 −z(底盖螺丝,绕 x 转 180°)
'west': [0, 0, 1, 0, 1, 0, -1, 0, 0], # 头朝 −x(西墙)——方向靠下面的包围盒判据兜底
'east': [0, 0, -1, 0, 1, 0, 1, 0, 0], # 头朝 +x(东墙)
}
def place(fn, rot, t):
c = doc.AddComponent4(DIR + fn + u'.SLDPRT', u'', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
[float(x) for x in ROT[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 c, [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
P(u'\n[3] 装 16 颗')
jobs = []
nA, dkA, kA, dA, LA, _ = SPECS['A']
seat = PLATE_BOT + CB_D # 沉孔底 = 头支承面 z −11.6
for x, y in CORNER_HOLES:
jobs.append(('A', nA, 'down', (x, y, seat),
[x - dkA / 2, y - dkA / 2, seat - kA, x + dkA / 2, y + dkA / 2, seat + LA]))
nB, dkB, kB, dB, LB, _ = SPECS['B']
for x, y in PCB_HOLES:
jobs.append(('B', nB, 'up', (x, y, PCB_TOP),
[x - dkB / 2, y - dkB / 2, PCB_TOP - LB, x + dkB / 2, y + dkB / 2, PCB_TOP + kB]))
nC, dkC, kC, dC, LC, _ = SPECS['C']
for y, z in FAN_HOLES:
jobs.append(('C', nC, 'west', (WALL_W, y, z),
[WALL_W - kC, y - dkC / 2, z - dkC / 2, WALL_W + LC, y + dkC / 2, z + dkC / 2]))
jobs.append(('C', nC, 'east', (WALL_E, y, z),
[WALL_E - LC, y - dkC / 2, z - dkC / 2, WALL_E + kC, y + dkC / 2, z + dkC / 2]))
for key, fn, rot, t, want in jobs:
c, b = place(fn, rot, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
if not ok and rot in ('west', 'east'): # 旋转矩阵行 / 列约定拿不准 ⇒ 换对侧再试一次
doc.ClearSelection2(True)
doc.Extension.SelectByID2(prop(c, 'Name2'), 'COMPONENT', 0, 0, 0, False, 0, EMPTY, 0)
doc.Extension.DeleteSelection2(0)
rot2 = 'east' if rot == 'west' else 'west'
ROT[rot], ROT[rot2] = ROT[rot2], ROT[rot]
c, b = place(fn, rot, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %s %-10s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (key, prop(c, 'Name2')[:22], b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
# ── [4] 干涉 ─────────────────────────────────────────────────────────────
P(u'\n[4] 干涉(基线 360;只允许 风扇螺丝 ↔ 风扇 这一类)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = False
idm.UseTransform = False
def getp(o, n):
try:
return getattr(o, n)
except AttributeError:
dd = o._oleobj_.GetIDsOfNames(n)
return o._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
n = getp(idm, 'GetInterferenceCount')
items = getp(idm, 'GetInterferences') or []
screw_hits = []
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(any(y.upper().startswith(s) for s in names) for y in nms):
screw_hits.append((o.Volume * 1e9, nms))
idm.Done()
fan_ok = [h for h in screw_hits if any(y.upper().startswith(nC.upper()) for y in h[1]) and any('OD60' in y.upper() for y in h[1])]
bad = [h for h in screw_hits if h not in fan_ok]
P(u' 总数 %s = 基线 360 + 螺丝相关 %d(其中 风扇螺丝 ↔ 风扇 %d,其它 %d)'
% (n, len(screw_hits), len(fan_ok), len(bad)))
for vv, nms in sorted(bad, reverse=True)[:12]:
P(u' ⛔ %.3f mm³ %s ↔ %s' % (vv, nms[0][:30], nms[1][:30]))
if fan_ok:
P(u' 风扇螺丝咬进风扇孔(自攻,预期):每处 %.2f~%.2f mm³'
% (min(h[0] for h in fan_ok), max(h[0] for h in fan_ok)))
okI = n == 360 + len(screw_hits) and not bad and len(fan_ok) == 8
P(u' %s' % (u'✅' if okI else u'⛔'))
allok &= okI
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
if allok:
doc.Extension.SaveAs3(DIR + ASM, 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n%s 顶层件 %d 总判 %s' % (u'存盘 err=%s' % e3.value if allok else u'没过,不存盘',
len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))