722 行
31 KiB
Python
722 行
31 KiB
Python
# -*- 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))
|