# -*- 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'⛔ 有项没过'))