285 行
13 KiB
Python
285 行
13 KiB
Python
# -*- 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'⛔ 有项没过'))
|