# -*- coding: utf-8 -*- # v3 短机版:风扇 6010(孔距 50.2、Ø5.2 过孔)+ 分风腔 10;内腔 x −16~113.5、外 −18.4~115.9、导轨唇边 −24.8 u"""**v2 = v1 + 西端插拔式滤网导轨 + 建模内倒角**(2026-09-13)。 导轨让包围盒西界变成唇边 x −43.8,add_fillets 的「包围盒角」判据会漏掉西侧两角 ⇒ 圆角在加导轨之前做。 (以下为 v1 说明)**一体顶盖** TopShell_v1(2026-09-13 用户改方案:顶盖一体打印 + 底盖一整块 + 螺丝固定)。 = 顶板 + 南/北侧墙 + 西/东端墙 + 4 根内角立柱,底部敞开,从上往下扣在底盖上。 外形与上一版六件式完全一致(175.0 × 110.8,顶外表面 z=74.4、墙底 z=−10), 内腔也一致(x −36~134 / y −1.655~104.345 / 顶内表面 z=72)⇒ 15 mm 分风腔、0.66 顶隙原样保留。 零件本地坐标:x、y = 装配坐标;**本地 z = 装配 z − 74.4**(顶外表面在本地 z=0,全部往 −z 拉)。 装配时纯平移 dz = +74.4,不旋转。 建模顺序是刻意安排的(只用本机验证过的「基准面草图 + 拉伸,孔画进同一张草图」): [1] 4 根角柱 10×10 + 柱内 Ø4.2 孔,前视面草图,向 −z 拉 84.4(孔此时是通的) [2] 四面墙:外框 + 内框的环形轮廓,向 −z 拉 84.4 [3] 顶板 + 11 条泄压缝,向 −z 拉 2.4 —— **顺带把柱孔上端封死**, 柱孔变成从底面进、深 82 的盲孔(热熔螺母压在底口 6 mm 内即可),顶面看不到孔 [4] 端墙外表面:Ø60 风口 + 4 × Ø3.4 风扇孔(面上草图切) [5] 南墙外表面:2 个 13×9 Type-C 窗口 [6] 北墙外表面:J33 窗口 + 风扇出线窗口 面上草图的 uv 映射按候选逐个试,每刀之后回读「目标孔/窗口是否出现在设计位置」; 候选都经过检查:错误映射只会把轮廓画到墙外(切不到料、返回 None)。 若出现「切出了东西但不在目标位置」立即停手、不存盘。 """ 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 DST = u'D:\\DCDC\\新建文件夹\\5blades\\TopShell_v3.SLDPRT' EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None) ZOFF = 74.4 # 本地 z = 装配 z − ZOFF X0, X1, Y0, Y1 = -18.4, 115.9, -4.055, 106.745 # 外形 IX0, IX1, IY0, IY1 = -16.0, 113.5, -1.655, 104.345 # 内腔 T = 2.4 H = 84.4 # 顶外 74.4 → 墙底 −10 SLOT_W, SLOT_L, SLOT_N, SLOT_SPAN = 1.5, 60.0, 11, 106.0 SLOT_XC, SLOT_YC = (X0 + X1) / 2.0, 51.345 POST = 10.0 POST_RECTS = [(IX0, IX0 + POST, IY0, IY0 + POST), (IX0, IX0 + POST, IY1 - POST, IY1), (IX1 - POST, IX1, IY0, IY0 + POST), (IX1 - POST, IX1, IY1 - POST, IY1)] POST_HOLES = [((a + b) / 2.0, (c + d) / 2.0) for a, b, c, d in POST_RECTS] R_INS = 2.1 FAN_CY, FAN_CZ = 51.345, 37.85 R_VENT, R_FAN = 30.0, 2.6 # 风扇孔 Ø5.2:机箱自攻螺丝要穿墙拧进风扇 Ø4.4 孔 END_CIRCLES = [(FAN_CY, FAN_CZ, R_VENT)] + [(FAN_CY + dy, FAN_CZ + dz, R_FAN) for dy in (-25.1, 25.1) for dz in (-25.1, 25.1)] SOUTH_WINDOWS = [(6.0, 19.0, -5.7, 3.3), (77.5, 90.5, -5.7, 3.3)] # x0,x1,z0,z1(装配) NORTH_WINDOWS = [(72.14, 87.85, -3.0, 12.0), (0.53, 19.03, -3.0, 15.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 def L(z): return z - ZOFF app = wc.GetActiveObject('SldWorks.Application') for d in list(app.GetDocuments or []): try: t = prop(d, 'GetTitle') or '' if 'TOPSHELL_V3' in t.upper(): app.CloseDoc(t) except Exception: pass tmpl = app.GetUserPreferenceStringValue(8) app.NewDocument(tmpl, 0, 0.0, 0.0) part = app.ActiveDoc P(u'新建零件 %s' % prop(part, 'GetTitle')) 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(name, typ): part.ClearSelection2(True) part.Extension.SelectByID2(name, typ, 0, 0, 0, False, 0, EMPTY, 0) return part.SelectionManager.GetSelectedObjectCount2(-1) def bodies(): return part.GetBodies2(0, True) or [] def faces(): b = bodies() return prop(D(b[0]), 'GetFaces') if b else [] def bbox(): b = bodies() if not b: return None xs = [[round(v * 1000, 3) for v in prop(D(x), 'GetBodyBox')] for x in b] return [min(v[0] for v in xs), min(v[1] for v in xs), min(v[2] for v in xs), max(v[3] for v in xs), max(v[4] for v in xs), max(v[5] for v in xs)] def cyls(): out = [] for f in faces(): fc = D(f) s = prop(fc, 'GetSurface') if prop(s, 'IsCylinder'): cp = list(prop(s, 'CylinderParams')) fb = [v * 1000 for v in prop(fc, 'GetBox')] ax = max(range(3), key=lambda i: abs(cp[3 + i])) out.append((round(cp[6] * 1000, 2), ax, fb)) return out def hist(): h = {} for r, _, _ in cyls(): h[r] = h.get(r, 0) + 1 return dict(sorted(h.items())) def planes(): u"""[(朝外法线轴, 符号, box)] —— 朝外 = PlaneParams × (sense ? −1 : +1)(NOTES §36)""" out = [] for f in faces(): fc = D(f) s = prop(fc, 'GetSurface') if not prop(s, 'IsPlane'): continue n = list(prop(s, 'PlaneParams'))[:3] if prop(fc, 'FaceInSurfaceSense'): n = [-v for v in n] ax = max(range(3), key=lambda i: abs(n[i])) out.append((ax, 1 if n[ax] > 0 else -1, [v * 1000 for v in prop(fc, 'GetBox')], prop(fc, 'GetArea') * 1e6, fc)) return out def boss(depth, rev): if pick(last_sketch(), 'SKETCH') != 1: 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, True, True, True, 0, 0.0, False) def check(tag, want, tol=0.02): got = bbox() ok = got is not None and all(abs(got[i] - want[i]) <= tol for i in range(6)) P(u' %s box %s %s' % (tag, got, u'✅' if ok else u'⛔ 期望 %s' % want)) if not ok: sys.exit(2) # ── [1] 角柱 + 柱孔 ─────────────────────────────────────────────────────── P(u'\n[1] 4 根角柱 %.0f×%.0f + Ø%.1f 孔,向 −z 拉 %.1f' % (POST, POST, 2 * R_INS, H)) pick(u'前视基准面', 'PLANE') sm.InsertSketch(True) sm.AddToDB = True for a, b, c, d in POST_RECTS: sm.CreateCornerRectangle(M(a), M(c), 0.0, M(b), M(d), 0.0) for hx, hy in POST_HOLES: sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_INS)) sm.AddToDB = False sm.InsertSketch(True) boss(H, True) check(u'角柱', [IX0, IY0, -H, IX1, IY1, 0.0]) # ── [2] 四面墙(环形轮廓)──────────────────────────────────────────────── P(u'\n[2] 四面墙 环形轮廓,向 −z 拉 %.1f' % H) pick(u'前视基准面', 'PLANE') sm.InsertSketch(True) sm.AddToDB = True sm.CreateCornerRectangle(M(X0), M(Y0), 0.0, M(X1), M(Y1), 0.0) sm.CreateCornerRectangle(M(IX0), M(IY0), 0.0, M(IX1), M(IY1), 0.0) sm.AddToDB = False sm.InsertSketch(True) boss(H, True) check(u'墙', [X0, Y0, -H, X1, Y1, 0.0]) # ── [3] 顶板 + 泄压缝(封住柱孔上端)────────────────────────────────────── P(u'\n[3] 顶板 + %d 条泄压缝,向 −z 拉 %.1f' % (SLOT_N, T)) step = SLOT_SPAN / (SLOT_N - 1) pick(u'前视基准面', 'PLANE') sm.InsertSketch(True) sm.AddToDB = True sm.CreateCornerRectangle(M(X0), M(Y0), 0.0, M(X1), M(Y1), 0.0) for i in range(SLOT_N): xc = SLOT_XC - SLOT_SPAN / 2.0 + i * step sm.CreateCornerRectangle(M(xc - SLOT_W / 2), M(SLOT_YC - SLOT_L / 2), 0.0, M(xc + SLOT_W / 2), M(SLOT_YC + SLOT_L / 2), 0.0) sm.AddToDB = False sm.InsertSketch(True) boss(T, True) check(u'顶板', [X0, Y0, -H, X1, Y1, 0.0]) ph = [c for c in cyls() if abs(c[0] - R_INS) < 0.01 and c[1] == 2] P(u' 柱孔 R%.1f 沿 z:%d 个(期望 4)' % (R_INS, len(ph))) for r, ax, fb in ph: P(u' 中心 (%.2f, %.2f) z %.2f~%.2f(期望 %.1f~%.1f,上端被顶板封住)' % ((fb[0] + fb[3]) / 2, (fb[1] + fb[4]) / 2, fb[2], fb[5], -H, -T)) if len(ph) != 4 or any(abs(fb[5] - (-T)) > 0.05 or abs(fb[2] + H) > 0.05 for _, _, fb in ph): P(u'⛔ 柱孔不对,停手'); sys.exit(3) slot_faces = sum(1 for ax, sg, fb, a, _ in planes() if ax == 0 and abs(fb[2] + T) < 0.05 and abs(fb[5]) < 0.05 and abs(fb[4] - fb[1] - SLOT_L) < 0.05) P(u' 泄压缝侧面 %d 个(期望 %d = 每条 2)%s' % (slot_faces, 2 * SLOT_N, u'✅' if slot_faces == 2 * SLOT_N else u'⛔')) if slot_faces != 2 * SLOT_N: sys.exit(3) # ── [3b] 外轮廓 4 条竖边倒 R3(此时包围盒 = 盒体本身,导轨还没加)───────── P(u'\n[3b] 外轮廓竖边倒 R3') _bx = bbox() _cand = [] for _e in prop(D(bodies()[0]), 'GetEdges'): _E = D(_e) try: _p0 = [v * 1000 for v in prop(D(prop(_E, 'GetStartVertex')), 'GetPoint')] _p1 = [v * 1000 for v in prop(D(prop(_E, 'GetEndVertex')), 'GetPoint')] except Exception: continue if abs(_p0[0] - _p1[0]) > 1e-6 or abs(_p0[1] - _p1[1]) > 1e-6 or abs(_p0[2] - _p1[2]) < 1e-6: continue if all(min(abs(_p[i] - _bx[i]), abs(_p[i] - _bx[i + 3])) < 0.01 for _p in (_p0, _p1) for i in (0, 1)): _cand.append(_E) _done = 0 for _E in _cand: for _r in (3.0, 2.0, 1.5): part.ClearSelection2(True) _E.Select4(False, EMPTY) try: _f = fm.FeatureFillet3(195, M(_r), 0, 0, 0, 0, 0, None, None, None, None, None, None, None) except Exception: _f = None if _f: _done += 1 break P(u' 候选 %d 条,成功 %d 条;直方图 %s' % (len(_cand), _done, hist())) if _done != 4 or hist().get(3.0, 0) != 4: P(u'⛔ 圆角没做全,停手'); sys.exit(8) # ── 面上草图切除(带候选映射)──────────────────────────────────────────── def pick_face(ax, sign, coord): best, ba = None, -1.0 for a_, s_, fb, area, fc in planes(): if a_ != ax or s_ != sign: continue if abs(fb[ax] - coord) > 0.02 or abs(fb[ax + 3] - coord) > 0.02: continue if area > ba: best, ba = fc, area if best is None: return False part.ClearSelection2(True) best.Select4(False, EMPTY) return part.SelectionManager.GetSelectedObjectCount2(-1) == 1 def do_cut(depth): if pick(last_sketch(), 'SKETCH') != 1: return None return fm.FeatureCut4(True, False, False, 0, 0, M(depth), 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) def sketch_xform(): sk = prop(sm, 'ActiveSketch') if not sk: return None arr = list(prop(D(sk), 'ModelToSketchTransform').ArrayData) Mx = [[arr[3 * c + r] for c in range(3)] for r in range(3)] # 列主序 return Mx, arr[9:12] def to_sketch(X, p): u"""模型点 p(mm)→ 草图坐标(米)""" Mx, t = X q = [v / 1000.0 for v in p] return [sum(Mx[r][c] * q[c] for c in range(3)) + t[r] for r in range(3)] def open_face_sketch(face, tag): ax, sign, coord = face for attempt in range(1, 4): if not pick_face(*face): P(u' ⛔ %s 选不到面' % tag); sys.exit(4) sm.InsertSketch(True) X = sketch_xform() ok, nax, sz = False, -1, 99.0 if X: nax = max(range(3), key=lambda i: abs(X[0][2][i])) probe = [0.0, 0.0, 0.0] probe[ax] = coord sz = to_sketch(X, probe)[2] * 1000.0 ok = nax == ax and abs(sz) < 0.01 P(u' %s 进草图 第 %d 次:法线轴 %s(目标 %s)、墙面点草图 z = %.4f mm %s' % (tag, attempt, 'xyz'[nax] if nax >= 0 else '?', 'xyz'[ax], sz, u'✅' if ok else u'✗ 平面不对,退出重进')) if ok: return X sm.InsertSketch(True) P(u' ⛔ %s 三次都没进到目标面,停手' % tag); sys.exit(4) def cut_shapes(tag, face, circles, rects, verify): nbefore = len(faces()) X = open_face_sketch(face, tag) sm.AddToDB = True for p, r in circles: q = to_sketch(X, p) sm.CreateCircleByRadius(q[0], q[1], 0.0, M(r)) for p0, p1 in rects: q0, q1 = to_sketch(X, p0), to_sketch(X, p1) sm.CreateCornerRectangle(q0[0], q0[1], 0.0, q1[0], q1[1], 0.0) sm.AddToDB = False sm.InsertSketch(True) c = do_cut(T + 0.2) ok = verify() P(u' %s 切除 → %-12r 面数 %d→%d 目标 %s' % (tag, prop(c, 'Name') if c else c, nbefore, len(faces()), u'✅' if ok else u'⛔')) if not ok: P(u' ⛔⛔ 目标没出现在设计位置 —— 停手、不存盘'); sys.exit(5) def end_verify(xwall): def v(): cs = cyls() for cy, cz, r in END_CIRCLES: hit = any(abs(rr - r) < 0.01 and ax_ == 0 and abs((fb[1] + fb[4]) / 2 - cy) < 0.05 and abs((fb[2] + fb[5]) / 2 - L(cz)) < 0.05 and min(abs(fb[0] - xwall), abs(fb[3] - xwall)) < 0.05 for rr, ax_, fb in cs) if not hit: return False return True return v def rect_verify(wins, ywall0, ywall1): def v(): ps = planes() for x0, x1, z0, z1 in wins: for xe in (x0, x1): hit = any(ax_ == 0 and abs(fb[0] - xe) < 0.05 and abs(fb[3] - xe) < 0.05 and abs(fb[2] - L(z0)) < 0.05 and abs(fb[5] - L(z1)) < 0.05 and fb[1] >= ywall0 - 0.05 and fb[4] <= ywall1 + 0.05 for ax_, sg, fb, a_, _ in ps) if not hit: return False return True return v P(u'\n[4] 端墙:Ø60 风口 + 4 × Ø%.1f 风扇孔(孔距 %.1f)' % (2 * R_FAN, 2 * abs(END_CIRCLES[1][0] - FAN_CY))) cut_shapes(u'西端墙', (0, -1, X0), [((X0, cy, L(cz)), r) for cy, cz, r in END_CIRCLES], [], end_verify(X0)) cut_shapes(u'东端墙', (0, +1, X1), [((X1, cy, L(cz)), r) for cy, cz, r in END_CIRCLES], [], end_verify(X1)) P(u'\n[5] 南墙:2 个 13×9 Type-C 窗口') cut_shapes(u'南墙', (1, -1, Y0), [], [((x0, Y0, L(z0)), (x1, Y0, L(z1))) for x0, x1, z0, z1 in SOUTH_WINDOWS], rect_verify(SOUTH_WINDOWS, Y0, IY0)) P(u'\n[6] 北墙:J33 窗口 + 风扇出线窗口') cut_shapes(u'北墙', (1, +1, Y1), [], [((x0, Y1, L(z0)), (x1, Y1, L(z1))) for x0, x1, z0, z1 in NORTH_WINDOWS], rect_verify(NORTH_WINDOWS, IY1, Y1)) # ── [6b] 西端插拔式滤网导轨(前视面草图 L 形轮廓,向 −z 拉满高)─────────── FW, CLR, SLOT_T, LIP_T, SPACER, LIP = 70.0, 0.3, 4.4, 2.0, 4.0, 3.0 XO = X0 - SLOT_T - LIP_T # 唇边外表面 −43.8 YS0, YS1 = FAN_CY - FW / 2 - CLR, FAN_CY + FW / 2 + CLR # 滑槽 y 16.045 ~ 86.645 P(u'\n[6b] 滤网导轨:滑槽 x %.1f~%.1f(厚 %.1f)、y %.3f~%.3f;唇边 x %.1f~%.1f 压边 %.1f' % (X0 - SLOT_T, X0, SLOT_T, YS0, YS1, XO, X0 - SLOT_T, LIP)) rails = [ [(X0, YS0 - SPACER), (XO, YS0 - SPACER), (XO, YS0 + LIP), (X0 - SLOT_T, YS0 + LIP), (X0 - SLOT_T, YS0), (X0, YS0)], [(X0, YS1 + SPACER), (XO, YS1 + SPACER), (XO, YS1 - LIP), (X0 - SLOT_T, YS1 - LIP), (X0 - SLOT_T, YS1), (X0, YS1)], ] pick(u'前视基准面', 'PLANE') sm.InsertSketch(True) sm.AddToDB = True for poly in rails: for i in range(len(poly)): a, b = poly[i], poly[(i + 1) % len(poly)] sm.CreateLine(M(a[0]), M(a[1]), 0.0, M(b[0]), M(b[1]), 0.0) sm.AddToDB = False sm.InsertSketch(True) boss(H, True) lips = [(fb[1], fb[4]) for ax, sg, fb, a, _ in planes() if ax == 0 and sg == -1 and abs(fb[0] - XO) < 0.02 and abs(fb[3] - XO) < 0.02] P(u' 唇边外表面 %d 个(期望 2):%s' % (len(lips), [(round(a, 3), round(b, 3)) for a, b in sorted(lips)])) want_l = [(YS0 - SPACER, YS0 + LIP), (YS1 - LIP, YS1 + SPACER)] if len(lips) != 2 or any(abs(a - c) > 0.02 or abs(b - d) > 0.02 for (a, b), (c, d) in zip(sorted(lips), want_l)): P(u'⛔ 导轨不对,停手'); sys.exit(9) # ── 验收 ────────────────────────────────────────────────────────────────── P(u'\n[7] 验收') check(u'最终', [XO, Y0, -H, X1, Y1, 0.0]) h = hist() P(u' 圆柱面直方图 %s(期望 {2.1: 4, 2.6: 8, 3.0: 4, 30.0: 2})' % h) if h.get(2.6, 0) != 8 or h.get(2.1, 0) != 4 or h.get(30.0, 0) != 2: P(u'⛔ 孔数不对,不存盘'); sys.exit(7) 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'\n另存 err=%s 存在=%s %.0f KB' % (e1.value, os.path.exists(DST), os.path.getsize(DST) / 1024.0 if os.path.exists(DST) else 0))