文件

438 行
18 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-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_v2.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
ZOFF = 74.4 # 本地 z = 装配 z − ZOFF
X0, X1, Y0, Y1 = -38.4, 136.4, -4.055, 106.745 # 外形
IX0, IX1, IY0, IY1 = -36.0, 134.0, -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, 1.7
END_CIRCLES = [(FAN_CY, FAN_CZ, R_VENT)] + [(FAN_CY + dy, FAN_CZ + dz, R_FAN)
for dy in (-25.0, 25.0) for dz in (-25.0, 25.0)]
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_V2' 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 try_cut(tag, face, draw, verify, candidates):
u"""face=(ax,sign,coord);draw(mapping) 在已进入的草图里画;verify() → 目标是否全部出现"""
for cname, mp in candidates:
if not pick_face(*face):
P(u' ⛔ %s 选不到面' % tag); sys.exit(4)
nbefore = len(faces())
sm.InsertSketch(True)
sm.AddToDB = True
draw(mp)
sm.AddToDB = False
sm.InsertSketch(True)
c = do_cut(T + 0.2)
ok = verify()
P(u' %s 试 %-18s → %-12r 面数 %d→%d 目标 %s'
% (tag, cname, prop(c, 'Name') if c else c, nbefore, len(faces()), u'✅' if ok else u'✗'))
if ok:
return
if c and len(faces()) != nbefore:
P(u' ⛔⛔ 切出了东西但不在目标位置 —— 停手、不存盘'); sys.exit(5)
P(u' ⛔ %s 所有候选映射都失败' % tag); sys.exit(6)
# x 常量墙:面内模型坐标 (y, z本地)。8 种候选(交换 × 两个符号),目标都在 y>0、z<0,
# 错误映射一律落到墙外(已逐个核:Ø60 风口半径 30 也不会跨进墙里)。
X_CANDS = []
for sw in (False, True):
for su in (-1, 1):
for sv in (1, -1):
def _m(y, z, sw=sw, su=su, sv=sv):
a, b = (z, y) if not sw else (y, z)
return su * a, sv * b
X_CANDS.append((u'%s u%+d v%+d' % ('zy' if not sw else 'yz', su, sv), _m))
def end_draw_factory():
def draw(mp):
for cy, cz, r in END_CIRCLES:
u, v = mp(cy, L(cz))
sm.CreateCircleByRadius(M(u), M(v), 0.0, M(r))
return draw
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
P(u'\n[4] 端墙:Ø60 风口 + 4 × Ø3.4 风扇孔')
try_cut(u'西端墙', (0, -1, X0), end_draw_factory(), end_verify(X0), X_CANDS)
try_cut(u'东端墙', (0, +1, X1), end_draw_factory(), end_verify(X1), X_CANDS)
# y 常量墙:u → +x(NOTES §36 两个朝向都成立),v → ±z本地。
# u 不翻(翻了 x 会落进墙里、切错位置),v 翻只会落到墙外。
def rect_draw_factory(wins):
def draw(mp):
for x0, x1, z0, z1 in wins:
ua, va = mp(x0, L(z0))
ub, vb = mp(x1, L(z1))
sm.CreateCornerRectangle(M(ua), M(va), 0.0, M(ub), M(vb), 0.0)
return draw
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
def ymap(sv):
return lambda x, z: (x, sv * z)
P(u'\n[5] 南墙:2 个 13×9 Type-C 窗口')
try_cut(u'南墙', (1, -1, Y0), rect_draw_factory(SOUTH_WINDOWS), rect_verify(SOUTH_WINDOWS, Y0, IY0),
[(u'u+x v+z', ymap(1)), (u'u+x v-z', ymap(-1))])
P(u'\n[6] 北墙:J33 窗口 + 风扇出线窗口')
try_cut(u'北墙', (1, +1, Y1), rect_draw_factory(NORTH_WINDOWS), rect_verify(NORTH_WINDOWS, IY1, Y1),
[(u'u+x v-z', ymap(-1)), (u'u+x v+z', ymap(1))])
# ── [6b] 西端插拔式滤网导轨(前视面草图 L 形轮廓,向 −z 拉满高)───────────
FW, CLR, SLOT_T, LIP_T, SPACER, LIP = 70.0, 0.3, 3.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(期望 {1.7: 8, 2.1: 4, 3.0: 4, 30.0: 2})' % h)
if h.get(1.7, 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))