# -*- coding: utf-8 -*- # v10:嵌入式底盖 —— 墙向下延长 3.6 包住底盖(墙底 −13.6),角柱仍止于 −10 当限位;其余同 v9 # v9:格栅 槽宽 6 / 竖筋 2.0 / 横筋 2.0 中心距 12(最长槽段 10),其余同 v7 # v7:无滤网版 —— 删西端滤网导轨,两端墙竖槽格栅,外形 x −18.4~115.9 # v6:两端墙都用竖槽格栅(西端 Ø60 圆孔取消),其余同 v5 # v5 外观:顶面 R1.5 圆角、圆头泄压槽、东风口竖槽格栅、Type-C 凹刻标识(其余同 v4) # v4:去北墙两个窗口;南墙 Type-C 开口 = 母座口内侧轮廓 8.34×2.60 R0.7 # 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 math 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_v10.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(底盖顶面限位) PLATE_T = 3.6 # 底盖厚 H_WALL = H + PLATE_T # v10:墙向下包住底盖 ⇒ 墙底 −13.6 SLOT_W, SLOT_L, SLOT_N, SLOT_SPAN = 1.5, 60.32, 11, 106.0 # C:圆头槽,长 60.32 使面积仍 = 90 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_V10' 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_WALL) 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_WALL, True) check(u'墙', [X0, Y0, -H_WALL, X1, Y1, 0.0]) def arc_dir(qc, q1, qm): u"""草图坐标里:从 q1 出发、逆时针先经过弧中点 qm ⇒ 1,否则 −1(半圆也适用)""" return 1 if (q1[0] - qc[0]) * (qm[1] - qc[1]) - (q1[1] - qc[1]) * (qm[0] - qc[0]) > 0 else -1 def stadium_front(xc, yc, w, L): u"""前视基准面(草图坐标 = 模型 x, y)画长沿 y 的圆头槽""" r, a = w / 2.0, L / 2.0 - w / 2.0 sm.CreateLine(M(xc + r), M(yc - a), 0.0, M(xc + r), M(yc + a), 0.0) sm.CreateLine(M(xc - r), M(yc + a), 0.0, M(xc - r), M(yc - a), 0.0) for cy, sg in ((yc + a, 1), (yc - a, -1)): qc, q1, q2, qm = (xc, cy), (xc + r, cy), (xc - r, cy), (xc, cy + sg * r) sm.CreateArc(M(qc[0]), M(qc[1]), 0.0, M(q1[0]), M(q1[1]), 0.0, M(q2[0]), M(q2[1]), 0.0, arc_dir(qc, q1, qm)) # ── [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 stadium_front(xc, SLOT_YC, SLOT_W, SLOT_L) # C:圆头槽,开孔面积与原 1.5×60 相同 sm.AddToDB = False sm.InsertSketch(True) boss(T, True) check(u'顶板', [X0, Y0, -H_WALL, 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 - SLOT_W)) < 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) # ── [3c] A:顶面外轮廓倒 R1.5(此时外轮廓 = 4 直边 + 4 段 R3 弧,导轨未加)────────── TOP_R = 1.5 P(u'\n[3c] 顶面外轮廓倒 R%.1f' % TOP_R) _bx = bbox() _sel = [] def _onb(p): return (min(abs(p[0] - _bx[0]), abs(p[0] - _bx[3])) < 0.01 or min(abs(p[1] - _bx[1]), abs(p[1] - _bx[4])) < 0.01) 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[2]) > 0.01 or abs(_p1[2]) > 0.01: continue if _onb(_p0) and _onb(_p1): _sel.append(_E) P(u' 顶面外轮廓边 %d 条(期望 8 = 4 直边 + 4 段 R3 弧)' % len(_sel)) if len(_sel) != 8: P(u'⛔ 外轮廓边数不对,停手'); sys.exit(8) part.ClearSelection2(True) for _i, _E in enumerate(_sel): _E.Select4(_i > 0, EMPTY) P(u' 选中 %d 条' % part.SelectionManager.GetSelectedObjectCount2(-1)) try: _f = fm.FeatureFillet3(195, M(TOP_R), 0, 0, 0, 0, 0, None, None, None, None, None, None, None) except Exception as _ex: _f = None P(u' 异常 %s' % str(_ex)[:80]) _h = hist() P(u' FeatureFillet3 → %r;直方图 %s(R1.5 圆柱面应 4 = 4 条直边)' % (prop(_f, 'Name') if _f else _f, _h)) if _h.get(TOP_R, 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))) # ── B:两端墙竖槽格栅 + 3 条横撑(v8:解决「长细筋打不出来」)───────────────────── GR_R, GR_W, GR_RIB = 32.0, 6.0, 2.0 # v9:槽宽 6、竖筋 2.0(用户选 ③) XB_W, XB_SEG = 2.0, 10.0 # 横筋宽 2.0,最长槽段 10 ⇒ 横筋中心距 12 XB_P = XB_SEG + XB_W XB_Z = tuple(k * XB_P for k in range(-int(GR_R // XB_P) - 1, int(GR_R // XB_P) + 2)) def grille_slots(): p, out = GR_W + GR_RIB, [] n = int((GR_R - GR_W / 2) // p) for k in range(-n, n + 1): y = k * p e = abs(y) + GR_W / 2 if e >= GR_R: continue L_ = 2 * math.sqrt(GR_R * GR_R - e * e) if L_ <= GR_W + 1: continue out.append((FAN_CY + y, L_)) return out def slot_segments(L_): u"""一条槽被横筋切成的段:[(z0, z1, round0, round1)],z 相对风扇中心;round = 该端是真正的槽头(半圆)。 v9 通用规则:横筋可以切到槽头附近;短到放不下「端头 + 0.8 直边」的碎段做成实心(不画)。""" r = GR_W / 2 h = L_ / 2 cuts = sorted((c - XB_W / 2, c + XB_W / 2) for c in XB_Z if c + XB_W / 2 > -h and c - XB_W / 2 < h) ivs, z = [], -h for lo, hi in cuts: ivs.append((z, lo)) z = hi ivs.append((z, h)) segs = [] for z0, z1 in ivs: r0, r1 = abs(z0 + h) < 1e-9, abs(z1 - h) < 1e-9 need = (r if r0 else 0.0) + (r if r1 else 0.0) + 0.8 if z1 - z0 < need: continue segs.append((z0, z1, r0, r1)) return segs, 0 def seg_area(z0, z1, r0, r1): r = GR_W / 2 a_ = (z1 - z0) - (r if r0 else 0) - (r if r1 else 0) return GR_W * a_ + (math.pi * r * r / 2) * (int(r0) + int(r1)) GRS = grille_slots() A_SLOTS = 0.0 N_FLAT = 0 LONGEST = 0.0 N_ROUND = 0 N_SEG = 0 for _ys, _L in GRS: _segs, _ = slot_segments(_L) A_SLOTS += sum(seg_area(*sg) for sg in _segs) N_FLAT += sum(int(not sg[2]) + int(not sg[3]) for sg in _segs) N_ROUND += sum(int(sg[2]) + int(sg[3]) for sg in _segs) N_SEG += len(_segs) LONGEST = max([LONGEST] + [sg[1] - sg[0] for sg in _segs]) A_HOLES = 4 * math.pi * R_FAN ** 2 P(u' 格栅:%d 条竖槽(宽 %.1f 竖筋 %.1f 外包 R%.0f)+ 横筋 %.1f(中心距 %.0f),共 %d 段;开孔 %.1f mm²(原 Ø60 的 %.1f%%),最长槽段 %.1f;半圆端 %d、平端 %d' % (len(GRS), GR_W, GR_RIB, GR_R, XB_W, XB_P, N_SEG, A_SLOTS, A_SLOTS / (math.pi * 900) * 100, LONGEST, N_ROUND, N_FLAT)) def grille_cut(tag, xw, sg): xi = xw - sg * 2.4 # 墙内表面 def face_area(): return sum(a_ for ax_, sgn, fb, a_, _ in planes() if ax_ == 0 and sgn == sg and abs(fb[0] - xw) < 0.02 and abs(fb[3] - xw) < 0.02) def flat_ends(): lo, hi = min(xw, xi), max(xw, xi) return sum(1 for ax_, sgn, fb, a_, _ in planes() if ax_ == 2 and abs(fb[0] - lo) < 0.05 and abs(fb[3] - hi) < 0.05 and abs((fb[4] - fb[1]) - GR_W) < 0.05) caps0 = sum(1 for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0) flat0 = flat_ends() a_before = face_area() nbefore = len(faces()) X = open_face_sketch((0, sg, xw), tag) sm.AddToDB = True for cy, cz, r in END_CIRCLES[1:]: q = to_sketch(X, (xw, cy, L(cz))) sm.CreateCircleByRadius(q[0], q[1], 0.0, M(r)) zc = L(FAN_CZ) r = GR_W / 2 def S(y, z): return to_sketch(X, (xw, y, zc + z)) def line(y0, z0, y1, z1): q0, q1 = S(y0, z0), S(y1, z1) sm.CreateLine(q0[0], q0[1], 0.0, q1[0], q1[1], 0.0) for ys, L_ in GRS: segs, _ = slot_segments(L_) for z0, z1, r0, r1 in segs: b0 = z0 + (r if r0 else 0) # 直边起止 b1 = z1 - (r if r1 else 0) line(ys + r, b0, ys + r, b1) line(ys - r, b1, ys - r, b0) for zz, rounded, s_ in ((b1, r1, 1), (b0, r0, -1)): if rounded: qc, q1, q2, qm = S(ys, zz), S(ys + r, zz), S(ys - r, zz), S(ys, zz + s_ * r) sm.CreateArc(qc[0], qc[1], 0.0, q1[0], q1[1], 0.0, q2[0], q2[1], 0.0, arc_dir(qc, q1, qm)) else: line(ys + r, zz, ys - r, zz) sm.AddToDB = False sm.InsertSketch(True) c = do_cut(T + 0.2) d_area = a_before - face_area() ok_area = abs(d_area - (A_SLOTS + A_HOLES)) < 0.5 caps = sum(1 for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0) - caps0 flats = flat_ends() - flat0 fan_ok = all(any(abs(rr - R_FAN) < 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] - xw), abs(fb[3] - xw)) < 0.05 for rr, ax_, fb in cyls()) for cy, cz, _ in END_CIRCLES[1:]) P(u' %s 切除 → %-12r 面数 %d→%d' % (tag, prop(c, 'Name') if c else c, nbefore, len(faces()))) P(u' 外表面面积减少 %.2f(理论 槽段 %.2f + 螺丝孔 %.2f = %.2f)%s' % (d_area, A_SLOTS, A_HOLES, A_SLOTS + A_HOLES, u'✅' if ok_area else u'⛔')) P(u' 槽头半圆柱 +%d(期望 %d)%s;横筋平端面 +%d(期望 %d)%s;风扇孔在位 %s' % (caps, N_ROUND, u'✅' if caps == N_ROUND else u'⛔', flats, N_FLAT, u'✅' if flats == N_FLAT else u'⛔', u'✅' if fan_ok else u'⛔')) if not (ok_area and caps == N_ROUND and flats == N_FLAT and fan_ok): P(u' ⛔⛔ %s 格栅不对,停手、不存盘' % tag); sys.exit(5) grille_cut(u'西端墙', X0, -1) grille_cut(u'东端墙', X1, +1) # ── [5] 南墙 Type-C:完全等于母座口内侧轮廓(用户拍板 2026-09-13)────────────── # 母座 USB-C_SMD-TYPE-C-31-M-12 实测(STEP,本地→装配 x=12.5−lx / z=−lz): # 内侧平直边 x ±4.17、z 0.35~2.95(本地)⇒ 8.34 × 2.60;四角圆柱 R0.70,圆心 (±3.47, 1.05/2.25) # ⇒ 装配口中心 J1 (12.50, −1.65)、J23 (84.00, −1.65);口面 y=0.20,墙外表面 y=−4.055(缩进 4.26) # 北墙原有 J33 / 风扇出线两个窗口:用户要求去掉,不再切。 USB_W, USB_H, USB_R = 8.34, 2.60, 0.70 USB_CX = (12.50, 84.00) USB_CZ = -1.65 P(u'\n[5] 南墙 Type-C ×2:%.2f × %.2f、R%.2f,中心 z=%.2f(= 母座口内侧轮廓)' % (USB_W, USB_H, USB_R, USB_CZ)) def usb_shapes(cx): hw, hh, r, cz = USB_W / 2, USB_H / 2, USB_R, L(USB_CZ) lines = [((cx + hw - r, cz + hh), (cx - hw + r, cz + hh)), ((cx - hw, cz + hh - r), (cx - hw, cz - hh + r)), ((cx - hw + r, cz - hh), (cx + hw - r, cz - hh)), ((cx + hw, cz - hh + r), (cx + hw, cz + hh - r))] arcs = [((cx + hw - r, cz + hh - r), (cx + hw, cz + hh - r), (cx + hw - r, cz + hh)), ((cx - hw + r, cz + hh - r), (cx - hw + r, cz + hh), (cx - hw, cz + hh - r)), ((cx - hw + r, cz - hh + r), (cx - hw, cz - hh + r), (cx - hw + r, cz - hh)), ((cx + hw - r, cz - hh + r), (cx + hw - r, cz - hh), (cx + hw, cz - hh + r))] return lines, arcs def usb_verify(): hw, hh, r, cz = USB_W / 2, USB_H / 2, USB_R, L(USB_CZ) cs = cyls() ps = planes() for cx in USB_CX: for ccx, ccz in ((cx + hw - r, cz + hh - r), (cx - hw + r, cz + hh - r), (cx - hw + r, cz - hh + r), (cx + hw - r, cz - hh + r)): if not any(abs(rr - r) < 0.01 and ax_ == 1 and fb[1] >= Y0 - 0.05 and fb[4] <= IY0 + 0.05 and (abs(fb[0] - (ccx - r)) < 0.05 or abs(fb[3] - (ccx + r)) < 0.05) and (abs(fb[2] - (ccz - r)) < 0.05 or abs(fb[5] - (ccz + r)) < 0.05) for rr, ax_, fb in cs): return False for xe in (cx - hw, cx + hw): if not any(ax_ == 0 and abs(fb[0] - xe) < 0.05 and abs(fb[3] - xe) < 0.05 and abs(fb[2] - (cz - hh + r)) < 0.05 and abs(fb[5] - (cz + hh - r)) < 0.05 for ax_, sg, fb, a_, _ in ps): return False for ze in (cz - hh, cz + hh): if not any(ax_ == 2 and abs(fb[2] - ze) < 0.05 and abs(fb[5] - ze) < 0.05 and abs(fb[0] - (cx - hw + r)) < 0.05 and abs(fb[3] - (cx + hw - r)) < 0.05 for ax_, sg, fb, a_, _ in ps): return False return True nbefore = len(faces()) X = open_face_sketch((1, -1, Y0), u'南墙') sm.AddToDB = True nseg = 0 for cx in USB_CX: lines, arcs = usb_shapes(cx) for p0, p1 in lines: q0, q1 = to_sketch(X, (p0[0], Y0, p0[1])), to_sketch(X, (p1[0], Y0, p1[1])) nseg += bool(sm.CreateLine(q0[0], q0[1], 0.0, q1[0], q1[1], 0.0)) for c, p1, p2 in arcs: qc = to_sketch(X, (c[0], Y0, c[1])) q1 = to_sketch(X, (p1[0], Y0, p1[1])) q2 = to_sketch(X, (p2[0], Y0, p2[1])) cross = (q1[0] - qc[0]) * (q2[1] - qc[1]) - (q1[1] - qc[1]) * (q2[0] - qc[0]) dirn = 1 if cross > 0 else -1 # 草图坐标系里取 90° 短弧:叉积正 = 逆时针 nseg += bool(sm.CreateArc(qc[0], qc[1], 0.0, q1[0], q1[1], 0.0, q2[0], q2[1], 0.0, dirn)) sm.AddToDB = False P(u' 草图段 %d(期望 16 = 2 × (4 直线 + 4 圆弧))' % nseg) sm.InsertSketch(True) if nseg != 16: P(u' ⛔ 草图段数不对,停手'); sys.exit(5) c = do_cut(T + 0.2) ok = usb_verify() P(u' 南墙 切除 → %-12r 面数 %d→%d 母座口轮廓 %s' % (prop(c, 'Name') if c else c, nbefore, len(faces()), u'✅ 4 圆角 + 4 直边 ×2 全在位' if ok else u'⛔')) if not ok: P(u' ⛔⛔ 开口没落在母座口轮廓上 —— 停手、不存盘'); sys.exit(5) # ── [5b] F:Type-C 右侧 9 mm 凹刻 0.6 —— J1「PWR 20V」闪电、J23「USB」箭头 + 圆点 ───── SYM_DEPTH, SYM_DX = 0.6, 9.0 BOLT = [(0.8, 3.5), (-1.8, -0.3), (-0.1, -0.3), (-0.8, -3.5), (1.8, 0.5), (0.1, 0.5)] ARROW = [(0.0, 3.5), (2.2, 0.9), (0.7, 0.9), (0.7, -1.2), (-0.7, -1.2), (-0.7, 0.9), (-2.2, 0.9)] DOT_C, DOT_R = (0.0, -2.8), 0.8 def shoelace(pts): return abs(sum(pts[i][0] * pts[(i + 1) % len(pts)][1] - pts[(i + 1) % len(pts)][0] * pts[i][1] for i in range(len(pts)))) / 2.0 A_SYM = shoelace(BOLT) + shoelace(ARROW) + math.pi * DOT_R ** 2 P(u'\n[5b] Type-C 标识凹刻 %.1f:闪电 %.2f + 箭头 %.2f + 圆点 %.2f = %.2f mm²' % (SYM_DEPTH, shoelace(BOLT), shoelace(ARROW), math.pi * DOT_R ** 2, A_SYM)) nbefore = len(faces()) X = open_face_sketch((1, -1, Y0), u'南墙标识') sm.AddToDB = True for (cx, poly) in ((USB_CX[0] + SYM_DX, BOLT), (USB_CX[1] + SYM_DX, ARROW)): qs = [to_sketch(X, (cx + du, Y0, L(USB_CZ + dv))) for du, dv in poly] for i in range(len(qs)): a_, b_ = qs[i], qs[(i + 1) % len(qs)] sm.CreateLine(a_[0], a_[1], 0.0, b_[0], b_[1], 0.0) qd = to_sketch(X, (USB_CX[1] + SYM_DX + DOT_C[0], Y0, L(USB_CZ + DOT_C[1]))) sm.CreateCircleByRadius(qd[0], qd[1], 0.0, M(DOT_R)) sm.AddToDB = False sm.InsertSketch(True) if pick(last_sketch(), 'SKETCH') != 1: P(u' ⛔ 标识草图没选中'); sys.exit(5) c = fm.FeatureCut4(True, False, False, 0, 0, M(SYM_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) floor = sum(a_ for ax_, sg, fb, a_, _ in planes() if ax_ == 1 and sg == -1 and abs(fb[1] - (Y0 + SYM_DEPTH)) < 0.02 and abs(fb[4] - (Y0 + SYM_DEPTH)) < 0.02) ok = abs(floor - A_SYM) < 0.05 P(u' 标识 切除 → %-12r 面数 %d→%d 凹槽底面积 %.3f(理论 %.3f)%s' % (prop(c, 'Name') if c else c, nbefore, len(faces()), floor, A_SYM, u'✅' if ok else u'⛔')) if not ok: P(u' ⛔⛔ 标识没刻对,停手、不存盘'); sys.exit(5) # ── 验收 ────────────────────────────────────────────────────────────────── P(u'\n[7] 验收') check(u'最终', [X0, Y0, -H_WALL, X1, Y1, 0.0]) h = hist() WANT_H = {0.7: 8, 0.75: 2 * SLOT_N, 0.8: 1, 1.5: 4, 2.1: 4, 2.6: 8, 3.0: 4, 30.0: 0} WANT_H[round(GR_W / 2, 2)] = WANT_H.get(round(GR_W / 2, 2), 0) + 2 * N_ROUND P(u' 圆柱面直方图 %s(期望 %s)' % (h, dict(sorted(WANT_H.items())))) if any(h.get(k, 0) != v for k, v in WANT_H.items()): 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))