R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-10-04 19:24:45 +08:00
共同撰写人 Claude Opus 5.5
父节点 cd01640c40
当前提交 cf7cc9cf90
共修改 759 个文件,包含 76962 行新增和 5299 行删除
+108
查看文件
@@ -0,0 +1,108 @@
# R2 外壳建模脚本(SolidWorks 侧)
这些脚本通过 pywin32 挂**已经开着**的 SolidWorks 实例建模,
产物落在 `D:\DCDC\新建文件夹\5blades\`(不进 git,太大)。
> **动手前必读** `.claude/skills/solidworks-assembly/NOTES-probed.md` §33~§35 ——
> 本机的 API 口径与坑(基准面轴映射、`AddToDB`、`FeatureCut4` 不可靠、
> 判据要用圆柱面直方图而不是包围盒……)每一条都真实踩过。
## ✅✅ 现行外壳:`TopShell_v11` + `BottomPlate_v6`(2026-09-30,Type-C 外侧沉槽)
用户实测 v10 的两个 Type-C 口墙太厚、线插不到底。`make_v11.py` 从 v10 派生:贯穿孔仍 = 母座口 8.34×2.60 R0.7,
**外侧加 13.4×7.4 R3.2 沉槽、深 1.4,槽底留 1.0 薄壁**(用户:不要完全掏空);标识右移到口中心 +10.5。
运行:`make_v11.py` → `topshell_v11.py` → `place_v11.py` → `export_stl_v11.py`(底盖不变)。
⚠ `place_v11.py` 之前要先在 SolidWorks 里**打开总装和 TopShell_v11 / BottomPlate_v6 / OD6010_m0_frame 三个零件**,
否则 `AddComponent4` 返回 None(零件必须已在内存里)。参数在 `make_v11.py` 文件头 `PK_*`。
### 螺丝(2026-09-30,`screws_v1.py`)
用户:原配螺丝太短。建 3 种螺丝零件并装进总装(16 颗),长度只改脚本头 `SPECS` 的 L 后重跑:
| 位置 | 规格 | 数量 | 头下长度去向 |
|---|---|---|---|
| 底盖 → 顶盖角柱 | **M3×10 薄头内六角 DIN 7984**(头 Ø5.5×2.0,平顶) | 4 | 头埋进沉孔 Ø6.5 深 2、与底面齐平;1.6 板 + **8.4 进角柱**(孔深 82) |
| PCB → 底盖立柱 | **M3×8 同上** | 4 | 1.68 板(STEP 实测)+ **6.3 进立柱**(孔深 10,余 3.7) |
| 风扇 → 端墙 | **风扇自攻螺丝 5×12**(螺纹 Ø5,平头 Ø7×2) | 8 | 2.4 墙 + **9.6 进风扇**(风扇厚 10,螺尖不进分风腔) |
复验:包围盒 16/16 在位;干涉 368 = 基线 360 + 8 处「风扇螺丝 ↔ 风扇孔」(自攻咬合,预期),底盖 / PCB 螺丝 0 处。
⚠ 实物要核:热熔螺母长度(按 ≤ 6 选的长度)、风扇螺丝螺纹外径(墙孔 Ø5.2,外径 > 5.1 就穿不过墙)。
## ⛔ 已被 v11 取代:`TopShell_v10` + `BottomPlate_v6`(嵌入式底盖)
`make_v10.py`(从 `topshell_v9 / bottomplate_v5 / place_v9` 派生)→ `topshell_v10.py` → `bottomplate_v6.py`
→ `add_fillets.py BottomPlate_v6.SLDPRT 1.0 z` → `place_v10.py`。配合间隙参数 `CLR`(默认 0.3)在 `make_v10.py` 文件头。
## ⛔ 已被 v10 取代:`TopShell_v9` + `BottomPlate_v5`(格栅加横筋,可打印)
`make_v8.py`(v8:3 条 1.2 横撑,已被 v9 取代)→ `patch_make_v9.py` 产出 **`make_v9.py`** →
`topshell_v9.py` → `place_v9.py`(复验已改为**不忽略隐藏实体**)→ `render_v9.py`。
格栅参数在 `make_v9.py`:`GR_W/GR_RIB`(槽宽/竖筋)、`XB_W/XB_SEG`(横筋宽/最长槽段)。
## ⛔ 已被 v9 取代:`TopShell_v7` + `BottomPlate_v5`(美化 + 无滤网,整机 134.3)
派生链(每步都是纯文本补丁、锚点断言次数):
`make_v4.py` 产物 → **`make_v5.py`**(顶面圆角 / 圆头槽 / 东格栅 / 标识;新写 `bottomplate_v4.py`;`filterframe_v3.py`)
→ **`make_v6.py`**(两端都格栅)→ **`make_v7.py`**(删滤网导轨与滤网框,底盖西沿回 −17.9)。
运行:`topshell_v7.py` → `bottomplate_v5.py` → `add_fillets.py BottomPlate_v5.SLDPRT 2.5 z` → `place_v7.py` → `render_v7.py`(按视线方向截图)。
`filterframe_v*.py` 与 v5/v6 的顶盖、总装脚本只作派生源,不再运行。
## ⛔ 已被 v7 取代:`TopShell_v4`(开口精简)
在短机版流程第 2 步之后加:`make_v4.py`(从 `topshell_v3.py` / `place_v3.py` 派生)→ `topshell_v4.py` → `place_v4.py`。
北墙无开口;南墙 Type-C 开口 = 母座口内侧轮廓(`USB_W/USB_H/USB_R` = 8.34/2.60/0.70,母座实测见 `probe_usbc2.py`)。
## ✅✅ 现行方案:短机版(2026-09-13,整机 140.7)
风扇 60×60×10(孔距 50.2)+ 分风腔 10。**生成顺序**:
1. `make_v3.py` —— 从 `topshell_v2 / bottomplate_v2 / filterframe_v1 / place_filter` 文本派生出 v3 四个脚本(几何常量集中在文件头)
2. `patch_v3_cut.py` —— 把顶盖的面上切除换成「读 `ModelToSketchTransform` 先验平面、再按变换精确画」(**必须跑**,否则东墙那刀可能切到顶板上)
3. `topshell_v3.py`(圆角已内联)、`bottomplate_v3.py` → `add_fillets.py BottomPlate_v3.SLDPRT 3.0 z`、`filterframe_v2.py`
4. `import_fan6010.py`(Orion OD6010 STEP → SLDPRT,只查干涉用)
5. **`place_v3.py`** —— 换件 + 风扇 25→10 + 干涉 / 螺丝孔 / 风扇腰孔 / 滤网间隙 全套复验
改实物尺寸(螺丝外径、头高、分风腔)只动 `make_v3.py` 文件头常量后按上面顺序重跑。
## ⛔ 前一版(已被短机版取代):两件式(2026-09-13)
| 件 | 定稿文件 | 脚本 |
|---|---|---|
| 一体顶盖 | `TopShell_v2.SLDPRT` | `make_topshell_v2.py` 从 `topshell_v1.py` 派生出 `topshell_v2.py`(圆角已内联,**不要再跑 add_fillets**:导轨让包围盒判据漏掉西侧两角)|
| 底盖 | `BottomPlate_v2.SLDPRT` | `bottomplate_v2.py` → `add_fillets.py BottomPlate_v2.SLDPRT 3.0 z` |
| 滤网框 | `FilterFrame_v1.SLDPRT` | `filterframe_v1.py`(判据:贴墙面/外表面**面积**等于设计值 ⇒ 窗口与格孔都开出来了)|
| 入总装 + 复验 | — | **`place_filter.py`**(由 `make_place_filter.py` 从 `place_twopiece.py` 派生;含滤网滑槽间隙核对)|
`topshell_v1.py` / `bottomplate_v1.py` / `place_twopiece.py` 是无滤网版,留作派生源。
## ⛔ 六件式(已作废,仅作参考):定稿件 ↔ 脚本
| 件 | 定稿文件 | 脚本 |
|---|---|---|
| 托盘 | `Tray_v9.SLDPRT` | `tray_v2.py`(⚠ 文件里的 `DST` 已指向废版 v11,**跑之前把 DST 改回想要的版本号**)|
| 端板 ×2 | `EndPlate_v13.SLDPRT` | `endplate_v11.py`(几何 + C 组 + 北侧 A 组)→ `endplate_v12_south.py`(南侧 A 组)→ `add_fillets.py … 3.0 x` → `endplate_v13_foot.py`(L 脚锁孔)。`endplate_v10.py` 是无孔版 |
| 装配内同轴核对 | — | **`check_coaxial.py`**(螺母孔 ↔ 相邻板过孔、脚锁孔 ↔ 托盘,只读)|
| 南板 | `SouthPanel_v4.SLDPRT` | `southpanel_v4.py` |
| 北板 | `NorthPanel_v3.SLDPRT` | `northpanel_v3.py` |
| 顶盖 | `TopPanel_v4.SLDPRT` | `toppanel_v4.py` |
| 圆角(全部件)| — | `add_fillets.py <零件名> <半径> <轴>`(端板轴 x / 南北板 y / 顶盖 z / 托盘 z)|
| 入总装 + 复验 | — | **`place_v10.py`**(15 mm 分风腔版的整体摆位);只换端板用 `swap_endplate_v13.py` |
`add_panels.py` / `add_endplates.py` 是 152.8 短机版的旧入口,**已被 `place_v10.py` 取代**,留作参考。
## 分析 / 探测(只读,不改模型)
| 脚本 | 用途 |
|---|---|
| `probe_top.py` | 回读总装顶层件的包围盒 + 刀片通道间距,存 `_top.json` |
| `probe_plenum.py` | 查分风腔切片与南北条带里有哪些底板器件(挡风板能占哪儿) |
| `shroud_feas.py` | **纯本地算**(吃 `_top.json`):逐通道风口覆盖率、旁通面积、分风腔深度 ↔ 阻力比 |
| `contact_map.py` | 贴合面检查 —— **干涉为零 ≠ 装配成立**,托盘悬空那次就是这么查出来的 |
| `screw_plan.py` | 按实测贴合面算螺丝位置(⚠ 内含的 A/C 组坐标是**旧的 x −13.70/111.70**,已被角柱方案取代)|
## 三条固定纪律
1. **改几何就换版本号**,别覆盖正开着的文件 —— 同名 `SaveAs3` 会返回 err=1 且**不写盘**。
2. **不信返回值**:另存看 `os.path.exists` + 时间戳;打孔看**圆柱面半径直方图**(按孔径分桶);
开口看面数。包围盒对通孔/圆角**没有分辨力**。
3. **零件保持打开** —— `AddComponent4` 要求目标文档在打开状态。
清理文档只能**点名关**,绝不能 `CloseAllDocuments`(会清空 `%TEMP%\swx*\IC~~\`,废掉总装引用)。
+198
查看文件
@@ -0,0 +1,198 @@
# -*- coding: utf-8 -*-
u"""把已确认可用的端板 `EndPlate_v2` 插进总装(两块:西端 + 东端)。
端板现状(圆柱面直方图实测 `{1.7: 4, 30.0: 1}`,都是真的):
· 本体 x 0~2.4、y −4~102、z −10~72
· Ø60 风口中心 (y,z) = (51.345, 37.85)
· 4× Ø3.4 风扇螺孔(50×50)
· L 脚 沿 +x 伸到 x=30.4、占 z −10~−7.6
⚠ 脚锁孔 2× Ø3.4 **尚未成形**(`FeatureCut4` 在本机不可靠,见 NOTES),留待 GUI 补。
摆位:
西端板:本体外表面贴到 x=−27.4 ⇒ 整体平移 x −27.4,其余不动(单位阵旋转)。
东端板:绕 z 轴转 180°,使板面朝向相反、L 脚伸向 −x;
转 180° 后本地 (x,y) → (−x, −y),再平移到本体占 x 123~125.4、y 与西端一致。
列主序旋转 = [−1,0,0, 0,−1,0, 0,0,1];
平移由「目标 box 最小值 − 旋转后 box 最小值」现算(不写死,NOTES 的固定动作)。
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
# v5:y 对称版(−1.655~104.345,以风口中心 51.345 居中),
# 修掉东端板绕 z 翻转后偏 4.69、撞穿北板 616 mm³ 的问题。
PLATE = u'D:\\DCDC\\新建文件夹\\5blades\\EndPlate_v9.SLDPRT'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
ROT_W = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] # 西端:不转
# ⛔⛔ 绕 **y 轴** 180°([−1,0,0, 0,1,0, 0,0,−1])是错的:它**把 z 也翻了** ——
# L 脚本来在底部 z 0~2.4,翻完再按"z 最小端对齐 −10"平移 dz=+62,
# 脚就跑到 **z 59.6~62**,正好扎进风扇(z 6.37~69.33)的腰部 ⇒ 实测 483.9 mm³。
# (西端不撞纯粹因为它没旋转,脚老实待在风扇底面 6.37 以下。)
# ✅ 用**绕 z 轴 180°**:z 不翻、脚留在底部 z 0~2.4,从风扇正下方掠过;
# x 翻使脚朝 −x 伸向机内。附带好处:锁孔本地 y 21.345/81.345 翻转后互换成
# 81.345/21.345,**与托盘现有孔位完全重合,托盘不用改**。
ROT_E = [-1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0] # 东端:绕 z 180°
# 绕 z 翻转后 y 也镜像了:风口中心从 +FAN_CY 跑到 −FAN_CY,
# 要平移 2×FAN_CY 才能对回风扇中心(风扇/刀片堆叠中心 y=51.345)。
FAN_CY = 51.345
WEST_X, EAST_X = -27.4, 123.0 # 两块板本体外表面的 x(东端板本体占 123~125.4)
PLATE_T = 2.4
def prop(o, n):
v = getattr(o, n)
return v() if type(v).__name__ in ('method', 'function', 'builtin_function_or_method') else v
def P(*a):
print(*a)
def M(v):
return v / 1000.0
app = wc.GetActiveObject('SldWorks.Application')
if not os.path.exists(PLATE):
P(u'⛔ 端板文件不存在 %s' % PLATE); sys.exit(2)
doc = None
for d in app.GetDocuments:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
if doc is None:
P(u'⛔ 没找到总装'); sys.exit(3)
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
P(u'总装 %s 顶层件 %d' % (prop(doc, 'GetTitle'), len(doc.GetComponents(True))))
# 幂等:先删掉已有的端板
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if 'ENDPLATE' in nm.upper():
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')
def mk(a16):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, list(a16))
xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, v))
if max(abs(a - b) for a, b in zip(list(xf.ArrayData), a16)) > 1e-9:
raise RuntimeError(u'CreateTransform 回读不符')
return xf
def setx(c, rot9, t3):
xf = mk(list(rot9) + [t3[0] / 1000.0, t3[1] / 1000.0, t3[2] / 1000.0, 1.0, 0.0, 0.0, 0.0])
try:
c.SetTransformAndSolve2(xf)
except Exception:
c.Transform2 = xf
doc.EditRebuild3
return [round(v * 1000.0, 3) for v in c.GetBox(False, False)]
P(u'\n插入两块端板:')
for tag, rot, want_x0 in ((u'西端', ROT_W, WEST_X), (u'东端', ROT_E, EAST_X)):
c = doc.AddComponent4(PLATE, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 返回 None(零件没开着?)' % tag); sys.exit(4)
b0 = setx(c, rot, (0.0, 0.0, 0.0)) # 先只转不移,回读
P(u' %s 只旋转 box %s' % (tag, b0))
# ⛔ 不能一律拿 box 的**最小端**去对齐:西端板未旋转,本体(板厚那 2.4)在 box 最小端;
# 而东端板绕 y 转 180° 之后,**本体跑到了 box 的最大端**、L 脚朝最小端伸。
# 第一版统一用最小端对齐 ⇒ 东端板 box 落在 x 123.00~153.40,脚朝 +x 伸出机外。
# ✅ 按「本体外表面」这个语义对齐:西端板对齐 box 最小端,东端板对齐 box 最大端。
if tag == u'西端':
# 未旋转:本体在 box 最小端
dx = want_x0 - b0[0]
# ⛔ 原来写 `dy = −4.0 − b0[1]`(把 box 硬对到 y=−4):零件改成 y 对称之后,
# 这条旧逻辑把西端板拽偏了 2.345,与按风口对齐的东端板**不一致**
# (实测西 −4.00~102.00 / 东 −1.66~104.34),东端仍撞北板 589 mm³。
# ✅ 零件已以风口中心 51.345 对称 ⇒ **dy = 0** 时风口天然落位,两块板 y 完全一致。
dy = 0.0
dz = -10.0 - b0[2]
else:
# 绕 z 180°:本体跑到 box 最大端 ⇒ 按最大端对齐(123+2.4=125.4)
dx = (want_x0 + PLATE_T) - b0[3]
# ⛔ y 不能再按 box 最小端对齐:绕 z 翻转把风口中心镜像到了 −FAN_CY,
# 必须平移 2×FAN_CY 才能对回风扇/刀片堆叠中心(y=51.345)。
# 附带结果:锁孔 y 21.345/81.345 互换位置,与托盘孔位仍然重合。
dy = 2.0 * FAN_CY
# ⛔ z **不要**再补偿:绕 z 不翻 z,脚仍在底部 z 0~2.4(风扇底面 6.37 以下)。
dz = 0.0
t = (dx, dy, dz)
b = setx(c, rot, t)
P(u' %s 摆位后 box x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f'
% (tag, b[0], b[3], b[1], b[4], b[2], b[5]))
# 截图
for nm2, vid in ((u'full_iso', 7), (u'full_front', 1), (u'full_top', 5)):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
P(u'\n截图完成')
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'存盘 err=%s' % e3.value)
P(u'\n干涉(fast):')
try:
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = True
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
P(u' 干涉 %s 处(基线 360)' % n)
hits = []
if n:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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('ENDPLATE' in y.upper() for y in nms):
hits.append((o.Volume * 1e9, nms))
if hits:
P(u' ⚠ 端板相关 %d 处:' % len(hits))
for vv, nms in sorted(hits, reverse=True)[:6]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:24], nms[1][:24]))
else:
P(u' ✅ 端板与风扇/刀片/底板/托盘零干涉')
idm.Done()
except Exception as ex:
P(u' 干涉失败 %s' % str(ex)[:80])
try:
doc.InterferenceDetectionManager.Done()
except Exception:
pass
+177
查看文件
@@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
u"""给外壳件加**外轮廓圆角**(用户 2026-09-13 要求)。v2 重写。
⛔ v1 失败的真因(不是 API 坏了):
最小实验(纯长方体 12 条边)倒 4 条竖边 → 成功、直方图 `{3.0: 4}`。
但真实零件上:托盘 60 条边里选了 12 条、端板 34 条里选了 8 条,
`FeatureFillet3` 一律返回 **None**。
共同点是**选中的边里混进了做不出来的**:
· 托盘:USB-C 缺口的内角边 —— 缺口只有 4.2 深,R3 在窄槽内角会自相交;
· 端板:L 脚与板面的交线 —— 同理超出可行范围。
SolidWorks 对这种"整批里有一条做不出"的情况**整批放弃**,还不报错。
✅ v2 的三条对策:
1. **只倒外轮廓的角**:按实体包围盒筛,边的两个端点必须都落在包围盒的角上,
这样自动排除所有内部缺口/开口/台阶产生的边。
2. **逐条单独倒**:一条失败不拖垮其余,且能精确报出是哪条边做不出来。
3. **失败自动降半径**:R3 → R2 → R1.5 → R1 依次重试。
判据仍是**圆柱面半径直方图**(包围盒对圆角没有分辨力)。
用法:python add_fillets.py <零件文件名> [半径] [轴]
"""
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\\'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
fname = sys.argv[1] if len(sys.argv) > 1 else 'Tray_v7.SLDPRT'
RAD0 = float(sys.argv[2]) if len(sys.argv) > 2 else 3.0
AXIS = (sys.argv[3] if len(sys.argv) > 3 else 'z').lower()
AI = {'x': 0, 'y': 1, 'z': 2}[AXIS]
PATH = DIR + fname
RETRY = [RAD0, 2.0, 1.5, 1.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
app = wc.GetActiveObject('SldWorks.Application')
if not os.path.exists(PATH):
P(u'⛔ 文件不存在 %s' % PATH); sys.exit(2)
part = None
for d in app.GetDocuments or []:
try:
if (prop(d, 'GetPathName') or '').lower() == PATH.lower():
part = d
break
except Exception:
pass
if part is None:
err = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
warn = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
part = app.OpenDoc6(PATH, 1, 0, '', err, warn)
P(u'打开 %s err=%s' % (fname, err.value))
if part is None:
P(u'⛔ 打不开'); sys.exit(3)
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(part, 'GetTitle'), False, 0, e0)
part = app.ActiveDoc
fm = part.FeatureManager
P(u'零件 %s,目标 R%.1f,沿 %s 轴' % (prop(part, 'GetTitle'), RAD0, AXIS))
def body():
b = part.GetBodies2(0, True) or []
return D(b[0]) if b else None
def cyl_hist():
bd = body()
if bd is None:
return {}
rs = {}
for f in prop(bd, 'GetFaces'):
s = prop(D(f), 'GetSurface')
if prop(s, 'IsCylinder'):
r = round(list(prop(s, 'CylinderParams'))[6] * 1000, 2)
rs[r] = rs.get(r, 0) + 1
return dict(sorted(rs.items()))
before = cyl_hist()
bx = [round(v * 1000, 3) for v in prop(body(), 'GetBodyBox')]
P(u'包围盒 %s' % bx)
P(u'倒角前 %s' % before)
# 另外两根轴(外轮廓角要在这两根轴上都处于极值)
oth = [i for i in range(3) if i != AI]
def at_corner(p):
u"""该点在另外两根轴上是否都落在包围盒边界(即外轮廓的角)"""
for i in oth:
if abs(p[i] - bx[i]) > 0.01 and abs(p[i] - bx[i + 3]) > 0.01:
return False
return True
cand = []
for e in prop(body(), 'GetEdges'):
E = D(e)
try:
p0 = [round(v * 1000, 3) for v in prop(D(prop(E, 'GetStartVertex')), 'GetPoint')]
p1 = [round(v * 1000, 3) for v in prop(D(prop(E, 'GetEndVertex')), 'GetPoint')]
except Exception:
continue
same = [i for i in range(3) if abs(p0[i] - p1[i]) < 1e-6]
if len(same) != 2 or AI in same:
continue
if abs(p0[AI] - p1[AI]) < 1e-6:
continue
if not (at_corner(p0) and at_corner(p1)):
continue # ⇐ 关键:排除缺口/台阶产生的内部边
cand.append((E, p0))
P(u'外轮廓上沿 %s 的直边 = %d 条(v1 不加这一层筛选时是 12 / 8 条,含做不出的内角)'
% (AXIS, len(cand)))
for _, p0 in cand:
P(u' 角 (%.2f, %.2f)' % (p0[oth[0]], p0[oth[1]]))
if not cand:
P(u'⛔ 没有可倒的外轮廓边'); sys.exit(4)
done, failed = 0, []
for idx, (E, p0) in enumerate(cand):
ok = False
for r in RETRY:
part.ClearSelection2(True)
E.Select4(False, EMPTY)
if part.SelectionManager.GetSelectedObjectCount2(-1) != 1:
break
try:
feat = fm.FeatureFillet3(195, M(r), 0, 0, 0, 0, 0,
None, None, None, None, None, None, None)
except Exception as ex:
feat = None
if feat:
P(u' 边%d 角(%.2f, %.2f) → R%.1f ✅' % (idx, p0[oth[0]], p0[oth[1]], r))
done += 1
ok = True
break
if not ok:
P(u' 边%d 角(%.2f, %.2f) → 四档半径都做不出 ⛔' % (idx, p0[oth[0]], p0[oth[1]]))
failed.append(idx)
after = cyl_hist()
P(u'\n倒角后 %s' % after)
P(u'成功 %d / %d 条%s' % (done, len(cand), (u',失败 %s' % failed) if failed else u''))
if done:
e1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
part.Extension.SaveAs3(PATH, 0, 1, EMPTY, EMPTY, e1, w1)
P(u'存盘 err=%s %.0f KB' % (e1.value, os.path.getsize(PATH) / 1024.0))
else:
P(u'⛔ 一条都没倒成,不存盘')
P(u'⚠ 零件保持打开')
+167
查看文件
@@ -0,0 +1,167 @@
# -*- coding: utf-8 -*-
u"""把三块新板(南板 / 北板 / 顶盖)插进总装,封成盒子。
三块都**只平移不旋转**(旋转是本会话的重灾区,能不转就不转):
· 南板 `SouthPanel`:本地 y 0~2.4 ⇒ 平移 **dy = −6.4**,贴到托盘南边缘 y=−4 的外侧
· 北板 `NorthPanel`:本地 y 0~2.4 ⇒ 平移 **dy = +102**,贴到北边缘外侧
· 顶盖 `TopPanel` :本地 z 0~2.4 ⇒ 平移 **dz = +72**,内表面落在 z=72
(刀片顶 71.34 ⇒ 间隙 0.66,兼做上端导轨,咬边 ≤1 ✅)
⛔ `AddComponent4` 要求被插入的文档**处于打开状态**(NOTES §15)——
三个零件脚本收尾时都保持打开了,这里直接引用。
⛔ 判据:插完回读每块板的装配 box 与期望比对;再跑 fast 干涉,
基线是 360(5 刀片 × 72 簧片蹭),多出来的才是新板撞了。
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
# (文件, 标识, 平移, 期望装配 box)
# ⛔ 南北板原来贴在 y=−4 / 102(托盘边缘)。端板改成 y 对称后占 **−1.655~104.345**,
# 北板 102~104.4 与端板重叠 2.345 ⇒ 实测干涉 589 mm³。
# ✅ 南北板改为贴在**端板外侧**:北板内表面 104.345、南板外表面 −1.655。
# 这样三块板与端板共面收口,也不再和托盘(−4~102)冲突(托盘比它们窄,缩在里面)。
EP_Y0, EP_Y1 = -1.655, 104.345 # 端板 y 对称范围
PANELS = [
# v2:Type-C 开孔由 11.94×6.0 放大到 13×9(原高度 6.0 带护套的线插不进去)
(u'SouthPanel_v2.SLDPRT', u'南板', (0.0, EP_Y0 - 2.4, 0.0),
[-27.4, EP_Y0 - 2.4, -10.0, 125.4, EP_Y0, 72.0]),
(u'NorthPanel_v2.SLDPRT', u'北板', (0.0, EP_Y1, 0.0),
[-27.4, EP_Y1, -10.0, 125.4, EP_Y1 + 2.4, 72.0]),
# v2:泄压缝改 11 条、按盒子中心 49 对称(原 8 条按底板坐标定,偏在东半侧)
(u'TopPanel_v2.SLDPRT', u'顶盖', (0.0, 0.0, 72.0),
[-27.4, -4.0, 72.0, 125.4, 102.0, 74.4]),
]
def prop(o, n):
v = getattr(o, n)
return v() if type(v).__name__ in ('method', 'function', 'builtin_function_or_method') else v
def P(*a):
print(*a)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
if doc is None:
P(u'⛔ 没找到总装'); sys.exit(3)
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
P(u'总装 %s,现有顶层件 %d' % (prop(doc, 'GetTitle'), len(doc.GetComponents(True))))
# 幂等:先删掉已插过的三块板
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2').upper()
if any(k in nm for k in ('SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL')):
doc.ClearSelection2(True)
doc.Extension.SelectByID2(prop(c, 'Name2'), 'COMPONENT', 0, 0, 0, False, 0, EMPTY, 0)
doc.Extension.DeleteSelection2(0)
P(u' 删掉旧的 %s' % prop(c, 'Name2'))
doc.EditRebuild3
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def mk(a16):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, list(a16))
xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, v))
if max(abs(a - b) for a, b in zip(list(xf.ArrayData), a16)) > 1e-9:
raise RuntimeError(u'CreateTransform 回读不符')
return xf
P(u'\n插入三块板:')
allok = True
for fn, tag, t, want in PANELS:
path = DIR + fn
if not os.path.exists(path):
P(u' ⛔ %s 文件不存在' % tag); allok = False; continue
c = doc.AddComponent4(path, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 返回 None(零件没开着?)' % tag); allok = False; continue
xf = mk(IDENT + [t[0] / 1000.0, t[1] / 1000.0, t[2] / 1000.0, 1.0, 0.0, 0.0, 0.0])
try:
c.SetTransformAndSolve2(xf)
except Exception:
c.Transform2 = xf
doc.EditRebuild3
b = [round(v * 1000, 2) for v in c.GetBox(False, False)]
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
if not ok:
allok = False
P(u' %s %-18s box x %8.2f~%8.2f y %8.2f~%8.2f z %7.2f~%7.2f %s'
% (tag, prop(c, 'Name2')[:18], b[0], b[3], b[1], b[4], b[2], b[5],
u'✅' if ok else (u'⛔ 期望 %s' % want)))
for nm2, vid in ((u'box_iso', 7), (u'box_front', 1), (u'box_right', 4)):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
P(u'\n截图完成')
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'存盘 err=%s' % e3.value)
P(u'\n干涉(fast,基线 360):')
try:
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = True
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
P(u' 干涉 %s 处' % n)
hits = []
if n:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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(k in y.upper() for y in nms for k in ('PANEL',)):
hits.append((o.Volume * 1e9, nms))
if hits:
P(u' ⚠ 新板相关 %d 处:' % len(hits))
for vv, nms in sorted(hits, reverse=True)[:8]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
else:
P(u' ✅ 三块新板与所有件零干涉')
idm.Done()
except Exception as ex:
P(u' 干涉失败 %s' % str(ex)[:80])
try:
doc.InterferenceDetectionManager.Done()
except Exception:
pass
+23
查看文件
@@ -0,0 +1,23 @@
# 把 Bambu 系统预设沿 inherits 链展开成一份完整 JSON(CLI 不会自己展开)。
import json, os, sys
SYS = os.path.join(os.environ['APPDATA'], 'BambuStudio', 'system', 'BBL')
def load(kind, name):
p = os.path.join(SYS, kind, name + '.json')
d = json.load(open(p, encoding='utf-8'))
parent = d.get('inherits')
base = load(kind, parent) if parent else {}
base.update(d)
base.pop('inherits', None)
return base
out = sys.argv[1]
spec = [('machine', 'Bambu Lab A1 0.4 nozzle', {}),
('process', '0.20mm Standard @BBL A1', {'wall_loops': '3'}),
('filament', 'Generic PETG @BBL A1', {})]
for kind, name, over in spec:
d = load(kind, name)
d.update(over)
d['from'] = 'system'
json.dump(d, open(os.path.join(out, kind + '.json'), 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
keys = ('filament_type', 'nozzle_temperature', 'textured_plate_temp', 'hot_plate_temp', 'wall_loops',
'printer_model', 'layer_height', 'sparse_infill_density')
print(kind, {k: d[k] for k in keys if k in d})
+27
查看文件
@@ -0,0 +1,27 @@
# 螺丝试装件的切片预设(2026-09-30):同 bambu_flatten.py,工艺仍用 0.20 Standard(换 0.12mm Fine 预设 CLI 返回 -18),
# 覆盖成层高 0.12、实心、加 brim。
import json, os, sys
SYS = os.path.join(os.environ['APPDATA'], 'BambuStudio', 'system', 'BBL')
def load(kind, name):
d = json.load(open(os.path.join(SYS, kind, name + '.json'), encoding='utf-8'))
parent = d.get('inherits')
base = load(kind, parent) if parent else {}
base.update(d)
base.pop('inherits', None)
return base
out = sys.argv[1]
spec = [('machine', 'Bambu Lab A1 0.4 nozzle', {}),
('process', '0.20mm Standard @BBL A1', {'layer_height': '0.12', 'wall_loops': '4', 'sparse_infill_density': '100%',
'brim_type': 'outer_only', 'brim_width': '3'}),
('filament', 'Generic PETG @BBL A1', {})]
for kind, name, over in spec:
d = load(kind, name)
d.update(over)
d['from'] = 'system'
json.dump(d, open(os.path.join(out, kind + '.json'), 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
keys = ('filament_type', 'nozzle_temperature', 'wall_loops', 'layer_height', 'sparse_infill_density', 'brim_type')
print(kind, {k: d[k] for k in keys if k in d})
+159
查看文件
@@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
u"""**一整块底盖** BottomPlate_v1(2026-09-13 两件式外壳)。
= 175.0 × 110.8 × 2.4 平板(与顶盖外形齐平)+ 底板 PCB 的 4 根 Ø8 × 10 立柱(柱顶 Ø4.2 螺母孔)
+ 四角 4 个 Ø3.4 过孔(M3 从下往上拧进顶盖角柱底部的热熔螺母)。
零件本地坐标 = 装配坐标平移 z +10:板占本地 z −2.4~0,立柱 0~10;装配时 dz = −10
⇒ 板 z −12.4~−10(顶盖墙底落在 z=−10)、柱顶 z=0 = 底板 PCB 底面。
与 Tray_v9 相比:外形放大到整机尺寸;取消端板锁孔(端板已并进顶盖);
取消南边两个 USB-C 缺口 —— 那是板面还在 z −2.4~0 时代的遗留,现在板面在 −10,
离 Type-C 插入通道底 −3.25 还有 6.75,缺口已无意义。
两种孔都画进拉伸草图一次成形(本机最可靠的路):
· 四角过孔画进 [1] 平板草图 ⇒ 通孔
· 螺母孔画进 [2] 立柱草图 ⇒ 只穿立柱那 10 mm,底下平板没有孔 ⇒ 盲孔深 10
"""
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\\BottomPlate_v1.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
X0, X1, Y0, Y1 = -38.4, 136.4, -4.055, 106.745
T = 2.4
CORNER_HOLES = [(-31.0, 3.345), (-31.0, 99.345), (129.0, 3.345), (129.0, 99.345)] # = 顶盖角柱中心
R_THRU = 1.7
PCB_POSTS = [(4.0, 4.0), (94.0, 4.0), (4.0, 94.0), (94.0, 94.0)]
POST_R, POST_H, R_INS = 4.0, 10.0, 2.1
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')
for d in list(app.GetDocuments or []):
try:
t = prop(d, 'GetTitle') or ''
if 'BOTTOMPLATE_V1' 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 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 bbox():
b = part.GetBodies2(0, True) or []
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 = []
b = part.GetBodies2(0, True) or []
for f in prop(D(b[0]), 'GetFaces') if b else []:
fc = D(f)
s = prop(fc, 'GetSurface')
if prop(s, 'IsCylinder'):
fb = [v * 1000 for v in prop(fc, 'GetBox')]
out.append((round(list(prop(s, 'CylinderParams'))[6] * 1000, 2), fb))
return out
P(u'\n[1] 平板 %.1f × %.1f × %.1f + 四角 Ø%.1f 过孔' % (X1 - X0, Y1 - Y0, T, 2 * R_THRU))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(M(X0), M(Y0), 0.0, M(X1), M(Y1), 0.0)
for hx, hy in CORNER_HOLES:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_THRU))
sm.AddToDB = False
sm.InsertSketch(True)
boss(T, True)
b = bbox()
P(u' box %s' % b)
if b is None or abs(b[2] + T) > 0.02 or abs(b[5]) > 0.02:
P(u'⛔ 停手'); sys.exit(2)
P(u'\n[2] 4 根 PCB 立柱 Ø%.0f × %.0f + 柱顶 Ø%.1f 螺母孔(盲孔)' % (2 * POST_R, POST_H, 2 * R_INS))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
for hx, hy in PCB_POSTS:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(POST_R))
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_INS))
sm.AddToDB = False
sm.InsertSketch(True)
boss(POST_H, False)
b = bbox()
P(u' box %s(期望 z −2.4~10)' % b)
cs = cyls()
h = {}
for r, _ in cs:
h[r] = h.get(r, 0) + 1
P(u' 圆柱面直方图 %s(期望 {1.7: 4, 2.1: 4, 4.0: 4})' % dict(sorted(h.items())))
ins = [fb for r, fb in cs if abs(r - R_INS) < 0.01]
thru = [fb for r, fb in cs if abs(r - R_THRU) < 0.01]
ok_ins = len(ins) == 4 and all(abs(fb[2]) < 0.05 and abs(fb[5] - POST_H) < 0.05 for fb in ins)
ok_thru = len(thru) == 4 and all(abs(fb[2] + T) < 0.05 and abs(fb[5]) < 0.05 for fb in thru)
P(u' 螺母孔 z 0~10(盲,底下平板无孔)%s;四角过孔 z −2.4~0(通)%s'
% (u'✅' if ok_ins else u'⛔', u'✅' if ok_thru else u'⛔'))
if not (ok_ins and ok_thru and b and abs(b[5] - POST_H) < 0.02):
P(u'⛔ 不存盘'); sys.exit(3)
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))
+159
查看文件
@@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
u"""**一整块底盖** BottomPlate_v2(2026-09-13 两件式外壳)。
= 175.0 × 110.8 × 2.4 平板(与顶盖外形齐平)+ 底板 PCB 的 4 根 Ø8 × 10 立柱(柱顶 Ø4.2 螺母孔)
+ 四角 4 个 Ø3.4 过孔(M3 从下往上拧进顶盖角柱底部的热熔螺母)。
零件本地坐标 = 装配坐标平移 z +10:板占本地 z −2.4~0,立柱 0~10;装配时 dz = −10
⇒ 板 z −12.4~−10(顶盖墙底落在 z=−10)、柱顶 z=0 = 底板 PCB 底面。
与 Tray_v9 相比:外形放大到整机尺寸;取消端板锁孔(端板已并进顶盖);
取消南边两个 USB-C 缺口 —— 那是板面还在 z −2.4~0 时代的遗留,现在板面在 −10,
离 Type-C 插入通道底 −3.25 还有 6.75,缺口已无意义。
两种孔都画进拉伸草图一次成形(本机最可靠的路):
· 四角过孔画进 [1] 平板草图 ⇒ 通孔
· 螺母孔画进 [2] 立柱草图 ⇒ 只穿立柱那 10 mm,底下平板没有孔 ⇒ 盲孔深 10
"""
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\\BottomPlate_v2.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
X0, X1, Y0, Y1 = -43.8, 136.4, -4.055, 106.745
T = 2.4
CORNER_HOLES = [(-31.0, 3.345), (-31.0, 99.345), (129.0, 3.345), (129.0, 99.345)] # = 顶盖角柱中心
R_THRU = 1.7
PCB_POSTS = [(4.0, 4.0), (94.0, 4.0), (4.0, 94.0), (94.0, 94.0)]
POST_R, POST_H, R_INS = 4.0, 10.0, 2.1
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')
for d in list(app.GetDocuments or []):
try:
t = prop(d, 'GetTitle') or ''
if 'BOTTOMPLATE_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 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 bbox():
b = part.GetBodies2(0, True) or []
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 = []
b = part.GetBodies2(0, True) or []
for f in prop(D(b[0]), 'GetFaces') if b else []:
fc = D(f)
s = prop(fc, 'GetSurface')
if prop(s, 'IsCylinder'):
fb = [v * 1000 for v in prop(fc, 'GetBox')]
out.append((round(list(prop(s, 'CylinderParams'))[6] * 1000, 2), fb))
return out
P(u'\n[1] 平板 %.1f × %.1f × %.1f + 四角 Ø%.1f 过孔' % (X1 - X0, Y1 - Y0, T, 2 * R_THRU))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(M(X0), M(Y0), 0.0, M(X1), M(Y1), 0.0)
for hx, hy in CORNER_HOLES:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_THRU))
sm.AddToDB = False
sm.InsertSketch(True)
boss(T, True)
b = bbox()
P(u' box %s' % b)
if b is None or abs(b[2] + T) > 0.02 or abs(b[5]) > 0.02:
P(u'⛔ 停手'); sys.exit(2)
P(u'\n[2] 4 根 PCB 立柱 Ø%.0f × %.0f + 柱顶 Ø%.1f 螺母孔(盲孔)' % (2 * POST_R, POST_H, 2 * R_INS))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
for hx, hy in PCB_POSTS:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(POST_R))
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_INS))
sm.AddToDB = False
sm.InsertSketch(True)
boss(POST_H, False)
b = bbox()
P(u' box %s(期望 z −2.4~10)' % b)
cs = cyls()
h = {}
for r, _ in cs:
h[r] = h.get(r, 0) + 1
P(u' 圆柱面直方图 %s(期望 {1.7: 4, 2.1: 4, 4.0: 4})' % dict(sorted(h.items())))
ins = [fb for r, fb in cs if abs(r - R_INS) < 0.01]
thru = [fb for r, fb in cs if abs(r - R_THRU) < 0.01]
ok_ins = len(ins) == 4 and all(abs(fb[2]) < 0.05 and abs(fb[5] - POST_H) < 0.05 for fb in ins)
ok_thru = len(thru) == 4 and all(abs(fb[2] + T) < 0.05 and abs(fb[5]) < 0.05 for fb in thru)
P(u' 螺母孔 z 0~10(盲,底下平板无孔)%s;四角过孔 z −2.4~0(通)%s'
% (u'✅' if ok_ins else u'⛔', u'✅' if ok_thru else u'⛔'))
if not (ok_ins and ok_thru and b and abs(b[5] - POST_H) < 0.02):
P(u'⛔ 不存盘'); sys.exit(3)
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))
+160
查看文件
@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
# v3 短机版:外形 x −24.8~115.9,四角过孔跟顶盖角柱到 x −11 / 108.5
u"""**一整块底盖** BottomPlate_v3(2026-09-13 两件式外壳)。
= 175.0 × 110.8 × 2.4 平板(与顶盖外形齐平)+ 底板 PCB 的 4 根 Ø8 × 10 立柱(柱顶 Ø4.2 螺母孔)
+ 四角 4 个 Ø3.4 过孔(M3 从下往上拧进顶盖角柱底部的热熔螺母)。
零件本地坐标 = 装配坐标平移 z +10:板占本地 z −2.4~0,立柱 0~10;装配时 dz = −10
⇒ 板 z −12.4~−10(顶盖墙底落在 z=−10)、柱顶 z=0 = 底板 PCB 底面。
与 Tray_v9 相比:外形放大到整机尺寸;取消端板锁孔(端板已并进顶盖);
取消南边两个 USB-C 缺口 —— 那是板面还在 z −2.4~0 时代的遗留,现在板面在 −10,
离 Type-C 插入通道底 −3.25 还有 6.75,缺口已无意义。
两种孔都画进拉伸草图一次成形(本机最可靠的路):
· 四角过孔画进 [1] 平板草图 ⇒ 通孔
· 螺母孔画进 [2] 立柱草图 ⇒ 只穿立柱那 10 mm,底下平板没有孔 ⇒ 盲孔深 10
"""
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\\BottomPlate_v3.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
X0, X1, Y0, Y1 = -24.8, 115.9, -4.055, 106.745
T = 2.4
CORNER_HOLES = [(-11.0, 3.345), (-11.0, 99.345), (108.5, 3.345), (108.5, 99.345)] # = 顶盖角柱中心
R_THRU = 1.7
PCB_POSTS = [(4.0, 4.0), (94.0, 4.0), (4.0, 94.0), (94.0, 94.0)]
POST_R, POST_H, R_INS = 4.0, 10.0, 2.1
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')
for d in list(app.GetDocuments or []):
try:
t = prop(d, 'GetTitle') or ''
if 'BOTTOMPLATE_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 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 bbox():
b = part.GetBodies2(0, True) or []
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 = []
b = part.GetBodies2(0, True) or []
for f in prop(D(b[0]), 'GetFaces') if b else []:
fc = D(f)
s = prop(fc, 'GetSurface')
if prop(s, 'IsCylinder'):
fb = [v * 1000 for v in prop(fc, 'GetBox')]
out.append((round(list(prop(s, 'CylinderParams'))[6] * 1000, 2), fb))
return out
P(u'\n[1] 平板 %.1f × %.1f × %.1f + 四角 Ø%.1f 过孔' % (X1 - X0, Y1 - Y0, T, 2 * R_THRU))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(M(X0), M(Y0), 0.0, M(X1), M(Y1), 0.0)
for hx, hy in CORNER_HOLES:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_THRU))
sm.AddToDB = False
sm.InsertSketch(True)
boss(T, True)
b = bbox()
P(u' box %s' % b)
if b is None or abs(b[2] + T) > 0.02 or abs(b[5]) > 0.02:
P(u'⛔ 停手'); sys.exit(2)
P(u'\n[2] 4 根 PCB 立柱 Ø%.0f × %.0f + 柱顶 Ø%.1f 螺母孔(盲孔)' % (2 * POST_R, POST_H, 2 * R_INS))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
for hx, hy in PCB_POSTS:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(POST_R))
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_INS))
sm.AddToDB = False
sm.InsertSketch(True)
boss(POST_H, False)
b = bbox()
P(u' box %s(期望 z −2.4~10)' % b)
cs = cyls()
h = {}
for r, _ in cs:
h[r] = h.get(r, 0) + 1
P(u' 圆柱面直方图 %s(期望 {1.7: 4, 2.1: 4, 4.0: 4})' % dict(sorted(h.items())))
ins = [fb for r, fb in cs if abs(r - R_INS) < 0.01]
thru = [fb for r, fb in cs if abs(r - R_THRU) < 0.01]
ok_ins = len(ins) == 4 and all(abs(fb[2]) < 0.05 and abs(fb[5] - POST_H) < 0.05 for fb in ins)
ok_thru = len(thru) == 4 and all(abs(fb[2] + T) < 0.05 and abs(fb[5]) < 0.05 for fb in thru)
P(u' 螺母孔 z 0~10(盲,底下平板无孔)%s;四角过孔 z −2.4~0(通)%s'
% (u'✅' if ok_ins else u'⛔', u'✅' if ok_thru else u'⛔'))
if not (ok_ins and ok_thru and b and abs(b[5] - POST_H) < 0.02):
P(u'⛔ 不存盘'); sys.exit(3)
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))
+245
查看文件
@@ -0,0 +1,245 @@
# -*- coding: utf-8 -*-
u"""**底盖 v4**(2026-09-13 外观美化 A + E)。
与 v3 的差别:
A 四边**内缩 0.5**:外形 x −24.3~115.4、y −3.555~106.245(顶盖外沿 −24.8~115.9 / −4.055~106.745),
接缝处形成一圈 0.5 阴影缝,藏住两件的打印公差;四角圆角用 R2.5(与顶盖 R3 同心)——圆角由 add_fillets 后做。
E **加厚 2.4 → 3.6**(向下长,底盖顶面仍在 z=−10,机内尺寸不变;整机高 +1.2):
· 四角 M3 过孔下方挖**沉头 Ø6.5 深 2.0**,螺丝头不外露,头下还剩 1.6(2.4 厚时只剩 0.4,会压裂)
· 4 个**脚垫窝 Ø10 深 1.0**(贴 Ø10 硅胶脚垫),放在螺丝内侧、不盖住螺丝 ⇒ 拆盖不用撕脚垫
PCB 立柱、柱顶螺母孔、四角过孔位置与 v3 相同。
本地坐标:板 z −3.6~0、立柱 0~10;装配 dz = −10 ⇒ 板 z −13.6~−10。
面上切除沿用 NOTES §37:进草图后读 ModelToSketchTransform 验平面,再按变换精确画。
"""
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\\BottomPlate_v4.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
INSET = 0.5
X0, X1, Y0, Y1 = -24.8 + INSET, 115.9 - INSET, -4.055 + INSET, 106.745 - INSET
T = 3.6
CORNER_HOLES = [(-11.0, 3.345), (-11.0, 99.345), (108.5, 3.345), (108.5, 99.345)]
R_THRU = 1.7
CB_R, CB_D = 3.25, 2.0 # 沉头 Ø6.5 深 2
PCB_POSTS = [(4.0, 4.0), (94.0, 4.0), (4.0, 94.0), (94.0, 94.0)]
POST_R, POST_H, R_INS = 4.0, 10.0, 2.1
FOOT_R, FOOT_D = 5.0, 1.0 # 脚垫窝 Ø10 深 1
_XC, _YC = (X0 + X1) / 2.0, 51.345
FEET = [(_XC - 45.0, _YC - 43.0), (_XC + 45.0, _YC - 43.0), (_XC - 45.0, _YC + 43.0), (_XC + 45.0, _YC + 43.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
app = wc.GetActiveObject('SldWorks.Application')
for d in list(app.GetDocuments or []):
try:
t = prop(d, 'GetTitle') or ''
if 'BOTTOMPLATE_V4' in t.upper():
app.CloseDoc(t)
except Exception:
pass
app.NewDocument(app.GetUserPreferenceStringValue(8), 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 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 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'):
fb = [v * 1000 for v in prop(fc, 'GetBox')]
out.append((round(list(prop(s, 'CylinderParams'))[6] * 1000, 2), fb))
return out
def planes():
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 open_face_sketch(ax, sign, coord, tag):
for attempt in range(1, 4):
best, ba = None, -1.0
for a_, s_, fb, area, fc in planes():
if a_ == ax and s_ == sign and abs(fb[ax] - coord) < 0.02 and abs(fb[ax + 3] - coord) < 0.02 and area > ba:
best, ba = fc, area
if best is None:
P(u' ⛔ %s 选不到面' % tag); sys.exit(4)
part.ClearSelection2(True)
best.Select4(False, EMPTY)
sm.InsertSketch(True)
sk = prop(sm, 'ActiveSketch')
if sk:
arr = list(prop(D(sk), 'ModelToSketchTransform').ArrayData)
Mx = [[arr[3 * c + r] for c in range(3)] for r in range(3)]
X = (Mx, arr[9:12])
nax = max(range(3), key=lambda i: abs(Mx[2][i]))
probe = [0.0, 0.0, 0.0]
probe[ax] = coord / 1000.0
sz = (sum(Mx[2][i] * probe[i] for i in range(3)) + arr[11]) * 1000
ok = nax == ax and abs(sz) < 0.01
P(u' %s 进草图 第 %d 次:法线轴 %s、面上点草图 z=%.4f %s'
% (tag, attempt, 'xyz'[nax], sz, u'✅' if ok else u'✗ 退出重进'))
if ok:
return X
sm.InsertSketch(True)
P(u' ⛔ %s 进不到目标面' % tag); sys.exit(4)
def to_sketch(X, p):
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 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)
P(u'\n[1] 平板 %.1f × %.1f × %.1f(四边内缩 %.1f)+ 四角 Ø%.1f 过孔' % (X1 - X0, Y1 - Y0, T, INSET, 2 * R_THRU))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(M(X0), M(Y0), 0.0, M(X1), M(Y1), 0.0)
for hx, hy in CORNER_HOLES:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_THRU))
sm.AddToDB = False
sm.InsertSketch(True)
boss(T, True)
P(u' box %s' % bbox())
P(u'\n[2] PCB 立柱 Ø%.0f × %.0f + 柱顶 Ø%.1f 螺母孔' % (2 * POST_R, POST_H, 2 * R_INS))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
for hx, hy in PCB_POSTS:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(POST_R))
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_INS))
sm.AddToDB = False
sm.InsertSketch(True)
boss(POST_H, False)
b = bbox()
P(u' box %s(期望 x %.1f~%.1f y %.3f~%.3f z %.1f~%.1f)' % (b, X0, X1, Y0, Y1, -T, POST_H))
if b is None or any(abs(b[i] - w) > 0.02 for i, w in enumerate([X0, Y0, -T, X1, Y1, POST_H])):
P(u'⛔ 停手'); sys.exit(2)
for tag, pts, r, dep in ((u'沉头', CORNER_HOLES, CB_R, CB_D), (u'脚垫窝', FEET, FOOT_R, FOOT_D)):
P(u'\n[3] 底面 %s ×4:Ø%.1f 深 %.1f' % (tag, 2 * r, dep))
X = open_face_sketch(2, -1, -T, tag)
sm.AddToDB = True
for hx, hy in pts:
q = to_sketch(X, (hx, hy, -T))
sm.CreateCircleByRadius(q[0], q[1], 0.0, M(r))
sm.AddToDB = False
sm.InsertSketch(True)
c = cut(dep)
got = [fb for rr, fb in cyls() if abs(rr - r) < 0.01]
okp = len(got) == 4 and all(abs(fb[2] + T) < 0.05 and abs(fb[5] - (-T + dep)) < 0.05 for fb in got) and \
all(any(abs((fb[0] + fb[3]) / 2 - hx) < 0.05 and abs((fb[1] + fb[4]) / 2 - hy) < 0.05 for fb in got) for hx, hy in pts)
P(u' 切除 → %r;R%.2f 圆柱面 %d 个,z %.1f~%.1f、圆心在位 %s'
% (prop(c, 'Name') if c else c, r, len(got), -T, -T + dep, u'✅' if okp else u'⛔'))
if not okp:
P(u'⛔ 不存盘'); sys.exit(3)
h = {}
for rr, fb in cyls():
h[rr] = h.get(rr, 0) + 1
thru = [fb for rr, fb in cyls() if abs(rr - R_THRU) < 0.01]
ins = [fb for rr, fb in cyls() if abs(rr - R_INS) < 0.01]
P(u'\n[4] 验收 直方图 %s(期望 {1.7: 4, 2.1: 4, 3.25: 4, 4.0: 4, 5.0: 4})' % dict(sorted(h.items())))
ok_thru = len(thru) == 4 and all(abs(fb[2] - (-T + CB_D)) < 0.05 and abs(fb[5]) < 0.05 for fb in thru)
ok_ins = len(ins) == 4 and all(abs(fb[2]) < 0.05 and abs(fb[5] - POST_H) < 0.05 for fb in ins)
P(u' 过孔 z %.1f~0(沉头以上 1.6 实体)%s;柱顶螺母孔 z 0~10 盲孔 %s'
% (-T + CB_D, u'✅' if ok_thru else u'⛔', u'✅' if ok_ins else u'⛔'))
want = {1.7: 4, 2.1: 4, 3.25: 4, 4.0: 4, 5.0: 4}
if not (ok_thru and ok_ins) or any(h.get(k, 0) != v for k, v in want.items()):
P(u'⛔ 不存盘'); sys.exit(3)
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))
+246
查看文件
@@ -0,0 +1,246 @@
# -*- coding: utf-8 -*-
# v5:无滤网版 —— 西边缘 −17.9(不再托滤网/导轨),脚垫窝随中心重排
u"""**底盖 v4**(2026-09-13 外观美化 A + E)。
与 v3 的差别:
A 四边**内缩 0.5**:外形 x −24.3~115.4、y −3.555~106.245(顶盖外沿 −24.8~115.9 / −4.055~106.745),
接缝处形成一圈 0.5 阴影缝,藏住两件的打印公差;四角圆角用 R2.5(与顶盖 R3 同心)——圆角由 add_fillets 后做。
E **加厚 2.4 → 3.6**(向下长,底盖顶面仍在 z=−10,机内尺寸不变;整机高 +1.2):
· 四角 M3 过孔下方挖**沉头 Ø6.5 深 2.0**,螺丝头不外露,头下还剩 1.6(2.4 厚时只剩 0.4,会压裂)
· 4 个**脚垫窝 Ø10 深 1.0**(贴 Ø10 硅胶脚垫),放在螺丝内侧、不盖住螺丝 ⇒ 拆盖不用撕脚垫
PCB 立柱、柱顶螺母孔、四角过孔位置与 v3 相同。
本地坐标:板 z −3.6~0、立柱 0~10;装配 dz = −10 ⇒ 板 z −13.6~−10。
面上切除沿用 NOTES §37:进草图后读 ModelToSketchTransform 验平面,再按变换精确画。
"""
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\\BottomPlate_v5.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
INSET = 0.5
X0, X1, Y0, Y1 = -18.4 + INSET, 115.9 - INSET, -4.055 + INSET, 106.745 - INSET # 无滤网:西沿回到顶盖外沿 −18.4
T = 3.6
CORNER_HOLES = [(-11.0, 3.345), (-11.0, 99.345), (108.5, 3.345), (108.5, 99.345)]
R_THRU = 1.7
CB_R, CB_D = 3.25, 2.0 # 沉头 Ø6.5 深 2
PCB_POSTS = [(4.0, 4.0), (94.0, 4.0), (4.0, 94.0), (94.0, 94.0)]
POST_R, POST_H, R_INS = 4.0, 10.0, 2.1
FOOT_R, FOOT_D = 5.0, 1.0 # 脚垫窝 Ø10 深 1
_XC, _YC = (X0 + X1) / 2.0, 51.345
FEET = [(_XC - 45.0, _YC - 43.0), (_XC + 45.0, _YC - 43.0), (_XC - 45.0, _YC + 43.0), (_XC + 45.0, _YC + 43.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
app = wc.GetActiveObject('SldWorks.Application')
for d in list(app.GetDocuments or []):
try:
t = prop(d, 'GetTitle') or ''
if 'BOTTOMPLATE_V5' in t.upper():
app.CloseDoc(t)
except Exception:
pass
app.NewDocument(app.GetUserPreferenceStringValue(8), 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 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 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'):
fb = [v * 1000 for v in prop(fc, 'GetBox')]
out.append((round(list(prop(s, 'CylinderParams'))[6] * 1000, 2), fb))
return out
def planes():
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 open_face_sketch(ax, sign, coord, tag):
for attempt in range(1, 4):
best, ba = None, -1.0
for a_, s_, fb, area, fc in planes():
if a_ == ax and s_ == sign and abs(fb[ax] - coord) < 0.02 and abs(fb[ax + 3] - coord) < 0.02 and area > ba:
best, ba = fc, area
if best is None:
P(u' ⛔ %s 选不到面' % tag); sys.exit(4)
part.ClearSelection2(True)
best.Select4(False, EMPTY)
sm.InsertSketch(True)
sk = prop(sm, 'ActiveSketch')
if sk:
arr = list(prop(D(sk), 'ModelToSketchTransform').ArrayData)
Mx = [[arr[3 * c + r] for c in range(3)] for r in range(3)]
X = (Mx, arr[9:12])
nax = max(range(3), key=lambda i: abs(Mx[2][i]))
probe = [0.0, 0.0, 0.0]
probe[ax] = coord / 1000.0
sz = (sum(Mx[2][i] * probe[i] for i in range(3)) + arr[11]) * 1000
ok = nax == ax and abs(sz) < 0.01
P(u' %s 进草图 第 %d 次:法线轴 %s、面上点草图 z=%.4f %s'
% (tag, attempt, 'xyz'[nax], sz, u'✅' if ok else u'✗ 退出重进'))
if ok:
return X
sm.InsertSketch(True)
P(u' ⛔ %s 进不到目标面' % tag); sys.exit(4)
def to_sketch(X, p):
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 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)
P(u'\n[1] 平板 %.1f × %.1f × %.1f(四边内缩 %.1f)+ 四角 Ø%.1f 过孔' % (X1 - X0, Y1 - Y0, T, INSET, 2 * R_THRU))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(M(X0), M(Y0), 0.0, M(X1), M(Y1), 0.0)
for hx, hy in CORNER_HOLES:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_THRU))
sm.AddToDB = False
sm.InsertSketch(True)
boss(T, True)
P(u' box %s' % bbox())
P(u'\n[2] PCB 立柱 Ø%.0f × %.0f + 柱顶 Ø%.1f 螺母孔' % (2 * POST_R, POST_H, 2 * R_INS))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
for hx, hy in PCB_POSTS:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(POST_R))
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_INS))
sm.AddToDB = False
sm.InsertSketch(True)
boss(POST_H, False)
b = bbox()
P(u' box %s(期望 x %.1f~%.1f y %.3f~%.3f z %.1f~%.1f)' % (b, X0, X1, Y0, Y1, -T, POST_H))
if b is None or any(abs(b[i] - w) > 0.02 for i, w in enumerate([X0, Y0, -T, X1, Y1, POST_H])):
P(u'⛔ 停手'); sys.exit(2)
for tag, pts, r, dep in ((u'沉头', CORNER_HOLES, CB_R, CB_D), (u'脚垫窝', FEET, FOOT_R, FOOT_D)):
P(u'\n[3] 底面 %s ×4:Ø%.1f 深 %.1f' % (tag, 2 * r, dep))
X = open_face_sketch(2, -1, -T, tag)
sm.AddToDB = True
for hx, hy in pts:
q = to_sketch(X, (hx, hy, -T))
sm.CreateCircleByRadius(q[0], q[1], 0.0, M(r))
sm.AddToDB = False
sm.InsertSketch(True)
c = cut(dep)
got = [fb for rr, fb in cyls() if abs(rr - r) < 0.01]
okp = len(got) == 4 and all(abs(fb[2] + T) < 0.05 and abs(fb[5] - (-T + dep)) < 0.05 for fb in got) and \
all(any(abs((fb[0] + fb[3]) / 2 - hx) < 0.05 and abs((fb[1] + fb[4]) / 2 - hy) < 0.05 for fb in got) for hx, hy in pts)
P(u' 切除 → %r;R%.2f 圆柱面 %d 个,z %.1f~%.1f、圆心在位 %s'
% (prop(c, 'Name') if c else c, r, len(got), -T, -T + dep, u'✅' if okp else u'⛔'))
if not okp:
P(u'⛔ 不存盘'); sys.exit(3)
h = {}
for rr, fb in cyls():
h[rr] = h.get(rr, 0) + 1
thru = [fb for rr, fb in cyls() if abs(rr - R_THRU) < 0.01]
ins = [fb for rr, fb in cyls() if abs(rr - R_INS) < 0.01]
P(u'\n[4] 验收 直方图 %s(期望 {1.7: 4, 2.1: 4, 3.25: 4, 4.0: 4, 5.0: 4})' % dict(sorted(h.items())))
ok_thru = len(thru) == 4 and all(abs(fb[2] - (-T + CB_D)) < 0.05 and abs(fb[5]) < 0.05 for fb in thru)
ok_ins = len(ins) == 4 and all(abs(fb[2]) < 0.05 and abs(fb[5] - POST_H) < 0.05 for fb in ins)
P(u' 过孔 z %.1f~0(沉头以上 1.6 实体)%s;柱顶螺母孔 z 0~10 盲孔 %s'
% (-T + CB_D, u'✅' if ok_thru else u'⛔', u'✅' if ok_ins else u'⛔'))
want = {1.7: 4, 2.1: 4, 3.25: 4, 4.0: 4, 5.0: 4}
if not (ok_thru and ok_ins) or any(h.get(k, 0) != v for k, v in want.items()):
P(u'⛔ 不存盘'); sys.exit(3)
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))
+247
查看文件
@@ -0,0 +1,247 @@
# -*- coding: utf-8 -*-
# v6:嵌入式 —— 外形 = 顶盖内腔四边各让 0.3,拧紧后顶面压住顶盖角柱底(z −10)、底面与墙底齐平(−13.6)
# v5:无滤网版 —— 西边缘 −17.9(不再托滤网/导轨),脚垫窝随中心重排
u"""**底盖 v4**(2026-09-13 外观美化 A + E)。
与 v3 的差别:
A 四边**内缩 0.5**:外形 x −24.3~115.4、y −3.555~106.245(顶盖外沿 −24.8~115.9 / −4.055~106.745),
接缝处形成一圈 0.5 阴影缝,藏住两件的打印公差;四角圆角用 R2.5(与顶盖 R3 同心)——圆角由 add_fillets 后做。
E **加厚 2.4 → 3.6**(向下长,底盖顶面仍在 z=−10,机内尺寸不变;整机高 +1.2):
· 四角 M3 过孔下方挖**沉头 Ø6.5 深 2.0**,螺丝头不外露,头下还剩 1.6(2.4 厚时只剩 0.4,会压裂)
· 4 个**脚垫窝 Ø10 深 1.0**(贴 Ø10 硅胶脚垫),放在螺丝内侧、不盖住螺丝 ⇒ 拆盖不用撕脚垫
PCB 立柱、柱顶螺母孔、四角过孔位置与 v3 相同。
本地坐标:板 z −3.6~0、立柱 0~10;装配 dz = −10 ⇒ 板 z −13.6~−10。
面上切除沿用 NOTES §37:进草图后读 ModelToSketchTransform 验平面,再按变换精确画。
"""
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\\BottomPlate_v6.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
CLR = 0.3 # v6:嵌进顶盖内腔,四边各让 0.3
X0, X1, Y0, Y1 = -16.0 + CLR, 113.5 - CLR, -1.655 + CLR, 104.345 - CLR # = 顶盖内腔 (-16.0, 113.5, -1.655, 104.345) 各让 CLR
T = 3.6
CORNER_HOLES = [(-11.0, 3.345), (-11.0, 99.345), (108.5, 3.345), (108.5, 99.345)]
R_THRU = 1.7
CB_R, CB_D = 3.25, 2.0 # 沉头 Ø6.5 深 2
PCB_POSTS = [(4.0, 4.0), (94.0, 4.0), (4.0, 94.0), (94.0, 94.0)]
POST_R, POST_H, R_INS = 4.0, 10.0, 2.1
FOOT_R, FOOT_D = 5.0, 1.0 # 脚垫窝 Ø10 深 1
_XC, _YC = (X0 + X1) / 2.0, 51.345
FEET = [(_XC - 45.0, _YC - 43.0), (_XC + 45.0, _YC - 43.0), (_XC - 45.0, _YC + 43.0), (_XC + 45.0, _YC + 43.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
app = wc.GetActiveObject('SldWorks.Application')
for d in list(app.GetDocuments or []):
try:
t = prop(d, 'GetTitle') or ''
if 'BOTTOMPLATE_V6' in t.upper():
app.CloseDoc(t)
except Exception:
pass
app.NewDocument(app.GetUserPreferenceStringValue(8), 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 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 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'):
fb = [v * 1000 for v in prop(fc, 'GetBox')]
out.append((round(list(prop(s, 'CylinderParams'))[6] * 1000, 2), fb))
return out
def planes():
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 open_face_sketch(ax, sign, coord, tag):
for attempt in range(1, 4):
best, ba = None, -1.0
for a_, s_, fb, area, fc in planes():
if a_ == ax and s_ == sign and abs(fb[ax] - coord) < 0.02 and abs(fb[ax + 3] - coord) < 0.02 and area > ba:
best, ba = fc, area
if best is None:
P(u' ⛔ %s 选不到面' % tag); sys.exit(4)
part.ClearSelection2(True)
best.Select4(False, EMPTY)
sm.InsertSketch(True)
sk = prop(sm, 'ActiveSketch')
if sk:
arr = list(prop(D(sk), 'ModelToSketchTransform').ArrayData)
Mx = [[arr[3 * c + r] for c in range(3)] for r in range(3)]
X = (Mx, arr[9:12])
nax = max(range(3), key=lambda i: abs(Mx[2][i]))
probe = [0.0, 0.0, 0.0]
probe[ax] = coord / 1000.0
sz = (sum(Mx[2][i] * probe[i] for i in range(3)) + arr[11]) * 1000
ok = nax == ax and abs(sz) < 0.01
P(u' %s 进草图 第 %d 次:法线轴 %s、面上点草图 z=%.4f %s'
% (tag, attempt, 'xyz'[nax], sz, u'✅' if ok else u'✗ 退出重进'))
if ok:
return X
sm.InsertSketch(True)
P(u' ⛔ %s 进不到目标面' % tag); sys.exit(4)
def to_sketch(X, p):
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 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)
P(u'\n[1] 平板 %.1f × %.1f × %.1f(嵌入顶盖,四边各让 %.1f)+ 四角 Ø%.1f 过孔' % (X1 - X0, Y1 - Y0, T, CLR, 2 * R_THRU))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(M(X0), M(Y0), 0.0, M(X1), M(Y1), 0.0)
for hx, hy in CORNER_HOLES:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_THRU))
sm.AddToDB = False
sm.InsertSketch(True)
boss(T, True)
P(u' box %s' % bbox())
P(u'\n[2] PCB 立柱 Ø%.0f × %.0f + 柱顶 Ø%.1f 螺母孔' % (2 * POST_R, POST_H, 2 * R_INS))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
for hx, hy in PCB_POSTS:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(POST_R))
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(R_INS))
sm.AddToDB = False
sm.InsertSketch(True)
boss(POST_H, False)
b = bbox()
P(u' box %s(期望 x %.1f~%.1f y %.3f~%.3f z %.1f~%.1f)' % (b, X0, X1, Y0, Y1, -T, POST_H))
if b is None or any(abs(b[i] - w) > 0.02 for i, w in enumerate([X0, Y0, -T, X1, Y1, POST_H])):
P(u'⛔ 停手'); sys.exit(2)
for tag, pts, r, dep in ((u'沉头', CORNER_HOLES, CB_R, CB_D), (u'脚垫窝', FEET, FOOT_R, FOOT_D)):
P(u'\n[3] 底面 %s ×4:Ø%.1f 深 %.1f' % (tag, 2 * r, dep))
X = open_face_sketch(2, -1, -T, tag)
sm.AddToDB = True
for hx, hy in pts:
q = to_sketch(X, (hx, hy, -T))
sm.CreateCircleByRadius(q[0], q[1], 0.0, M(r))
sm.AddToDB = False
sm.InsertSketch(True)
c = cut(dep)
got = [fb for rr, fb in cyls() if abs(rr - r) < 0.01]
okp = len(got) == 4 and all(abs(fb[2] + T) < 0.05 and abs(fb[5] - (-T + dep)) < 0.05 for fb in got) and \
all(any(abs((fb[0] + fb[3]) / 2 - hx) < 0.05 and abs((fb[1] + fb[4]) / 2 - hy) < 0.05 for fb in got) for hx, hy in pts)
P(u' 切除 → %r;R%.2f 圆柱面 %d 个,z %.1f~%.1f、圆心在位 %s'
% (prop(c, 'Name') if c else c, r, len(got), -T, -T + dep, u'✅' if okp else u'⛔'))
if not okp:
P(u'⛔ 不存盘'); sys.exit(3)
h = {}
for rr, fb in cyls():
h[rr] = h.get(rr, 0) + 1
thru = [fb for rr, fb in cyls() if abs(rr - R_THRU) < 0.01]
ins = [fb for rr, fb in cyls() if abs(rr - R_INS) < 0.01]
P(u'\n[4] 验收 直方图 %s(期望 {1.7: 4, 2.1: 4, 3.25: 4, 4.0: 4, 5.0: 4})' % dict(sorted(h.items())))
ok_thru = len(thru) == 4 and all(abs(fb[2] - (-T + CB_D)) < 0.05 and abs(fb[5]) < 0.05 for fb in thru)
ok_ins = len(ins) == 4 and all(abs(fb[2]) < 0.05 and abs(fb[5] - POST_H) < 0.05 for fb in ins)
P(u' 过孔 z %.1f~0(沉头以上 1.6 实体)%s;柱顶螺母孔 z 0~10 盲孔 %s'
% (-T + CB_D, u'✅' if ok_thru else u'⛔', u'✅' if ok_ins else u'⛔'))
want = {1.7: 4, 2.1: 4, 3.25: 4, 4.0: 4, 5.0: 4}
if not (ok_thru and ok_ins) or any(h.get(k, 0) != v for k, v in want.items()):
P(u'⛔ 不存盘'); sys.exit(3)
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))
+107
查看文件
@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
u"""只读:端板 R2.1 螺母孔 ↔ 相邻板 R1.7 过孔 的同轴配对(按「横向对齐 + 轴向最近」配)。
swap_endplate_v12.py 的第一版只比横向偏差,南北板过孔 (x,z) 完全相同,
北侧 4 孔全被配到了南板 —— 等于没验北板。这里修正为轴向最近。
"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
import win32com.client as wc
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = app.ActiveDoc
print(u'总装', prop(doc, 'GetTitle'))
def world_holes(comp, radius):
xf = list(prop(comp, 'Transform2').ArrayData)
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
for bd in comp.GetBodies2(0) or []:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > 0.01:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
# 圆柱面的轴点在 CylinderParams 里是任意一点 ⇒ 取面 box 的轴向中点更有意义
fb = [v * 1000 for v in prop(D(f), 'GetBox')]
out.append((ga, gp, fb))
return out
nuts, thru = [], []
for c in doc.GetComponents(True):
nm = prop(c, 'Name2').upper()
if 'ENDPLATE' in nm:
nuts += [(nm, *h) for h in world_holes(c, 2.1)]
elif any(k in nm for k in ('SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL')):
thru += [(nm, *h) for h in world_holes(c, 1.7)]
# 过孔的装配 box 需要单独取(组件级 box 在世界坐标):用轴点 + 板位置判断即可
comp_box = {prop(c, 'Name2').upper(): [v * 1000 for v in c.GetBox(False, False)]
for c in doc.GetComponents(True)}
ok_n = 0
for nm, ga, gp, _ in nuts:
ai = max(range(3), key=lambda i: abs(ga[i]))
o = [i for i in range(3) if i != ai]
best = None
for tn, ta, tp, _ in thru:
if max(range(3), key=lambda i: abs(ta[i])) != ai:
continue
lat = max(abs(gp[i] - tp[i]) for i in range(3) if i != ai)
if lat >= 0.05:
continue
cb = comp_box[tn]
axial = abs((cb[ai] + cb[ai + 3]) / 2.0 - gp[ai]) # 板中面 ↔ 螺母孔轴点
if best is None or axial < best[0]:
best = (axial, tn, lat)
# 螺母孔在角柱里、紧贴该板 ⇒ 轴向距离应在 板厚 + 柱宽 量级(≤ 15)
ok = best is not None and best[0] <= 15.0
ok_n += ok
print(u' %-15s 轴%s (%s=%7.2f, %s=%6.2f) ↔ %-15s 横偏 %.3f 轴距 %5.2f %s'
% (nm[:15], 'xyz'[ai], 'xyz'[o[0]], gp[o[0]], 'xyz'[o[1]], gp[o[1]],
best[1][:15] if best else '-', best[2] if best else -1,
best[0] if best else -1, u'✅' if ok else u'⛔'))
print(u'配到相邻板 %d / %d' % (ok_n, len(nuts)))
# ── 端板 L 脚锁孔(R1.7、轴沿 z)↔ 托盘锁孔(R1.7、轴沿 z)────────────────
feet, tray = [], []
for c in doc.GetComponents(True):
nm = prop(c, 'Name2').upper()
hs = world_holes(c, 1.7)
zs = [(nm, ga, gp) for ga, gp, _ in hs if abs(ga[2]) > 0.99]
if 'ENDPLATE' in nm:
feet += zs # 风扇螺孔是轴沿 x 的,已被过滤掉
elif 'TRAY' in nm:
tray += zs
fok = 0
for nm, ga, gp in feet:
best = min((max(abs(gp[0] - tp[0]), abs(gp[1] - tp[1])), tn) for tn, _, tp in tray) if tray else None
ok = best is not None and best[0] < 0.05
fok += ok
print(u' %-15s 脚锁孔 (x=%7.2f, y=%6.2f) ↔ %-10s 横偏 %.3f %s'
% (nm[:15], gp[0], gp[1], best[1][:10] if best else '-', best[0] if best else -1, u'✅' if ok else u'⛔'))
print(u'脚锁孔对上托盘 %d / %d(期望 4)' % (fok, len(feet)))
+88
查看文件
@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
u"""算外壳各件两两之间的**贴合面**,为螺丝位置提供依据。
判据:两件在某一轴上「一个的 max ≈ 另一个的 min」(间隙 < 0.05),
且在另外两轴上有实际重叠 ⇒ 这两个面是贴合的,重叠矩形就是可打螺丝的区域。
外壳五件(装配坐标):
Tray_v7 x −4~102 y −4~102 z −12.4~0
EndPlate ×2 x −27.4~0 / 98~125.4 y −1.655~104.345 z −10~72
SouthPanel x −27.4~125.4 y −4.055~−1.655 z −10~72
NorthPanel x −27.4~125.4 y 104.345~106.745 z −10~72
TopPanel_v2 x −27.4~125.4 y −4~102 z 72~74.4
"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
import pythoncom
import win32com.client as wc
ASM = 'PhoneRail_5blades'
SHELL = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL')
AX = 'xyz'
def prop(o, n):
v = getattr(o, n)
return v() if type(v).__name__ in ('method', 'function', 'builtin_function_or_method') else v
def P(*a):
print(*a)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
if doc is None:
P(u'⛔ 没找到总装'); sys.exit(3)
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
parts = []
for c in doc.GetComponents(True):
nm = prop(c, 'Name2')
if any(k in nm.upper() for k in SHELL):
b = [round(v * 1000, 3) for v in c.GetBox(False, False)]
parts.append((nm, b))
parts.sort()
P(u'外壳件 %d 个:' % len(parts))
for nm, b in parts:
P(u' %-18s x %8.2f~%8.2f y %8.2f~%8.2f z %8.2f~%8.2f'
% (nm[:18], b[0], b[3], b[1], b[4], b[2], b[5]))
def ov(a0, a1, b0, b1):
lo, hi = max(a0, b0), min(a1, b1)
return (lo, hi) if hi - lo > 0.01 else None
P(u'\n贴合面(可打螺丝的区域):')
found = 0
for i in range(len(parts)):
for j in range(i + 1, len(parts)):
na, ba = parts[i]
nb, bb = parts[j]
for ax in range(3):
o1 = ov(ba[(ax + 1) % 3], ba[(ax + 1) % 3 + 3], bb[(ax + 1) % 3], bb[(ax + 1) % 3 + 3])
o2 = ov(ba[(ax + 2) % 3], ba[(ax + 2) % 3 + 3], bb[(ax + 2) % 3], bb[(ax + 2) % 3 + 3])
if not (o1 and o2):
continue
for tag, gap in ((u'%s.max=%s.min' % (na[:12], nb[:12]), abs(ba[ax + 3] - bb[ax])),
(u'%s.max=%s.min' % (nb[:12], na[:12]), abs(bb[ax + 3] - ba[ax]))):
if gap < 0.05:
found += 1
a1n, a2n = AX[(ax + 1) % 3], AX[(ax + 2) % 3]
P(u' %-28s 沿 %s 贴合' % (tag, AX[ax]))
P(u' 重叠区 %s %.2f~%.2f (%.1f) × %s %.2f~%.2f (%.1f)'
% (a1n, o1[0], o1[1], o1[1] - o1[0], a2n, o2[0], o2[1], o2[1] - o2[0]))
if not found:
P(u' ⚠ 一处贴合面都没有 —— 各板之间是悬空的,螺丝无处可打')
P(u'\n⇒ 螺丝方案要点:贴合区太窄(< 8 mm)就打不下 M3 沉头,')
P(u' 需要在端板/托盘上加凸台(boss)把贴合面加宽。')
+259
查看文件
@@ -0,0 +1,259 @@
# -*- coding: utf-8 -*-
u"""端板 v10 —— 15 mm 分风腔版(2026-09-13 用户拍板「两端各加 11 mm」)。
与 v9 的三处差别,每一处都有实测依据:
1. **L 脚加长 25 → 36**。端板整体外移 11(西端外表面 −27.4 → −38.4),
脚必须仍够到托盘上那两个锁孔(装配 x = −1.9 / 99.9,托盘不动)。
本地孔位随之 25.5 → **36.5**(离脚端 1.9,与 v9 的相对位置一模一样)。
2. **四根角柱**(本件出两根,东端板靠 180° 旋转出另两根)。
这是为了修**两条已坐实的螺丝缺陷**,不是新功能:
⛔ C 组(顶盖→端板)原打在 x=−13.70,可端板在 z=72 那一层**只有 2.4 厚的板本体**
(x −27.4~−25),L 脚在底部 z 0~2.4 ⇒ 4 颗螺丝拧的是空气。
贴合面分析给的「27.4 × 103.7 重叠区」是**包围盒**口径,看不出这一点
—— 与 NOTES 里「包围盒对通孔没有分辨力」同一族的假判据。
⛔ A 组(侧板→端板)要拧进 2.4 的板边,攻不住 M3;v6/v7/v8 三版凸台全部失败
(面上草图只有第一次生效、凸块被板体吞并)。
✅ 角柱把两组螺丝要的「肉」一次给够:柱截面 8(y) × 10(x)、通高 z 0~72。
C 组沿 z 从柱顶打进去、A 组沿 y 从侧板打进柱侧面,两个方向都有 8~10 mm 材料。
做法是**右视基准面草图 + 沿 x 拉伸**,与 L 脚同款 —— 本机唯一可靠的那条路。
角柱摆位(避让实测过的一切):
· 南柱 y −1.655~6.655、北柱 y 96.035~104.345 —— **关于风口中心 51.345 严格对称**,
东端板绕 z 翻转后自动对位(v5 那次不对称吃过亏)。
· 风扇占 y 19.87~82.82 ⇒ 两柱都不碰风扇。
· 柱沿 x 只伸 10(装配 x −36~−26),底板 PCB 从 x=0.01 才开始 ⇒ 不压板。
3. 其余(Ø60 风口、4 × Ø3.4 风扇螺孔、板厚、y 对称范围、z 范围)**与 v9 逐字相同**。
⚠ 仍留给 GUI 的孔(本机 `FeatureCut4` 做不出这两类轴向):
· C 组 4 × Ø4.2 深 6(沿 z,柱顶面 z=72,装配 x=−31/129、y=2.50/100.19)
· A 组 8 × Ø4.2 深 6(沿 y,柱的南/北外侧面,z=10 与 z=60)
——现在**至少有肉可打**了,这是 v10 的全部意义。
"""
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\\EndPlate_v10.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
PLATE_T = 2.4
Y0, Y1 = 51.345 - 53.0, 51.345 + 53.0 # −1.655 ~ 104.345(以风口中心对称)
Z0, Z1 = -10.0, 72.0
FAN_CY, FAN_CZ = 51.345, 37.85
VENT_D = 60.0
FAN_SCREW = 50.0
SCREW_D = 3.4
# ⛔ 25 → 36:端板外移 11 之后,脚要够到托盘上原有的锁孔(装配 x −1.9 / 99.9)。
FOOT_LEN = 36.0
FOOT_T = 2.4
FOOT_HOLES_LOCAL = [(36.5, 21.345), (36.5, 81.345)] # 本地 x = 装配 x − (−38.4)
# 角柱:截面 8(y) × 通高 z 0~72,沿 x 从板内表面伸 POST_LEN
POST_W = 8.0
POST_LEN = 10.0
POST_Z0, POST_Z1 = 0.0, Z1 # 坐在托盘面板上表面,顶面与板顶齐平
POST_Y = [(Y0, Y0 + POST_W), (Y1 - POST_W, Y1)]
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 RZ(z):
u"""右视基准面:草图 u → 模型 −z(NOTES §34 实测)"""
return M(-z)
app = wc.GetActiveObject('SldWorks.Application')
# 只关同名的旧 v10(重跑时),不碰 v9 —— 总装还引用着它
for d in list(app.GetDocuments or []):
try:
t = prop(d, 'GetTitle') or ''
if 'ENDPLATE_V10' in t.upper():
app.CloseDoc(t)
P(u'关掉旧文档 %s' % 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 sk_name():
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 extrude(depth, rev=False):
if pick(sk_name(), '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 bodies():
return part.GetBodies2(0, True) or []
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 faces():
b = bodies()
return prop(D(b[0]), 'GetFaces') if b else []
def cyl_hist():
rs = {}
for f in faces():
s = prop(D(f), 'GetSurface')
if prop(s, 'IsCylinder'):
r = round(list(prop(s, 'CylinderParams'))[6] * 1000, 2)
rs[r] = rs.get(r, 0) + 1
return dict(sorted(rs.items()))
def check(tag, want, tol=0.02):
got = bbox()
ok = got and all(abs(got[i] - want[i]) <= tol for i in range(6))
P(u' %s box %s 期望 %s %s' % (tag, got, want, u'✅' if ok else u'⛔'))
return ok
# ── 1) 板本体 + Ø60 风口 + 4 风扇螺孔:同一张草图一次成形 ─────────────────
P(u'\n[1] 板本体 %.0f(z) × %.0f(y) × %.1f,含 Ø%.0f 风口 + 4× Ø%.1f 螺孔'
% (Z1 - Z0, Y1 - Y0, PLATE_T, VENT_D, SCREW_D))
pick(u'右视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True # ⛔ 不开这个,自动捕捉会静默改掉圆心与半径
sm.CreateCornerRectangle(RZ(Z0), M(Y0), 0.0, RZ(Z1), M(Y1), 0.0)
sm.CreateCircleByRadius(RZ(FAN_CZ), M(FAN_CY), 0.0, M(VENT_D / 2))
for dy in (-FAN_SCREW / 2, FAN_SCREW / 2):
for dz in (-FAN_SCREW / 2, FAN_SCREW / 2):
sm.CreateCircleByRadius(RZ(FAN_CZ + dz), M(FAN_CY + dy), 0.0, M(SCREW_D / 2))
sm.AddToDB = False
segs = prop(D(prop(sm, 'ActiveSketch')), 'GetSketchSegments')
P(u' 草图 segment = %d(期望 4 线 + 5 圆 = 9)' % (len(segs) if segs else 0))
for _s in (segs or []): # 回读几何:只数条数查不出吸附
_ss = D(_s)
if prop(_ss, 'GetType') != 1:
continue
_c = D(prop(_ss, 'GetCenterPoint2'))
P(u' 圆 r=%.3f 圆心=(%.3f, %.3f)'
% (prop(_ss, 'GetRadius') * 1000, prop(_c, 'X') * 1000, prop(_c, 'Y') * 1000))
sm.InsertSketch(True)
extrude(PLATE_T)
if not check(u'板本体', [0.0, Y0, Z0, PLATE_T, Y1, Z1]):
P(u'⛔ 停手'); sys.exit(2)
h = cyl_hist()
P(u' 圆柱面直方图 %s' % h)
if h.get(30.0, 0) < 1 or h.get(1.7, 0) < 4:
P(u'⛔ 风口或螺孔没成形(包围盒看不出来),停手'); sys.exit(3)
# ── 2) L 脚(加长到 36)─────────────────────────────────────────────────
P(u'\n[2] L 脚 长 %.0f 厚 %.1f(z %.1f~%.1f,压在托盘面板上表面)'
% (FOOT_LEN, FOOT_T, 0.0, FOOT_T))
pick(u'右视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(RZ(0.0), M(Y0), 0.0, RZ(FOOT_T), M(Y1), 0.0)
sm.AddToDB = False
sm.InsertSketch(True)
extrude(PLATE_T + FOOT_LEN)
if not check(u'加 L 脚后', [0.0, Y0, Z0, PLATE_T + FOOT_LEN, Y1, Z1]):
P(u'⛔ 停手'); sys.exit(4)
# ── 3) 两根角柱(C 组与 A 组螺丝的「肉」)────────────────────────────────
P(u'\n[3] 角柱 ×2:截面 %.0f(y) × 通高 z %.1f~%.1f,沿 x 伸 %.0f'
% (POST_W, POST_Z0, POST_Z1, POST_LEN))
P(u' 南柱 y %.3f~%.3f / 北柱 y %.3f~%.3f(关于 %.3f 对称,风扇占 19.87~82.82 不碰)'
% (POST_Y[0][0], POST_Y[0][1], POST_Y[1][0], POST_Y[1][1], FAN_CY))
nf_before = len(faces())
pick(u'右视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
for py0, py1 in POST_Y:
sm.CreateCornerRectangle(RZ(POST_Z0), M(py0), 0.0, RZ(POST_Z1), M(py1), 0.0)
sm.AddToDB = False
_g = prop(D(prop(sm, 'ActiveSketch')), 'GetSketchSegments')
P(u' 草图 segment = %d(期望 8)' % (len(_g) if _g else 0))
sm.InsertSketch(True)
extrude(PLATE_T + POST_LEN)
# ⛔ 包围盒在这一步**没有分辨力**:柱只伸 12.4 < 脚的 38.4,box 一点不变。
# ⇒ 判据换成「x = PLATE_T+POST_LEN 处有没有法线 +x 的端面」,逐根数。
if not check(u'加角柱后(box 应与上一步相同)', [0.0, Y0, Z0, PLATE_T + FOOT_LEN, Y1, Z1]):
P(u'⛔ 停手'); sys.exit(5)
x_end = PLATE_T + POST_LEN
hit = []
for f in faces():
fc = D(f)
s = prop(fc, 'GetSurface')
if not prop(s, 'IsPlane'):
continue
if list(prop(s, 'PlaneParams'))[0] < 0.99: # 只要法线 +x
continue
fb = [round(v * 1000, 3) for v in prop(fc, 'GetBox')]
if abs(fb[0] - x_end) < 0.02 and abs(fb[3] - x_end) < 0.02:
hit.append((fb[1], fb[4], fb[2], fb[5], round(prop(fc, 'GetArea') * 1e6, 1)))
P(u' x=%.1f 处法线 +x 的端面 %d 个(期望 2 = 两根柱)' % (x_end, len(hit)))
for y0f, y1f, z0f, z1f, a in sorted(hit):
P(u' y %8.3f~%8.3f z %7.2f~%7.2f 面积 %7.1f mm²' % (y0f, y1f, z0f, z1f, a))
P(u' 面数 %d → %d' % (nf_before, len(faces())))
if len(hit) != 2:
P(u'⛔ 角柱没成形(包围盒查不出来,靠端面计数抓到的),停手'); sys.exit(6)
want_area = POST_W * (POST_Z1 - POST_Z0)
if any(abs(a - want_area) > 1.0 for *_, a in hit):
P(u'⚠ 柱端面面积与期望 %.0f mm² 不符 —— 可能被脚吞并了一段,人工看一眼' % want_area)
P(u'\n最终 box %s' % bbox())
P(u'最终圆柱面 %s' % cyl_hist())
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))
if not os.path.exists(DST):
P(u'⛔ 没落盘,停手(零件留着便于排查)'); sys.exit(7)
P(u'⚠ 零件保持打开(AddComponent4 要求目标文档在打开状态)')
+301
查看文件
@@ -0,0 +1,301 @@
# -*- coding: utf-8 -*-
u"""端板 v11 = v10 的几何 + **试着把 A/C 两组螺母孔做出来**。
v10 把「肉」给够了(4 根角柱),但孔一直留给 GUI,因为 `FeatureCut4` 在本机
连续 7 次返回 None。⇒ 本脚本按 NOTES 里**唯一成功过的那个配方**再试一次:
✅ 成功的那次:托盘柱顶孔 —— 草图建在**实体的面**上(而且是 z 常量面),沿面法线切。
⛔ 失败的那些:草图建在**基准面**上(前视/右视)去切。
C 组(沿 z,打进角柱顶面 z=72)与那次**构型完全相同**,成功概率最高。
A 组(沿 y,打进角柱外侧面)是同一招换个面,一并试。
⚠ 两条必须守住的纪律:
1. **不动 v10**。v10 已在总装里验过(干涉 360、贴合 10 组),v11 没通过就原样丢掉。
2. **判据是圆柱面半径直方图,而且要双向看**:
· 新增:R2.1 应该 +6(4 个 A + 2 个 C);
· **没丢**:R1.7 仍是 4(风扇螺孔)、R30 仍是 1(风口)。
NOTES 记过一次面上草图的异常拉伸**把已有 8 个孔全吞掉**,只看"新增"是抓不到的。
局部坐标(西端板;装配 x = 本地 x − 38.4,z 与装配同值):
· 角柱 本地 x 2.4~12.4(中心 **7.4** = 装配 −31)、通高 z 0~72
南柱 y −1.655~6.345(中心 **2.345**)/北柱 y 96.345~104.345(中心 **100.345**)
· C 组 2 孔:柱顶面 z=72,(x,y) = (7.4, 2.345) / (7.4, 100.345),Ø4.2 深 6
· A 组 4 孔:柱外侧面 y=−1.655 与 y=104.345,(x,z) = (7.4, 10) / (7.4, 60),Ø4.2 深 6
"""
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\\EndPlate_v11.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
PLATE_T = 2.4
Y0, Y1 = 51.345 - 53.0, 51.345 + 53.0
Z0, Z1 = -10.0, 72.0
FAN_CY, FAN_CZ = 51.345, 37.85
VENT_D = 60.0
FAN_SCREW = 50.0
SCREW_D = 3.4
FOOT_LEN = 36.0
FOOT_T = 2.4
POST_W = 8.0
POST_LEN = 10.0
POST_Z0, POST_Z1 = 0.0, Z1
POST_Y = [(Y0, Y0 + POST_W), (Y1 - POST_W, Y1)]
POST_CX = PLATE_T + POST_LEN / 2.0 # 7.4
INSERT_D, INSERT_DEPTH = 4.2, 6.0
C_HOLES = [(POST_CX, (POST_Y[0][0] + POST_Y[0][1]) / 2.0),
(POST_CX, (POST_Y[1][0] + POST_Y[1][1]) / 2.0)] # (x, y) @ z=72
A_Z = (10.0, 60.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 RZ(z):
return M(-z)
app = wc.GetActiveObject('SldWorks.Application')
for d in list(app.GetDocuments or []):
try:
t = prop(d, 'GetTitle') or ''
if 'ENDPLATE_V11' in t.upper():
app.CloseDoc(t)
P(u'关掉旧文档 %s' % 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 sk_name():
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 extrude(depth, rev=False):
if pick(sk_name(), '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 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 cyl_hist():
rs = {}
for f in faces():
s = prop(D(f), 'GetSurface')
if prop(s, 'IsCylinder'):
r = round(list(prop(s, 'CylinderParams'))[6] * 1000, 2)
rs[r] = rs.get(r, 0) + 1
return dict(sorted(rs.items()))
def pick_face(axis, sign, coord, tag):
u"""遍历选面:法线沿 ±axis、且该面整体落在 coord 上;取面积最大的那个。
⛔ 绝不按坐标 SelectByID2 撞面 —— 实测会静默选中邻面(NOTES §34)。"""
ai = {'x': 0, 'y': 1, 'z': 2}[axis]
best, barea, bbx = None, -1.0, None
for f in faces():
fc = D(f)
s = prop(fc, 'GetSurface')
if not prop(s, 'IsPlane'):
continue
# ⛔ PlaneParams 是**曲面**法线,不是面的朝外法线。本机实测(14 个平面逐一核过):
# FaceInSurfaceSense == True ⇒ 朝外法线 = −PlaneParams
# FaceInSurfaceSense == False ⇒ 朝外法线 = +PlaneParams
# v11 第一次跑就栽在这:三个面一个都没选到(FeatureCut4 根本没被调用)。
n = list(prop(s, 'PlaneParams'))[ai]
if prop(fc, 'FaceInSurfaceSense'):
n = -n
if sign * n < 0.99:
continue
fb = [round(v * 1000, 3) for v in prop(fc, 'GetBox')]
if abs(fb[ai] - coord) > 0.02 or abs(fb[ai + 3] - coord) > 0.02:
continue
a = prop(fc, 'GetArea') * 1e6
if a > barea:
best, barea, bbx = fc, a, fb
if best is None:
P(u' ⛔ %s:没找到法线 %s%s、位于 %.3f 的平面' % (tag, '+' if sign > 0 else '-', axis, coord))
return None
part.ClearSelection2(True)
best.Select4(False, EMPTY)
n = part.SelectionManager.GetSelectedObjectCount2(-1)
P(u' %s:选中面 面积 %.1f mm² box x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f 选中数=%d'
% (tag, barea, bbx[0], bbx[3], bbx[1], bbx[4], bbx[2], bbx[5], n))
return best if n == 1 else None
def cut(depth, tag):
if pick(sk_name(), 'SKETCH') != 1:
P(u' ⛔ %s 草图没选中' % tag)
return None
try:
c = 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)
except Exception as ex:
P(u' ⛔ %s FeatureCut4 异常 %s' % (tag, str(ex)[:90]))
return None
P(u' %s FeatureCut4 → %r' % (tag, prop(c, 'Name') if c else c))
return c
# ══ 1~3:与 v10 逐字相同的几何 ═══════════════════════════════════════════
P(u'\n[1] 板本体 + Ø60 风口 + 4 风扇螺孔')
pick(u'右视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(RZ(Z0), M(Y0), 0.0, RZ(Z1), M(Y1), 0.0)
sm.CreateCircleByRadius(RZ(FAN_CZ), M(FAN_CY), 0.0, M(VENT_D / 2))
for dy in (-FAN_SCREW / 2, FAN_SCREW / 2):
for dz in (-FAN_SCREW / 2, FAN_SCREW / 2):
sm.CreateCircleByRadius(RZ(FAN_CZ + dz), M(FAN_CY + dy), 0.0, M(SCREW_D / 2))
sm.AddToDB = False
sm.InsertSketch(True)
extrude(PLATE_T)
P(u' box %s 直方图 %s' % (bbox(), cyl_hist()))
P(u'\n[2] L 脚 长 %.0f' % FOOT_LEN)
pick(u'右视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(RZ(0.0), M(Y0), 0.0, RZ(FOOT_T), M(Y1), 0.0)
sm.AddToDB = False
sm.InsertSketch(True)
extrude(PLATE_T + FOOT_LEN)
P(u'\n[3] 角柱 ×2')
pick(u'右视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
for py0, py1 in POST_Y:
sm.CreateCornerRectangle(RZ(POST_Z0), M(py0), 0.0, RZ(POST_Z1), M(py1), 0.0)
sm.AddToDB = False
sm.InsertSketch(True)
extrude(PLATE_T + POST_LEN)
base_box = bbox()
base_hist = cyl_hist()
P(u' box %s 直方图 %s' % (base_box, base_hist))
if base_hist.get(1.7, 0) != 4 or base_hist.get(30.0, 0) != 1:
P(u'⛔ 基础几何就不对,停手'); sys.exit(2)
# ══ 4:C 组 —— 柱顶面(z 常量面)沿 z 切,与托盘柱顶孔同款配方 ═══════════
P(u'\n[4] C 组 2 × Ø%.1f 深 %.1f(沿 z,柱顶面 z=%.1f)' % (INSERT_D, INSERT_DEPTH, Z1))
f = pick_face('z', +1, Z1, u'柱顶面')
if f is not None:
sm.InsertSketch(True)
sm.AddToDB = True
# ⚠ z 常量面上草图的 uv 恰好 = 模型 xy(托盘那次的成功是因为这个,不是"面草图接受模型坐标")
for hx, hy in C_HOLES:
sm.CreateCircleByRadius(M(hx), M(hy), M(Z1), M(INSERT_D / 2))
sm.AddToDB = False
_g = prop(D(prop(sm, 'ActiveSketch')), 'GetSketchSegments')
P(u' 草图 segment = %d(期望 2)' % (len(_g) if _g else 0))
sm.InsertSketch(True)
cut(INSERT_DEPTH, u'C 组')
P(u' 直方图 %s' % cyl_hist())
# ══ 5:A 组 —— 柱外侧面(y 常量面)沿 y 切 ═══════════════════════════════
# ⛔ y 端面草图的映射:u → 模型 x、v → 模型 **−z**,第三参恒 0(NOTES §35 实测)。
# 直接把模型 z 塞进 v 会把孔甩到 z 的镜像位置。
P(u'\n[5] A 组 4 × Ø%.1f 深 %.1f(沿 y,柱外侧面 y=%.3f / %.3f)'
% (INSERT_D, INSERT_DEPTH, Y0, Y1))
for ynom, sign, tag in ((Y0, -1, u'南侧面'), (Y1, +1, u'北侧面')):
f = pick_face('y', sign, ynom, tag)
if f is None:
continue
sm.InsertSketch(True)
sm.AddToDB = True
for az in A_Z:
sm.CreateCircleByRadius(M(POST_CX), M(-az), 0.0, M(INSERT_D / 2))
sm.AddToDB = False
_g = prop(D(prop(sm, 'ActiveSketch')), 'GetSketchSegments')
P(u' 草图 segment = %d(期望 2)' % (len(_g) if _g else 0))
sm.InsertSketch(True)
cut(INSERT_DEPTH, tag)
P(u' 直方图 %s' % cyl_hist())
# ══ 6:验收 —— 双向判据 ═════════════════════════════════════════════════
P(u'\n[6] 验收')
h = cyl_hist()
b = bbox()
n_new = h.get(round(INSERT_D / 2, 2), 0)
n_fan = h.get(1.7, 0)
n_vent = h.get(30.0, 0)
box_ok = b and all(abs(b[i] - base_box[i]) < 0.02 for i in range(6))
P(u' 包围盒 %s %s' % (b, u'✅ 未变' if box_ok else u'⛔ 变了,说明切歪了'))
P(u' Ø%.1f 螺母孔 = %d 个(期望 6 = 4 A + 2 C)%s'
% (INSERT_D, n_new, u'✅' if n_new == 6 else (u'⚠ 少了 %d 个,剩下的留 GUI' % (6 - n_new))))
P(u' Ø3.4 风扇孔 = %d(必须仍是 4)%s' % (n_fan, u'✅' if n_fan == 4 else u'⛔ 被吃掉了'))
P(u' Ø60 风口 = %d(必须仍是 1)%s' % (n_vent, u'✅' if n_vent == 1 else u'⛔ 被吃掉了'))
if n_fan != 4 or n_vent != 1 or not box_ok:
P(u'\n⛔⛔ 已有特征被破坏 —— **不存盘**,v10 保持可用。')
sys.exit(3)
if n_new == 0:
P(u'\n⛔ 一个孔都没切出来 ——**不存盘**,孔仍留给 GUI。'
u'(先看上面:是选面失败还是 FeatureCut4 返回 None,别混为一谈)')
sys.exit(4)
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))
P(u'⚠ 零件保持打开')
+181
查看文件
@@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
u"""在已开着的 EndPlate_v11 上补南侧面 A 组 2 个孔,另存为 v12。
v11 实测:C 组 2 孔 + 北侧面 A 组 2 孔全部**位置正确**(回读轴线核过),
只有南侧面(朝外法线 −y)那一刀 `FeatureCut4 → None`。
北侧面(朝外 +y)用的映射 u→x、v→−z 是对的;
假设:**朝外 −y 的面上草图 u 轴是镜像的**(u→−x),圆画到了 x=−7.4、落在材料外,
没东西可切所以返回 None。
做法:按候选映射依次试,每刀之后回读「南侧应有的孔」是否出现在
x 7.4 / y −1.655~4.345 / z 10、60;对了就停。
候选里错误的映射都会把圆画到材料外(x −7.4 或 z −10/−60 处没有料)⇒ 只会返回 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\\EndPlate_v12.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
Y0 = -1.655
POST_CX = 7.4
A_Z = (10.0, 60.0)
R_INS = 2.1
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')
part = None
for d in app.GetDocuments or []:
if 'ENDPLATE_V11' in (prop(d, 'GetTitle') or '').upper():
part = d
break
if part is None:
P(u'⛔ EndPlate_v11 没开着'); sys.exit(2)
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(part, 'GetTitle'), False, 0, e0)
part = app.ActiveDoc
sm, fm = part.SketchManager, part.FeatureManager
P(u'零件 %s' % prop(part, 'GetTitle'))
def faces():
b = part.GetBodies2(0, True) or []
return prop(D(b[0]), 'GetFaces') if b else []
def holes():
u"""返回 [(r, 轴, box)]:所有圆柱面"""
out = []
for f in faces():
fc = D(f)
s = prop(fc, 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
fb = [round(v * 1000, 2) for v in prop(fc, 'GetBox')]
out.append((round(cp[6] * 1000, 2), (round(cp[3]), round(cp[4]), round(cp[5])), fb))
return out
def hist():
h = {}
for r, _, _ in holes():
h[r] = h.get(r, 0) + 1
return dict(sorted(h.items()))
def south_ok():
u"""南侧 2 孔是否都在正确位置"""
got = 0
for r, ax, fb in holes():
if abs(r - R_INS) > 0.01 or abs(ax[1]) != 1:
continue
cx, cz = (fb[0] + fb[3]) / 2, (fb[2] + fb[5]) / 2
if abs(cx - POST_CX) < 0.05 and fb[1] < 0 and any(abs(cz - z) < 0.05 for z in A_Z):
got += 1
return got
def pick_sketch():
nm, f = None, prop(part, 'FirstFeature')
while f:
if prop(f, 'GetTypeName2') == 'ProfileFeature':
nm = prop(f, 'Name')
f = prop(f, 'GetNextFeature')
part.ClearSelection2(True)
part.Extension.SelectByID2(nm, 'SKETCH', 0, 0, 0, False, 0, EMPTY, 0)
return part.SelectionManager.GetSelectedObjectCount2(-1)
def pick_south_face():
best, ba = None, -1.0
for f in faces():
fc = D(f)
s = prop(fc, 'GetSurface')
if not prop(s, 'IsPlane'):
continue
n = list(prop(s, 'PlaneParams'))[1]
if prop(fc, 'FaceInSurfaceSense'): # 朝外 = −曲面法线
n = -n
if n > -0.99:
continue
fb = [v * 1000 for v in prop(fc, 'GetBox')]
if abs(fb[1] - Y0) > 0.02 or abs(fb[4] - Y0) > 0.02:
continue
a = prop(fc, 'GetArea') * 1e6
if a > ba:
best, ba = fc, a
if best is None:
return False
part.ClearSelection2(True)
best.Select4(False, EMPTY)
return part.SelectionManager.GetSelectedObjectCount2(-1) == 1
before = hist()
P(u'开始 直方图 %s 南侧已有正确孔 %d' % (before, south_ok()))
if before.get(1.7, 0) != 4 or before.get(30.0, 0) != 1 or before.get(R_INS, 0) != 4:
P(u'⛔ 起点不是预期的 v11,停手'); sys.exit(3)
CANDIDATES = [(u'u→−x, v→−z', -1, -1), (u'u→+x, v→+z', +1, +1), (u'u→−x, v→+z', -1, +1)]
done = False
for tag, su, sv in CANDIDATES:
if not pick_south_face():
P(u' ⛔ 选不到南侧面'); break
sm.InsertSketch(True)
sm.AddToDB = True
for z in A_Z:
sm.CreateCircleByRadius(M(su * POST_CX), M(sv * z), 0.0, M(R_INS))
sm.AddToDB = False
sm.InsertSketch(True)
if pick_sketch() != 1:
P(u' ⛔ 草图没选中'); break
c = fm.FeatureCut4(True, False, False, 0, 0, M(6.0), 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)
n_ok = south_ok()
P(u' 试 %-14s → %-12r 直方图 %s 南侧正确孔 %d/2'
% (tag, prop(c, 'Name') if c else c, hist(), n_ok))
if n_ok == 2:
done = True
break
if c:
P(u' ⛔ 切出了东西但位置不对 —— 停手、不存盘'); sys.exit(4)
h = hist()
P(u'\n验收 直方图 %s' % h)
if not done or h.get(R_INS, 0) != 6 or h.get(1.7, 0) != 4 or h.get(30.0, 0) != 1:
P(u'⛔ 没达标,不存盘(v11 仍可用:C 组 + 北侧 A 组已成形)'); sys.exit(5)
P(u'全部孔位:')
for r, ax, fb in holes():
if abs(r - R_INS) < 0.01:
P(u' R%.2f 轴%s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f' % (r, ax, fb[0], fb[3], fb[1], fb[4], fb[2], fb[5]))
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))
+151
查看文件
@@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
u"""在已开着的 EndPlate_v12(已倒角、12 个螺母孔已验)上补 **L 脚 2 个 Ø3.4 锁孔**,另存 v13。
配方与 C 组相同(已验证可行):z 常量面(脚顶面 z=2.4,朝外 +z)上建草图,uv = 模型 xy,沿 z 切。
孔位:本地 x=36.5(装配 −1.9,对托盘原锁孔)、y=21.345 / 81.345,切深 2.6(脚厚 2.4,切穿)。
判据(双向):R1.7 由 4(风扇孔)→ **6**,且新增的 2 个轴沿 z、位于 x 36.5 / y 21.345、81.345;
R2.1 仍 6、R3.0 仍 4、R30 仍 1。
"""
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
SRC_TAG = 'ENDPLATE_V12'
DST = u'D:\\DCDC\\新建文件夹\\5blades\\EndPlate_v13.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
FOOT_TOP = 2.4
HOLES = [(36.5, 21.345), (36.5, 81.345)]
R = 1.7
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')
part = None
for d in app.GetDocuments or []:
if SRC_TAG in (prop(d, 'GetTitle') or '').upper():
part = d
break
if part is None:
P(u'⛔ %s 没开着' % SRC_TAG); sys.exit(2)
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(part, 'GetTitle'), False, 0, e0)
part = app.ActiveDoc
sm, fm = part.SketchManager, part.FeatureManager
P(u'零件 %s' % prop(part, 'GetTitle'))
def faces():
b = part.GetBodies2(0, True) or []
return prop(D(b[0]), 'GetFaces') if b else []
def cyls():
out = []
for f in faces():
fc = D(f)
s = prop(fc, 'GetSurface')
if prop(s, 'IsCylinder'):
cp = list(prop(s, 'CylinderParams'))
fb = [round(v * 1000, 2) for v in prop(fc, 'GetBox')]
out.append((round(cp[6] * 1000, 2), (round(cp[3]), round(cp[4]), round(cp[5])), fb))
return out
def hist():
h = {}
for r, _, _ in cyls():
h[r] = h.get(r, 0) + 1
return dict(sorted(h.items()))
def foot_ok():
n = 0
for r, ax, fb in cyls():
if abs(r - R) > 0.01 or abs(ax[2]) != 1:
continue
cx, cy = (fb[0] + fb[3]) / 2, (fb[1] + fb[4]) / 2
if any(abs(cx - hx) < 0.05 and abs(cy - hy) < 0.05 for hx, hy in HOLES):
n += 1
return n
h0 = hist()
P(u'开始 直方图 %s' % h0)
if h0.get(2.1, 0) != 6 or h0.get(1.7, 0) != 4 or h0.get(30.0, 0) != 1:
P(u'⛔ 起点不是预期的 v12,停手'); sys.exit(3)
best, ba = None, -1.0
for f in faces():
fc = D(f)
s = prop(fc, 'GetSurface')
if not prop(s, 'IsPlane'):
continue
nz = list(prop(s, 'PlaneParams'))[2]
if prop(fc, 'FaceInSurfaceSense'): # 朝外 = −曲面法线(本机实测)
nz = -nz
if nz < 0.99:
continue
fb = [v * 1000 for v in prop(fc, 'GetBox')]
if abs(fb[2] - FOOT_TOP) > 0.02 or abs(fb[5] - FOOT_TOP) > 0.02:
continue
a = prop(fc, 'GetArea') * 1e6
if a > ba:
best, ba = fc, a
if best is None:
P(u'⛔ 找不到脚顶面'); sys.exit(4)
part.ClearSelection2(True)
best.Select4(False, EMPTY)
P(u'脚顶面 面积 %.1f mm² 选中数 %d' % (ba, part.SelectionManager.GetSelectedObjectCount2(-1)))
sm.InsertSketch(True)
sm.AddToDB = True
for hx, hy in HOLES:
sm.CreateCircleByRadius(M(hx), M(hy), M(FOOT_TOP), M(R))
sm.AddToDB = False
sm.InsertSketch(True)
nm, f = None, prop(part, 'FirstFeature')
while f:
if prop(f, 'GetTypeName2') == 'ProfileFeature':
nm = prop(f, 'Name')
f = prop(f, 'GetNextFeature')
part.ClearSelection2(True)
part.Extension.SelectByID2(nm, 'SKETCH', 0, 0, 0, False, 0, EMPTY, 0)
c = fm.FeatureCut4(True, False, False, 0, 0, M(2.6), 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)
P(u'FeatureCut4 → %r' % (prop(c, 'Name') if c else c))
h = hist()
n = foot_ok()
P(u'直方图 %s 脚锁孔正确 %d/2' % (h, n))
if n != 2 or h.get(1.7, 0) != 6 or h.get(2.1, 0) != 6 or h.get(30.0, 0) != 1 or h.get(3.0, 0) != 4:
P(u'⛔ 未达标,不存盘'); sys.exit(5)
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'另存 err=%s 存在=%s %.0f KB'
% (e1.value, os.path.exists(DST), os.path.getsize(DST) / 1024.0 if os.path.exists(DST) else 0))
+167
查看文件
@@ -0,0 +1,167 @@
# -*- coding: utf-8 -*-
# 螺丝试装件 STL(2026-09-30):由 export_stl_v11 派生,只换 PARTS
# v11:导出 TopShell_v11(Type-C 开口放大)+ BottomPlate_v6
u"""导出 TopShell_v10 / BottomPlate_v6 为打印用 STL(2026-09-13)。
1. 从 swconst.tlb 读 swLengthUnit_e 里毫米的值(不靠记忆)
2. 备份当前 STL 用户设置 → 设为:二进制、毫米、自定义精度(偏差 0.01 mm、角度 5°)、不自动平移、
关预览与保存后信息框(否则会弹模态框卡住 COM,NOTES §34)
3. SaveAs3 导出(零件坐标)→ 恢复原设置
4. numpy-stl 读回核对:包围盒 = 模型包围盒(顺带验单位)、每条边恰被 2 个三角形共用(封闭)、
体积与 SolidWorks 质量属性体积一致
5. 顶盖翻转 180°(绕 x)使**顶面贴床**;两件都平移到 x/y/z ≥ 0;报告贴床面积
"""
import io
import os
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
import numpy as np
import pythoncom
import win32com.client as wc
from stl import mesh as stlmesh
PARTS = [(u'Screw_M3x10_DIN7984', True), (u'Screw_M3x8_DIN7984', True), (u'FanScrew_5x12', True)] # 头朝下立着打 # (零件名, 是否倒扣打印)
SRC_DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
OUT_DIR = SRC_DIR + u'print\\'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
T_BIN, T_INFO, T_NOTRANS, T_PREVIEW = 69, 70, 71, 191
I_QUAL, I_UNITS = 78, 211
D_DEV, D_ANG = 2, 3
Q_CUSTOM = 3
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)
tlb = pythoncom.LoadTypeLib(r'D:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\swconst.tlb')
MM = None
for i in range(tlb.GetTypeInfoCount()):
if tlb.GetDocumentation(i)[0] != 'swLengthUnit_e':
continue
ti = tlb.GetTypeInfo(i)
for v in range(ti.GetTypeAttr().cVars):
vd = ti.GetVarDesc(v)
if ti.GetNames(vd.memid)[0] == 'swMM':
MM = vd.value
P(u'swLengthUnit_e.swMM = %s' % MM)
if MM is None:
P(u'⛔ 类型库里找不到毫米枚举,停手'); sys.exit(2)
app = wc.GetActiveObject('SldWorks.Application')
backup = {
'T': {k: app.GetUserPreferenceToggle(k) for k in (T_BIN, T_INFO, T_NOTRANS, T_PREVIEW)},
'I': {k: app.GetUserPreferenceIntegerValue(k) for k in (I_QUAL, I_UNITS)},
'D': {k: app.GetUserPreferenceDoubleValue(k) for k in (D_DEV, D_ANG)},
}
P(u'原 STL 设置 %s' % backup)
app.SetUserPreferenceToggle(T_BIN, True)
app.SetUserPreferenceToggle(T_INFO, False)
app.SetUserPreferenceToggle(T_NOTRANS, True)
app.SetUserPreferenceToggle(T_PREVIEW, False)
app.SetUserPreferenceIntegerValue(I_QUAL, Q_CUSTOM)
app.SetUserPreferenceIntegerValue(I_UNITS, MM)
app.SetUserPreferenceDoubleValue(D_DEV, 0.01 / 1000.0)
app.SetUserPreferenceDoubleValue(D_ANG, np.radians(5.0))
P(u'已设:二进制 / 毫米 / 自定义 偏差 0.01 mm 角度 5° / 不平移 / 关预览与信息框')
os.makedirs(OUT_DIR, exist_ok=True)
results = []
try:
for name, flip in PARTS:
path = SRC_DIR + name + u'.SLDPRT'
part = None
for d in app.GetDocuments or []:
if (prop(d, 'GetPathName') or '').lower() == path.lower():
part = d
if part is None:
e = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
part = app.OpenDoc6(path, 1, 1, '', e, w)
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(part, 'GetTitle'), False, 0, e0)
part = app.ActiveDoc
bodies = part.GetBodies2(0, True) or []
bb = [v * 1000 for v in prop(D(bodies[0]), 'GetBodyBox')]
vol_sw = None
try:
mp = part.Extension.CreateMassProperty
vol_sw = prop(D(mp), 'Volume') * 1e9
except Exception as ex:
P(u' 质量属性读取失败 %s' % str(ex)[:60])
# ⛔ 零件里若有选中的面/边,SaveAs STL 只导出选中部分(首次导出顶盖只得到标识凹槽 252 个三角形、
# 底盖只得到一个倒角 36 个三角形)⇒ 导出前必须清空选择
part.ClearSelection2(True)
P(u' 导出前选中数 %d(应 0)' % part.SelectionManager.GetSelectedObjectCount2(-1))
raw = OUT_DIR + u'_raw_' + name + u'.stl'
e1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
part.Extension.SaveAs3(raw, 0, 1, EMPTY, EMPTY, e1, w1)
ok_file = os.path.exists(raw) and os.path.getsize(raw) > 1000
P(u'\n== %s == 导出 err=%s 存在=%s %.0f KB' % (name, e1.value, ok_file, os.path.getsize(raw) / 1024 if ok_file else 0))
if not ok_file:
results.append((name, False)); continue
m = stlmesh.Mesh.from_file(raw)
pts = m.vectors.reshape(-1, 3)
mn, mx = pts.min(axis=0), pts.max(axis=0)
box_ok = np.allclose(mn, bb[0:3], atol=0.05) and np.allclose(mx, bb[3:6], atol=0.05)
P(u' 三角形 %d;STL 包围盒 %s ~ %s' % (len(m.vectors), np.round(mn, 3), np.round(mx, 3)))
P(u' 模型包围盒 %s ~ %s %s(单位毫米)' % (np.round(bb[0:3], 3), np.round(bb[3:6], 3), u'✅' if box_ok else u'⛔'))
q = np.round(pts / 1e-4).astype(np.int64).reshape(-1, 3, 3)
edges = {}
for tri in q:
a, b, c = (tuple(v) for v in tri)
for e_ in ((a, b), (b, c), (c, a)):
k = (min(e_), max(e_))
edges[k] = edges.get(k, 0) + 1
bad = sum(1 for v in edges.values() if v != 2)
P(u' 边 %d 条,非「恰 2 个三角形共用」的边 %d 条 %s' % (len(edges), bad, u'✅ 封闭' if bad == 0 else u'⛔ 有破洞/非流形'))
vol, cog, inertia = m.get_mass_properties()
vol = abs(vol)
if vol_sw:
dv = abs(vol - vol_sw) / vol_sw * 100
P(u' 体积 STL %.1f mm³ / SolidWorks %.1f mm³,差 %.3f%% %s' % (vol, vol_sw, dv, u'✅' if dv < 0.5 else u'⛔'))
else:
dv = 0.0
P(u' 体积 STL %.1f mm³(SolidWorks 体积没读到,未比对)' % vol)
if flip:
m.rotate([1.0, 0.0, 0.0], np.radians(180.0)) # 绕 x 翻 180°:顶面朝下贴床
pts = m.vectors.reshape(-1, 3)
m.vectors -= pts.min(axis=0)
pts = m.vectors.reshape(-1, 3)
size = pts.max(axis=0) - pts.min(axis=0)
z0 = m.vectors[:, :, 2]
on_bed = np.all(np.abs(z0) < 1e-3, axis=1)
bed_area = float(m.areas[on_bed].sum())
out = OUT_DIR + u'PhoneRail_' + name + u'.stl'
m.save(out)
P(u' 打印朝向:%s;外形 %.1f × %.1f × %.1f;贴床面积 %.0f mm²' %
(u'倒扣(顶面贴床)' if flip else u'平放(底面贴床)', size[0], size[1], size[2], bed_area))
P(u' → %s %.0f KB' % (out, os.path.getsize(out) / 1024))
os.remove(raw)
results.append((name, box_ok and bad == 0 and dv < 0.5))
finally:
for k, v in backup['T'].items():
app.SetUserPreferenceToggle(k, v)
for k, v in backup['I'].items():
app.SetUserPreferenceIntegerValue(k, v)
for k, v in backup['D'].items():
app.SetUserPreferenceDoubleValue(k, v)
P(u'\n已恢复原 STL 设置')
P(u'总判:%s' % (u'✅ 全过' if all(ok for _, ok in results) and len(results) == len(PARTS) else u'⛔ %s' % results))
+165
查看文件
@@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
u"""导出 TopShell_v10 / BottomPlate_v6 为打印用 STL(2026-09-13)。
1. 从 swconst.tlb 读 swLengthUnit_e 里毫米的值(不靠记忆)
2. 备份当前 STL 用户设置 → 设为:二进制、毫米、自定义精度(偏差 0.01 mm、角度 5°)、不自动平移、
关预览与保存后信息框(否则会弹模态框卡住 COM,NOTES §34)
3. SaveAs3 导出(零件坐标)→ 恢复原设置
4. numpy-stl 读回核对:包围盒 = 模型包围盒(顺带验单位)、每条边恰被 2 个三角形共用(封闭)、
体积与 SolidWorks 质量属性体积一致
5. 顶盖翻转 180°(绕 x)使**顶面贴床**;两件都平移到 x/y/z ≥ 0;报告贴床面积
"""
import io
import os
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
import numpy as np
import pythoncom
import win32com.client as wc
from stl import mesh as stlmesh
PARTS = [(u'TopShell_v10', True), (u'BottomPlate_v6', False)] # (零件名, 是否倒扣打印)
SRC_DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
OUT_DIR = SRC_DIR + u'print\\'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
T_BIN, T_INFO, T_NOTRANS, T_PREVIEW = 69, 70, 71, 191
I_QUAL, I_UNITS = 78, 211
D_DEV, D_ANG = 2, 3
Q_CUSTOM = 3
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)
tlb = pythoncom.LoadTypeLib(r'D:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\swconst.tlb')
MM = None
for i in range(tlb.GetTypeInfoCount()):
if tlb.GetDocumentation(i)[0] != 'swLengthUnit_e':
continue
ti = tlb.GetTypeInfo(i)
for v in range(ti.GetTypeAttr().cVars):
vd = ti.GetVarDesc(v)
if ti.GetNames(vd.memid)[0] == 'swMM':
MM = vd.value
P(u'swLengthUnit_e.swMM = %s' % MM)
if MM is None:
P(u'⛔ 类型库里找不到毫米枚举,停手'); sys.exit(2)
app = wc.GetActiveObject('SldWorks.Application')
backup = {
'T': {k: app.GetUserPreferenceToggle(k) for k in (T_BIN, T_INFO, T_NOTRANS, T_PREVIEW)},
'I': {k: app.GetUserPreferenceIntegerValue(k) for k in (I_QUAL, I_UNITS)},
'D': {k: app.GetUserPreferenceDoubleValue(k) for k in (D_DEV, D_ANG)},
}
P(u'原 STL 设置 %s' % backup)
app.SetUserPreferenceToggle(T_BIN, True)
app.SetUserPreferenceToggle(T_INFO, False)
app.SetUserPreferenceToggle(T_NOTRANS, True)
app.SetUserPreferenceToggle(T_PREVIEW, False)
app.SetUserPreferenceIntegerValue(I_QUAL, Q_CUSTOM)
app.SetUserPreferenceIntegerValue(I_UNITS, MM)
app.SetUserPreferenceDoubleValue(D_DEV, 0.01 / 1000.0)
app.SetUserPreferenceDoubleValue(D_ANG, np.radians(5.0))
P(u'已设:二进制 / 毫米 / 自定义 偏差 0.01 mm 角度 5° / 不平移 / 关预览与信息框')
os.makedirs(OUT_DIR, exist_ok=True)
results = []
try:
for name, flip in PARTS:
path = SRC_DIR + name + u'.SLDPRT'
part = None
for d in app.GetDocuments or []:
if (prop(d, 'GetPathName') or '').lower() == path.lower():
part = d
if part is None:
e = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
part = app.OpenDoc6(path, 1, 1, '', e, w)
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(part, 'GetTitle'), False, 0, e0)
part = app.ActiveDoc
bodies = part.GetBodies2(0, True) or []
bb = [v * 1000 for v in prop(D(bodies[0]), 'GetBodyBox')]
vol_sw = None
try:
mp = part.Extension.CreateMassProperty
vol_sw = prop(D(mp), 'Volume') * 1e9
except Exception as ex:
P(u' 质量属性读取失败 %s' % str(ex)[:60])
# ⛔ 零件里若有选中的面/边,SaveAs STL 只导出选中部分(首次导出顶盖只得到标识凹槽 252 个三角形、
# 底盖只得到一个倒角 36 个三角形)⇒ 导出前必须清空选择
part.ClearSelection2(True)
P(u' 导出前选中数 %d(应 0)' % part.SelectionManager.GetSelectedObjectCount2(-1))
raw = OUT_DIR + u'_raw_' + name + u'.stl'
e1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
part.Extension.SaveAs3(raw, 0, 1, EMPTY, EMPTY, e1, w1)
ok_file = os.path.exists(raw) and os.path.getsize(raw) > 1000
P(u'\n== %s == 导出 err=%s 存在=%s %.0f KB' % (name, e1.value, ok_file, os.path.getsize(raw) / 1024 if ok_file else 0))
if not ok_file:
results.append((name, False)); continue
m = stlmesh.Mesh.from_file(raw)
pts = m.vectors.reshape(-1, 3)
mn, mx = pts.min(axis=0), pts.max(axis=0)
box_ok = np.allclose(mn, bb[0:3], atol=0.05) and np.allclose(mx, bb[3:6], atol=0.05)
P(u' 三角形 %d;STL 包围盒 %s ~ %s' % (len(m.vectors), np.round(mn, 3), np.round(mx, 3)))
P(u' 模型包围盒 %s ~ %s %s(单位毫米)' % (np.round(bb[0:3], 3), np.round(bb[3:6], 3), u'✅' if box_ok else u'⛔'))
q = np.round(pts / 1e-4).astype(np.int64).reshape(-1, 3, 3)
edges = {}
for tri in q:
a, b, c = (tuple(v) for v in tri)
for e_ in ((a, b), (b, c), (c, a)):
k = (min(e_), max(e_))
edges[k] = edges.get(k, 0) + 1
bad = sum(1 for v in edges.values() if v != 2)
P(u' 边 %d 条,非「恰 2 个三角形共用」的边 %d 条 %s' % (len(edges), bad, u'✅ 封闭' if bad == 0 else u'⛔ 有破洞/非流形'))
vol, cog, inertia = m.get_mass_properties()
vol = abs(vol)
if vol_sw:
dv = abs(vol - vol_sw) / vol_sw * 100
P(u' 体积 STL %.1f mm³ / SolidWorks %.1f mm³,差 %.3f%% %s' % (vol, vol_sw, dv, u'✅' if dv < 0.5 else u'⛔'))
else:
dv = 0.0
P(u' 体积 STL %.1f mm³(SolidWorks 体积没读到,未比对)' % vol)
if flip:
m.rotate([1.0, 0.0, 0.0], np.radians(180.0)) # 绕 x 翻 180°:顶面朝下贴床
pts = m.vectors.reshape(-1, 3)
m.vectors -= pts.min(axis=0)
pts = m.vectors.reshape(-1, 3)
size = pts.max(axis=0) - pts.min(axis=0)
z0 = m.vectors[:, :, 2]
on_bed = np.all(np.abs(z0) < 1e-3, axis=1)
bed_area = float(m.areas[on_bed].sum())
out = OUT_DIR + u'PhoneRail_' + name + u'.stl'
m.save(out)
P(u' 打印朝向:%s;外形 %.1f × %.1f × %.1f;贴床面积 %.0f mm²' %
(u'倒扣(顶面贴床)' if flip else u'平放(底面贴床)', size[0], size[1], size[2], bed_area))
P(u' → %s %.0f KB' % (out, os.path.getsize(out) / 1024))
os.remove(raw)
results.append((name, box_ok and bad == 0 and dv < 0.5))
finally:
for k, v in backup['T'].items():
app.SetUserPreferenceToggle(k, v)
for k, v in backup['I'].items():
app.SetUserPreferenceIntegerValue(k, v)
for k, v in backup['D'].items():
app.SetUserPreferenceDoubleValue(k, v)
P(u'\n已恢复原 STL 设置')
P(u'总判:%s' % (u'✅ 全过' if all(ok for _, ok in results) and len(results) == len(PARTS) else u'⛔ %s' % results))
+166
查看文件
@@ -0,0 +1,166 @@
# -*- coding: utf-8 -*-
# v11:导出 TopShell_v11(Type-C 开口放大)+ BottomPlate_v6
u"""导出 TopShell_v10 / BottomPlate_v6 为打印用 STL(2026-09-13)。
1. 从 swconst.tlb 读 swLengthUnit_e 里毫米的值(不靠记忆)
2. 备份当前 STL 用户设置 → 设为:二进制、毫米、自定义精度(偏差 0.01 mm、角度 5°)、不自动平移、
关预览与保存后信息框(否则会弹模态框卡住 COM,NOTES §34)
3. SaveAs3 导出(零件坐标)→ 恢复原设置
4. numpy-stl 读回核对:包围盒 = 模型包围盒(顺带验单位)、每条边恰被 2 个三角形共用(封闭)、
体积与 SolidWorks 质量属性体积一致
5. 顶盖翻转 180°(绕 x)使**顶面贴床**;两件都平移到 x/y/z ≥ 0;报告贴床面积
"""
import io
import os
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
import numpy as np
import pythoncom
import win32com.client as wc
from stl import mesh as stlmesh
PARTS = [(u'TopShell_v11', True), (u'BottomPlate_v6', False)] # (零件名, 是否倒扣打印)
SRC_DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
OUT_DIR = SRC_DIR + u'print\\'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
T_BIN, T_INFO, T_NOTRANS, T_PREVIEW = 69, 70, 71, 191
I_QUAL, I_UNITS = 78, 211
D_DEV, D_ANG = 2, 3
Q_CUSTOM = 3
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)
tlb = pythoncom.LoadTypeLib(r'D:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\swconst.tlb')
MM = None
for i in range(tlb.GetTypeInfoCount()):
if tlb.GetDocumentation(i)[0] != 'swLengthUnit_e':
continue
ti = tlb.GetTypeInfo(i)
for v in range(ti.GetTypeAttr().cVars):
vd = ti.GetVarDesc(v)
if ti.GetNames(vd.memid)[0] == 'swMM':
MM = vd.value
P(u'swLengthUnit_e.swMM = %s' % MM)
if MM is None:
P(u'⛔ 类型库里找不到毫米枚举,停手'); sys.exit(2)
app = wc.GetActiveObject('SldWorks.Application')
backup = {
'T': {k: app.GetUserPreferenceToggle(k) for k in (T_BIN, T_INFO, T_NOTRANS, T_PREVIEW)},
'I': {k: app.GetUserPreferenceIntegerValue(k) for k in (I_QUAL, I_UNITS)},
'D': {k: app.GetUserPreferenceDoubleValue(k) for k in (D_DEV, D_ANG)},
}
P(u'原 STL 设置 %s' % backup)
app.SetUserPreferenceToggle(T_BIN, True)
app.SetUserPreferenceToggle(T_INFO, False)
app.SetUserPreferenceToggle(T_NOTRANS, True)
app.SetUserPreferenceToggle(T_PREVIEW, False)
app.SetUserPreferenceIntegerValue(I_QUAL, Q_CUSTOM)
app.SetUserPreferenceIntegerValue(I_UNITS, MM)
app.SetUserPreferenceDoubleValue(D_DEV, 0.01 / 1000.0)
app.SetUserPreferenceDoubleValue(D_ANG, np.radians(5.0))
P(u'已设:二进制 / 毫米 / 自定义 偏差 0.01 mm 角度 5° / 不平移 / 关预览与信息框')
os.makedirs(OUT_DIR, exist_ok=True)
results = []
try:
for name, flip in PARTS:
path = SRC_DIR + name + u'.SLDPRT'
part = None
for d in app.GetDocuments or []:
if (prop(d, 'GetPathName') or '').lower() == path.lower():
part = d
if part is None:
e = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
part = app.OpenDoc6(path, 1, 1, '', e, w)
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(part, 'GetTitle'), False, 0, e0)
part = app.ActiveDoc
bodies = part.GetBodies2(0, True) or []
bb = [v * 1000 for v in prop(D(bodies[0]), 'GetBodyBox')]
vol_sw = None
try:
mp = part.Extension.CreateMassProperty
vol_sw = prop(D(mp), 'Volume') * 1e9
except Exception as ex:
P(u' 质量属性读取失败 %s' % str(ex)[:60])
# ⛔ 零件里若有选中的面/边,SaveAs STL 只导出选中部分(首次导出顶盖只得到标识凹槽 252 个三角形、
# 底盖只得到一个倒角 36 个三角形)⇒ 导出前必须清空选择
part.ClearSelection2(True)
P(u' 导出前选中数 %d(应 0)' % part.SelectionManager.GetSelectedObjectCount2(-1))
raw = OUT_DIR + u'_raw_' + name + u'.stl'
e1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
part.Extension.SaveAs3(raw, 0, 1, EMPTY, EMPTY, e1, w1)
ok_file = os.path.exists(raw) and os.path.getsize(raw) > 1000
P(u'\n== %s == 导出 err=%s 存在=%s %.0f KB' % (name, e1.value, ok_file, os.path.getsize(raw) / 1024 if ok_file else 0))
if not ok_file:
results.append((name, False)); continue
m = stlmesh.Mesh.from_file(raw)
pts = m.vectors.reshape(-1, 3)
mn, mx = pts.min(axis=0), pts.max(axis=0)
box_ok = np.allclose(mn, bb[0:3], atol=0.05) and np.allclose(mx, bb[3:6], atol=0.05)
P(u' 三角形 %d;STL 包围盒 %s ~ %s' % (len(m.vectors), np.round(mn, 3), np.round(mx, 3)))
P(u' 模型包围盒 %s ~ %s %s(单位毫米)' % (np.round(bb[0:3], 3), np.round(bb[3:6], 3), u'✅' if box_ok else u'⛔'))
q = np.round(pts / 1e-4).astype(np.int64).reshape(-1, 3, 3)
edges = {}
for tri in q:
a, b, c = (tuple(v) for v in tri)
for e_ in ((a, b), (b, c), (c, a)):
k = (min(e_), max(e_))
edges[k] = edges.get(k, 0) + 1
bad = sum(1 for v in edges.values() if v != 2)
P(u' 边 %d 条,非「恰 2 个三角形共用」的边 %d 条 %s' % (len(edges), bad, u'✅ 封闭' if bad == 0 else u'⛔ 有破洞/非流形'))
vol, cog, inertia = m.get_mass_properties()
vol = abs(vol)
if vol_sw:
dv = abs(vol - vol_sw) / vol_sw * 100
P(u' 体积 STL %.1f mm³ / SolidWorks %.1f mm³,差 %.3f%% %s' % (vol, vol_sw, dv, u'✅' if dv < 0.5 else u'⛔'))
else:
dv = 0.0
P(u' 体积 STL %.1f mm³(SolidWorks 体积没读到,未比对)' % vol)
if flip:
m.rotate([1.0, 0.0, 0.0], np.radians(180.0)) # 绕 x 翻 180°:顶面朝下贴床
pts = m.vectors.reshape(-1, 3)
m.vectors -= pts.min(axis=0)
pts = m.vectors.reshape(-1, 3)
size = pts.max(axis=0) - pts.min(axis=0)
z0 = m.vectors[:, :, 2]
on_bed = np.all(np.abs(z0) < 1e-3, axis=1)
bed_area = float(m.areas[on_bed].sum())
out = OUT_DIR + u'PhoneRail_' + name + u'.stl'
m.save(out)
P(u' 打印朝向:%s;外形 %.1f × %.1f × %.1f;贴床面积 %.0f mm²' %
(u'倒扣(顶面贴床)' if flip else u'平放(底面贴床)', size[0], size[1], size[2], bed_area))
P(u' → %s %.0f KB' % (out, os.path.getsize(out) / 1024))
os.remove(raw)
results.append((name, box_ok and bad == 0 and dv < 0.5))
finally:
for k, v in backup['T'].items():
app.SetUserPreferenceToggle(k, v)
for k, v in backup['I'].items():
app.SetUserPreferenceIntegerValue(k, v)
for k, v in backup['D'].items():
app.SetUserPreferenceDoubleValue(k, v)
P(u'\n已恢复原 STL 设置')
P(u'总判:%s' % (u'✅ 全过' if all(ok for _, ok in results) and len(results) == len(PARTS) else u'⛔ %s' % results))
+159
查看文件
@@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
u"""**插拔式滤网框** FilterFrame_v1(2026-09-13)—— 插进 TopShell_v2 西端墙外侧的导轨里。
70(宽) × 90(高) × 3(厚),中间 62×62 窗口(盖住 Ø60 风口,4 颗风扇螺丝头 Ø5.6×1.8 都落在窗口里,
框体可以平贴墙面;进风吸力把它压在墙上 ⇒ 基本自密封)。
窗口里有 1 mm 厚、1 mm 宽的 5×5 格栅,用来托住粘上去的滤网纱 / 滤网棉(滤材本身外购)。
零件本地:前视基准面,a(=x) 0~70 → 装配 y,b(=y) 0~90 → 装配 z,c(=z) → 装配 x。
[A] 贴墙层 c 0~2:外框 + 62×62 窗口(向 +z 拉 2)
[B] 外层 c −1~0:外框 + 6×6 格孔(向 −z 拉 1)—— 同一基准面两个方向拉,不需要偏移面
装配:旋转 [0,1,0, 0,0,1, 1,0,0](本地 x→y、y→z、z→x),平移 (−40.4, 16.345, −10)
⇒ 装配 x −41.4~−38.4(贴墙)、y 16.345~86.345、z −10~80(底落在底盖上,顶露出顶盖 5.6 好捏)。
"""
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\\FilterFrame_v1.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
W, HH = 70.0, 90.0
TA, TB = 2.0, 1.0
WIN = 62.0
WC_A, WC_B = W / 2.0, 37.85 + 10.0 # 窗口中心(本地)= 风口中心
WA0, WA1 = WC_A - WIN / 2, WC_A + WIN / 2
WB0, WB1 = WC_B - WIN / 2, WC_B + WIN / 2
NCELL, RIB = 6, 1.0
CELL = (WIN - (NCELL - 1) * RIB) / NCELL # 9.5
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')
for d in list(app.GetDocuments or []):
try:
if 'FILTERFRAME_V1' in (prop(d, 'GetTitle') or '').upper():
app.CloseDoc(prop(d, 'GetTitle'))
except Exception:
pass
app.NewDocument(app.GetUserPreferenceStringValue(8), 0, 0.0, 0.0)
part = app.ActiveDoc
sm, fm = part.SketchManager, part.FeatureManager
P(u'新建零件 %s' % prop(part, 'GetTitle'))
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 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 bbox():
b = part.GetBodies2(0, True) or []
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 zfaces(zc, sign):
u"""朝外法线 ±z、位于 z=zc 的平面:[(面积, box)]"""
out = []
b = part.GetBodies2(0, True) or []
for f in prop(D(b[0]), 'GetFaces') if b else []:
fc = D(f)
s = prop(fc, 'GetSurface')
if not prop(s, 'IsPlane'):
continue
n = list(prop(s, 'PlaneParams'))[2]
if prop(fc, 'FaceInSurfaceSense'):
n = -n
fb = [v * 1000 for v in prop(fc, 'GetBox')]
if sign * n > 0.99 and abs(fb[2] - zc) < 0.02 and abs(fb[5] - zc) < 0.02:
out.append((prop(fc, 'GetArea') * 1e6, fb))
return out
P(u'\n[A] 贴墙层 %.0f×%.0f×%.1f + %.0f×%.0f 窗口' % (W, HH, TA, WIN, WIN))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(0.0, 0.0, 0.0, M(W), M(HH), 0.0)
sm.CreateCornerRectangle(M(WA0), M(WB0), 0.0, M(WA1), M(WB1), 0.0)
sm.AddToDB = False
sm.InsertSketch(True)
boss(TA, False)
P(u' box %s' % bbox())
P(u'\n[B] 外层 %.1f 厚 + %d×%d 格孔(格 %.2f、筋 %.1f)' % (TB, NCELL, NCELL, CELL, RIB))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(0.0, 0.0, 0.0, M(W), M(HH), 0.0)
for i in range(NCELL):
for j in range(NCELL):
a0 = WA0 + i * (CELL + RIB)
b0 = WB0 + j * (CELL + RIB)
sm.CreateCornerRectangle(M(a0), M(b0), 0.0, M(a0 + CELL), M(b0 + CELL), 0.0)
sm.AddToDB = False
sm.InsertSketch(True)
boss(TB, True)
b = bbox()
P(u' box %s(期望 [0, 0, −1, 70, 90, 2])' % b)
ok_box = b is not None and all(abs(b[i] - w) < 0.02 for i, w in enumerate([0, 0, -TB, W, HH, TA]))
# 判据:贴墙面(z=+2 朝 +z)只剩一个「回」字框 ⇒ 面积 = 70×90 − 62×62;
# 外表面(z=−1 朝 −z)= 框 + 格栅 ⇒ 面积 = 70×90 − 36 格。两个数都对,窗口和格孔才都真的开出来。
fa = zfaces(TA, +1)
fb_ = zfaces(-TB, -1)
area_a = sum(a for a, _ in fa)
area_b = sum(a for a, _ in fb_)
want_a = W * HH - WIN * WIN
want_b = W * HH - NCELL * NCELL * CELL * CELL
P(u' 贴墙面面积 %.1f(期望 %.1f)%s' % (area_a, want_a, u'✅' if abs(area_a - want_a) < 1 else u'⛔'))
P(u' 外表面面积 %.1f(期望 %.1f)%s' % (area_b, want_b, u'✅' if abs(area_b - want_b) < 1 else u'⛔'))
if not (ok_box and abs(area_a - want_a) < 1 and abs(area_b - want_b) < 1):
P(u'⛔ 不存盘'); sys.exit(2)
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))
+160
查看文件
@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
# v2:贴墙层加厚到 3(总厚 4),其余同 v1
u"""**插拔式滤网框** FilterFrame_v1(2026-09-13)—— 插进 TopShell_v2 西端墙外侧的导轨里。
70(宽) × 90(高) × 3(厚),中间 62×62 窗口(盖住 Ø60 风口,4 颗风扇螺丝头 Ø5.6×1.8 都落在窗口里,
框体可以平贴墙面;进风吸力把它压在墙上 ⇒ 基本自密封)。
窗口里有 1 mm 厚、1 mm 宽的 5×5 格栅,用来托住粘上去的滤网纱 / 滤网棉(滤材本身外购)。
零件本地:前视基准面,a(=x) 0~70 → 装配 y,b(=y) 0~90 → 装配 z,c(=z) → 装配 x。
[A] 贴墙层 c 0~2:外框 + 62×62 窗口(向 +z 拉 2)
[B] 外层 c −1~0:外框 + 6×6 格孔(向 −z 拉 1)—— 同一基准面两个方向拉,不需要偏移面
装配:旋转 [0,1,0, 0,0,1, 1,0,0](本地 x→y、y→z、z→x),平移 (−40.4, 16.345, −10)
⇒ 装配 x −41.4~−38.4(贴墙)、y 16.345~86.345、z −10~80(底落在底盖上,顶露出顶盖 5.6 好捏)。
"""
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\\FilterFrame_v2.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
W, HH = 70.0, 90.0
TA, TB = 3.0, 1.0 # 贴墙层 2 → 3:机箱自攻螺丝头比 M3 盘头高,会顶到格栅筋
WIN = 62.0
WC_A, WC_B = W / 2.0, 37.85 + 10.0 # 窗口中心(本地)= 风口中心
WA0, WA1 = WC_A - WIN / 2, WC_A + WIN / 2
WB0, WB1 = WC_B - WIN / 2, WC_B + WIN / 2
NCELL, RIB = 6, 1.0
CELL = (WIN - (NCELL - 1) * RIB) / NCELL # 9.5
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')
for d in list(app.GetDocuments or []):
try:
if 'FILTERFRAME_V2' in (prop(d, 'GetTitle') or '').upper():
app.CloseDoc(prop(d, 'GetTitle'))
except Exception:
pass
app.NewDocument(app.GetUserPreferenceStringValue(8), 0, 0.0, 0.0)
part = app.ActiveDoc
sm, fm = part.SketchManager, part.FeatureManager
P(u'新建零件 %s' % prop(part, 'GetTitle'))
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 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 bbox():
b = part.GetBodies2(0, True) or []
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 zfaces(zc, sign):
u"""朝外法线 ±z、位于 z=zc 的平面:[(面积, box)]"""
out = []
b = part.GetBodies2(0, True) or []
for f in prop(D(b[0]), 'GetFaces') if b else []:
fc = D(f)
s = prop(fc, 'GetSurface')
if not prop(s, 'IsPlane'):
continue
n = list(prop(s, 'PlaneParams'))[2]
if prop(fc, 'FaceInSurfaceSense'):
n = -n
fb = [v * 1000 for v in prop(fc, 'GetBox')]
if sign * n > 0.99 and abs(fb[2] - zc) < 0.02 and abs(fb[5] - zc) < 0.02:
out.append((prop(fc, 'GetArea') * 1e6, fb))
return out
P(u'\n[A] 贴墙层 %.0f×%.0f×%.1f + %.0f×%.0f 窗口' % (W, HH, TA, WIN, WIN))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(0.0, 0.0, 0.0, M(W), M(HH), 0.0)
sm.CreateCornerRectangle(M(WA0), M(WB0), 0.0, M(WA1), M(WB1), 0.0)
sm.AddToDB = False
sm.InsertSketch(True)
boss(TA, False)
P(u' box %s' % bbox())
P(u'\n[B] 外层 %.1f 厚 + %d×%d 格孔(格 %.2f、筋 %.1f)' % (TB, NCELL, NCELL, CELL, RIB))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(0.0, 0.0, 0.0, M(W), M(HH), 0.0)
for i in range(NCELL):
for j in range(NCELL):
a0 = WA0 + i * (CELL + RIB)
b0 = WB0 + j * (CELL + RIB)
sm.CreateCornerRectangle(M(a0), M(b0), 0.0, M(a0 + CELL), M(b0 + CELL), 0.0)
sm.AddToDB = False
sm.InsertSketch(True)
boss(TB, True)
b = bbox()
P(u' box %s(期望 [0, 0, −1, 70, 90, 2])' % b)
ok_box = b is not None and all(abs(b[i] - w) < 0.02 for i, w in enumerate([0, 0, -TB, W, HH, TA]))
# 判据:贴墙面(z=+2 朝 +z)只剩一个「回」字框 ⇒ 面积 = 70×90 − 62×62;
# 外表面(z=−1 朝 −z)= 框 + 格栅 ⇒ 面积 = 70×90 − 36 格。两个数都对,窗口和格孔才都真的开出来。
fa = zfaces(TA, +1)
fb_ = zfaces(-TB, -1)
area_a = sum(a for a, _ in fa)
area_b = sum(a for a, _ in fb_)
want_a = W * HH - WIN * WIN
want_b = W * HH - NCELL * NCELL * CELL * CELL
P(u' 贴墙面面积 %.1f(期望 %.1f)%s' % (area_a, want_a, u'✅' if abs(area_a - want_a) < 1 else u'⛔'))
P(u' 外表面面积 %.1f(期望 %.1f)%s' % (area_b, want_b, u'✅' if abs(area_b - want_b) < 1 else u'⛔'))
if not (ok_box and abs(area_a - want_a) < 1 and abs(area_b - want_b) < 1):
P(u'⛔ 不存盘'); sys.exit(2)
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))
+178
查看文件
@@ -0,0 +1,178 @@
# -*- coding: utf-8 -*-
# v3 外观:高 84.4 与顶面齐平 + 顶边 R8 指甲槽(深 4)
# v2:贴墙层加厚到 3(总厚 4),其余同 v1
u"""**插拔式滤网框** FilterFrame_v1(2026-09-13)—— 插进 TopShell_v2 西端墙外侧的导轨里。
70(宽) × 90(高) × 3(厚),中间 62×62 窗口(盖住 Ø60 风口,4 颗风扇螺丝头 Ø5.6×1.8 都落在窗口里,
框体可以平贴墙面;进风吸力把它压在墙上 ⇒ 基本自密封)。
窗口里有 1 mm 厚、1 mm 宽的 5×5 格栅,用来托住粘上去的滤网纱 / 滤网棉(滤材本身外购)。
零件本地:前视基准面,a(=x) 0~70 → 装配 y,b(=y) 0~90 → 装配 z,c(=z) → 装配 x。
[A] 贴墙层 c 0~2:外框 + 62×62 窗口(向 +z 拉 2)
[B] 外层 c −1~0:外框 + 6×6 格孔(向 −z 拉 1)—— 同一基准面两个方向拉,不需要偏移面
装配:旋转 [0,1,0, 0,0,1, 1,0,0](本地 x→y、y→z、z→x),平移 (−40.4, 16.345, −10)
⇒ 装配 x −41.4~−38.4(贴墙)、y 16.345~86.345、z −10~80(底落在底盖上,顶露出顶盖 5.6 好捏)。
"""
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\\FilterFrame_v3.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
W, HH = 70.0, 84.4 # D:顶边与顶盖顶面齐平(z −10 ~ 74.4)
TA, TB = 3.0, 1.0 # 贴墙层 2 → 3:机箱自攻螺丝头比 M3 盘头高,会顶到格栅筋
WIN = 62.0
WC_A, WC_B = W / 2.0, 37.85 + 10.0 # 窗口中心(本地)= 风口中心
WA0, WA1 = WC_A - WIN / 2, WC_A + WIN / 2
WB0, WB1 = WC_B - WIN / 2, WC_B + WIN / 2
NCELL, RIB = 6, 1.0
CELL = (WIN - (NCELL - 1) * RIB) / NCELL # 9.5
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')
for d in list(app.GetDocuments or []):
try:
if 'FILTERFRAME_V3' in (prop(d, 'GetTitle') or '').upper():
app.CloseDoc(prop(d, 'GetTitle'))
except Exception:
pass
app.NewDocument(app.GetUserPreferenceStringValue(8), 0, 0.0, 0.0)
part = app.ActiveDoc
sm, fm = part.SketchManager, part.FeatureManager
P(u'新建零件 %s' % prop(part, 'GetTitle'))
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 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 bbox():
b = part.GetBodies2(0, True) or []
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 zfaces(zc, sign):
u"""朝外法线 ±z、位于 z=zc 的平面:[(面积, box)]"""
out = []
b = part.GetBodies2(0, True) or []
for f in prop(D(b[0]), 'GetFaces') if b else []:
fc = D(f)
s = prop(fc, 'GetSurface')
if not prop(s, 'IsPlane'):
continue
n = list(prop(s, 'PlaneParams'))[2]
if prop(fc, 'FaceInSurfaceSense'):
n = -n
fb = [v * 1000 for v in prop(fc, 'GetBox')]
if sign * n > 0.99 and abs(fb[2] - zc) < 0.02 and abs(fb[5] - zc) < 0.02:
out.append((prop(fc, 'GetArea') * 1e6, fb))
return out
SCOOP_R, SCOOP_D = 8.0, 4.0 # D:顶边指甲槽,圆心在顶边上方 4、切进框体 4(弦长 13.9)
def outline():
cx, cy = W / 2.0, HH + SCOOP_R - SCOOP_D
half = math.sqrt(SCOOP_R ** 2 - (SCOOP_R - SCOOP_D) ** 2)
for a_, b_ in (((0, 0), (W, 0)), ((W, 0), (W, HH)), ((W, HH), (cx + half, HH)),
((cx - half, HH), (0, HH)), ((0, HH), (0, 0))):
sm.CreateLine(M(a_[0]), M(a_[1]), 0.0, M(b_[0]), M(b_[1]), 0.0)
q1, q2, qm = (cx + half, HH), (cx - half, HH), (cx, cy - SCOOP_R)
d = 1 if (q1[0] - cx) * (qm[1] - cy) - (q1[1] - cy) * (qm[0] - cx) > 0 else -1
sm.CreateArc(M(cx), M(cy), 0.0, M(q1[0]), M(q1[1]), 0.0, M(q2[0]), M(q2[1]), 0.0, d)
A_SCOOP = SCOOP_R ** 2 * math.acos((SCOOP_R - SCOOP_D) / SCOOP_R) - (SCOOP_R - SCOOP_D) * math.sqrt(SCOOP_R ** 2 - (SCOOP_R - SCOOP_D) ** 2)
P(u'\n[A] 贴墙层 %.0f×%.0f×%.1f + %.0f×%.0f 窗口' % (W, HH, TA, WIN, WIN))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
outline()
sm.CreateCornerRectangle(M(WA0), M(WB0), 0.0, M(WA1), M(WB1), 0.0)
sm.AddToDB = False
sm.InsertSketch(True)
boss(TA, False)
P(u' box %s' % bbox())
P(u'\n[B] 外层 %.1f 厚 + %d×%d 格孔(格 %.2f、筋 %.1f)' % (TB, NCELL, NCELL, CELL, RIB))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
outline()
for i in range(NCELL):
for j in range(NCELL):
a0 = WA0 + i * (CELL + RIB)
b0 = WB0 + j * (CELL + RIB)
sm.CreateCornerRectangle(M(a0), M(b0), 0.0, M(a0 + CELL), M(b0 + CELL), 0.0)
sm.AddToDB = False
sm.InsertSketch(True)
boss(TB, True)
b = bbox()
P(u' box %s(期望 [0, 0, −1, 70, 90, 2])' % b)
ok_box = b is not None and all(abs(b[i] - w) < 0.02 for i, w in enumerate([0, 0, -TB, W, HH, TA]))
# 判据:贴墙面(z=+2 朝 +z)只剩一个「回」字框 ⇒ 面积 = 70×90 − 62×62;
# 外表面(z=−1 朝 −z)= 框 + 格栅 ⇒ 面积 = 70×90 − 36 格。两个数都对,窗口和格孔才都真的开出来。
fa = zfaces(TA, +1)
fb_ = zfaces(-TB, -1)
area_a = sum(a for a, _ in fa)
area_b = sum(a for a, _ in fb_)
want_a = W * HH - WIN * WIN - A_SCOOP
want_b = W * HH - NCELL * NCELL * CELL * CELL - A_SCOOP
P(u' 贴墙面面积 %.1f(期望 %.1f)%s' % (area_a, want_a, u'✅' if abs(area_a - want_a) < 1 else u'⛔'))
P(u' 外表面面积 %.1f(期望 %.1f)%s' % (area_b, want_b, u'✅' if abs(area_b - want_b) < 1 else u'⛔'))
if not (ok_box and abs(area_a - want_a) < 1 and abs(area_b - want_b) < 1):
P(u'⛔ 不存盘'); sys.exit(2)
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))
+100
查看文件
@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
u"""导入 Orion OD6010(60×60×10)STEP → 另存 SLDPRT → 解剖:包围盒、安装孔、风道。
沿用 NOTES §32 的做法:LoadFile4 得到未保存零件(GetPathName 为空)⇒ 必须先 SaveAs3 才能 AddComponent4。
判据沿用 §32 的教训:配对安装孔前先按「关于原点对称 + 贯穿全厚」筛,别对 |x|、|y| 各取极值。
"""
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
SRC = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.stp'
DST = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
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)
app = wc.GetActiveObject('SldWorks.Application')
for d in list(app.GetDocuments or []):
t = prop(d, 'GetTitle') or ''
if 'OD6015' in t.upper() or 'OD6010' in t.upper():
app.CloseDoc(t)
data = app.GetImportFileData(SRC)
err = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.LoadFile4(SRC, 'r', data, err)
P(u'LoadFile4 err=%s' % err.value)
part = app.ActiveDoc
P(u'文档 %s type=%s' % (prop(part, 'GetTitle'), prop(part, 'GetType')))
if prop(part, 'GetType') != 1:
P(u'⛔ 不是单体零件,停手'); sys.exit(2)
bodies = part.GetBodies2(0, True) or []
P(u'实体 %d 个' % len(bodies))
bx = None
for b in bodies:
bb = [round(v * 1000, 3) for v in prop(D(b), 'GetBodyBox')]
bx = bb if bx is None else [min(bx[i], bb[i]) for i in range(3)] + [max(bx[i + 3], bb[i + 3]) for i in range(3)]
P(u'包围盒 x %.3f~%.3f y %.3f~%.3f z %.3f~%.3f ⇒ %.2f × %.2f × %.2f'
% (bx[0], bx[3], bx[1], bx[4], bx[2], bx[5], bx[3] - bx[0], bx[4] - bx[1], bx[5] - bx[2]))
thick_ax = min(range(3), key=lambda i: bx[i + 3] - bx[i])
P(u'厚度方向 = %s(%.3f)' % ('xyz'[thick_ax], bx[thick_ax + 3] - bx[thick_ax]))
cands = []
big = []
for b in bodies:
for f in prop(D(b), 'GetFaces'):
fc = D(f)
s = prop(fc, 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
r = cp[6] * 1000
ax = max(range(3), key=lambda i: abs(cp[3 + i]))
fb = [v * 1000 for v in prop(fc, 'GetBox')]
c = [(fb[i] + fb[i + 3]) / 2 for i in range(3)]
if ax == thick_ax and 1.5 < r < 2.8:
cands.append((round(r, 3), c, fb[thick_ax], fb[thick_ax + 3]))
if ax == thick_ax and r > 25:
big.append((round(r, 3), fb[thick_ax], fb[thick_ax + 3]))
o = [i for i in range(3) if i != thick_ax]
full = bx[thick_ax + 3] - bx[thick_ax]
P(u'\n安装孔候选(半径 1.5~2.8、轴沿厚度方向)%d 个:' % len(cands))
for r, c, z0, z1 in cands:
P(u' R%.3f (%s=%.3f, %s=%.3f) 厚度向 %.2f~%.2f%s'
% (r, 'xyz'[o[0]], c[o[0]], 'xyz'[o[1]], c[o[1]], z0, z1,
u' ← 贯穿' if abs((z1 - z0) - full) < 0.05 else u''))
thru = [(r, c) for r, c, z0, z1 in cands if abs((z1 - z0) - full) < 0.05]
P(u'\n贯穿全厚的安装孔 %d 个:' % len(thru))
cx = (bx[o[0]] + bx[o[0] + 3]) / 2
cy = (bx[o[1]] + bx[o[1] + 3]) / 2
for r, c in thru:
P(u' R%.3f 相对中心 (%+.3f, %+.3f)' % (r, c[o[0]] - cx, c[o[1]] - cy))
if len(thru) == 4:
dxs = sorted(abs(c[o[0]] - cx) for _, c in thru)
dys = sorted(abs(c[o[1]] - cy) for _, c in thru)
P(u' ⇒ 孔距 %.3f × %.3f(标准 60 风扇应为 50.00 × 50.00)'
% (2 * sum(dxs) / 4, 2 * sum(dys) / 4))
P(u'风道圆柱(R>25):%s' % big)
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(零件保持打开,供 AddComponent4)'
% (e1.value, os.path.exists(DST), os.path.getsize(DST) / 1024.0 if os.path.exists(DST) else 0))
+52
查看文件
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
u"""由 place_twopiece.py 派生 place_filter.py:换 TopShell_v2 / BottomPlate_v2 + 插滤网框 + 滑槽间隙核对。纯文本补丁。"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
T = r'C:\Users\35138\.claude\jobs\11836093\tmp'
s = open(T + r'\place_twopiece.py', encoding='utf-8').read()
def rep(old, new):
global s
if s.count(old) != 1:
raise SystemExit(u'锚点 %r 出现 %d 次' % (old[:50], s.count(old)))
s = s.replace(old, new)
rep(u"u\"\"\"总装换成**两件式外壳**",
u"u\"\"\"**v2**:两件式 + 西端插拔式滤网(TopShell_v2 / BottomPlate_v2 / FilterFrame_v1)。\n(以下为 v1 说明)总装换成**两件式外壳**")
# 删件列表 OLD 与干涉筛选两处都要加滤网框 —— 锚点恰好出现 2 次,一并替换
if s.count(u"'TOPSHELL', 'BOTTOMPLATE')") != 2:
raise SystemExit(u'锚点次数变了')
s = s.replace(u"'TOPSHELL', 'BOTTOMPLATE')", u"'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')")
rep(u"(u'TopShell_v1.SLDPRT', (0, 0, 74.4), [-38.4,", u"(u'TopShell_v2.SLDPRT', (0, 0, 74.4), [-43.8,")
rep(u"(u'BottomPlate_v1.SLDPRT', (0, 0, -10.0), [-38.4,", u"(u'BottomPlate_v2.SLDPRT', (0, 0, -10.0), [-43.8,")
FILTER = u'''
P(u'\\n[2b] 插滤网框(本地 x→y、y→z、z→x)')
_fc = doc.AddComponent4(DIR + u'FilterFrame_v1.SLDPRT', '', 0.0, 0.0, 0.0)
if _fc is None:
P(u' ⛔ FilterFrame AddComponent4 → None'); sys.exit(3)
_v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
[0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, -0.0404, 0.016345, -0.010, 1.0, 0.0, 0.0, 0.0])
_xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, _v))
try:
_fc.SetTransformAndSolve2(_xf)
except Exception:
_fc.Transform2 = _xf
doc.EditRebuild3
_b = [round(x * 1000.0, 3) for x in _fc.GetBox(False, False)]
_w = [-41.4, 16.345, -10.0, -38.4, 86.345, 80.0]
_ok = all(abs(_b[i] - _w[i]) < 0.05 for i in range(6))
allok &= _ok
P(u' 滤网框 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (_b[0], _b[3], _b[1], _b[4], _b[2], _b[5], u'✅' if _ok else u'⛔ 期望 %s' % _w))
P(u' 滑槽间隙:贴墙 %.2f(应 0)/ 唇边侧 %.2f(应 0.4)/ 南 %.2f、北 %.2f(应 0.3)/ 底 %.2f(落在底盖上,应 0)/ 露出顶盖 %.2f'
% (-38.4 - _b[3], _b[0] - (-41.8), _b[1] - 16.045, 86.645 - _b[4], _b[2] - (-10.0), _b[5] - 74.4))
'''
rep(u"\ntops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}", FILTER + u"\ntops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}")
rep(u"((u'tp_iso', 7), (u'tp_south', 6), (u'tp_bottom', 2))", u"((u'tf_iso', 7), (u'tf_west', 3), (u'tf_top', 1))")
open(T + r'\place_filter.py', 'w', encoding='utf-8').write(s)
print(u'写出 place_filter.py')
+98
查看文件
@@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
u"""由 topshell_v1.py 派生 topshell_v2.py(加西端滤网导轨 + 内联倒角),只做文本补丁,不碰 SolidWorks。"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
SRC = r'C:\Users\35138\.claude\jobs\11836093\tmp\topshell_v1.py'
DST = r'C:\Users\35138\.claude\jobs\11836093\tmp\topshell_v2.py'
s = open(SRC, encoding='utf-8').read()
def rep(old, new, count=1):
global s
n = s.count(old)
if n != count:
raise SystemExit(u'锚点 %r 出现 %d 次(期望 %d)' % (old[:40], n, count))
s = s.replace(old, new)
rep(u'# -*- coding: utf-8 -*-\nu"""**一体顶盖** TopShell_v1',
u'# -*- coding: utf-8 -*-\nu"""**v2 = v1 + 西端插拔式滤网导轨 + 建模内倒角**(2026-09-13)。\n'
u'导轨让包围盒西界变成唇边 x −43.8,add_fillets 的「包围盒角」判据会漏掉西侧两角 ⇒ 圆角在加导轨之前做。\n\n'
u'(以下为 v1 说明)**一体顶盖** TopShell_v1')
rep(u"TopShell_v1.SLDPRT'", u"TopShell_v2.SLDPRT'")
rep(u"'TOPSHELL_V1'", u"'TOPSHELL_V2'")
FILLET = u'''
# ── [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)
'''
rep(u'\n# ── 面上草图切除(带候选映射)', FILLET + u'\n# ── 面上草图切除(带候选映射)')
RAILS = u'''
# ── [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)
'''
rep(u'\n# ── 验收 ──', RAILS + u'\n# ── 验收 ──')
rep(u"check(u'最终', [X0, Y0, -H, X1, Y1, 0.0])", u"check(u'最终', [XO, Y0, -H, X1, Y1, 0.0])")
rep(u"(期望 {1.7: 8, 2.1: 4, 30.0: 2})", u"(期望 {1.7: 8, 2.1: 4, 3.0: 4, 30.0: 2})")
open(DST, 'w', encoding='utf-8').write(s)
print(u'写出', DST)
+80
查看文件
@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
u"""派生嵌入式底盖版(2026-09-13 用户:底盖螺丝拧紧后嵌入顶盖、被顶盖包裹):
topshell_v9 → topshell_v10:四面墙向下延长一个底盖厚度 3.6(墙底 z −10 → −13.6),**角柱仍止于 z=−10** 当限位
bottomplate_v5 → bottomplate_v6:外形 = 顶盖内腔(x −16~113.5、y −1.655~104.345)四边各让 0.3,四角 R1(由 add_fillets 做)
place_v9 → place_v10:新包围盒 + 嵌入判据(底面与墙底齐平、四边间隙 0.3)
整机外形不变 134.3 × 110.8 × 88.0(墙底正好补到原底盖底面 −13.6)。纯文本补丁,锚点断言次数。"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
T = u'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp\\'
CLR = 0.3
IX0, IX1, IY0, IY1 = -16.0, 113.5, -1.655, 104.345
BP = (IX0 + CLR, IY0 + CLR, IX1 - CLR, IY1 - CLR)
def patch(src, dst, pairs, head):
s = open(T + src, encoding='utf-8').read()
for old, new, n in pairs:
c = s.count(old)
if c != n:
raise SystemExit(u'%s:锚点 %r 出现 %d 次(期望 %d)' % (src, old[:70], c, n))
s = s.replace(old, new)
s = s.replace(u'# -*- coding: utf-8 -*-\n', u'# -*- coding: utf-8 -*-\n# ' + head + u'\n', 1)
open(T + dst, 'w', encoding='utf-8').write(s)
print(u'写出', dst)
patch('topshell_v9.py', 'topshell_v10.py', [
(u"TopShell_v9.SLDPRT'", u"TopShell_v10.SLDPRT'", 1),
(u"'TOPSHELL_V9'", u"'TOPSHELL_V10'", 1),
(u"H = 84.4 # 顶外 74.4 → 墙底 −10",
u"H = 84.4 # 顶外 74.4 → 角柱底 −10(底盖顶面限位)\n"
u"PLATE_T = 3.6 # 底盖厚\n"
u"H_WALL = H + PLATE_T # v10:墙向下包住底盖 ⇒ 墙底 −13.6", 1),
(u"P(u'\\n[2] 四面墙 环形轮廓,向 −z 拉 %.1f' % H)", u"P(u'\\n[2] 四面墙 环形轮廓,向 −z 拉 %.1f(包住底盖)' % H_WALL)", 1),
(u"boss(H, True)\ncheck(u'墙', [X0, Y0, -H, X1, Y1, 0.0])", u"boss(H_WALL, True)\ncheck(u'墙', [X0, Y0, -H_WALL, X1, Y1, 0.0])", 1),
(u"check(u'顶板', [X0, Y0, -H, X1, Y1, 0.0])", u"check(u'顶板', [X0, Y0, -H_WALL, X1, Y1, 0.0])", 1),
(u"check(u'最终', [X0, Y0, -H, X1, Y1, 0.0])", u"check(u'最终', [X0, Y0, -H_WALL, X1, Y1, 0.0])", 1),
], u'v10:嵌入式底盖 —— 墙向下延长 3.6 包住底盖(墙底 −13.6),角柱仍止于 −10 当限位;其余同 v9')
patch('bottomplate_v5.py', 'bottomplate_v6.py', [
(u"BottomPlate_v5.SLDPRT'", u"BottomPlate_v6.SLDPRT'", 1),
(u"'BOTTOMPLATE_V5'", u"'BOTTOMPLATE_V6'", 1),
(u"INSET = 0.5\nX0, X1, Y0, Y1 = -18.4 + INSET, 115.9 - INSET, -4.055 + INSET, 106.745 - INSET # 无滤网:西沿回到顶盖外沿 −18.4",
u"CLR = %.1f # v6:嵌进顶盖内腔,四边各让 %.1f\n"
u"X0, X1, Y0, Y1 = %.1f + CLR, %.1f - CLR, %.3f + CLR, %.3f - CLR # = 顶盖内腔 %s 各让 CLR"
% (CLR, CLR, IX0, IX1, IY0, IY1, (IX0, IX1, IY0, IY1)), 1),
], u'v6:嵌入式 —— 外形 = 顶盖内腔四边各让 0.3,拧紧后顶面压住顶盖角柱底(z −10)、底面与墙底齐平(−13.6)')
p = open(T + 'place_v9.py', encoding='utf-8').read()
pairs = [
(u"(u'TopShell_v9.SLDPRT', (0, 0, 74.4), [-18.4, -4.055, -10.0, 115.9, 106.745, 74.4])",
u"(u'TopShell_v10.SLDPRT', (0, 0, 74.4), [-18.4, -4.055, -13.6, 115.9, 106.745, 74.4])"),
(u"(u'BottomPlate_v5.SLDPRT', (0, 0, -10.0), [-17.9, -3.555, -13.6, 115.4, 106.245, 0.0])",
u"(u'BottomPlate_v6.SLDPRT', (0, 0, -10.0), [%.1f, %.3f, -13.6, %.1f, %.3f, 0.0])" % BP),
(u"""P(u'\\n[4] 贴合:顶盖墙底 ↔ 底盖顶面')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
P(u' 顶盖 z 下沿 %.3f / 底盖板面 z −10(底盖 box 上沿 %.3f 是 PCB 立柱顶)'
% (ts[2], bp[5]))
allok &= abs(ts[2] + 10.0) < 0.05""",
u"""P(u'\\n[4] 嵌入:底盖藏在顶盖墙内')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
_gap = (bp[0] - (%.1f), bp[1] - (%.3f), (%.1f) - bp[3], (%.3f) - bp[4])
P(u' 墙底 z %%.3f / 底盖底面 z %%.3f(应齐平)' %% (ts[2], bp[2]))
P(u' 底盖离墙内侧间隙 西 %%.3f 南 %%.3f 东 %%.3f 北 %%.3f(应各 0.3)' %% _gap)
P(u' 底盖顶面 z −10(= 角柱底面限位,由零件本地 −3.6~0 + 装配 dz −10 保证)')
allok &= abs(ts[2] - bp[2]) < 0.05 and all(abs(g - 0.3) < 0.05 for g in _gap)""" % (IX0, IY0, IX1, IY1)),
(u"((u'v9_iso', 7),)", u"((u'v10_iso', 7), (u'v10_bottom', 2))"),
]
for old, new in pairs:
if p.count(old) != 1:
raise SystemExit(u'place 锚点 %r ×%d' % (old[:60], p.count(old)))
p = p.replace(old, new)
p = p.replace(u'# -*- coding: utf-8 -*-\n', u'# -*- coding: utf-8 -*-\n# v10:嵌入式底盖总装(TopShell_v10 + BottomPlate_v6)\n', 1)
open(T + 'place_v10.py', 'w', encoding='utf-8').write(p)
print(u'写出 place_v10.py')
print(u'底盖外形 x %.1f~%.1f y %.3f~%.3f' % (BP[0], BP[2], BP[1], BP[3]))
+101
查看文件
@@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
u"""派生 v11(2026-09-30 用户实测:两个 Type-C 口墙太厚,线插不到底):
topshell_v10 → topshell_v11:南墙两个 Type-C 贯穿孔仍 = 母座口内侧轮廓 8.34×2.60 R0.7,
外侧加 PK_W × PK_H、R PK_R 的沉槽,槽底留 PK_LEFT 薄壁(用户:不要完全掏空,留 1 mm);
中心不变 J1 x12.5 / J23 x84、z −1.65;标识右移到 SYM_DX。
place_v10 → place_v11、export_stl_v10 → export_stl_v11:只换顶盖件名。
底盖 BottomPlate_v6 不变。纯文本补丁,锚点断言次数。
为什么是护套尺寸:母座口面 y=0.20,墙外表面 y=−4.055 ⇒ 口缩在墙里 4.26;开口只有口那么大时
只有插头金属壳能进墙,要金属壳 ≳ 4.26 + 口深 4.05 ≈ 8.3 才插得到底,常见线约 6.5(R2 规划 §外壳 v4 的已知风险)。
沉槽让护套伸进墙 PK_D = 2.4 − 1.0 = 1.4 ⇒ 护套前端到口面 4.26 − 1.4 = 2.86,金属壳要 ≳ 2.86 + 4.05 ≈ 6.9(原 8.3)。
常见 USB-C 线护套 12×6.5 左右,编织 / 加粗线可到 13×7 ⇒ 槽取 13.4 × 7.4 留余量。"""
import io
import os
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
T = os.path.dirname(os.path.abspath(__file__)) + os.sep
PK_W, PK_H, PK_R = 13.4, 7.4, 3.2 # 外侧沉槽(护套能进);R 取 3.2 是为了和已有的圆柱面半径都不撞,直方图才验得出
PK_LEFT = 1.0 # 槽底留的薄壁(用户 2026-09-30:不要完全掏空,留 1 mm)
SYM_DX = 10.5 # 标识中心离口中心;箭头半宽 2.2 ⇒ 左缘 8.3,离沉槽边 6.7 还剩 1.6 的墙
def patch(src, dst, pairs, head):
s = open(T + src, encoding='utf-8').read()
for old, new, n in pairs:
c = s.count(old)
if c != n:
raise SystemExit(u'%s:锚点 %r 出现 %d 次(期望 %d)' % (src, old[:70], c, n))
s = s.replace(old, new)
s = s.replace(u'# -*- coding: utf-8 -*-\n', u'# -*- coding: utf-8 -*-\n# ' + head + u'\n', 1)
open(T + dst, 'w', encoding='utf-8').write(s)
print(u'写出', dst)
POCKET = u"""
# ── [5a] v11:Type-C 外侧沉槽 —— 护套进墙、槽底留 PK_LEFT 薄壁;贯穿的仍是上面那个母座口小孔 ──
WALL_T = IY0 - Y0 # 南墙厚 2.4
PK_W, PK_H, PK_R, PK_LEFT = %.2f, %.2f, %.2f, %.2f
PK_D = WALL_T - PK_LEFT
A_PORT = USB_W * USB_H - (4 - math.pi) * USB_R ** 2
A_PK = PK_W * PK_H - (4 - math.pi) * PK_R ** 2
P(u'\\n[5a] 南墙 Type-C 沉槽 ×2:%%.2f × %%.2f、R%%.2f,深 %%.2f(墙 %%.2f,留 %%.2f)' %% (PK_W, PK_H, PK_R, PK_D, WALL_T, PK_LEFT))
_port = (USB_W, USB_H, USB_R)
USB_W, USB_H, USB_R = PK_W, PK_H, PK_R # usb_shapes 读全局;画完就还原
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])
nseg += bool(sm.CreateArc(qc[0], qc[1], 0.0, q1[0], q1[1], 0.0, q2[0], q2[1], 0.0, 1 if cross > 0 else -1))
sm.AddToDB = False
sm.InsertSketch(True)
USB_W, USB_H, USB_R = _port
if nseg != 16 or pick(last_sketch(), 'SKETCH') != 1:
P(u' ⛔ 沉槽草图不对(段 %%d)' %% nseg); sys.exit(5)
c = fm.FeatureCut4(True, False, False, 0, 0, M(PK_D), 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 + PK_D)) < 0.02 and abs(fb[4] - (Y0 + PK_D)) < 0.02)
want = 2 * (A_PK - A_PORT)
ok = abs(floor - want) < 0.05
P(u' 沉槽 切除 → %%-12r 面数 %%d→%%d 槽底薄壁面积 %%.3f(理论 2×(%%.3f−%%.3f) = %%.3f)%%s'
%% (prop(c, 'Name') if c else c, nbefore, len(faces()), floor, A_PK, A_PORT, want, u'✅' if ok else u'⛔'))
if not ok:
P(u' ⛔⛔ 沉槽深度 / 轮廓不对,停手、不存盘'); sys.exit(5)
""" % (PK_W, PK_H, PK_R, PK_LEFT)
patch('topshell_v10.py', 'topshell_v11.py', [
(u"TopShell_v10.SLDPRT'", u"TopShell_v11.SLDPRT'", 1),
(u"'TOPSHELL_V10'", u"'TOPSHELL_V11'", 1),
(u"# ── [5b] F:Type-C 右侧", POCKET.lstrip('\n') + u"# ── [5b] F:Type-C 右侧", 1),
(u"SYM_DEPTH, SYM_DX = 0.6, 9.0", u"SYM_DEPTH, SYM_DX = 0.6, %.1f # v11:随沉槽右移" % SYM_DX, 1),
(u"WANT_H = {0.7: 8, 0.75: 2 * SLOT_N,", u"WANT_H = {0.7: 8, %.1f: 8, 0.75: 2 * SLOT_N," % PK_R, 1),
], u'v11:南墙 Type-C 外侧沉槽 %.1f×%.1f R%.1f、槽底留 %.1f 薄壁(小孔仍 = 母座口),标识右移到 %.1f;其余同 v10'
% (PK_W, PK_H, PK_R, PK_LEFT, SYM_DX))
patch('place_v10.py', 'place_v11.py', [
(u"(u'TopShell_v10.SLDPRT',", u"(u'TopShell_v11.SLDPRT',", 1),
(u"OUT = r'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp'", u"OUT = os.path.join(os.environ['TEMP'], 'phonerail_shell')\nos.makedirs(OUT, exist_ok=True)", 1),
(u"((u'v10_iso', 7), (u'v10_bottom', 2))", u"((u'v11_iso', 7), (u'v11_bottom', 2))", 1),
], u'v11:总装换 TopShell_v11(Type-C 外侧沉槽、留 1 mm 薄壁),底盖仍 BottomPlate_v6')
patch('export_stl_v10.py', 'export_stl_v11.py', [
(u"PARTS = [(u'TopShell_v10', True), (u'BottomPlate_v6', False)]",
u"PARTS = [(u'TopShell_v11', True), (u'BottomPlate_v6', False)]", 1),
], u'v11:导出 TopShell_v11(Type-C 开口放大)+ BottomPlate_v6')
+141
查看文件
@@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
u"""派生「短机版」四个脚本(2026-09-13 用户要求缩短整机):
风扇 60×60×25 → **60×60×10(广诚散热,孔距 50.2、配机箱自攻螺丝)**,分风腔 15 → **10**。
topshell_v2 → topshell_v3、bottomplate_v2 → bottomplate_v3、filterframe_v1 → filterframe_v2、place_filter → place_v3。
纯文本补丁,每个锚点断言出现次数。"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
T = u'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp\\'
# ── 几何(全部由刀片堆叠 x 4.01~93.49 + 实物风扇 10 + 分风腔 10 推出)─────────
IX0, IX1 = -16.0, 113.5 # 内腔(= 风扇外侧面):西 4.01−10.01−10 / 东 93.49+10.01+10
X0, X1 = IX0 - 2.4, IX1 + 2.4 # −18.4 / 115.9
SLOT_T = 4.4 # 滤网框 3+1 厚 + 0.4 间隙
XO = X0 - SLOT_T - 2.0 # −24.8
TA = 3.0 # 滤网框贴墙层加厚(让开自攻螺丝头)
PX_W, PX_E = IX0 + 5.0, IX1 - 5.0 # 角柱中心 −11 / 108.5
def patch(src, dst, pairs, head):
s = open(T + src, encoding='utf-8').read()
for old, new, n in pairs:
c = s.count(old)
if c != n:
raise SystemExit(u'%s:锚点 %r 出现 %d 次(期望 %d)' % (src, old[:60], c, n))
s = s.replace(old, new)
s = s.replace(u'# -*- coding: utf-8 -*-\n', u'# -*- coding: utf-8 -*-\n# ' + head + u'\n', 1)
open(T + dst, 'w', encoding='utf-8').write(s)
print(u'写出', dst)
patch('topshell_v2.py', 'topshell_v3.py', [
(u"TopShell_v2.SLDPRT'", u"TopShell_v3.SLDPRT'", 1),
(u"'TOPSHELL_V2'", u"'TOPSHELL_V3'", 1),
(u"X0, X1, Y0, Y1 = -38.4, 136.4, -4.055, 106.745", u"X0, X1, Y0, Y1 = %.1f, %.1f, -4.055, 106.745" % (X0, X1), 1),
(u"IX0, IX1, IY0, IY1 = -36.0, 134.0, -1.655, 104.345", u"IX0, IX1, IY0, IY1 = %.1f, %.1f, -1.655, 104.345" % (IX0, IX1), 1),
(u"R_VENT, R_FAN = 30.0, 1.7", u"R_VENT, R_FAN = 30.0, 2.6 # 风扇孔 Ø5.2:机箱自攻螺丝要穿墙拧进风扇 Ø4.4 孔", 1),
(u"(-25.0, 25.0)", u"(-25.1, 25.1)", 2),
(u"FW, CLR, SLOT_T, LIP_T, SPACER, LIP = 70.0, 0.3, 3.4, 2.0, 4.0, 3.0",
u"FW, CLR, SLOT_T, LIP_T, SPACER, LIP = 70.0, 0.3, %.1f, 2.0, 4.0, 3.0" % SLOT_T, 1),
(u"if h.get(1.7, 0) != 8 or h.get(2.1, 0) != 4 or h.get(30.0, 0) != 2:",
u"if h.get(2.6, 0) != 8 or h.get(2.1, 0) != 4 or h.get(30.0, 0) != 2:", 1),
(u"(期望 {1.7: 8, 2.1: 4, 3.0: 4, 30.0: 2})", u"(期望 {2.1: 4, 2.6: 8, 3.0: 4, 30.0: 2})", 1),
], u'v3 短机版:风扇 6010(孔距 50.2、Ø5.2 过孔)+ 分风腔 10;内腔 x −16~113.5、外 −18.4~115.9、导轨唇边 −24.8')
patch('bottomplate_v2.py', 'bottomplate_v3.py', [
(u"BottomPlate_v2", u"BottomPlate_v3", 2),
(u"'BOTTOMPLATE_V2'", u"'BOTTOMPLATE_V3'", 1),
(u"X0, X1, Y0, Y1 = -43.8, 136.4,", u"X0, X1, Y0, Y1 = %.1f, %.1f," % (XO, X1), 1),
(u"CORNER_HOLES = [(-31.0, 3.345), (-31.0, 99.345), (129.0, 3.345), (129.0, 99.345)]",
u"CORNER_HOLES = [(%.1f, 3.345), (%.1f, 99.345), (%.1f, 3.345), (%.1f, 99.345)]" % (PX_W, PX_W, PX_E, PX_E), 1),
], u'v3 短机版:外形 x −24.8~115.9,四角过孔跟顶盖角柱到 x −11 / 108.5')
patch('filterframe_v1.py', 'filterframe_v2.py', [
(u"FilterFrame_v1.SLDPRT'", u"FilterFrame_v2.SLDPRT'", 1),
(u"'FILTERFRAME_V1'", u"'FILTERFRAME_V2'", 1),
(u"TA, TB = 2.0, 1.0", u"TA, TB = %.1f, 1.0 # 贴墙层 2 → 3:机箱自攻螺丝头比 M3 盘头高,会顶到格栅筋" % TA, 1),
], u'v2:贴墙层加厚到 3(总厚 4),其余同 v1')
patch('place_filter.py', 'place_v3.py', [
(u"(u'TopShell_v2.SLDPRT', (0, 0, 74.4), [-43.8, -4.055, -10.0, 136.4,",
u"(u'TopShell_v3.SLDPRT', (0, 0, 74.4), [%.1f, -4.055, -10.0, %.1f," % (XO, X1), 1),
(u"(u'BottomPlate_v2.SLDPRT', (0, 0, -10.0), [-43.8, -4.055, -12.4, 136.4,",
u"(u'BottomPlate_v3.SLDPRT', (0, 0, -10.0), [%.1f, -4.055, -12.4, %.1f," % (XO, X1), 1),
(u"u'FilterFrame_v1.SLDPRT'", u"u'FilterFrame_v2.SLDPRT'", 1),
(u"-0.0404, 0.016345, -0.010,", u"%.4f, 0.016345, -0.010," % ((X0 - TA) / 1000.0), 1),
(u"_w = [-41.4, 16.345, -10.0, -38.4, 86.345, 80.0]",
u"_w = [%.1f, 16.345, -10.0, %.1f, 86.345, 80.0]" % (X0 - TA - 1.0, X0), 1),
(u"(-38.4 - _b[3], _b[0] - (-41.8),", u"(%.1f - _b[3], _b[0] - (%.1f)," % (X0, X0 - SLOT_T), 1),
(u"pw > 14 and pe > 14 else", u"pw > 9 and pe > 9 else", 1),
(u"allok &= pw > 14 and pe > 14", u"allok &= pw > 9 and pe > 9", 1),
(u"'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')):", u"'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME', 'OD60')):", 1),
(u"wall = world_holes(shell, 1.7)", u"wall = world_holes(shell, 2.6)", 1),
(u"((u'tf_iso', 7), (u'tf_west', 3), (u'tf_top', 1))", u"((u'v3_iso', 7), (u'v3_west', 3), (u'v3_top', 1))", 1),
(u" fan_h += world_holes(fc, 2.15)\nallok &= pair(u'端墙风扇孔', wall, fan_h, 0, 8)",
u''' fan_h += world_holes(fc, 2.15)
# Orion OD6010 模型的安装孔是**腰形孔**:每个孔由两个 R2.15 半圆柱组成(中心 ±1.075),
# 模型孔距 50.0;实物(广诚 6010)孔距 50.2 ⇒ 墙孔按 ±25.1 开,与模型预期差 0.1。
# ⇒ 先把离墙孔 1.5 以内的半圆柱轴点取平均得到腰形孔中心,再比偏差,容差放到 0.15。
_ok = 0
_ws = [p for ax, p in wall if ax == 0]
for p in _ws:
near = [q for ax, q in fan_h if ax == 0 and max(abs(p[1] - q[1]), abs(p[2] - q[2])) < 1.5]
if not near:
P(u' 墙孔 (y=%.2f, z=%.2f) 附近没有风扇孔 ⛔' % (p[1], p[2]))
continue
cy = sum(q[1] for q in near) / len(near)
cz = sum(q[2] for q in near) / len(near)
off = max(abs(p[1] - cy), abs(p[2] - cz))
_ok += off <= 0.15
P(u' 墙孔 (y=%.2f, z=%.2f) ↔ 模型腰孔中心 (%.2f, %.2f) 偏 %.3f %s'
% (p[1], p[2], cy, cz, off, u'✅(模型 50.0 vs 实物 50.2)' if off <= 0.15 else u'⛔'))
P(u' 风扇孔对上 %d / %d(期望 8)' % (_ok, len(_ws)))
allok &= _ok == 8 and len(_ws) == 8''', 1),
(u"\ntops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}",
u'''
P(u'\\n[1b] 风扇 60×60×25 → 60×60×10(按实物 10 mm:外侧面贴墙)')
FAN10 = u'D:\\\\DCDC\\\\新建文件夹\\\\OD6010_m0_frame.SLDPRT'
_old = []
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if 'OD60' in nm.upper():
_old.append((list(prop(c, 'Transform2').ArrayData)[0:9], [x * 1000 for x in c.GetBox(False, False)]))
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
if len(_old) != 2:
P(u' ⛔ 旧风扇不是 2 只'); sys.exit(4)
def _setrt(c, rot, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
list(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 [x * 1000.0 for x in c.GetBox(False, False)]
for rot, b_old in _old:
west = b_old[0] < 50
c = doc.AddComponent4(FAN10, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ OD6010 AddComponent4 → None'); sys.exit(5)
b0 = _setrt(c, rot, (0, 0, 0))
dx = (%.1f - b0[0]) if west else (%.1f - b0[3])
dy = 51.345 - (b0[1] + b0[4]) / 2
dz = 37.85 - (b0[2] + b0[5]) / 2
b = _setrt(c, rot, (dx, dy, dz))
P(u' %%s风扇 x %%.2f~%%.2f y %%.2f~%%.2f z %%.2f~%%.2f(模型厚 %%.2f,实物 10)'
%% (u'西' if west else u'东', b[0], b[3], b[1], b[4], b[2], b[5], b[3] - b[0]))
''' % (IX0, IX1) + u"\ntops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}", 1),
], u'v3:短机版总装(风扇换 6010、分风腔 10、TopShell_v3 / BottomPlate_v3 / FilterFrame_v2)')
print(u'几何:外 x %.1f~%.1f(整机 %.1f),角柱中心 %.1f / %.1f' % (XO, X1, X1 - XO, PX_W, PX_E))
+123
查看文件
@@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
u"""派生 topshell_v4.py / place_v4.py(2026-09-13 用户要求):
· 去掉北墙 J33 调试插针窗口 + J26/J27 风扇插座出线窗口;
· 南墙两个 Type-C 开口改为**完全等于母座口内侧轮廓**:8.34 × 2.60、四角 R0.7,
中心 J1 (12.50, −1.65) / J23 (84.00, −1.65)(STEP 实测,见 probe_usbc2.py)。
⚠ 用户已知悉:母座口在墙外表面内 4.26 mm,护套进不了墙,普通数据线可能插不到底。
纯文本补丁,锚点断言次数。"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
T = u'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp\\'
s = open(T + 'topshell_v3.py', encoding='utf-8').read()
for old, new, n in ((u"TopShell_v3.SLDPRT'", u"TopShell_v4.SLDPRT'", 1),
(u"'TOPSHELL_V3'", u"'TOPSHELL_V4'", 1)):
if s.count(old) != n:
raise SystemExit(u'锚点 %r ×%d' % (old, s.count(old)))
s = s.replace(old, new)
a = s.index(u"P(u'\\n[5] ")
b = s.index(u'\n# ── [6b]')
if u'NORTH_WINDOWS, IY1, Y1))' not in s[a:b]:
raise SystemExit(u'⛔ [5]/[6] 区块不是预期内容')
NEW = u'''# ── [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)
'''
s = s[:a] + NEW + s[b:]
s = s.replace(u"if h.get(2.6, 0) != 8 or h.get(2.1, 0) != 4 or h.get(30.0, 0) != 2:",
u"if h.get(2.6, 0) != 8 or h.get(2.1, 0) != 4 or h.get(30.0, 0) != 2 or h.get(0.7, 0) != 8:")
s = s.replace(u"(期望 {2.1: 4, 2.6: 8, 3.0: 4, 30.0: 2})", u"(期望 {0.7: 8, 2.1: 4, 2.6: 8, 3.0: 4, 30.0: 2})")
if u'h.get(0.7, 0) != 8' not in s:
raise SystemExit(u'⛔ 验收行没改上')
s = s.replace(u'# -*- coding: utf-8 -*-\n',
u'# -*- coding: utf-8 -*-\n# v4:去北墙两个窗口;南墙 Type-C 开口 = 母座口内侧轮廓 8.34×2.60 R0.7\n', 1)
open(T + 'topshell_v4.py', 'w', encoding='utf-8').write(s)
print(u'写出 topshell_v4.py')
p = open(T + 'place_v3.py', encoding='utf-8').read()
for old, new in ((u"(u'TopShell_v3.SLDPRT',", u"(u'TopShell_v4.SLDPRT',"),
(u"((u'v3_iso', 7), (u'v3_west', 3), (u'v3_top', 1))",
u"((u'v4_iso', 7), (u'v4_south', 6), (u'v4_north', 5))")):
if p.count(old) != 1:
raise SystemExit(u'place 锚点 %r ×%d' % (old, p.count(old)))
p = p.replace(old, new)
open(T + 'place_v4.py', 'w', encoding='utf-8').write(p)
print(u'写出 place_v4.py')
+284
查看文件
@@ -0,0 +1,284 @@
# -*- coding: utf-8 -*-
u"""外观美化(2026-09-13 用户选 A+D、B+C、E+F):派生 topshell_v5 / filterframe_v3 / place_v5,新写 bottomplate_v4。
A 顶面外轮廓 R1.5 圆角;底盖四边内缩 0.5(阴影缝,圆角 R2.5 与顶盖 R3 同心)
D 滤网框高 90 → 84.4(与顶面齐平)+ 顶边指甲槽
B 东风口 Ø60 圆孔 → 11 条竖向圆头槽格栅(外包 R32、槽宽 4.5、筋 1.2;开孔 = 原 Ø60 的 81.2%)
C 顶面 11 条 1.5×60 直角缝 → 圆头槽 1.5×60.32(开孔面积不变 = 90 mm²/条)
E 底盖加厚 2.4 → 3.6(向下,内部尺寸不变)+ 四角沉头 Ø6.5 深 2.0 + 4 个 Ø10 深 1.0 脚垫窝
F 南墙 Type-C 右侧 9 mm 凹刻 0.6:J1「PWR 20V」= 闪电;J23「USB」= 箭头 + 圆点
"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
T = u'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp\\'
def patch_text(s, pairs, src):
for old, new, n in pairs:
c = s.count(old)
if c != n:
raise SystemExit(u'%s:锚点 %r 出现 %d 次(期望 %d)' % (src, old[:70], c, n))
s = s.replace(old, new)
return s
# ═══════════════════════════ topshell_v5 ═══════════════════════════════════
s = open(T + 'topshell_v4.py', encoding='utf-8').read()
SLOTS_OLD = u'''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)
'''
SLOTS_NEW = u'''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 相同
'''
HELPERS = u'''
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))
'''
TOPFILLET = u'''
# ── [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)
'''
EAST_OLD = u"cut_shapes(u'东端墙', (0, +1, X1), [((X1, cy, L(cz)), r) for cy, cz, r in END_CIRCLES], [], end_verify(X1))"
EAST_NEW = u'''# ── B:东端墙 Ø60 圆孔 → 竖向圆头槽格栅(西端被滤网盖住,保持圆孔)─────────────────
GR_R, GR_W, GR_RIB = 32.0, 4.5, 1.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 east_face_area():
return sum(a_ for ax_, sg, fb, a_, _ in planes()
if ax_ == 0 and sg == 1 and abs(fb[0] - X1) < 0.02 and abs(fb[3] - X1) < 0.02)
GRS = grille_slots()
A_SLOTS = sum(GR_W * (L_ - GR_W) + math.pi * (GR_W / 2) ** 2 for _, L_ in GRS)
A_HOLES = 4 * math.pi * R_FAN ** 2
P(u' 东端格栅:%d 条槽 宽 %.1f 筋 %.1f 外包 R%.0f,槽面积 %.1f(原 Ø60 = %.1f,%.1f%%)'
% (len(GRS), GR_W, GR_RIB, GR_R, A_SLOTS, math.pi * 900, A_SLOTS / (math.pi * 900) * 100))
a_before = east_face_area()
nbefore = len(faces())
X = open_face_sketch((0, +1, X1), u'东端墙')
sm.AddToDB = True
for cy, cz, r in END_CIRCLES[1:]:
q = to_sketch(X, (X1, cy, L(cz)))
sm.CreateCircleByRadius(q[0], q[1], 0.0, M(r))
zc = L(FAN_CZ)
for ys, L_ in GRS:
r, a = GR_W / 2, L_ / 2 - GR_W / 2
for p0, p1 in (((ys + r, zc - a), (ys + r, zc + a)), ((ys - r, zc + a), (ys - r, zc - a))):
q0, q1 = to_sketch(X, (X1, p0[0], p0[1])), to_sketch(X, (X1, p1[0], p1[1]))
sm.CreateLine(q0[0], q0[1], 0.0, q1[0], q1[1], 0.0)
for cz_, sg in ((zc + a, 1), (zc - a, -1)):
qc = to_sketch(X, (X1, ys, cz_))
q1 = to_sketch(X, (X1, ys + r, cz_))
q2 = to_sketch(X, (X1, ys - r, cz_))
qm = to_sketch(X, (X1, ys, cz_ + sg * 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))
sm.AddToDB = False
sm.InsertSketch(True)
c = do_cut(T + 0.2)
a_after = east_face_area()
d_area = a_before - a_after
ok_area = abs(d_area - (A_SLOTS + A_HOLES)) < 0.5
ends = sum(1 for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0)
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] - X1), abs(fb[3] - X1)) < 0.05
for rr, ax_, fb in cyls()) for cy, cz, _ in END_CIRCLES[1:])
P(u' 东端墙 切除 → %-12r 面数 %d→%d' % (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' 槽端半圆柱 R%.2f = %d(期望 %d)%s;风扇孔 4 个在位 %s'
% (GR_W / 2, ends, 2 * len(GRS), u'✅' if ends == 2 * len(GRS) else u'⛔', u'✅' if fan_ok else u'⛔'))
if not (ok_area and ends == 2 * len(GRS) and fan_ok):
P(u' ⛔⛔ 格栅不对,停手、不存盘'); sys.exit(5)'''
SYM_ANCHOR = u" P(u' ⛔⛔ 开口没落在母座口轮廓上 —— 停手、不存盘'); sys.exit(5)\n"
SYMBOLS = SYM_ANCHOR + u'''
# ── [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)
'''
HIST_OLD = u'''P(u' 圆柱面直方图 %s(期望 {0.7: 8, 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 or h.get(0.7, 0) != 8:'''
HIST_NEW = u'''WANT_H = {0.7: 8, 0.75: 2 * SLOT_N, 0.8: 1, 1.5: 4, 2.1: 4, round(GR_W / 2, 2): 2 * len(GRS), 2.6: 8, 3.0: 4, 30.0: 1}
P(u' 圆柱面直方图 %s(期望 %s)' % (h, dict(sorted(WANT_H.items()))))
if any(h.get(k, 0) != v for k, v in WANT_H.items()):'''
s = patch_text(s, [
(u"TopShell_v4.SLDPRT'", u"TopShell_v5.SLDPRT'", 1),
(u"'TOPSHELL_V4'", u"'TOPSHELL_V5'", 1),
(u"import os\n", u"import math\nimport os\n", 1),
(u"SLOT_W, SLOT_L, SLOT_N, SLOT_SPAN = 1.5, 60.0, 11, 106.0",
u"SLOT_W, SLOT_L, SLOT_N, SLOT_SPAN = 1.5, 60.32, 11, 106.0 # C:圆头槽,长 60.32 使面积仍 = 90", 1),
(u"\n# ── [3] 顶板 + 泄压缝", HELPERS + u"\n# ── [3] 顶板 + 泄压缝", 1),
(SLOTS_OLD, SLOTS_NEW, 1),
(u"and abs(fb[4] - fb[1] - SLOT_L) < 0.05)", u"and abs(fb[4] - fb[1] - (SLOT_L - SLOT_W)) < 0.05)", 1),
(u"\n# ── 面上草图切除(带候选映射)", TOPFILLET + u"\n# ── 面上草图切除(带候选映射)", 1),
(EAST_OLD, EAST_NEW, 1),
(SYM_ANCHOR, SYMBOLS, 1),
(HIST_OLD, HIST_NEW, 1),
], 'topshell_v4.py')
s = s.replace(u'# -*- coding: utf-8 -*-\n',
u'# -*- coding: utf-8 -*-\n# v5 外观:顶面 R1.5 圆角、圆头泄压槽、东风口竖槽格栅、Type-C 凹刻标识(其余同 v4)\n', 1)
open(T + 'topshell_v5.py', 'w', encoding='utf-8').write(s)
print(u'写出 topshell_v5.py')
# ═══════════════════════════ filterframe_v3 ════════════════════════════════
f = open(T + 'filterframe_v2.py', encoding='utf-8').read()
OUTLINE = u'''SCOOP_R, SCOOP_D = 8.0, 4.0 # D:顶边指甲槽,圆心在顶边上方 4、切进框体 4(弦长 13.9)
def outline():
cx, cy = W / 2.0, HH + SCOOP_R - SCOOP_D
half = math.sqrt(SCOOP_R ** 2 - (SCOOP_R - SCOOP_D) ** 2)
for a_, b_ in (((0, 0), (W, 0)), ((W, 0), (W, HH)), ((W, HH), (cx + half, HH)),
((cx - half, HH), (0, HH)), ((0, HH), (0, 0))):
sm.CreateLine(M(a_[0]), M(a_[1]), 0.0, M(b_[0]), M(b_[1]), 0.0)
q1, q2, qm = (cx + half, HH), (cx - half, HH), (cx, cy - SCOOP_R)
d = 1 if (q1[0] - cx) * (qm[1] - cy) - (q1[1] - cy) * (qm[0] - cx) > 0 else -1
sm.CreateArc(M(cx), M(cy), 0.0, M(q1[0]), M(q1[1]), 0.0, M(q2[0]), M(q2[1]), 0.0, d)
A_SCOOP = SCOOP_R ** 2 * math.acos((SCOOP_R - SCOOP_D) / SCOOP_R) - (SCOOP_R - SCOOP_D) * math.sqrt(SCOOP_R ** 2 - (SCOOP_R - SCOOP_D) ** 2)
'''
f = patch_text(f, [
(u"FilterFrame_v2.SLDPRT'", u"FilterFrame_v3.SLDPRT'", 1),
(u"'FILTERFRAME_V2'", u"'FILTERFRAME_V3'", 1),
(u"import os\n", u"import math\nimport os\n", 1),
(u"W, HH = 70.0, 90.0", u"W, HH = 70.0, 84.4 # D:顶边与顶盖顶面齐平(z −10 ~ 74.4)", 1),
(u"sm.CreateCornerRectangle(0.0, 0.0, 0.0, M(W), M(HH), 0.0)", u"outline()", 2),
(u"want_a = W * HH - WIN * WIN", u"want_a = W * HH - WIN * WIN - A_SCOOP", 1),
(u"want_b = W * HH - NCELL * NCELL * CELL * CELL", u"want_b = W * HH - NCELL * NCELL * CELL * CELL - A_SCOOP", 1),
], 'filterframe_v2.py')
i = f.index(u"P(u'\\n[A] ")
f = f[:i] + OUTLINE + f[i:]
f = f.replace(u'# -*- coding: utf-8 -*-\n', u'# -*- coding: utf-8 -*-\n# v3 外观:高 84.4 与顶面齐平 + 顶边 R8 指甲槽(深 4)\n', 1)
open(T + 'filterframe_v3.py', 'w', encoding='utf-8').write(f)
print(u'写出 filterframe_v3.py')
# ═══════════════════════════ place_v5 ══════════════════════════════════════
p = open(T + 'place_v4.py', encoding='utf-8').read()
p = patch_text(p, [
(u"(u'TopShell_v4.SLDPRT',", u"(u'TopShell_v5.SLDPRT',", 1),
(u"(u'BottomPlate_v3.SLDPRT', (0, 0, -10.0), [-24.8, -4.055, -12.4, 115.9, 106.745, 0.0])",
u"(u'BottomPlate_v4.SLDPRT', (0, 0, -10.0), [-24.3, -3.555, -13.6, 115.4, 106.245, 0.0])", 1),
(u"u'FilterFrame_v2.SLDPRT'", u"u'FilterFrame_v3.SLDPRT'", 1),
(u"_w = [-22.4, 16.345, -10.0, -18.4, 86.345, 80.0]", u"_w = [-22.4, 16.345, -10.0, -18.4, 86.345, 74.4]", 1),
(u"((u'v4_iso', 7), (u'v4_south', 6), (u'v4_north', 5))",
u"((u'v5_iso', 7), (u'v5_east', 0), (u'v5_south', 6), (u'v5_bottom', 2), (u'v5_top', 1))", 1),
], 'place_v4.py')
open(T + 'place_v5.py', 'w', encoding='utf-8').write(p)
print(u'写出 place_v5.py')
+119
查看文件
@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
u"""派生 topshell_v6.py / place_v6.py(2026-09-13 用户:两侧都改为风扇格栅)。
v5 的东墙格栅内联代码 → 函数 grille_cut(tag, xw, sg),西墙(朝外 −x)和东墙(朝外 +x)各调一次;
西墙原 Ø60 圆孔那一刀删掉。纯文本补丁,锚点断言次数。"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
T = u'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp\\'
s = open(T + 'topshell_v5.py', encoding='utf-8').read()
def rep(old, new, n=1):
global s
c = s.count(old)
if c != n:
raise SystemExit(u'锚点 %r 出现 %d 次(期望 %d)' % (old[:70], c, n))
s = s.replace(old, new)
rep(u"TopShell_v5.SLDPRT'", u"TopShell_v6.SLDPRT'")
rep(u"'TOPSHELL_V5'", u"'TOPSHELL_V6'")
rep(u"cut_shapes(u'西端墙', (0, -1, X0), [((X0, cy, L(cz)), r) for cy, cz, r in END_CIRCLES], [], end_verify(X0))\n", u"")
a = s.index(u'# ── B:东端墙 Ø60 圆孔 → 竖向圆头槽格栅')
end_marker = u" P(u' ⛔⛔ 格栅不对,停手、不存盘'); sys.exit(5)"
b = s.index(end_marker) + len(end_marker)
NEW = u'''# ── B:两端墙 Ø60 圆孔 → 竖向圆头槽格栅(2026-09-13 用户:两侧都改格栅)──────────────
GR_R, GR_W, GR_RIB = 32.0, 4.5, 1.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
GRS = grille_slots()
A_SLOTS = sum(GR_W * (L_ - GR_W) + math.pi * (GR_W / 2) ** 2 for _, L_ in GRS)
A_HOLES = 4 * math.pi * R_FAN ** 2
P(u' 格栅:%d 条槽 宽 %.1f 筋 %.1f 外包 R%.0f,槽面积 %.1f(原 Ø60 = %.1f,%.1f%%)'
% (len(GRS), GR_W, GR_RIB, GR_R, A_SLOTS, math.pi * 900, A_SLOTS / (math.pi * 900) * 100))
def grille_cut(tag, xw, sg):
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)
ends0 = sum(1 for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0)
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)
for ys, L_ in GRS:
r, a = GR_W / 2, L_ / 2 - GR_W / 2
for p0, p1 in (((ys + r, zc - a), (ys + r, zc + a)), ((ys - r, zc + a), (ys - r, zc - a))):
q0, q1 = to_sketch(X, (xw, p0[0], p0[1])), to_sketch(X, (xw, p1[0], p1[1]))
sm.CreateLine(q0[0], q0[1], 0.0, q1[0], q1[1], 0.0)
for cz_, s_ in ((zc + a, 1), (zc - a, -1)):
qc = to_sketch(X, (xw, ys, cz_))
q1 = to_sketch(X, (xw, ys + r, cz_))
q2 = to_sketch(X, (xw, ys - r, cz_))
qm = to_sketch(X, (xw, ys, cz_ + 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))
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
ends = sum(1 for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0) - ends0
caps_ok = all(abs((fb[4] - fb[1]) - GR_W) < 0.02 and abs((fb[5] - fb[2]) - GR_W / 2) < 0.02
for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0)
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),全部「y 跨槽宽、z 跨半径」= 竖槽 %s;风扇孔 4 个在位 %s'
% (ends, 2 * len(GRS), u'✅' if caps_ok else u'⛔', u'✅' if fan_ok else u'⛔'))
if not (ok_area and ends == 2 * len(GRS) and caps_ok and fan_ok):
P(u' ⛔⛔ %s 格栅不对,停手、不存盘' % tag); sys.exit(5)
grille_cut(u'西端墙', X0, -1)
grille_cut(u'东端墙', X1, +1)'''
s = s[:a] + NEW + s[b:]
rep(u"WANT_H = {0.7: 8, 0.75: 2 * SLOT_N, 0.8: 1, 1.5: 4, 2.1: 4, round(GR_W / 2, 2): 2 * len(GRS), 2.6: 8, 3.0: 4, 30.0: 1}",
u"WANT_H = {0.7: 8, 0.75: 2 * SLOT_N, 0.8: 1, 1.5: 4, 2.1: 4, round(GR_W / 2, 2): 4 * len(GRS), 2.6: 8, 3.0: 4, 30.0: 0}")
s = s.replace(u'# -*- coding: utf-8 -*-\n', u'# -*- coding: utf-8 -*-\n# v6:两端墙都用竖槽格栅(西端 Ø60 圆孔取消),其余同 v5\n', 1)
if u"end_verify(X0)" in s:
raise SystemExit(u'⛔ 西墙圆孔那一刀还在')
open(T + 'topshell_v6.py', 'w', encoding='utf-8').write(s)
print(u'写出 topshell_v6.py')
p = open(T + 'place_v5.py', encoding='utf-8').read()
for old, new in ((u"(u'TopShell_v5.SLDPRT',", u"(u'TopShell_v6.SLDPRT',"),
(u"((u'v5_iso', 7), (u'v5_east', 0), (u'v5_south', 6), (u'v5_bottom', 2), (u'v5_top', 1))",
u"((u'v6_iso', 7),)")):
if p.count(old) != 1:
raise SystemExit(u'place 锚点 %r ×%d' % (old, p.count(old)))
p = p.replace(old, new)
open(T + 'place_v6.py', 'w', encoding='utf-8').write(p)
print(u'写出 place_v6.py')
+62
查看文件
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
u"""派生无滤网版(2026-09-13 用户:去掉滤网设计,两端都用风扇格栅):
topshell_v6 → topshell_v7:删西端滤网导轨(整机不再有 6.4 外凸),两端格栅保留
bottomplate_v4 → bottomplate_v5:西边缘 −24.3 → −17.9(顶盖外沿 −18.4 再内缩 0.5)
place_v6 → place_v7:删滤网框插入与滑槽间隙核对;新包围盒
整机长 140.7 → 134.3。纯文本补丁,锚点断言次数。"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
T = u'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp\\'
def patch(src, dst, pairs, cuts, head):
s = open(T + src, encoding='utf-8').read()
for a_mark, b_mark in cuts:
if s.count(a_mark) != 1 or s.count(b_mark) != 1:
raise SystemExit(u'%s:删段锚点次数不对 %r / %r' % (src, a_mark[:40], b_mark[:40]))
a = s.index(a_mark)
b = s.index(b_mark)
if b <= a:
raise SystemExit(u'%s:删段锚点顺序不对' % src)
s = s[:a] + s[b:]
for old, new, n in pairs:
c = s.count(old)
if c != n:
raise SystemExit(u'%s:锚点 %r 出现 %d 次(期望 %d)' % (src, old[:70], c, n))
s = s.replace(old, new)
s = s.replace(u'# -*- coding: utf-8 -*-\n', u'# -*- coding: utf-8 -*-\n# ' + head + u'\n', 1)
open(T + dst, 'w', encoding='utf-8').write(s)
print(u'写出', dst)
patch('topshell_v6.py', 'topshell_v7.py', [
(u"TopShell_v6.SLDPRT'", u"TopShell_v7.SLDPRT'", 1),
(u"'TOPSHELL_V6'", u"'TOPSHELL_V7'", 1),
(u"check(u'最终', [XO, Y0, -H, X1, Y1, 0.0])", u"check(u'最终', [X0, Y0, -H, X1, Y1, 0.0])", 1),
], [(u'# ── [6b] 西端插拔式滤网导轨', u'\n# ── 验收')],
u'v7:无滤网版 —— 删西端滤网导轨,两端墙竖槽格栅,外形 x −18.4~115.9')
patch('bottomplate_v4.py', 'bottomplate_v5.py', [
(u"BottomPlate_v4.SLDPRT'", u"BottomPlate_v5.SLDPRT'", 1),
(u"'BOTTOMPLATE_V4'", u"'BOTTOMPLATE_V5'", 1),
(u"X0, X1, Y0, Y1 = -24.8 + INSET, 115.9 - INSET, -4.055 + INSET, 106.745 - INSET",
u"X0, X1, Y0, Y1 = -18.4 + INSET, 115.9 - INSET, -4.055 + INSET, 106.745 - INSET # 无滤网:西沿回到顶盖外沿 −18.4", 1),
], [], u'v5:无滤网版 —— 西边缘 −17.9(不再托滤网/导轨),脚垫窝随中心重排')
patch('place_v6.py', 'place_v7.py', [
(u"(u'TopShell_v6.SLDPRT', (0, 0, 74.4), [-24.8, -4.055, -10.0, 115.9, 106.745, 74.4])",
u"(u'TopShell_v7.SLDPRT', (0, 0, 74.4), [-18.4, -4.055, -10.0, 115.9, 106.745, 74.4])", 1),
(u"(u'BottomPlate_v4.SLDPRT', (0, 0, -10.0), [-24.3, -3.555, -13.6, 115.4, 106.245, 0.0])",
u"(u'BottomPlate_v5.SLDPRT', (0, 0, -10.0), [-17.9, -3.555, -13.6, 115.4, 106.245, 0.0])", 1),
(u"((u'v6_iso', 7),)", u"((u'v7_iso', 7),)", 1),
], [(u"\nP(u'\\n[2b] 插滤网框", u"\nP(u'\\n[1b] 风扇")],
u'v7:无滤网版总装 —— 删滤网框(OLD 列表里的 FILTERFRAME 负责把旧件删掉)')
for f in ('topshell_v7.py', 'place_v7.py'):
txt = open(T + f, encoding='utf-8').read()
for bad in (u'[6b]', u'XO, Y0', u'FilterFrame_v', u'滑槽间隙'):
if bad in txt:
raise SystemExit(u'⛔ %s 里还残留 %r' % (f, bad))
print(u'残留检查通过')
+187
查看文件
@@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
u"""派生 topshell_v8 / place_v8 / render_v8(2026-09-13 用户:格栅无法打印 → 方案 A「竖槽 + 3 条横撑」)。
问题:倒扣打印时端墙竖着长高,竖槽之间的筋 1.2 宽 × 2.4 厚 × 最长 64 高(长细比 > 50),
只有下端连着墙往上悬空长 ⇒ 晃动、拉丝、打断。
改法:在风扇中心 z −16 / 0 / +16 加 3 条 1.2 宽的水平横撑,每条竖槽被切成若干段,
最长悬空筋段 64 → 16;开孔 2295 → 2138 mm²(原 Ø60 的 81.2% → 75.6%)。
每段轮廓:挨着槽头的一端保持半圆,挨着横撑的一端是平边;横撑必须整条落在槽的直边段里或完全不碰槽(断言)。
纯文本补丁,锚点断言次数。"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
T = u'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp\\'
s = open(T + 'topshell_v7.py', encoding='utf-8').read()
def rep(old, new, n=1):
global s
c = s.count(old)
if c != n:
raise SystemExit(u'锚点 %r 出现 %d 次(期望 %d)' % (old[:70], c, n))
s = s.replace(old, new)
rep(u"TopShell_v7.SLDPRT'", u"TopShell_v8.SLDPRT'")
rep(u"'TOPSHELL_V7'", u"'TOPSHELL_V8'")
a = s.index(u'# ── B:两端墙 Ø60 圆孔 → 竖向圆头槽格栅')
end_marker = u"grille_cut(u'东端墙', X1, +1)"
b = s.index(end_marker) + len(end_marker)
NEW = u'''# ── B:两端墙竖槽格栅 + 3 条横撑(v8:解决「长细筋打不出来」)─────────────────────
GR_R, GR_W, GR_RIB = 32.0, 4.5, 1.2
XB_Z, XB_W = (-16.0, 0.0, 16.0), 1.2 # 横撑中心(相对风扇中心 z)与宽度
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=该端是半圆槽头"""
r = GR_W / 2
h, a_ = L_ / 2, L_ / 2 - r
cuts = []
for c in XB_Z:
lo, hi = c - XB_W / 2, c + XB_W / 2
if hi <= -h or lo >= h:
continue # 横撑碰不到这条槽
if lo < -a_ or hi > a_:
raise SystemExit(u'⛔ 横撑 z=%.1f 切进了槽头半圆(槽半长 %.2f),调整 XB_Z' % (c, h))
cuts.append((lo, hi))
cuts.sort()
segs, z = [], -h
for lo, hi in cuts:
segs.append((z, lo, z == -h, False))
z = hi
segs.append((z, h, z == -h, True))
return segs, len(cuts)
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
for _ys, _L in GRS:
_segs, _nc = slot_segments(_L)
A_SLOTS += sum(seg_area(*sg) for sg in _segs)
N_FLAT += 2 * _nc
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)+ 3 条横撑 %.1f,开孔 %.1f mm²(原 Ø60 的 %.1f%%),最长槽段 %.1f'
% (len(GRS), GR_W, GR_RIB, GR_R, XB_W, A_SLOTS, A_SLOTS / (math.pi * 900) * 100, LONGEST))
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, 2 * len(GRS), u'✅' if caps == 2 * len(GRS) else u'⛔',
flats, N_FLAT, u'✅' if flats == N_FLAT else u'⛔', u'✅' if fan_ok else u'⛔'))
if not (ok_area and caps == 2 * len(GRS) and flats == N_FLAT and fan_ok):
P(u' ⛔⛔ %s 格栅不对,停手、不存盘' % tag); sys.exit(5)
grille_cut(u'西端墙', X0, -1)
grille_cut(u'东端墙', X1, +1)'''
s = s[:a] + NEW + s[b:]
s = s.replace(u'# -*- coding: utf-8 -*-\n', u'# -*- coding: utf-8 -*-\n# v8:格栅竖槽加 3 条横撑(最长悬空筋 64 → 16),其余同 v7\n', 1)
open(T + 'topshell_v8.py', 'w', encoding='utf-8').write(s)
print(u'写出 topshell_v8.py')
p = open(T + 'place_v7.py', encoding='utf-8').read()
for old, new in ((u"(u'TopShell_v7.SLDPRT',", u"(u'TopShell_v8.SLDPRT',"), (u"((u'v7_iso', 7),)", u"((u'v8_iso', 7),)")):
if p.count(old) != 1:
raise SystemExit(u'place 锚点 %r ×%d' % (old, p.count(old)))
p = p.replace(old, new)
open(T + 'place_v8.py', 'w', encoding='utf-8').write(p)
print(u'写出 place_v8.py')
rsrc = open(T + 'render_v7.py', encoding='utf-8').read()
rsrc = rsrc.replace(u"v7_", u"v8_")
ZOOM_OLD = u" doc.ViewZoomTo2(0.000, -0.005, -0.012, 0.100, -0.005, 0.008)"
if rsrc.count(ZOOM_OLD) != 1:
raise SystemExit(u'render 锚点')
rsrc += u'''
vid = find((1.0, 0.0, 0.0))
if vid is not None:
doc.ShowNamedView2('', vid)
doc.ViewZoomTo2(-0.0184, 0.015, 0.002, -0.0184, 0.088, 0.074) # 拉近西端格栅
doc.SaveBMP(os.path.join(OUT, 'v8_west_zoom.bmp'), 1600, 1100)
print(u' v8_west_zoom ← vid %d + ViewZoomTo2' % vid)
'''
open(T + 'render_v8.py', 'w', encoding='utf-8').write(rsrc)
print(u'写出 render_v8.py')
+199
查看文件
@@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-
u"""派生 topshell_v8 / place_v8 / render_v8(2026-09-13 用户:格栅无法打印 → 方案 A「竖槽 + 3 条横撑」)。
问题:倒扣打印时端墙竖着长高,竖槽之间的筋 1.2 宽 × 2.4 厚 × 最长 64 高(长细比 > 50),
只有下端连着墙往上悬空长 ⇒ 晃动、拉丝、打断。
改法:在风扇中心 z −16 / 0 / +16 加 3 条 1.2 宽的水平横撑,每条竖槽被切成若干段,
最长悬空筋段 64 → 16;开孔 2295 → 2138 mm²(原 Ø60 的 81.2% → 75.6%)。
每段轮廓:挨着槽头的一端保持半圆,挨着横撑的一端是平边;横撑必须整条落在槽的直边段里或完全不碰槽(断言)。
纯文本补丁,锚点断言次数。"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
T = u'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp\\'
s = open(T + 'topshell_v7.py', encoding='utf-8').read()
def rep(old, new, n=1):
global s
c = s.count(old)
if c != n:
raise SystemExit(u'锚点 %r 出现 %d 次(期望 %d)' % (old[:70], c, n))
s = s.replace(old, new)
rep(u"TopShell_v7.SLDPRT'", u"TopShell_v9.SLDPRT'")
rep(u"'TOPSHELL_V7'", u"'TOPSHELL_V9'")
a = s.index(u'# ── B:两端墙 Ø60 圆孔 → 竖向圆头槽格栅')
end_marker = u"grille_cut(u'东端墙', X1, +1)"
b = s.index(end_marker) + len(end_marker)
NEW = u'''# ── 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)'''
s = s[:a] + NEW + s[b:]
_old_h = u"WANT_H = {0.7: 8, 0.75: 2 * SLOT_N, 0.8: 1, 1.5: 4, 2.1: 4, round(GR_W / 2, 2): 4 * len(GRS), 2.6: 8, 3.0: 4, 30.0: 0}"
if s.count(_old_h) != 1:
raise SystemExit(u'直方图锚点')
# ⚠ 槽宽 6 ⇒ 槽头半径 3.0,与顶盖竖边圆角 R3 同半径:必须累加,不能在字典字面量里重复键(后者会覆盖前者)
s = s.replace(_old_h, u"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}\n"
u"WANT_H[round(GR_W / 2, 2)] = WANT_H.get(round(GR_W / 2, 2), 0) + 2 * N_ROUND")
s = s.replace(u'# -*- coding: utf-8 -*-\n', u'# -*- coding: utf-8 -*-\n# v9:格栅 槽宽 6 / 竖筋 2.0 / 横筋 2.0 中心距 12(最长槽段 10),其余同 v7\n', 1)
open(T + 'topshell_v9.py', 'w', encoding='utf-8').write(s)
print(u'写出 topshell_v9.py')
p = open(T + 'place_v7.py', encoding='utf-8').read()
for old, new in ((u"(u'TopShell_v7.SLDPRT',", u"(u'TopShell_v9.SLDPRT',"), (u"((u'v7_iso', 7),)", u"((u'v9_iso', 7),)")):
if p.count(old) != 1:
raise SystemExit(u'place 锚点 %r ×%d' % (old, p.count(old)))
p = p.replace(old, new)
open(T + 'place_v9.py', 'w', encoding='utf-8').write(p)
print(u'写出 place_v9.py')
rsrc = open(T + 'render_v7.py', encoding='utf-8').read()
rsrc = rsrc.replace(u"v7_", u"v9_")
ZOOM_OLD = u" doc.ViewZoomTo2(0.000, -0.005, -0.012, 0.100, -0.005, 0.008)"
if rsrc.count(ZOOM_OLD) != 1:
raise SystemExit(u'render 锚点')
rsrc += u'''
vid = find((1.0, 0.0, 0.0))
if vid is not None:
doc.ShowNamedView2('', vid)
doc.ViewZoomTo2(-0.0184, 0.015, 0.002, -0.0184, 0.088, 0.074) # 拉近西端格栅
doc.SaveBMP(os.path.join(OUT, 'v9_west_zoom.bmp'), 1600, 1100)
print(u' v9_west_zoom ← vid %d + ViewZoomTo2' % vid)
'''
open(T + 'render_v9.py', 'w', encoding='utf-8').write(rsrc)
print(u'写出 render_v9.py')
+152
查看文件
@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
u"""北侧板 v3 —— 跟着 15 mm 分风腔加长(2026-09-13)。
与 v2 的差别同南板 v4:长度 152.8 → **175.0**(x −38.4~136.4)、
A 组螺丝孔挪到端板角柱中心 **x −31/129,z 10/60**(旧的 x −13.70/111.70 打在空气里)。
两个开口(J33 应急排针、J26/J27 风扇座出线)对准的是底板上的座子,座子没动 ⇒ 位置不动。
⛔ 竖板、法线沿 y ⇒ 上视基准面,草图 (u,v) → 模型 (x, −z)。
⛔ 画草图前 `sm.AddToDB = True`。
"""
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\\NorthPanel_v3.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
X0, X1 = -38.4, 136.4
Z0, Z1 = -10.0, 72.0
PANEL_T = 2.4
OPENINGS = [(72.14, 87.85, -3.0, 12.0), # J33 应急排针
(0.53, 19.03, -3.0, 15.0)] # J26/J27 风扇座出线
SCREW_D = 3.4
POST_CX_W, POST_CX_E = -31.0, 129.0
SCREW_HOLES = [(POST_CX_W, 10.0), (POST_CX_W, 60.0),
(POST_CX_E, 10.0), (POST_CX_E, 60.0),
(30.0, -5.0), (49.0, -5.0), (68.0, -5.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 UZ(z):
return M(-z)
app = wc.GetActiveObject('SldWorks.Application')
for d in list(app.GetDocuments or []):
try:
t = prop(d, 'GetTitle') or ''
if 'NORTHPANEL_V3' in t.upper():
app.CloseDoc(t)
P(u'关掉旧文档 %s' % 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 sk_name():
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 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)]
P(u'\n[1] 板本体 %.1f(x) × %.1f(z) × %.1f,含 %d 个开口 + %d 个 Ø%.1f 螺丝孔'
% (X1 - X0, Z1 - Z0, PANEL_T, len(OPENINGS), len(SCREW_HOLES), SCREW_D))
pick(u'上视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(M(X0), UZ(Z0), 0.0, M(X1), UZ(Z1), 0.0)
for ox0, ox1, oz0, oz1 in OPENINGS:
sm.CreateCornerRectangle(M(ox0), UZ(oz0), 0.0, M(ox1), UZ(oz1), 0.0)
for sx, sz in SCREW_HOLES:
sm.CreateCircleByRadius(M(sx), UZ(sz), 0.0, M(SCREW_D / 2))
sm.AddToDB = False
_segs = prop(D(prop(sm, 'ActiveSketch')), 'GetSketchSegments')
want_segs = 4 * (1 + len(OPENINGS)) + len(SCREW_HOLES)
P(u' 草图 segment = %d(期望 %d)' % (len(_segs) if _segs else 0, want_segs))
sm.InsertSketch(True)
if pick(sk_name(), 'SKETCH') != 1:
P(u'⛔ 草图没选中'); sys.exit(2)
feat = fm.FeatureExtrusion3(True, False, False, 0, 0, M(PANEL_T), 0.0,
False, False, False, False, 0.0, 0.0,
False, False, False, False, True, True, True, 0, 0.0, False)
P(u' 拉伸 → %r' % (prop(feat, 'Name') if feat else feat))
b = bbox()
P(u' box %s' % b)
if not b:
P(u'⛔ 没出实体,停手'); sys.exit(3)
bd = D(bodies()[0])
nf = len(prop(bd, 'GetFaces'))
want_faces = 6 + 4 * len(OPENINGS)
P(u' 面数 = %d(期望 ≥ %d)%s' % (nf, want_faces, u'✅' if nf >= want_faces else u'⛔ 开口没成形'))
hist = {}
for f in prop(bd, 'GetFaces'):
s = prop(D(f), 'GetSurface')
if prop(s, 'IsCylinder'):
r = round(list(prop(s, 'CylinderParams'))[6] * 1000, 2)
hist[r] = hist.get(r, 0) + 1
P(u' 圆柱面直方图 %s' % dict(sorted(hist.items())))
n34 = hist.get(round(SCREW_D / 2, 2), 0)
P(u' Ø%.1f 螺丝孔 = %d 个(期望 %d)%s'
% (SCREW_D, n34, len(SCREW_HOLES), u'✅' if n34 >= len(SCREW_HOLES) else u'⛔ 没打全'))
if n34 < len(SCREW_HOLES):
P(u'⛔ 停手'); sys.exit(4)
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))
if not os.path.exists(DST):
P(u'⛔ 没落盘,停手'); sys.exit(5)
P(u'⚠ 零件保持打开')
+101
查看文件
@@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
u"""由 make_v8.py 文本派生 make_v9.py(槽宽 6 / 竖筋 2.0 / 横筋 2.0 中心距 12 / 最长槽段 10)。"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
T = u'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp\\'
s = open(T + 'make_v8.py', encoding='utf-8').read()
def rep(old, new, n=1):
global s
c = s.count(old)
if c != n:
raise SystemExit(u'锚点 %r 出现 %d 次(期望 %d)' % (old[:60], c, n))
s = s.replace(old, new)
rep(u'u"TopShell_v8.SLDPRT\'"', u'u"TopShell_v9.SLDPRT\'"')
rep(u'u"\'TOPSHELL_V8\'"', u'u"\'TOPSHELL_V9\'"')
rep(u"GR_R, GR_W, GR_RIB = 32.0, 4.5, 1.2\nXB_Z, XB_W = (-16.0, 0.0, 16.0), 1.2 # 横撑中心(相对风扇中心 z)与宽度",
u"GR_R, GR_W, GR_RIB = 32.0, 6.0, 2.0 # v9:槽宽 6、竖筋 2.0(用户选 ③)\n"
u"XB_W, XB_SEG = 2.0, 10.0 # 横筋宽 2.0,最长槽段 10 ⇒ 横筋中心距 12\n"
u"XB_P = XB_SEG + XB_W\n"
u"XB_Z = tuple(k * XB_P for k in range(-int(GR_R // XB_P) - 1, int(GR_R // XB_P) + 2))")
a = s.index(u"def slot_segments(L_):")
b = s.index(u"def seg_area(z0, z1, r0, r1):")
NEWSEG = u'''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
'''
s = s[:a] + NEWSEG + s[b:]
rep(u"""for _ys, _L in GRS:
_segs, _nc = slot_segments(_L)
A_SLOTS += sum(seg_area(*sg) for sg in _segs)
N_FLAT += 2 * _nc
LONGEST = max([LONGEST] + [sg[1] - sg[0] for sg in _segs])""",
u"""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])""")
rep(u"""P(u' 格栅:%d 条竖槽(宽 %.1f 筋 %.1f 外包 R%.0f)+ 3 条横撑 %.1f,开孔 %.1f mm²(原 Ø60 的 %.1f%%),最长槽段 %.1f'
% (len(GRS), GR_W, GR_RIB, GR_R, XB_W, A_SLOTS, A_SLOTS / (math.pi * 900) * 100, LONGEST))""",
u"""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))""")
rep(u""" P(u' 槽头半圆柱 +%d(期望 %d)%s;横撑平端面 +%d(期望 %d)%s;风扇孔在位 %s'
% (caps, 2 * len(GRS), u'✅' if caps == 2 * len(GRS) else u'⛔',
flats, N_FLAT, u'✅' if flats == N_FLAT else u'⛔', u'✅' if fan_ok else u'⛔'))
if not (ok_area and caps == 2 * len(GRS) and flats == N_FLAT and fan_ok):""",
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):""")
# 顶盖直方图里 R2.25 的期望(v7 的 4×len(GRS))要换成 N_ROUND×2(两面墙)与新半径
rep(u"'# -*- coding: utf-8 -*-\\n# v8:格栅竖槽加 3 条横撑(最长悬空筋 64 → 16),其余同 v7\\n'",
u"'# -*- coding: utf-8 -*-\\n# v9:格栅 槽宽 6 / 竖筋 2.0 / 横筋 2.0 中心距 12(最长槽段 10),其余同 v7\\n'")
rep(u"s = s[:a] + NEW + s[b:]\n",
u"s = s[:a] + NEW + s[b:]\n"
u"_old_h = u\"WANT_H = {0.7: 8, 0.75: 2 * SLOT_N, 0.8: 1, 1.5: 4, 2.1: 4, round(GR_W / 2, 2): 4 * len(GRS), 2.6: 8, 3.0: 4, 30.0: 0}\"\n"
u"if s.count(_old_h) != 1:\n"
u" raise SystemExit(u'直方图锚点')\n"
u"# ⚠ 槽宽 6 ⇒ 槽头半径 3.0,与顶盖竖边圆角 R3 同半径:必须累加,不能在字典字面量里重复键(后者会覆盖前者)\n"
u"s = s.replace(_old_h, u\"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}\\n\"\n"
u" u\"WANT_H[round(GR_W / 2, 2)] = WANT_H.get(round(GR_W / 2, 2), 0) + 2 * N_ROUND\")\n")
rep(u"open(T + 'topshell_v8.py', 'w', encoding='utf-8').write(s)\nprint(u'写出 topshell_v8.py')",
u"open(T + 'topshell_v9.py', 'w', encoding='utf-8').write(s)\nprint(u'写出 topshell_v9.py')")
rep(u"""(u"(u'TopShell_v7.SLDPRT',", u"(u'TopShell_v8.SLDPRT',"), (u"((u'v7_iso', 7),)", u"((u'v8_iso', 7),)")""",
u"""(u"(u'TopShell_v7.SLDPRT',", u"(u'TopShell_v9.SLDPRT',"), (u"((u'v7_iso', 7),)", u"((u'v9_iso', 7),)")""")
rep(u"open(T + 'place_v8.py', 'w', encoding='utf-8').write(p)\nprint(u'写出 place_v8.py')",
u"open(T + 'place_v9.py', 'w', encoding='utf-8').write(p)\nprint(u'写出 place_v9.py')")
rep(u'rsrc = rsrc.replace(u"v7_", u"v8_")', u'rsrc = rsrc.replace(u"v7_", u"v9_")')
rep(u"v8_west_zoom", u"v9_west_zoom", 2)
rep(u"open(T + 'render_v8.py', 'w', encoding='utf-8').write(rsrc)\nprint(u'写出 render_v8.py')",
u"open(T + 'render_v9.py', 'w', encoding='utf-8').write(rsrc)\nprint(u'写出 render_v9.py')")
open(T + 'make_v9.py', 'w', encoding='utf-8').write(s)
print(u'写出 make_v9.py')
+124
查看文件
@@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
u"""把 topshell_v3.py 里「候选映射逐个试」的切除换成「读草图变换 + 先验平面再下刀」。
起因(2026-09-13 实测):东端墙那一刀 Select4 返回 True、选中数 1、选中面 box 也对,
但 InsertSketch 之后草图却建在了**顶板**上(风口从顶上切穿),靠「切出了东西但不在目标位置」才拦下。
⇒ 选中数不能证明草图建在哪;**进草图后读 ActiveSketch.ModelToSketchTransform**:
列主序 M[r][c] = a[3c+r],s = M·p + t(米);第 3 行 = 草图法线。
法线轴 ≠ 目标轴 或 墙面上一点的草图 z ≠ 0 ⇒ 退出重进,三次不成就停手。
同一个变换顺带给出精确的 uv 映射,不再需要候选法。
"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
P_ = u'C:\\Users\\35138\\.claude\\jobs\\11836093\\tmp\\topshell_v3.py'
s = open(P_, encoding='utf-8').read()
a = s.index(u'def try_cut(')
b = s.index(u'\n# ── [6b]')
NEW = u'''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))
'''
s = s[:a] + NEW + s[b:]
if s.count(u'try_cut(') or s.count(u'X_CANDS'):
raise SystemExit(u'⛔ 旧的候选法残留')
open(P_, 'w', encoding='utf-8').write(s)
print(u'topshell_v3.py 已换成「读草图变换 + 先验平面」版')
+233
查看文件
@@ -0,0 +1,233 @@
# -*- coding: utf-8 -*-
u"""**v2**:两件式 + 西端插拔式滤网(TopShell_v2 / BottomPlate_v2 / FilterFrame_v1)。
(以下为 v1 说明)总装换成**两件式外壳**:TopShell_v1(一体顶盖)+ BottomPlate_v1(一整块底盖)。
删:Tray_v9、EndPlate_v13 ×2、SouthPanel_v4、NorthPanel_v3、TopPanel_v4(六件式全部)
插:TopShell_v1 dz +74.4、BottomPlate_v1 dz −10(纯平移)
不动:底板、5 片刀片、2 只风扇
判据:
1. 两件装配 box;分风腔深度复测(西 15.01 / 东 15.51)
2. 干涉 = 360 基线
3. 顶盖墙底 ↔ 底盖顶面 **面贴合**(z=−10)
4. 同轴:底盖四角 Ø3.4 过孔 ↔ 顶盖角柱 Ø4.2 螺母孔(轴 z)
5. 同轴:顶盖端墙 Ø3.4 风扇孔 ↔ 风扇模型 R2.15 安装孔(轴 x)—— 六件式时代没核过
6. 截图
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
OLD = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL', 'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def setx(c, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
IDENT + [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 [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
allok = True
P(u'[1] 删六件式')
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if any(k in nm.upper() for k in OLD):
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
left = [prop(c, 'Name2') for c in doc.GetComponents(True)
if any(k in prop(c, 'Name2').upper() for k in OLD)]
if left:
P(u' ⛔ 没删干净(EditDelete 式假成功?)%s' % left); sys.exit(2)
P(u'\n[2] 插两件')
for fn, t, want in ((u'TopShell_v2.SLDPRT', (0, 0, 74.4), [-43.8, -4.055, -10.0, 136.4, 106.745, 74.4]),
(u'BottomPlate_v2.SLDPRT', (0, 0, -10.0), [-43.8, -4.055, -12.4, 136.4, 106.745, 0.0])):
c = doc.AddComponent4(DIR + fn, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
b = setx(c, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %-22s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (fn, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
P(u'\n[2b] 插滤网框(本地 x→y、y→z、z→x)')
_fc = doc.AddComponent4(DIR + u'FilterFrame_v1.SLDPRT', '', 0.0, 0.0, 0.0)
if _fc is None:
P(u' ⛔ FilterFrame AddComponent4 → None'); sys.exit(3)
_v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
[0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, -0.0404, 0.016345, -0.010, 1.0, 0.0, 0.0, 0.0])
_xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, _v))
try:
_fc.SetTransformAndSolve2(_xf)
except Exception:
_fc.Transform2 = _xf
doc.EditRebuild3
_b = [round(x * 1000.0, 3) for x in _fc.GetBox(False, False)]
_w = [-41.4, 16.345, -10.0, -38.4, 86.345, 80.0]
_ok = all(abs(_b[i] - _w[i]) < 0.05 for i in range(6))
allok &= _ok
P(u' 滤网框 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (_b[0], _b[3], _b[1], _b[4], _b[2], _b[5], u'✅' if _ok else u'⛔ 期望 %s' % _w))
P(u' 滑槽间隙:贴墙 %.2f(应 0)/ 唇边侧 %.2f(应 0.4)/ 南 %.2f、北 %.2f(应 0.3)/ 底 %.2f(落在底盖上,应 0)/ 露出顶盖 %.2f'
% (-38.4 - _b[3], _b[0] - (-41.8), _b[1] - 16.045, 86.645 - _b[4], _b[2] - (-10.0), _b[5] - 74.4))
tops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}
box = {n: [x * 1000 for x in c.GetBox(False, False)] for n, c in tops.items()}
fans = sorted([b for n, b in box.items() if 'OD6025' in n.upper()], key=lambda b: b[0])
blades = [b for n, b in box.items() if 'BLADE' in n.upper()]
pw = min(b[0] for b in blades) - fans[0][3]
pe = fans[1][0] - max(b[3] for b in blades)
P(u' 分风腔 西 %.2f / 东 %.2f %s' % (pw, pe, u'✅' if pw > 14 and pe > 14 else u'⛔'))
allok &= pw > 14 and pe > 14
P(u'\n[3] 干涉(基线 360)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = True
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
hits = []
if n and n != 360:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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(k in y.upper() for y in nms for k in ('TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')):
hits.append((o.Volume * 1e9, nms))
idm.Done()
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
for vv, nms in sorted(hits, reverse=True)[:8]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
allok &= n == 360
P(u'\n[4] 贴合:顶盖墙底 ↔ 底盖顶面')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
P(u' 顶盖 z 下沿 %.3f / 底盖板面 z −10(底盖 box 上沿 %.3f 是 PCB 立柱顶)'
% (ts[2], bp[5]))
allok &= abs(ts[2] + 10.0) < 0.05
def world_holes(comp, radius, tol=0.01):
xf = list(prop(comp, 'Transform2').ArrayData)
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
for bd in comp.GetBodies2(0) or []:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > tol:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
out.append((max(range(3), key=lambda i: abs(ga[i])), gp))
return out
def pair(tag, A, B, axis, want):
A = [p for ax, p in A if ax == axis]
B = [p for ax, p in B if ax == axis]
o = [i for i in range(3) if i != axis]
ok = 0
for p in A:
lat = min((max(abs(p[i] - q[i]) for i in o) for q in B), default=99)
ok += lat < 0.05
P(u' %s (%s=%7.2f, %s=%6.2f) 横偏 %.3f %s'
% (tag, 'xyz'[o[0]], p[o[0]], 'xyz'[o[1]], p[o[1]], lat, u'✅' if lat < 0.05 else u'⛔'))
P(u' %s 对上 %d / %d(期望 %d)' % (tag, ok, len(A), want))
return ok == want and len(A) == want
shell = [c for n, c in tops.items() if 'TOPSHELL' in n.upper()][0]
plate = [c for n, c in tops.items() if 'BOTTOMPLATE' in n.upper()][0]
fanc = [c for n, c in tops.items() if 'OD6025' in n.upper()]
P(u'\n[5] 同轴:底盖四角过孔 ↔ 顶盖角柱螺母孔')
thru = [h for h in world_holes(plate, 1.7)]
nuts = [h for h in world_holes(shell, 2.1)]
allok &= pair(u'底盖过孔', thru, nuts, 2, 4)
P(u'\n[6] 同轴:顶盖端墙风扇孔 ↔ 风扇安装孔(R2.15)')
wall = world_holes(shell, 1.7)
fan_h = []
for fc in fanc:
fan_h += world_holes(fc, 2.15)
allok &= pair(u'端墙风扇孔', wall, fan_h, 0, 8)
for nm2, vid in ((u'tf_iso', 7), (u'tf_west', 3), (u'tf_top', 1)):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n存盘 err=%s 顶层件 %d 总判 %s'
% (e3.value, len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))
+211
查看文件
@@ -0,0 +1,211 @@
# -*- coding: utf-8 -*-
u"""总装换成**两件式外壳**:TopShell_v1(一体顶盖)+ BottomPlate_v1(一整块底盖)。
删:Tray_v9、EndPlate_v13 ×2、SouthPanel_v4、NorthPanel_v3、TopPanel_v4(六件式全部)
插:TopShell_v1 dz +74.4、BottomPlate_v1 dz −10(纯平移)
不动:底板、5 片刀片、2 只风扇
判据:
1. 两件装配 box;分风腔深度复测(西 15.01 / 东 15.51)
2. 干涉 = 360 基线
3. 顶盖墙底 ↔ 底盖顶面 **面贴合**(z=−10)
4. 同轴:底盖四角 Ø3.4 过孔 ↔ 顶盖角柱 Ø4.2 螺母孔(轴 z)
5. 同轴:顶盖端墙 Ø3.4 风扇孔 ↔ 风扇模型 R2.15 安装孔(轴 x)—— 六件式时代没核过
6. 截图
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
OLD = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL', 'TOPSHELL', 'BOTTOMPLATE')
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def setx(c, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
IDENT + [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 [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
allok = True
P(u'[1] 删六件式')
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if any(k in nm.upper() for k in OLD):
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
left = [prop(c, 'Name2') for c in doc.GetComponents(True)
if any(k in prop(c, 'Name2').upper() for k in OLD)]
if left:
P(u' ⛔ 没删干净(EditDelete 式假成功?)%s' % left); sys.exit(2)
P(u'\n[2] 插两件')
for fn, t, want in ((u'TopShell_v1.SLDPRT', (0, 0, 74.4), [-38.4, -4.055, -10.0, 136.4, 106.745, 74.4]),
(u'BottomPlate_v1.SLDPRT', (0, 0, -10.0), [-38.4, -4.055, -12.4, 136.4, 106.745, 0.0])):
c = doc.AddComponent4(DIR + fn, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
b = setx(c, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %-22s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (fn, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
tops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}
box = {n: [x * 1000 for x in c.GetBox(False, False)] for n, c in tops.items()}
fans = sorted([b for n, b in box.items() if 'OD6025' in n.upper()], key=lambda b: b[0])
blades = [b for n, b in box.items() if 'BLADE' in n.upper()]
pw = min(b[0] for b in blades) - fans[0][3]
pe = fans[1][0] - max(b[3] for b in blades)
P(u' 分风腔 西 %.2f / 东 %.2f %s' % (pw, pe, u'✅' if pw > 14 and pe > 14 else u'⛔'))
allok &= pw > 14 and pe > 14
P(u'\n[3] 干涉(基线 360)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = True
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
hits = []
if n and n != 360:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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(k in y.upper() for y in nms for k in ('TOPSHELL', 'BOTTOMPLATE')):
hits.append((o.Volume * 1e9, nms))
idm.Done()
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
for vv, nms in sorted(hits, reverse=True)[:8]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
allok &= n == 360
P(u'\n[4] 贴合:顶盖墙底 ↔ 底盖顶面')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
P(u' 顶盖 z 下沿 %.3f / 底盖板面 z −10(底盖 box 上沿 %.3f 是 PCB 立柱顶)'
% (ts[2], bp[5]))
allok &= abs(ts[2] + 10.0) < 0.05
def world_holes(comp, radius, tol=0.01):
xf = list(prop(comp, 'Transform2').ArrayData)
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
for bd in comp.GetBodies2(0) or []:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > tol:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
out.append((max(range(3), key=lambda i: abs(ga[i])), gp))
return out
def pair(tag, A, B, axis, want):
A = [p for ax, p in A if ax == axis]
B = [p for ax, p in B if ax == axis]
o = [i for i in range(3) if i != axis]
ok = 0
for p in A:
lat = min((max(abs(p[i] - q[i]) for i in o) for q in B), default=99)
ok += lat < 0.05
P(u' %s (%s=%7.2f, %s=%6.2f) 横偏 %.3f %s'
% (tag, 'xyz'[o[0]], p[o[0]], 'xyz'[o[1]], p[o[1]], lat, u'✅' if lat < 0.05 else u'⛔'))
P(u' %s 对上 %d / %d(期望 %d)' % (tag, ok, len(A), want))
return ok == want and len(A) == want
shell = [c for n, c in tops.items() if 'TOPSHELL' in n.upper()][0]
plate = [c for n, c in tops.items() if 'BOTTOMPLATE' in n.upper()][0]
fanc = [c for n, c in tops.items() if 'OD6025' in n.upper()]
P(u'\n[5] 同轴:底盖四角过孔 ↔ 顶盖角柱螺母孔')
thru = [h for h in world_holes(plate, 1.7)]
nuts = [h for h in world_holes(shell, 2.1)]
allok &= pair(u'底盖过孔', thru, nuts, 2, 4)
P(u'\n[6] 同轴:顶盖端墙风扇孔 ↔ 风扇安装孔(R2.15)')
wall = world_holes(shell, 1.7)
fan_h = []
for fc in fanc:
fan_h += world_holes(fc, 2.15)
allok &= pair(u'端墙风扇孔', wall, fan_h, 0, 8)
for nm2, vid in ((u'tp_iso', 7), (u'tp_south', 6), (u'tp_bottom', 2)):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n存盘 err=%s 顶层件 %d 总判 %s'
% (e3.value, len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))
+275
查看文件
@@ -0,0 +1,275 @@
# -*- coding: utf-8 -*-
# v10:嵌入式底盖总装(TopShell_v10 + BottomPlate_v6)
# v7:无滤网版总装 —— 删滤网框(OLD 列表里的 FILTERFRAME 负责把旧件删掉)
# v3:短机版总装(风扇换 6010、分风腔 10、TopShell_v3 / BottomPlate_v3 / FilterFrame_v2)
u"""**v2**:两件式 + 西端插拔式滤网(TopShell_v2 / BottomPlate_v2 / FilterFrame_v1)。
(以下为 v1 说明)总装换成**两件式外壳**:TopShell_v1(一体顶盖)+ BottomPlate_v1(一整块底盖)。
删:Tray_v9、EndPlate_v13 ×2、SouthPanel_v4、NorthPanel_v3、TopPanel_v4(六件式全部)
插:TopShell_v1 dz +74.4、BottomPlate_v1 dz −10(纯平移)
不动:底板、5 片刀片、2 只风扇
判据:
1. 两件装配 box;分风腔深度复测(西 15.01 / 东 15.51)
2. 干涉 = 360 基线
3. 顶盖墙底 ↔ 底盖顶面 **面贴合**(z=−10)
4. 同轴:底盖四角 Ø3.4 过孔 ↔ 顶盖角柱 Ø4.2 螺母孔(轴 z)
5. 同轴:顶盖端墙 Ø3.4 风扇孔 ↔ 风扇模型 R2.15 安装孔(轴 x)—— 六件式时代没核过
6. 截图
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
OLD = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL', 'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def setx(c, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
IDENT + [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 [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
allok = True
P(u'[1] 删六件式')
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if any(k in nm.upper() for k in OLD):
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
left = [prop(c, 'Name2') for c in doc.GetComponents(True)
if any(k in prop(c, 'Name2').upper() for k in OLD)]
if left:
P(u' ⛔ 没删干净(EditDelete 式假成功?)%s' % left); sys.exit(2)
P(u'\n[2] 插两件')
for fn, t, want in ((u'TopShell_v10.SLDPRT', (0, 0, 74.4), [-18.4, -4.055, -13.6, 115.9, 106.745, 74.4]),
(u'BottomPlate_v6.SLDPRT', (0, 0, -10.0), [-15.7, -1.355, -13.6, 113.2, 104.045, 0.0])):
c = doc.AddComponent4(DIR + fn, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
b = setx(c, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %-22s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (fn, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
P(u'\n[1b] 风扇 60×60×25 → 60×60×10(按实物 10 mm:外侧面贴墙)')
FAN10 = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.SLDPRT'
_old = []
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if 'OD60' in nm.upper():
_old.append((list(prop(c, 'Transform2').ArrayData)[0:9], [x * 1000 for x in c.GetBox(False, False)]))
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
if len(_old) != 2:
P(u' ⛔ 旧风扇不是 2 只'); sys.exit(4)
def _setrt(c, rot, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
list(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 [x * 1000.0 for x in c.GetBox(False, False)]
for rot, b_old in _old:
west = b_old[0] < 50
c = doc.AddComponent4(FAN10, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ OD6010 AddComponent4 → None'); sys.exit(5)
b0 = _setrt(c, rot, (0, 0, 0))
dx = (-16.0 - b0[0]) if west else (113.5 - b0[3])
dy = 51.345 - (b0[1] + b0[4]) / 2
dz = 37.85 - (b0[2] + b0[5]) / 2
b = _setrt(c, rot, (dx, dy, dz))
P(u' %s风扇 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f(模型厚 %.2f,实物 10)'
% (u'西' if west else u'东', b[0], b[3], b[1], b[4], b[2], b[5], b[3] - b[0]))
tops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}
box = {n: [x * 1000 for x in c.GetBox(False, False)] for n, c in tops.items()}
fans = sorted([b for n, b in box.items() if 'OD60' in n.upper()], key=lambda b: b[0])
blades = [b for n, b in box.items() if 'BLADE' in n.upper()]
pw = min(b[0] for b in blades) - fans[0][3]
pe = fans[1][0] - max(b[3] for b in blades)
P(u' 分风腔 西 %.2f / 东 %.2f %s' % (pw, pe, u'✅' if pw > 9 and pe > 9 else u'⛔'))
allok &= pw > 9 and pe > 9
P(u'\n[3] 干涉(基线 360)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = False # ⚠ 用户可能在 GUI 里隐藏了零件,复验时不能忽略隐藏实体(2026-09-13 中间刀片板体被隐藏 ⇒ 误报 288)
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
hits = []
if n and n != 360:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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(k in y.upper() for y in nms for k in ('TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME', 'OD60')):
hits.append((o.Volume * 1e9, nms))
idm.Done()
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
for vv, nms in sorted(hits, reverse=True)[:8]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
allok &= n == 360
P(u'\n[4] 嵌入:底盖藏在顶盖墙内')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
_gap = (bp[0] - (-16.0), bp[1] - (-1.655), (113.5) - bp[3], (104.345) - bp[4])
P(u' 墙底 z %.3f / 底盖底面 z %.3f(应齐平)' % (ts[2], bp[2]))
P(u' 底盖离墙内侧间隙 西 %.3f 南 %.3f 东 %.3f 北 %.3f(应各 0.3)' % _gap)
P(u' 底盖顶面 z −10(= 角柱底面限位,由零件本地 −3.6~0 + 装配 dz −10 保证)')
allok &= abs(ts[2] - bp[2]) < 0.05 and all(abs(g - 0.3) < 0.05 for g in _gap)
def world_holes(comp, radius, tol=0.01):
xf = list(prop(comp, 'Transform2').ArrayData)
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
for bd in comp.GetBodies2(0) or []:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > tol:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
out.append((max(range(3), key=lambda i: abs(ga[i])), gp))
return out
def pair(tag, A, B, axis, want):
A = [p for ax, p in A if ax == axis]
B = [p for ax, p in B if ax == axis]
o = [i for i in range(3) if i != axis]
ok = 0
for p in A:
lat = min((max(abs(p[i] - q[i]) for i in o) for q in B), default=99)
ok += lat < 0.05
P(u' %s (%s=%7.2f, %s=%6.2f) 横偏 %.3f %s'
% (tag, 'xyz'[o[0]], p[o[0]], 'xyz'[o[1]], p[o[1]], lat, u'✅' if lat < 0.05 else u'⛔'))
P(u' %s 对上 %d / %d(期望 %d)' % (tag, ok, len(A), want))
return ok == want and len(A) == want
shell = [c for n, c in tops.items() if 'TOPSHELL' in n.upper()][0]
plate = [c for n, c in tops.items() if 'BOTTOMPLATE' in n.upper()][0]
fanc = [c for n, c in tops.items() if 'OD60' in n.upper()]
P(u'\n[5] 同轴:底盖四角过孔 ↔ 顶盖角柱螺母孔')
thru = [h for h in world_holes(plate, 1.7)]
nuts = [h for h in world_holes(shell, 2.1)]
allok &= pair(u'底盖过孔', thru, nuts, 2, 4)
P(u'\n[6] 同轴:顶盖端墙风扇孔 ↔ 风扇安装孔(R2.15)')
wall = world_holes(shell, 2.6)
fan_h = []
for fc in fanc:
fan_h += world_holes(fc, 2.15)
# Orion OD6010 模型的安装孔是**腰形孔**:每个孔由两个 R2.15 半圆柱组成(中心 ±1.075),
# 模型孔距 50.0;实物(广诚 6010)孔距 50.2 ⇒ 墙孔按 ±25.1 开,与模型预期差 0.1。
# ⇒ 先把离墙孔 1.5 以内的半圆柱轴点取平均得到腰形孔中心,再比偏差,容差放到 0.15。
_ok = 0
_ws = [p for ax, p in wall if ax == 0]
for p in _ws:
near = [q for ax, q in fan_h if ax == 0 and max(abs(p[1] - q[1]), abs(p[2] - q[2])) < 1.5]
if not near:
P(u' 墙孔 (y=%.2f, z=%.2f) 附近没有风扇孔 ⛔' % (p[1], p[2]))
continue
cy = sum(q[1] for q in near) / len(near)
cz = sum(q[2] for q in near) / len(near)
off = max(abs(p[1] - cy), abs(p[2] - cz))
_ok += off <= 0.15
P(u' 墙孔 (y=%.2f, z=%.2f) ↔ 模型腰孔中心 (%.2f, %.2f) 偏 %.3f %s'
% (p[1], p[2], cy, cz, off, u'✅(模型 50.0 vs 实物 50.2)' if off <= 0.15 else u'⛔'))
P(u' 风扇孔对上 %d / %d(期望 8)' % (_ok, len(_ws)))
allok &= _ok == 8 and len(_ws) == 8
for nm2, vid in ((u'v10_iso', 7), (u'v10_bottom', 2)):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n存盘 err=%s 顶层件 %d 总判 %s'
% (e3.value, len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))
+277
查看文件
@@ -0,0 +1,277 @@
# -*- coding: utf-8 -*-
# v11:总装换 TopShell_v11(Type-C 外侧沉槽、留 1 mm 薄壁),底盖仍 BottomPlate_v6
# v10:嵌入式底盖总装(TopShell_v10 + BottomPlate_v6)
# v7:无滤网版总装 —— 删滤网框(OLD 列表里的 FILTERFRAME 负责把旧件删掉)
# v3:短机版总装(风扇换 6010、分风腔 10、TopShell_v3 / BottomPlate_v3 / FilterFrame_v2)
u"""**v2**:两件式 + 西端插拔式滤网(TopShell_v2 / BottomPlate_v2 / FilterFrame_v1)。
(以下为 v1 说明)总装换成**两件式外壳**:TopShell_v1(一体顶盖)+ BottomPlate_v1(一整块底盖)。
删:Tray_v9、EndPlate_v13 ×2、SouthPanel_v4、NorthPanel_v3、TopPanel_v4(六件式全部)
插:TopShell_v1 dz +74.4、BottomPlate_v1 dz −10(纯平移)
不动:底板、5 片刀片、2 只风扇
判据:
1. 两件装配 box;分风腔深度复测(西 15.01 / 东 15.51)
2. 干涉 = 360 基线
3. 顶盖墙底 ↔ 底盖顶面 **面贴合**(z=−10)
4. 同轴:底盖四角 Ø3.4 过孔 ↔ 顶盖角柱 Ø4.2 螺母孔(轴 z)
5. 同轴:顶盖端墙 Ø3.4 风扇孔 ↔ 风扇模型 R2.15 安装孔(轴 x)—— 六件式时代没核过
6. 截图
"""
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
OUT = os.path.join(os.environ['TEMP'], 'phonerail_shell')
os.makedirs(OUT, exist_ok=True)
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
OLD = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL', 'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def setx(c, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
IDENT + [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 [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
allok = True
P(u'[1] 删六件式')
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if any(k in nm.upper() for k in OLD):
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
left = [prop(c, 'Name2') for c in doc.GetComponents(True)
if any(k in prop(c, 'Name2').upper() for k in OLD)]
if left:
P(u' ⛔ 没删干净(EditDelete 式假成功?)%s' % left); sys.exit(2)
P(u'\n[2] 插两件')
for fn, t, want in ((u'TopShell_v11.SLDPRT', (0, 0, 74.4), [-18.4, -4.055, -13.6, 115.9, 106.745, 74.4]),
(u'BottomPlate_v6.SLDPRT', (0, 0, -10.0), [-15.7, -1.355, -13.6, 113.2, 104.045, 0.0])):
c = doc.AddComponent4(DIR + fn, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
b = setx(c, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %-22s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (fn, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
P(u'\n[1b] 风扇 60×60×25 → 60×60×10(按实物 10 mm:外侧面贴墙)')
FAN10 = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.SLDPRT'
_old = []
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if 'OD60' in nm.upper():
_old.append((list(prop(c, 'Transform2').ArrayData)[0:9], [x * 1000 for x in c.GetBox(False, False)]))
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
if len(_old) != 2:
P(u' ⛔ 旧风扇不是 2 只'); sys.exit(4)
def _setrt(c, rot, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
list(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 [x * 1000.0 for x in c.GetBox(False, False)]
for rot, b_old in _old:
west = b_old[0] < 50
c = doc.AddComponent4(FAN10, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ OD6010 AddComponent4 → None'); sys.exit(5)
b0 = _setrt(c, rot, (0, 0, 0))
dx = (-16.0 - b0[0]) if west else (113.5 - b0[3])
dy = 51.345 - (b0[1] + b0[4]) / 2
dz = 37.85 - (b0[2] + b0[5]) / 2
b = _setrt(c, rot, (dx, dy, dz))
P(u' %s风扇 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f(模型厚 %.2f,实物 10)'
% (u'西' if west else u'东', b[0], b[3], b[1], b[4], b[2], b[5], b[3] - b[0]))
tops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}
box = {n: [x * 1000 for x in c.GetBox(False, False)] for n, c in tops.items()}
fans = sorted([b for n, b in box.items() if 'OD60' in n.upper()], key=lambda b: b[0])
blades = [b for n, b in box.items() if 'BLADE' in n.upper()]
pw = min(b[0] for b in blades) - fans[0][3]
pe = fans[1][0] - max(b[3] for b in blades)
P(u' 分风腔 西 %.2f / 东 %.2f %s' % (pw, pe, u'✅' if pw > 9 and pe > 9 else u'⛔'))
allok &= pw > 9 and pe > 9
P(u'\n[3] 干涉(基线 360)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = False # ⚠ 用户可能在 GUI 里隐藏了零件,复验时不能忽略隐藏实体(2026-09-13 中间刀片板体被隐藏 ⇒ 误报 288)
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
hits = []
if n and n != 360:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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(k in y.upper() for y in nms for k in ('TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME', 'OD60')):
hits.append((o.Volume * 1e9, nms))
idm.Done()
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
for vv, nms in sorted(hits, reverse=True)[:8]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
allok &= n == 360
P(u'\n[4] 嵌入:底盖藏在顶盖墙内')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
_gap = (bp[0] - (-16.0), bp[1] - (-1.655), (113.5) - bp[3], (104.345) - bp[4])
P(u' 墙底 z %.3f / 底盖底面 z %.3f(应齐平)' % (ts[2], bp[2]))
P(u' 底盖离墙内侧间隙 西 %.3f 南 %.3f 东 %.3f 北 %.3f(应各 0.3)' % _gap)
P(u' 底盖顶面 z −10(= 角柱底面限位,由零件本地 −3.6~0 + 装配 dz −10 保证)')
allok &= abs(ts[2] - bp[2]) < 0.05 and all(abs(g - 0.3) < 0.05 for g in _gap)
def world_holes(comp, radius, tol=0.01):
xf = list(prop(comp, 'Transform2').ArrayData)
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
for bd in comp.GetBodies2(0) or []:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > tol:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
out.append((max(range(3), key=lambda i: abs(ga[i])), gp))
return out
def pair(tag, A, B, axis, want):
A = [p for ax, p in A if ax == axis]
B = [p for ax, p in B if ax == axis]
o = [i for i in range(3) if i != axis]
ok = 0
for p in A:
lat = min((max(abs(p[i] - q[i]) for i in o) for q in B), default=99)
ok += lat < 0.05
P(u' %s (%s=%7.2f, %s=%6.2f) 横偏 %.3f %s'
% (tag, 'xyz'[o[0]], p[o[0]], 'xyz'[o[1]], p[o[1]], lat, u'✅' if lat < 0.05 else u'⛔'))
P(u' %s 对上 %d / %d(期望 %d)' % (tag, ok, len(A), want))
return ok == want and len(A) == want
shell = [c for n, c in tops.items() if 'TOPSHELL' in n.upper()][0]
plate = [c for n, c in tops.items() if 'BOTTOMPLATE' in n.upper()][0]
fanc = [c for n, c in tops.items() if 'OD60' in n.upper()]
P(u'\n[5] 同轴:底盖四角过孔 ↔ 顶盖角柱螺母孔')
thru = [h for h in world_holes(plate, 1.7)]
nuts = [h for h in world_holes(shell, 2.1)]
allok &= pair(u'底盖过孔', thru, nuts, 2, 4)
P(u'\n[6] 同轴:顶盖端墙风扇孔 ↔ 风扇安装孔(R2.15)')
wall = world_holes(shell, 2.6)
fan_h = []
for fc in fanc:
fan_h += world_holes(fc, 2.15)
# Orion OD6010 模型的安装孔是**腰形孔**:每个孔由两个 R2.15 半圆柱组成(中心 ±1.075),
# 模型孔距 50.0;实物(广诚 6010)孔距 50.2 ⇒ 墙孔按 ±25.1 开,与模型预期差 0.1。
# ⇒ 先把离墙孔 1.5 以内的半圆柱轴点取平均得到腰形孔中心,再比偏差,容差放到 0.15。
_ok = 0
_ws = [p for ax, p in wall if ax == 0]
for p in _ws:
near = [q for ax, q in fan_h if ax == 0 and max(abs(p[1] - q[1]), abs(p[2] - q[2])) < 1.5]
if not near:
P(u' 墙孔 (y=%.2f, z=%.2f) 附近没有风扇孔 ⛔' % (p[1], p[2]))
continue
cy = sum(q[1] for q in near) / len(near)
cz = sum(q[2] for q in near) / len(near)
off = max(abs(p[1] - cy), abs(p[2] - cz))
_ok += off <= 0.15
P(u' 墙孔 (y=%.2f, z=%.2f) ↔ 模型腰孔中心 (%.2f, %.2f) 偏 %.3f %s'
% (p[1], p[2], cy, cz, off, u'✅(模型 50.0 vs 实物 50.2)' if off <= 0.15 else u'⛔'))
P(u' 风扇孔对上 %d / %d(期望 8)' % (_ok, len(_ws)))
allok &= _ok == 8 and len(_ws) == 8
for nm2, vid in ((u'v11_iso', 7), (u'v11_bottom', 2)):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n存盘 err=%s 顶层件 %d 总判 %s'
% (e3.value, len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))
+292
查看文件
@@ -0,0 +1,292 @@
# -*- coding: utf-8 -*-
# v3:短机版总装(风扇换 6010、分风腔 10、TopShell_v3 / BottomPlate_v3 / FilterFrame_v2)
u"""**v2**:两件式 + 西端插拔式滤网(TopShell_v2 / BottomPlate_v2 / FilterFrame_v1)。
(以下为 v1 说明)总装换成**两件式外壳**:TopShell_v1(一体顶盖)+ BottomPlate_v1(一整块底盖)。
删:Tray_v9、EndPlate_v13 ×2、SouthPanel_v4、NorthPanel_v3、TopPanel_v4(六件式全部)
插:TopShell_v1 dz +74.4、BottomPlate_v1 dz −10(纯平移)
不动:底板、5 片刀片、2 只风扇
判据:
1. 两件装配 box;分风腔深度复测(西 15.01 / 东 15.51)
2. 干涉 = 360 基线
3. 顶盖墙底 ↔ 底盖顶面 **面贴合**(z=−10)
4. 同轴:底盖四角 Ø3.4 过孔 ↔ 顶盖角柱 Ø4.2 螺母孔(轴 z)
5. 同轴:顶盖端墙 Ø3.4 风扇孔 ↔ 风扇模型 R2.15 安装孔(轴 x)—— 六件式时代没核过
6. 截图
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
OLD = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL', 'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def setx(c, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
IDENT + [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 [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
allok = True
P(u'[1] 删六件式')
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if any(k in nm.upper() for k in OLD):
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
left = [prop(c, 'Name2') for c in doc.GetComponents(True)
if any(k in prop(c, 'Name2').upper() for k in OLD)]
if left:
P(u' ⛔ 没删干净(EditDelete 式假成功?)%s' % left); sys.exit(2)
P(u'\n[2] 插两件')
for fn, t, want in ((u'TopShell_v3.SLDPRT', (0, 0, 74.4), [-24.8, -4.055, -10.0, 115.9, 106.745, 74.4]),
(u'BottomPlate_v3.SLDPRT', (0, 0, -10.0), [-24.8, -4.055, -12.4, 115.9, 106.745, 0.0])):
c = doc.AddComponent4(DIR + fn, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
b = setx(c, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %-22s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (fn, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
P(u'\n[2b] 插滤网框(本地 x→y、y→z、z→x)')
_fc = doc.AddComponent4(DIR + u'FilterFrame_v2.SLDPRT', '', 0.0, 0.0, 0.0)
if _fc is None:
P(u' ⛔ FilterFrame AddComponent4 → None'); sys.exit(3)
_v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
[0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, -0.0214, 0.016345, -0.010, 1.0, 0.0, 0.0, 0.0])
_xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, _v))
try:
_fc.SetTransformAndSolve2(_xf)
except Exception:
_fc.Transform2 = _xf
doc.EditRebuild3
_b = [round(x * 1000.0, 3) for x in _fc.GetBox(False, False)]
_w = [-22.4, 16.345, -10.0, -18.4, 86.345, 80.0]
_ok = all(abs(_b[i] - _w[i]) < 0.05 for i in range(6))
allok &= _ok
P(u' 滤网框 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (_b[0], _b[3], _b[1], _b[4], _b[2], _b[5], u'✅' if _ok else u'⛔ 期望 %s' % _w))
P(u' 滑槽间隙:贴墙 %.2f(应 0)/ 唇边侧 %.2f(应 0.4)/ 南 %.2f、北 %.2f(应 0.3)/ 底 %.2f(落在底盖上,应 0)/ 露出顶盖 %.2f'
% (-18.4 - _b[3], _b[0] - (-22.8), _b[1] - 16.045, 86.645 - _b[4], _b[2] - (-10.0), _b[5] - 74.4))
P(u'\n[1b] 风扇 60×60×25 → 60×60×10(按实物 10 mm:外侧面贴墙)')
FAN10 = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.SLDPRT'
_old = []
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if 'OD60' in nm.upper():
_old.append((list(prop(c, 'Transform2').ArrayData)[0:9], [x * 1000 for x in c.GetBox(False, False)]))
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
if len(_old) != 2:
P(u' ⛔ 旧风扇不是 2 只'); sys.exit(4)
def _setrt(c, rot, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
list(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 [x * 1000.0 for x in c.GetBox(False, False)]
for rot, b_old in _old:
west = b_old[0] < 50
c = doc.AddComponent4(FAN10, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ OD6010 AddComponent4 → None'); sys.exit(5)
b0 = _setrt(c, rot, (0, 0, 0))
dx = (-16.0 - b0[0]) if west else (113.5 - b0[3])
dy = 51.345 - (b0[1] + b0[4]) / 2
dz = 37.85 - (b0[2] + b0[5]) / 2
b = _setrt(c, rot, (dx, dy, dz))
P(u' %s风扇 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f(模型厚 %.2f,实物 10)'
% (u'西' if west else u'东', b[0], b[3], b[1], b[4], b[2], b[5], b[3] - b[0]))
tops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}
box = {n: [x * 1000 for x in c.GetBox(False, False)] for n, c in tops.items()}
fans = sorted([b for n, b in box.items() if 'OD60' in n.upper()], key=lambda b: b[0])
blades = [b for n, b in box.items() if 'BLADE' in n.upper()]
pw = min(b[0] for b in blades) - fans[0][3]
pe = fans[1][0] - max(b[3] for b in blades)
P(u' 分风腔 西 %.2f / 东 %.2f %s' % (pw, pe, u'✅' if pw > 9 and pe > 9 else u'⛔'))
allok &= pw > 9 and pe > 9
P(u'\n[3] 干涉(基线 360)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = True
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
hits = []
if n and n != 360:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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(k in y.upper() for y in nms for k in ('TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME', 'OD60')):
hits.append((o.Volume * 1e9, nms))
idm.Done()
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
for vv, nms in sorted(hits, reverse=True)[:8]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
allok &= n == 360
P(u'\n[4] 贴合:顶盖墙底 ↔ 底盖顶面')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
P(u' 顶盖 z 下沿 %.3f / 底盖板面 z −10(底盖 box 上沿 %.3f 是 PCB 立柱顶)'
% (ts[2], bp[5]))
allok &= abs(ts[2] + 10.0) < 0.05
def world_holes(comp, radius, tol=0.01):
xf = list(prop(comp, 'Transform2').ArrayData)
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
for bd in comp.GetBodies2(0) or []:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > tol:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
out.append((max(range(3), key=lambda i: abs(ga[i])), gp))
return out
def pair(tag, A, B, axis, want):
A = [p for ax, p in A if ax == axis]
B = [p for ax, p in B if ax == axis]
o = [i for i in range(3) if i != axis]
ok = 0
for p in A:
lat = min((max(abs(p[i] - q[i]) for i in o) for q in B), default=99)
ok += lat < 0.05
P(u' %s (%s=%7.2f, %s=%6.2f) 横偏 %.3f %s'
% (tag, 'xyz'[o[0]], p[o[0]], 'xyz'[o[1]], p[o[1]], lat, u'✅' if lat < 0.05 else u'⛔'))
P(u' %s 对上 %d / %d(期望 %d)' % (tag, ok, len(A), want))
return ok == want and len(A) == want
shell = [c for n, c in tops.items() if 'TOPSHELL' in n.upper()][0]
plate = [c for n, c in tops.items() if 'BOTTOMPLATE' in n.upper()][0]
fanc = [c for n, c in tops.items() if 'OD60' in n.upper()]
P(u'\n[5] 同轴:底盖四角过孔 ↔ 顶盖角柱螺母孔')
thru = [h for h in world_holes(plate, 1.7)]
nuts = [h for h in world_holes(shell, 2.1)]
allok &= pair(u'底盖过孔', thru, nuts, 2, 4)
P(u'\n[6] 同轴:顶盖端墙风扇孔 ↔ 风扇安装孔(R2.15)')
wall = world_holes(shell, 2.6)
fan_h = []
for fc in fanc:
fan_h += world_holes(fc, 2.15)
# Orion OD6010 模型的安装孔是**腰形孔**:每个孔由两个 R2.15 半圆柱组成(中心 ±1.075),
# 模型孔距 50.0;实物(广诚 6010)孔距 50.2 ⇒ 墙孔按 ±25.1 开,与模型预期差 0.1。
# ⇒ 先把离墙孔 1.5 以内的半圆柱轴点取平均得到腰形孔中心,再比偏差,容差放到 0.15。
_ok = 0
_ws = [p for ax, p in wall if ax == 0]
for p in _ws:
near = [q for ax, q in fan_h if ax == 0 and max(abs(p[1] - q[1]), abs(p[2] - q[2])) < 1.5]
if not near:
P(u' 墙孔 (y=%.2f, z=%.2f) 附近没有风扇孔 ⛔' % (p[1], p[2]))
continue
cy = sum(q[1] for q in near) / len(near)
cz = sum(q[2] for q in near) / len(near)
off = max(abs(p[1] - cy), abs(p[2] - cz))
_ok += off <= 0.15
P(u' 墙孔 (y=%.2f, z=%.2f) ↔ 模型腰孔中心 (%.2f, %.2f) 偏 %.3f %s'
% (p[1], p[2], cy, cz, off, u'✅(模型 50.0 vs 实物 50.2)' if off <= 0.15 else u'⛔'))
P(u' 风扇孔对上 %d / %d(期望 8)' % (_ok, len(_ws)))
allok &= _ok == 8 and len(_ws) == 8
for nm2, vid in ((u'v3_iso', 7), (u'v3_west', 3), (u'v3_top', 1)):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n存盘 err=%s 顶层件 %d 总判 %s'
% (e3.value, len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))
+292
查看文件
@@ -0,0 +1,292 @@
# -*- coding: utf-8 -*-
# v3:短机版总装(风扇换 6010、分风腔 10、TopShell_v3 / BottomPlate_v3 / FilterFrame_v2)
u"""**v2**:两件式 + 西端插拔式滤网(TopShell_v2 / BottomPlate_v2 / FilterFrame_v1)。
(以下为 v1 说明)总装换成**两件式外壳**:TopShell_v1(一体顶盖)+ BottomPlate_v1(一整块底盖)。
删:Tray_v9、EndPlate_v13 ×2、SouthPanel_v4、NorthPanel_v3、TopPanel_v4(六件式全部)
插:TopShell_v1 dz +74.4、BottomPlate_v1 dz −10(纯平移)
不动:底板、5 片刀片、2 只风扇
判据:
1. 两件装配 box;分风腔深度复测(西 15.01 / 东 15.51)
2. 干涉 = 360 基线
3. 顶盖墙底 ↔ 底盖顶面 **面贴合**(z=−10)
4. 同轴:底盖四角 Ø3.4 过孔 ↔ 顶盖角柱 Ø4.2 螺母孔(轴 z)
5. 同轴:顶盖端墙 Ø3.4 风扇孔 ↔ 风扇模型 R2.15 安装孔(轴 x)—— 六件式时代没核过
6. 截图
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
OLD = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL', 'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def setx(c, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
IDENT + [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 [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
allok = True
P(u'[1] 删六件式')
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if any(k in nm.upper() for k in OLD):
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
left = [prop(c, 'Name2') for c in doc.GetComponents(True)
if any(k in prop(c, 'Name2').upper() for k in OLD)]
if left:
P(u' ⛔ 没删干净(EditDelete 式假成功?)%s' % left); sys.exit(2)
P(u'\n[2] 插两件')
for fn, t, want in ((u'TopShell_v4.SLDPRT', (0, 0, 74.4), [-24.8, -4.055, -10.0, 115.9, 106.745, 74.4]),
(u'BottomPlate_v3.SLDPRT', (0, 0, -10.0), [-24.8, -4.055, -12.4, 115.9, 106.745, 0.0])):
c = doc.AddComponent4(DIR + fn, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
b = setx(c, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %-22s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (fn, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
P(u'\n[2b] 插滤网框(本地 x→y、y→z、z→x)')
_fc = doc.AddComponent4(DIR + u'FilterFrame_v2.SLDPRT', '', 0.0, 0.0, 0.0)
if _fc is None:
P(u' ⛔ FilterFrame AddComponent4 → None'); sys.exit(3)
_v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
[0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, -0.0214, 0.016345, -0.010, 1.0, 0.0, 0.0, 0.0])
_xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, _v))
try:
_fc.SetTransformAndSolve2(_xf)
except Exception:
_fc.Transform2 = _xf
doc.EditRebuild3
_b = [round(x * 1000.0, 3) for x in _fc.GetBox(False, False)]
_w = [-22.4, 16.345, -10.0, -18.4, 86.345, 80.0]
_ok = all(abs(_b[i] - _w[i]) < 0.05 for i in range(6))
allok &= _ok
P(u' 滤网框 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (_b[0], _b[3], _b[1], _b[4], _b[2], _b[5], u'✅' if _ok else u'⛔ 期望 %s' % _w))
P(u' 滑槽间隙:贴墙 %.2f(应 0)/ 唇边侧 %.2f(应 0.4)/ 南 %.2f、北 %.2f(应 0.3)/ 底 %.2f(落在底盖上,应 0)/ 露出顶盖 %.2f'
% (-18.4 - _b[3], _b[0] - (-22.8), _b[1] - 16.045, 86.645 - _b[4], _b[2] - (-10.0), _b[5] - 74.4))
P(u'\n[1b] 风扇 60×60×25 → 60×60×10(按实物 10 mm:外侧面贴墙)')
FAN10 = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.SLDPRT'
_old = []
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if 'OD60' in nm.upper():
_old.append((list(prop(c, 'Transform2').ArrayData)[0:9], [x * 1000 for x in c.GetBox(False, False)]))
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
if len(_old) != 2:
P(u' ⛔ 旧风扇不是 2 只'); sys.exit(4)
def _setrt(c, rot, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
list(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 [x * 1000.0 for x in c.GetBox(False, False)]
for rot, b_old in _old:
west = b_old[0] < 50
c = doc.AddComponent4(FAN10, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ OD6010 AddComponent4 → None'); sys.exit(5)
b0 = _setrt(c, rot, (0, 0, 0))
dx = (-16.0 - b0[0]) if west else (113.5 - b0[3])
dy = 51.345 - (b0[1] + b0[4]) / 2
dz = 37.85 - (b0[2] + b0[5]) / 2
b = _setrt(c, rot, (dx, dy, dz))
P(u' %s风扇 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f(模型厚 %.2f,实物 10)'
% (u'西' if west else u'东', b[0], b[3], b[1], b[4], b[2], b[5], b[3] - b[0]))
tops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}
box = {n: [x * 1000 for x in c.GetBox(False, False)] for n, c in tops.items()}
fans = sorted([b for n, b in box.items() if 'OD60' in n.upper()], key=lambda b: b[0])
blades = [b for n, b in box.items() if 'BLADE' in n.upper()]
pw = min(b[0] for b in blades) - fans[0][3]
pe = fans[1][0] - max(b[3] for b in blades)
P(u' 分风腔 西 %.2f / 东 %.2f %s' % (pw, pe, u'✅' if pw > 9 and pe > 9 else u'⛔'))
allok &= pw > 9 and pe > 9
P(u'\n[3] 干涉(基线 360)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = True
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
hits = []
if n and n != 360:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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(k in y.upper() for y in nms for k in ('TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME', 'OD60')):
hits.append((o.Volume * 1e9, nms))
idm.Done()
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
for vv, nms in sorted(hits, reverse=True)[:8]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
allok &= n == 360
P(u'\n[4] 贴合:顶盖墙底 ↔ 底盖顶面')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
P(u' 顶盖 z 下沿 %.3f / 底盖板面 z −10(底盖 box 上沿 %.3f 是 PCB 立柱顶)'
% (ts[2], bp[5]))
allok &= abs(ts[2] + 10.0) < 0.05
def world_holes(comp, radius, tol=0.01):
xf = list(prop(comp, 'Transform2').ArrayData)
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
for bd in comp.GetBodies2(0) or []:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > tol:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
out.append((max(range(3), key=lambda i: abs(ga[i])), gp))
return out
def pair(tag, A, B, axis, want):
A = [p for ax, p in A if ax == axis]
B = [p for ax, p in B if ax == axis]
o = [i for i in range(3) if i != axis]
ok = 0
for p in A:
lat = min((max(abs(p[i] - q[i]) for i in o) for q in B), default=99)
ok += lat < 0.05
P(u' %s (%s=%7.2f, %s=%6.2f) 横偏 %.3f %s'
% (tag, 'xyz'[o[0]], p[o[0]], 'xyz'[o[1]], p[o[1]], lat, u'✅' if lat < 0.05 else u'⛔'))
P(u' %s 对上 %d / %d(期望 %d)' % (tag, ok, len(A), want))
return ok == want and len(A) == want
shell = [c for n, c in tops.items() if 'TOPSHELL' in n.upper()][0]
plate = [c for n, c in tops.items() if 'BOTTOMPLATE' in n.upper()][0]
fanc = [c for n, c in tops.items() if 'OD60' in n.upper()]
P(u'\n[5] 同轴:底盖四角过孔 ↔ 顶盖角柱螺母孔')
thru = [h for h in world_holes(plate, 1.7)]
nuts = [h for h in world_holes(shell, 2.1)]
allok &= pair(u'底盖过孔', thru, nuts, 2, 4)
P(u'\n[6] 同轴:顶盖端墙风扇孔 ↔ 风扇安装孔(R2.15)')
wall = world_holes(shell, 2.6)
fan_h = []
for fc in fanc:
fan_h += world_holes(fc, 2.15)
# Orion OD6010 模型的安装孔是**腰形孔**:每个孔由两个 R2.15 半圆柱组成(中心 ±1.075),
# 模型孔距 50.0;实物(广诚 6010)孔距 50.2 ⇒ 墙孔按 ±25.1 开,与模型预期差 0.1。
# ⇒ 先把离墙孔 1.5 以内的半圆柱轴点取平均得到腰形孔中心,再比偏差,容差放到 0.15。
_ok = 0
_ws = [p for ax, p in wall if ax == 0]
for p in _ws:
near = [q for ax, q in fan_h if ax == 0 and max(abs(p[1] - q[1]), abs(p[2] - q[2])) < 1.5]
if not near:
P(u' 墙孔 (y=%.2f, z=%.2f) 附近没有风扇孔 ⛔' % (p[1], p[2]))
continue
cy = sum(q[1] for q in near) / len(near)
cz = sum(q[2] for q in near) / len(near)
off = max(abs(p[1] - cy), abs(p[2] - cz))
_ok += off <= 0.15
P(u' 墙孔 (y=%.2f, z=%.2f) ↔ 模型腰孔中心 (%.2f, %.2f) 偏 %.3f %s'
% (p[1], p[2], cy, cz, off, u'✅(模型 50.0 vs 实物 50.2)' if off <= 0.15 else u'⛔'))
P(u' 风扇孔对上 %d / %d(期望 8)' % (_ok, len(_ws)))
allok &= _ok == 8 and len(_ws) == 8
for nm2, vid in ((u'v4_iso', 7), (u'v4_south', 6), (u'v4_north', 5)):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n存盘 err=%s 顶层件 %d 总判 %s'
% (e3.value, len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))
+292
查看文件
@@ -0,0 +1,292 @@
# -*- coding: utf-8 -*-
# v3:短机版总装(风扇换 6010、分风腔 10、TopShell_v3 / BottomPlate_v3 / FilterFrame_v2)
u"""**v2**:两件式 + 西端插拔式滤网(TopShell_v2 / BottomPlate_v2 / FilterFrame_v1)。
(以下为 v1 说明)总装换成**两件式外壳**:TopShell_v1(一体顶盖)+ BottomPlate_v1(一整块底盖)。
删:Tray_v9、EndPlate_v13 ×2、SouthPanel_v4、NorthPanel_v3、TopPanel_v4(六件式全部)
插:TopShell_v1 dz +74.4、BottomPlate_v1 dz −10(纯平移)
不动:底板、5 片刀片、2 只风扇
判据:
1. 两件装配 box;分风腔深度复测(西 15.01 / 东 15.51)
2. 干涉 = 360 基线
3. 顶盖墙底 ↔ 底盖顶面 **面贴合**(z=−10)
4. 同轴:底盖四角 Ø3.4 过孔 ↔ 顶盖角柱 Ø4.2 螺母孔(轴 z)
5. 同轴:顶盖端墙 Ø3.4 风扇孔 ↔ 风扇模型 R2.15 安装孔(轴 x)—— 六件式时代没核过
6. 截图
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
OLD = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL', 'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def setx(c, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
IDENT + [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 [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
allok = True
P(u'[1] 删六件式')
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if any(k in nm.upper() for k in OLD):
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
left = [prop(c, 'Name2') for c in doc.GetComponents(True)
if any(k in prop(c, 'Name2').upper() for k in OLD)]
if left:
P(u' ⛔ 没删干净(EditDelete 式假成功?)%s' % left); sys.exit(2)
P(u'\n[2] 插两件')
for fn, t, want in ((u'TopShell_v5.SLDPRT', (0, 0, 74.4), [-24.8, -4.055, -10.0, 115.9, 106.745, 74.4]),
(u'BottomPlate_v4.SLDPRT', (0, 0, -10.0), [-24.3, -3.555, -13.6, 115.4, 106.245, 0.0])):
c = doc.AddComponent4(DIR + fn, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
b = setx(c, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %-22s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (fn, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
P(u'\n[2b] 插滤网框(本地 x→y、y→z、z→x)')
_fc = doc.AddComponent4(DIR + u'FilterFrame_v3.SLDPRT', '', 0.0, 0.0, 0.0)
if _fc is None:
P(u' ⛔ FilterFrame AddComponent4 → None'); sys.exit(3)
_v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
[0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, -0.0214, 0.016345, -0.010, 1.0, 0.0, 0.0, 0.0])
_xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, _v))
try:
_fc.SetTransformAndSolve2(_xf)
except Exception:
_fc.Transform2 = _xf
doc.EditRebuild3
_b = [round(x * 1000.0, 3) for x in _fc.GetBox(False, False)]
_w = [-22.4, 16.345, -10.0, -18.4, 86.345, 74.4]
_ok = all(abs(_b[i] - _w[i]) < 0.05 for i in range(6))
allok &= _ok
P(u' 滤网框 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (_b[0], _b[3], _b[1], _b[4], _b[2], _b[5], u'✅' if _ok else u'⛔ 期望 %s' % _w))
P(u' 滑槽间隙:贴墙 %.2f(应 0)/ 唇边侧 %.2f(应 0.4)/ 南 %.2f、北 %.2f(应 0.3)/ 底 %.2f(落在底盖上,应 0)/ 露出顶盖 %.2f'
% (-18.4 - _b[3], _b[0] - (-22.8), _b[1] - 16.045, 86.645 - _b[4], _b[2] - (-10.0), _b[5] - 74.4))
P(u'\n[1b] 风扇 60×60×25 → 60×60×10(按实物 10 mm:外侧面贴墙)')
FAN10 = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.SLDPRT'
_old = []
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if 'OD60' in nm.upper():
_old.append((list(prop(c, 'Transform2').ArrayData)[0:9], [x * 1000 for x in c.GetBox(False, False)]))
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
if len(_old) != 2:
P(u' ⛔ 旧风扇不是 2 只'); sys.exit(4)
def _setrt(c, rot, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
list(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 [x * 1000.0 for x in c.GetBox(False, False)]
for rot, b_old in _old:
west = b_old[0] < 50
c = doc.AddComponent4(FAN10, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ OD6010 AddComponent4 → None'); sys.exit(5)
b0 = _setrt(c, rot, (0, 0, 0))
dx = (-16.0 - b0[0]) if west else (113.5 - b0[3])
dy = 51.345 - (b0[1] + b0[4]) / 2
dz = 37.85 - (b0[2] + b0[5]) / 2
b = _setrt(c, rot, (dx, dy, dz))
P(u' %s风扇 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f(模型厚 %.2f,实物 10)'
% (u'西' if west else u'东', b[0], b[3], b[1], b[4], b[2], b[5], b[3] - b[0]))
tops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}
box = {n: [x * 1000 for x in c.GetBox(False, False)] for n, c in tops.items()}
fans = sorted([b for n, b in box.items() if 'OD60' in n.upper()], key=lambda b: b[0])
blades = [b for n, b in box.items() if 'BLADE' in n.upper()]
pw = min(b[0] for b in blades) - fans[0][3]
pe = fans[1][0] - max(b[3] for b in blades)
P(u' 分风腔 西 %.2f / 东 %.2f %s' % (pw, pe, u'✅' if pw > 9 and pe > 9 else u'⛔'))
allok &= pw > 9 and pe > 9
P(u'\n[3] 干涉(基线 360)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = True
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
hits = []
if n and n != 360:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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(k in y.upper() for y in nms for k in ('TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME', 'OD60')):
hits.append((o.Volume * 1e9, nms))
idm.Done()
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
for vv, nms in sorted(hits, reverse=True)[:8]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
allok &= n == 360
P(u'\n[4] 贴合:顶盖墙底 ↔ 底盖顶面')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
P(u' 顶盖 z 下沿 %.3f / 底盖板面 z −10(底盖 box 上沿 %.3f 是 PCB 立柱顶)'
% (ts[2], bp[5]))
allok &= abs(ts[2] + 10.0) < 0.05
def world_holes(comp, radius, tol=0.01):
xf = list(prop(comp, 'Transform2').ArrayData)
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
for bd in comp.GetBodies2(0) or []:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > tol:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
out.append((max(range(3), key=lambda i: abs(ga[i])), gp))
return out
def pair(tag, A, B, axis, want):
A = [p for ax, p in A if ax == axis]
B = [p for ax, p in B if ax == axis]
o = [i for i in range(3) if i != axis]
ok = 0
for p in A:
lat = min((max(abs(p[i] - q[i]) for i in o) for q in B), default=99)
ok += lat < 0.05
P(u' %s (%s=%7.2f, %s=%6.2f) 横偏 %.3f %s'
% (tag, 'xyz'[o[0]], p[o[0]], 'xyz'[o[1]], p[o[1]], lat, u'✅' if lat < 0.05 else u'⛔'))
P(u' %s 对上 %d / %d(期望 %d)' % (tag, ok, len(A), want))
return ok == want and len(A) == want
shell = [c for n, c in tops.items() if 'TOPSHELL' in n.upper()][0]
plate = [c for n, c in tops.items() if 'BOTTOMPLATE' in n.upper()][0]
fanc = [c for n, c in tops.items() if 'OD60' in n.upper()]
P(u'\n[5] 同轴:底盖四角过孔 ↔ 顶盖角柱螺母孔')
thru = [h for h in world_holes(plate, 1.7)]
nuts = [h for h in world_holes(shell, 2.1)]
allok &= pair(u'底盖过孔', thru, nuts, 2, 4)
P(u'\n[6] 同轴:顶盖端墙风扇孔 ↔ 风扇安装孔(R2.15)')
wall = world_holes(shell, 2.6)
fan_h = []
for fc in fanc:
fan_h += world_holes(fc, 2.15)
# Orion OD6010 模型的安装孔是**腰形孔**:每个孔由两个 R2.15 半圆柱组成(中心 ±1.075),
# 模型孔距 50.0;实物(广诚 6010)孔距 50.2 ⇒ 墙孔按 ±25.1 开,与模型预期差 0.1。
# ⇒ 先把离墙孔 1.5 以内的半圆柱轴点取平均得到腰形孔中心,再比偏差,容差放到 0.15。
_ok = 0
_ws = [p for ax, p in wall if ax == 0]
for p in _ws:
near = [q for ax, q in fan_h if ax == 0 and max(abs(p[1] - q[1]), abs(p[2] - q[2])) < 1.5]
if not near:
P(u' 墙孔 (y=%.2f, z=%.2f) 附近没有风扇孔 ⛔' % (p[1], p[2]))
continue
cy = sum(q[1] for q in near) / len(near)
cz = sum(q[2] for q in near) / len(near)
off = max(abs(p[1] - cy), abs(p[2] - cz))
_ok += off <= 0.15
P(u' 墙孔 (y=%.2f, z=%.2f) ↔ 模型腰孔中心 (%.2f, %.2f) 偏 %.3f %s'
% (p[1], p[2], cy, cz, off, u'✅(模型 50.0 vs 实物 50.2)' if off <= 0.15 else u'⛔'))
P(u' 风扇孔对上 %d / %d(期望 8)' % (_ok, len(_ws)))
allok &= _ok == 8 and len(_ws) == 8
for nm2, vid in ((u'v5_iso', 7), (u'v5_east', 0), (u'v5_south', 6), (u'v5_bottom', 2), (u'v5_top', 1)):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n存盘 err=%s 顶层件 %d 总判 %s'
% (e3.value, len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))
+292
查看文件
@@ -0,0 +1,292 @@
# -*- coding: utf-8 -*-
# v3:短机版总装(风扇换 6010、分风腔 10、TopShell_v3 / BottomPlate_v3 / FilterFrame_v2)
u"""**v2**:两件式 + 西端插拔式滤网(TopShell_v2 / BottomPlate_v2 / FilterFrame_v1)。
(以下为 v1 说明)总装换成**两件式外壳**:TopShell_v1(一体顶盖)+ BottomPlate_v1(一整块底盖)。
删:Tray_v9、EndPlate_v13 ×2、SouthPanel_v4、NorthPanel_v3、TopPanel_v4(六件式全部)
插:TopShell_v1 dz +74.4、BottomPlate_v1 dz −10(纯平移)
不动:底板、5 片刀片、2 只风扇
判据:
1. 两件装配 box;分风腔深度复测(西 15.01 / 东 15.51)
2. 干涉 = 360 基线
3. 顶盖墙底 ↔ 底盖顶面 **面贴合**(z=−10)
4. 同轴:底盖四角 Ø3.4 过孔 ↔ 顶盖角柱 Ø4.2 螺母孔(轴 z)
5. 同轴:顶盖端墙 Ø3.4 风扇孔 ↔ 风扇模型 R2.15 安装孔(轴 x)—— 六件式时代没核过
6. 截图
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
OLD = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL', 'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def setx(c, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
IDENT + [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 [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
allok = True
P(u'[1] 删六件式')
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if any(k in nm.upper() for k in OLD):
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
left = [prop(c, 'Name2') for c in doc.GetComponents(True)
if any(k in prop(c, 'Name2').upper() for k in OLD)]
if left:
P(u' ⛔ 没删干净(EditDelete 式假成功?)%s' % left); sys.exit(2)
P(u'\n[2] 插两件')
for fn, t, want in ((u'TopShell_v6.SLDPRT', (0, 0, 74.4), [-24.8, -4.055, -10.0, 115.9, 106.745, 74.4]),
(u'BottomPlate_v4.SLDPRT', (0, 0, -10.0), [-24.3, -3.555, -13.6, 115.4, 106.245, 0.0])):
c = doc.AddComponent4(DIR + fn, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
b = setx(c, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %-22s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (fn, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
P(u'\n[2b] 插滤网框(本地 x→y、y→z、z→x)')
_fc = doc.AddComponent4(DIR + u'FilterFrame_v3.SLDPRT', '', 0.0, 0.0, 0.0)
if _fc is None:
P(u' ⛔ FilterFrame AddComponent4 → None'); sys.exit(3)
_v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
[0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, -0.0214, 0.016345, -0.010, 1.0, 0.0, 0.0, 0.0])
_xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, _v))
try:
_fc.SetTransformAndSolve2(_xf)
except Exception:
_fc.Transform2 = _xf
doc.EditRebuild3
_b = [round(x * 1000.0, 3) for x in _fc.GetBox(False, False)]
_w = [-22.4, 16.345, -10.0, -18.4, 86.345, 74.4]
_ok = all(abs(_b[i] - _w[i]) < 0.05 for i in range(6))
allok &= _ok
P(u' 滤网框 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (_b[0], _b[3], _b[1], _b[4], _b[2], _b[5], u'✅' if _ok else u'⛔ 期望 %s' % _w))
P(u' 滑槽间隙:贴墙 %.2f(应 0)/ 唇边侧 %.2f(应 0.4)/ 南 %.2f、北 %.2f(应 0.3)/ 底 %.2f(落在底盖上,应 0)/ 露出顶盖 %.2f'
% (-18.4 - _b[3], _b[0] - (-22.8), _b[1] - 16.045, 86.645 - _b[4], _b[2] - (-10.0), _b[5] - 74.4))
P(u'\n[1b] 风扇 60×60×25 → 60×60×10(按实物 10 mm:外侧面贴墙)')
FAN10 = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.SLDPRT'
_old = []
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if 'OD60' in nm.upper():
_old.append((list(prop(c, 'Transform2').ArrayData)[0:9], [x * 1000 for x in c.GetBox(False, False)]))
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
if len(_old) != 2:
P(u' ⛔ 旧风扇不是 2 只'); sys.exit(4)
def _setrt(c, rot, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
list(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 [x * 1000.0 for x in c.GetBox(False, False)]
for rot, b_old in _old:
west = b_old[0] < 50
c = doc.AddComponent4(FAN10, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ OD6010 AddComponent4 → None'); sys.exit(5)
b0 = _setrt(c, rot, (0, 0, 0))
dx = (-16.0 - b0[0]) if west else (113.5 - b0[3])
dy = 51.345 - (b0[1] + b0[4]) / 2
dz = 37.85 - (b0[2] + b0[5]) / 2
b = _setrt(c, rot, (dx, dy, dz))
P(u' %s风扇 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f(模型厚 %.2f,实物 10)'
% (u'西' if west else u'东', b[0], b[3], b[1], b[4], b[2], b[5], b[3] - b[0]))
tops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}
box = {n: [x * 1000 for x in c.GetBox(False, False)] for n, c in tops.items()}
fans = sorted([b for n, b in box.items() if 'OD60' in n.upper()], key=lambda b: b[0])
blades = [b for n, b in box.items() if 'BLADE' in n.upper()]
pw = min(b[0] for b in blades) - fans[0][3]
pe = fans[1][0] - max(b[3] for b in blades)
P(u' 分风腔 西 %.2f / 东 %.2f %s' % (pw, pe, u'✅' if pw > 9 and pe > 9 else u'⛔'))
allok &= pw > 9 and pe > 9
P(u'\n[3] 干涉(基线 360)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = True
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
hits = []
if n and n != 360:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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(k in y.upper() for y in nms for k in ('TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME', 'OD60')):
hits.append((o.Volume * 1e9, nms))
idm.Done()
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
for vv, nms in sorted(hits, reverse=True)[:8]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
allok &= n == 360
P(u'\n[4] 贴合:顶盖墙底 ↔ 底盖顶面')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
P(u' 顶盖 z 下沿 %.3f / 底盖板面 z −10(底盖 box 上沿 %.3f 是 PCB 立柱顶)'
% (ts[2], bp[5]))
allok &= abs(ts[2] + 10.0) < 0.05
def world_holes(comp, radius, tol=0.01):
xf = list(prop(comp, 'Transform2').ArrayData)
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
for bd in comp.GetBodies2(0) or []:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > tol:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
out.append((max(range(3), key=lambda i: abs(ga[i])), gp))
return out
def pair(tag, A, B, axis, want):
A = [p for ax, p in A if ax == axis]
B = [p for ax, p in B if ax == axis]
o = [i for i in range(3) if i != axis]
ok = 0
for p in A:
lat = min((max(abs(p[i] - q[i]) for i in o) for q in B), default=99)
ok += lat < 0.05
P(u' %s (%s=%7.2f, %s=%6.2f) 横偏 %.3f %s'
% (tag, 'xyz'[o[0]], p[o[0]], 'xyz'[o[1]], p[o[1]], lat, u'✅' if lat < 0.05 else u'⛔'))
P(u' %s 对上 %d / %d(期望 %d)' % (tag, ok, len(A), want))
return ok == want and len(A) == want
shell = [c for n, c in tops.items() if 'TOPSHELL' in n.upper()][0]
plate = [c for n, c in tops.items() if 'BOTTOMPLATE' in n.upper()][0]
fanc = [c for n, c in tops.items() if 'OD60' in n.upper()]
P(u'\n[5] 同轴:底盖四角过孔 ↔ 顶盖角柱螺母孔')
thru = [h for h in world_holes(plate, 1.7)]
nuts = [h for h in world_holes(shell, 2.1)]
allok &= pair(u'底盖过孔', thru, nuts, 2, 4)
P(u'\n[6] 同轴:顶盖端墙风扇孔 ↔ 风扇安装孔(R2.15)')
wall = world_holes(shell, 2.6)
fan_h = []
for fc in fanc:
fan_h += world_holes(fc, 2.15)
# Orion OD6010 模型的安装孔是**腰形孔**:每个孔由两个 R2.15 半圆柱组成(中心 ±1.075),
# 模型孔距 50.0;实物(广诚 6010)孔距 50.2 ⇒ 墙孔按 ±25.1 开,与模型预期差 0.1。
# ⇒ 先把离墙孔 1.5 以内的半圆柱轴点取平均得到腰形孔中心,再比偏差,容差放到 0.15。
_ok = 0
_ws = [p for ax, p in wall if ax == 0]
for p in _ws:
near = [q for ax, q in fan_h if ax == 0 and max(abs(p[1] - q[1]), abs(p[2] - q[2])) < 1.5]
if not near:
P(u' 墙孔 (y=%.2f, z=%.2f) 附近没有风扇孔 ⛔' % (p[1], p[2]))
continue
cy = sum(q[1] for q in near) / len(near)
cz = sum(q[2] for q in near) / len(near)
off = max(abs(p[1] - cy), abs(p[2] - cz))
_ok += off <= 0.15
P(u' 墙孔 (y=%.2f, z=%.2f) ↔ 模型腰孔中心 (%.2f, %.2f) 偏 %.3f %s'
% (p[1], p[2], cy, cz, off, u'✅(模型 50.0 vs 实物 50.2)' if off <= 0.15 else u'⛔'))
P(u' 风扇孔对上 %d / %d(期望 8)' % (_ok, len(_ws)))
allok &= _ok == 8 and len(_ws) == 8
for nm2, vid in ((u'v6_iso', 7),):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n存盘 err=%s 顶层件 %d 总判 %s'
% (e3.value, len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))
+272
查看文件
@@ -0,0 +1,272 @@
# -*- coding: utf-8 -*-
# v7:无滤网版总装 —— 删滤网框(OLD 列表里的 FILTERFRAME 负责把旧件删掉)
# v3:短机版总装(风扇换 6010、分风腔 10、TopShell_v3 / BottomPlate_v3 / FilterFrame_v2)
u"""**v2**:两件式 + 西端插拔式滤网(TopShell_v2 / BottomPlate_v2 / FilterFrame_v1)。
(以下为 v1 说明)总装换成**两件式外壳**:TopShell_v1(一体顶盖)+ BottomPlate_v1(一整块底盖)。
删:Tray_v9、EndPlate_v13 ×2、SouthPanel_v4、NorthPanel_v3、TopPanel_v4(六件式全部)
插:TopShell_v1 dz +74.4、BottomPlate_v1 dz −10(纯平移)
不动:底板、5 片刀片、2 只风扇
判据:
1. 两件装配 box;分风腔深度复测(西 15.01 / 东 15.51)
2. 干涉 = 360 基线
3. 顶盖墙底 ↔ 底盖顶面 **面贴合**(z=−10)
4. 同轴:底盖四角 Ø3.4 过孔 ↔ 顶盖角柱 Ø4.2 螺母孔(轴 z)
5. 同轴:顶盖端墙 Ø3.4 风扇孔 ↔ 风扇模型 R2.15 安装孔(轴 x)—— 六件式时代没核过
6. 截图
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
OLD = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL', 'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def setx(c, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
IDENT + [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 [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
allok = True
P(u'[1] 删六件式')
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if any(k in nm.upper() for k in OLD):
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
left = [prop(c, 'Name2') for c in doc.GetComponents(True)
if any(k in prop(c, 'Name2').upper() for k in OLD)]
if left:
P(u' ⛔ 没删干净(EditDelete 式假成功?)%s' % left); sys.exit(2)
P(u'\n[2] 插两件')
for fn, t, want in ((u'TopShell_v7.SLDPRT', (0, 0, 74.4), [-18.4, -4.055, -10.0, 115.9, 106.745, 74.4]),
(u'BottomPlate_v5.SLDPRT', (0, 0, -10.0), [-17.9, -3.555, -13.6, 115.4, 106.245, 0.0])):
c = doc.AddComponent4(DIR + fn, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
b = setx(c, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %-22s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (fn, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
P(u'\n[1b] 风扇 60×60×25 → 60×60×10(按实物 10 mm:外侧面贴墙)')
FAN10 = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.SLDPRT'
_old = []
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if 'OD60' in nm.upper():
_old.append((list(prop(c, 'Transform2').ArrayData)[0:9], [x * 1000 for x in c.GetBox(False, False)]))
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
if len(_old) != 2:
P(u' ⛔ 旧风扇不是 2 只'); sys.exit(4)
def _setrt(c, rot, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
list(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 [x * 1000.0 for x in c.GetBox(False, False)]
for rot, b_old in _old:
west = b_old[0] < 50
c = doc.AddComponent4(FAN10, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ OD6010 AddComponent4 → None'); sys.exit(5)
b0 = _setrt(c, rot, (0, 0, 0))
dx = (-16.0 - b0[0]) if west else (113.5 - b0[3])
dy = 51.345 - (b0[1] + b0[4]) / 2
dz = 37.85 - (b0[2] + b0[5]) / 2
b = _setrt(c, rot, (dx, dy, dz))
P(u' %s风扇 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f(模型厚 %.2f,实物 10)'
% (u'西' if west else u'东', b[0], b[3], b[1], b[4], b[2], b[5], b[3] - b[0]))
tops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}
box = {n: [x * 1000 for x in c.GetBox(False, False)] for n, c in tops.items()}
fans = sorted([b for n, b in box.items() if 'OD60' in n.upper()], key=lambda b: b[0])
blades = [b for n, b in box.items() if 'BLADE' in n.upper()]
pw = min(b[0] for b in blades) - fans[0][3]
pe = fans[1][0] - max(b[3] for b in blades)
P(u' 分风腔 西 %.2f / 东 %.2f %s' % (pw, pe, u'✅' if pw > 9 and pe > 9 else u'⛔'))
allok &= pw > 9 and pe > 9
P(u'\n[3] 干涉(基线 360)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = True
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
hits = []
if n and n != 360:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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(k in y.upper() for y in nms for k in ('TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME', 'OD60')):
hits.append((o.Volume * 1e9, nms))
idm.Done()
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
for vv, nms in sorted(hits, reverse=True)[:8]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
allok &= n == 360
P(u'\n[4] 贴合:顶盖墙底 ↔ 底盖顶面')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
P(u' 顶盖 z 下沿 %.3f / 底盖板面 z −10(底盖 box 上沿 %.3f 是 PCB 立柱顶)'
% (ts[2], bp[5]))
allok &= abs(ts[2] + 10.0) < 0.05
def world_holes(comp, radius, tol=0.01):
xf = list(prop(comp, 'Transform2').ArrayData)
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
for bd in comp.GetBodies2(0) or []:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > tol:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
out.append((max(range(3), key=lambda i: abs(ga[i])), gp))
return out
def pair(tag, A, B, axis, want):
A = [p for ax, p in A if ax == axis]
B = [p for ax, p in B if ax == axis]
o = [i for i in range(3) if i != axis]
ok = 0
for p in A:
lat = min((max(abs(p[i] - q[i]) for i in o) for q in B), default=99)
ok += lat < 0.05
P(u' %s (%s=%7.2f, %s=%6.2f) 横偏 %.3f %s'
% (tag, 'xyz'[o[0]], p[o[0]], 'xyz'[o[1]], p[o[1]], lat, u'✅' if lat < 0.05 else u'⛔'))
P(u' %s 对上 %d / %d(期望 %d)' % (tag, ok, len(A), want))
return ok == want and len(A) == want
shell = [c for n, c in tops.items() if 'TOPSHELL' in n.upper()][0]
plate = [c for n, c in tops.items() if 'BOTTOMPLATE' in n.upper()][0]
fanc = [c for n, c in tops.items() if 'OD60' in n.upper()]
P(u'\n[5] 同轴:底盖四角过孔 ↔ 顶盖角柱螺母孔')
thru = [h for h in world_holes(plate, 1.7)]
nuts = [h for h in world_holes(shell, 2.1)]
allok &= pair(u'底盖过孔', thru, nuts, 2, 4)
P(u'\n[6] 同轴:顶盖端墙风扇孔 ↔ 风扇安装孔(R2.15)')
wall = world_holes(shell, 2.6)
fan_h = []
for fc in fanc:
fan_h += world_holes(fc, 2.15)
# Orion OD6010 模型的安装孔是**腰形孔**:每个孔由两个 R2.15 半圆柱组成(中心 ±1.075),
# 模型孔距 50.0;实物(广诚 6010)孔距 50.2 ⇒ 墙孔按 ±25.1 开,与模型预期差 0.1。
# ⇒ 先把离墙孔 1.5 以内的半圆柱轴点取平均得到腰形孔中心,再比偏差,容差放到 0.15。
_ok = 0
_ws = [p for ax, p in wall if ax == 0]
for p in _ws:
near = [q for ax, q in fan_h if ax == 0 and max(abs(p[1] - q[1]), abs(p[2] - q[2])) < 1.5]
if not near:
P(u' 墙孔 (y=%.2f, z=%.2f) 附近没有风扇孔 ⛔' % (p[1], p[2]))
continue
cy = sum(q[1] for q in near) / len(near)
cz = sum(q[2] for q in near) / len(near)
off = max(abs(p[1] - cy), abs(p[2] - cz))
_ok += off <= 0.15
P(u' 墙孔 (y=%.2f, z=%.2f) ↔ 模型腰孔中心 (%.2f, %.2f) 偏 %.3f %s'
% (p[1], p[2], cy, cz, off, u'✅(模型 50.0 vs 实物 50.2)' if off <= 0.15 else u'⛔'))
P(u' 风扇孔对上 %d / %d(期望 8)' % (_ok, len(_ws)))
allok &= _ok == 8 and len(_ws) == 8
for nm2, vid in ((u'v7_iso', 7),):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n存盘 err=%s 顶层件 %d 总判 %s'
% (e3.value, len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))
+272
查看文件
@@ -0,0 +1,272 @@
# -*- coding: utf-8 -*-
# v7:无滤网版总装 —— 删滤网框(OLD 列表里的 FILTERFRAME 负责把旧件删掉)
# v3:短机版总装(风扇换 6010、分风腔 10、TopShell_v3 / BottomPlate_v3 / FilterFrame_v2)
u"""**v2**:两件式 + 西端插拔式滤网(TopShell_v2 / BottomPlate_v2 / FilterFrame_v1)。
(以下为 v1 说明)总装换成**两件式外壳**:TopShell_v1(一体顶盖)+ BottomPlate_v1(一整块底盖)。
删:Tray_v9、EndPlate_v13 ×2、SouthPanel_v4、NorthPanel_v3、TopPanel_v4(六件式全部)
插:TopShell_v1 dz +74.4、BottomPlate_v1 dz −10(纯平移)
不动:底板、5 片刀片、2 只风扇
判据:
1. 两件装配 box;分风腔深度复测(西 15.01 / 东 15.51)
2. 干涉 = 360 基线
3. 顶盖墙底 ↔ 底盖顶面 **面贴合**(z=−10)
4. 同轴:底盖四角 Ø3.4 过孔 ↔ 顶盖角柱 Ø4.2 螺母孔(轴 z)
5. 同轴:顶盖端墙 Ø3.4 风扇孔 ↔ 风扇模型 R2.15 安装孔(轴 x)—— 六件式时代没核过
6. 截图
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
OLD = ('TRAY', 'ENDPLATE', 'SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL', 'TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME')
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def setx(c, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
IDENT + [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 [round(x * 1000.0, 3) for x in c.GetBox(False, False)]
allok = True
P(u'[1] 删六件式')
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if any(k in nm.upper() for k in OLD):
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
left = [prop(c, 'Name2') for c in doc.GetComponents(True)
if any(k in prop(c, 'Name2').upper() for k in OLD)]
if left:
P(u' ⛔ 没删干净(EditDelete 式假成功?)%s' % left); sys.exit(2)
P(u'\n[2] 插两件')
for fn, t, want in ((u'TopShell_v9.SLDPRT', (0, 0, 74.4), [-18.4, -4.055, -10.0, 115.9, 106.745, 74.4]),
(u'BottomPlate_v5.SLDPRT', (0, 0, -10.0), [-17.9, -3.555, -13.6, 115.4, 106.245, 0.0])):
c = doc.AddComponent4(DIR + fn, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % fn); sys.exit(3)
b = setx(c, t)
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %-22s x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (fn, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
P(u'\n[1b] 风扇 60×60×25 → 60×60×10(按实物 10 mm:外侧面贴墙)')
FAN10 = u'D:\\DCDC\\新建文件夹\\OD6010_m0_frame.SLDPRT'
_old = []
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if 'OD60' in nm.upper():
_old.append((list(prop(c, 'Transform2').ArrayData)[0:9], [x * 1000 for x in c.GetBox(False, False)]))
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
if len(_old) != 2:
P(u' ⛔ 旧风扇不是 2 只'); sys.exit(4)
def _setrt(c, rot, t):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
list(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 [x * 1000.0 for x in c.GetBox(False, False)]
for rot, b_old in _old:
west = b_old[0] < 50
c = doc.AddComponent4(FAN10, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ OD6010 AddComponent4 → None'); sys.exit(5)
b0 = _setrt(c, rot, (0, 0, 0))
dx = (-16.0 - b0[0]) if west else (113.5 - b0[3])
dy = 51.345 - (b0[1] + b0[4]) / 2
dz = 37.85 - (b0[2] + b0[5]) / 2
b = _setrt(c, rot, (dx, dy, dz))
P(u' %s风扇 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f(模型厚 %.2f,实物 10)'
% (u'西' if west else u'东', b[0], b[3], b[1], b[4], b[2], b[5], b[3] - b[0]))
tops = {prop(c, 'Name2'): c for c in doc.GetComponents(True)}
box = {n: [x * 1000 for x in c.GetBox(False, False)] for n, c in tops.items()}
fans = sorted([b for n, b in box.items() if 'OD60' in n.upper()], key=lambda b: b[0])
blades = [b for n, b in box.items() if 'BLADE' in n.upper()]
pw = min(b[0] for b in blades) - fans[0][3]
pe = fans[1][0] - max(b[3] for b in blades)
P(u' 分风腔 西 %.2f / 东 %.2f %s' % (pw, pe, u'✅' if pw > 9 and pe > 9 else u'⛔'))
allok &= pw > 9 and pe > 9
P(u'\n[3] 干涉(基线 360)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = False # ⚠ 用户可能在 GUI 里隐藏了零件,复验时不能忽略隐藏实体(2026-09-13 中间刀片板体被隐藏 ⇒ 误报 288)
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
hits = []
if n and n != 360:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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(k in y.upper() for y in nms for k in ('TOPSHELL', 'BOTTOMPLATE', 'FILTERFRAME', 'OD60')):
hits.append((o.Volume * 1e9, nms))
idm.Done()
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
for vv, nms in sorted(hits, reverse=True)[:8]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:26], nms[1][:26]))
allok &= n == 360
P(u'\n[4] 贴合:顶盖墙底 ↔ 底盖顶面')
ts = [b for nn, b in box.items() if 'TOPSHELL' in nn.upper()][0]
bp = [b for nn, b in box.items() if 'BOTTOMPLATE' in nn.upper()][0]
P(u' 顶盖 z 下沿 %.3f / 底盖板面 z −10(底盖 box 上沿 %.3f 是 PCB 立柱顶)'
% (ts[2], bp[5]))
allok &= abs(ts[2] + 10.0) < 0.05
def world_holes(comp, radius, tol=0.01):
xf = list(prop(comp, 'Transform2').ArrayData)
R, T = xf[0:9], [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
for bd in comp.GetBodies2(0) or []:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > tol:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
out.append((max(range(3), key=lambda i: abs(ga[i])), gp))
return out
def pair(tag, A, B, axis, want):
A = [p for ax, p in A if ax == axis]
B = [p for ax, p in B if ax == axis]
o = [i for i in range(3) if i != axis]
ok = 0
for p in A:
lat = min((max(abs(p[i] - q[i]) for i in o) for q in B), default=99)
ok += lat < 0.05
P(u' %s (%s=%7.2f, %s=%6.2f) 横偏 %.3f %s'
% (tag, 'xyz'[o[0]], p[o[0]], 'xyz'[o[1]], p[o[1]], lat, u'✅' if lat < 0.05 else u'⛔'))
P(u' %s 对上 %d / %d(期望 %d)' % (tag, ok, len(A), want))
return ok == want and len(A) == want
shell = [c for n, c in tops.items() if 'TOPSHELL' in n.upper()][0]
plate = [c for n, c in tops.items() if 'BOTTOMPLATE' in n.upper()][0]
fanc = [c for n, c in tops.items() if 'OD60' in n.upper()]
P(u'\n[5] 同轴:底盖四角过孔 ↔ 顶盖角柱螺母孔')
thru = [h for h in world_holes(plate, 1.7)]
nuts = [h for h in world_holes(shell, 2.1)]
allok &= pair(u'底盖过孔', thru, nuts, 2, 4)
P(u'\n[6] 同轴:顶盖端墙风扇孔 ↔ 风扇安装孔(R2.15)')
wall = world_holes(shell, 2.6)
fan_h = []
for fc in fanc:
fan_h += world_holes(fc, 2.15)
# Orion OD6010 模型的安装孔是**腰形孔**:每个孔由两个 R2.15 半圆柱组成(中心 ±1.075),
# 模型孔距 50.0;实物(广诚 6010)孔距 50.2 ⇒ 墙孔按 ±25.1 开,与模型预期差 0.1。
# ⇒ 先把离墙孔 1.5 以内的半圆柱轴点取平均得到腰形孔中心,再比偏差,容差放到 0.15。
_ok = 0
_ws = [p for ax, p in wall if ax == 0]
for p in _ws:
near = [q for ax, q in fan_h if ax == 0 and max(abs(p[1] - q[1]), abs(p[2] - q[2])) < 1.5]
if not near:
P(u' 墙孔 (y=%.2f, z=%.2f) 附近没有风扇孔 ⛔' % (p[1], p[2]))
continue
cy = sum(q[1] for q in near) / len(near)
cz = sum(q[2] for q in near) / len(near)
off = max(abs(p[1] - cy), abs(p[2] - cz))
_ok += off <= 0.15
P(u' 墙孔 (y=%.2f, z=%.2f) ↔ 模型腰孔中心 (%.2f, %.2f) 偏 %.3f %s'
% (p[1], p[2], cy, cz, off, u'✅(模型 50.0 vs 实物 50.2)' if off <= 0.15 else u'⛔'))
P(u' 风扇孔对上 %d / %d(期望 8)' % (_ok, len(_ws)))
allok &= _ok == 8 and len(_ws) == 8
for nm2, vid in ((u'v9_iso', 7),):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception as ex:
P(u'截图 %s 失败 %s' % (nm2, str(ex)[:40]))
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n存盘 err=%s 顶层件 %d 总判 %s'
% (e3.value, len(doc.GetComponents(True)), u'✅ 全过' if allok else u'⛔ 有项没过'))
+22
查看文件
@@ -0,0 +1,22 @@
import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', write_through=True)
import win32com.client as wc
def prop(o,n):
v = getattr(o,n)
return v() if type(v).__name__ in ('method','function','builtin_function_or_method') else v
D = lambda x: x if hasattr(x,'_oleobj_') else wc.Dispatch(x)
app = wc.GetActiveObject('SldWorks.Application')
part = app.ActiveDoc
print('doc', prop(part,'GetTitle'))
b = part.GetBodies2(0, True) or []
fs = prop(D(b[0]), 'GetFaces')
print('faces', len(fs))
for f in fs:
fc = D(f); s = prop(fc,'GetSurface')
if not prop(s,'IsPlane'): continue
pp = [round(v,3) for v in list(prop(s,'PlaneParams'))]
fb = [round(v*1000,2) for v in prop(fc,'GetBox')]
try: sense = prop(fc,'FaceInSurfaceSense')
except Exception as e: sense = 'ERR'
print(' n=(%5.2f,%5.2f,%5.2f) p=(%7.2f,%7.2f,%7.2f) sense=%s box x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f A=%.0f' % (
pp[0],pp[1],pp[2], pp[3]*1000,pp[4]*1000,pp[5]*1000, sense, fb[0],fb[3],fb[1],fb[4],fb[2],fb[5], prop(fc,'GetArea')*1e6))
+20
查看文件
@@ -0,0 +1,20 @@
import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', write_through=True)
import win32com.client as wc
def prop(o,n):
v = getattr(o,n)
return v() if type(v).__name__ in ('method','function','builtin_function_or_method') else v
D = lambda x: x if hasattr(x,'_oleobj_') else wc.Dispatch(x)
app = wc.GetActiveObject('SldWorks.Application')
part = app.ActiveDoc
print('doc', prop(part,'GetTitle'))
fs = prop(D((part.GetBodies2(0,True) or [])[0]), 'GetFaces')
for f in fs:
fc = D(f); s = prop(fc,'GetSurface')
if not prop(s,'IsCylinder'): continue
cp = list(prop(s,'CylinderParams'))
r = cp[6]*1000
if abs(r-2.1) > 0.01: continue
fb = [round(v*1000,2) for v in prop(fc,'GetBox')]
print(' R%.2f axis=(%.0f,%.0f,%.0f) box x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f' % (
r, cp[3],cp[4],cp[5], fb[0],fb[3],fb[1],fb[4],fb[2],fb[5]))
+38
查看文件
@@ -0,0 +1,38 @@
# read-only: what lives in the two plenum slices and in the north bypass strip
import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', write_through=True)
import win32com.client as wc
def prop(o, n):
v = getattr(o, n)
return v() if type(v).__name__ in ('method','function','builtin_function_or_method') else v
app = wc.GetActiveObject('SldWorks.Application')
doc = app.ActiveDoc
kids = []
for c in doc.GetComponents(False):
nm = prop(c, 'Name2')
if 'BASE-PCB' not in nm.upper() or '/' not in nm:
continue
try:
b = [round(v*1000, 2) for v in c.GetBox(False, False)]
except Exception:
continue
kids.append((nm.split('/')[-1], b))
print('base-PCB children:', len(kids))
def show(title, pred):
sel = [(n,b) for n,b in kids if pred(b)]
sel.sort(key=lambda r: -(r[1][5]))
print('\n== %s : %d parts ==' % (title, len(sel)))
for n,b in sel[:18]:
print(' %-26s x %7.2f~%7.2f y %7.2f~%7.2f z %7.2f~%7.2f' % (n[:26], b[0],b[3], b[1],b[4], b[2],b[5]))
# west plenum slice: everything with x below 6 (blade front is 4.01)
show('WEST plenum slice x < 6', lambda b: b[0] < 6.0)
# east plenum slice
show('EAST plenum slice x > 92', lambda b: b[3] > 92.0)
# north bypass strip: y above 90
show('NORTH strip y > 90', lambda b: b[4] > 90.0)
# south strip (blade1 phone side): y below 8
show('SOUTH strip y < 8', lambda b: b[1] < 8.0)
+40
查看文件
@@ -0,0 +1,40 @@
import sys, io, json
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', write_through=True)
import pythoncom, win32com.client as wc
def prop(o, n):
v = getattr(o, n)
return v() if type(v).__name__ in ('method','function','builtin_function_or_method') else v
app = wc.GetActiveObject('SldWorks.Application')
doc = app.ActiveDoc
print('active:', prop(doc,'GetTitle'), '| docs', prop(app,'GetDocumentCount'))
rows = []
for c in doc.GetComponents(True):
nm = prop(c, 'Name2')
try:
b = [round(v*1000, 3) for v in c.GetBox(False, False)]
except Exception:
b = None
rows.append((nm, b))
rows.sort(key=lambda r: (r[1][0] if r[1] else 0))
print('top-level comps:', len(rows))
print('%-34s %8s %8s %8s %8s %8s %8s' % ('name','x0','x1','y0','y1','z0','z1'))
for nm, b in rows:
if b:
print('%-34s %8.2f %8.2f %8.2f %8.2f %8.2f %8.2f' % (nm[:34], b[0],b[3],b[1],b[4],b[2],b[5]))
else:
print('%-34s <no box>' % nm[:34])
blades = sorted([(b[1],b[4],nm) for nm,b in rows if b and 'BLADE' in nm.upper()])
print('\nblade y spans (%d):' % len(blades))
prev = None
for y0,y1,nm in blades:
gap = '' if prev is None else ' gap %.3f' % (y0-prev)
print(' y %8.3f ~ %8.3f centre %8.3f%s' % (y0, y1, (y0+y1)/2, gap))
prev = y1
fans = [(nm,b) for nm,b in rows if b and ('OD6025' in nm.upper() or 'FAN' in nm.upper())]
print('\nfans:')
for nm,b in fans:
print(' %-30s x %8.2f~%8.2f y %8.2f~%8.2f z %8.2f~%8.2f' % (nm[:30], b[0],b[3],b[1],b[4],b[2],b[5]))
json.dump([{'name':nm,'box':b} for nm,b in rows], open('_top.json','w',encoding='utf-8'), ensure_ascii=False, indent=1)
print('\nsaved _top.json')
+29
查看文件
@@ -0,0 +1,29 @@
import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', write_through=True)
import win32com.client as wc
def prop(o,n):
v=getattr(o,n); return v() if type(v).__name__ in ('method','function','builtin_function_or_method') else v
D=lambda x: x if hasattr(x,'_oleobj_') else wc.Dispatch(x)
app=wc.GetActiveObject('SldWorks.Application')
doc=[d for d in app.GetDocuments if 'PhoneRail_5blades' in (prop(d,'GetTitle') or '')][0]
comp=[c for c in doc.GetComponents(False) if prop(c,'Name2').endswith('/USB-C_SMD-TYPE-C-31-M-12_1~USB-C_SMD-TYPE-C-31-M-12_1-1') or (('/J1~USB-C' in prop(c,'Name2')) and prop(c,'Name2').count('/')==2)]
cands=[c for c in doc.GetComponents(False) if 'BASE-PCB' in prop(c,'Name2').upper() and 'USB-C' in prop(c,'Name2') and (c.GetBodies2(0) or [])]
c=[k for k in cands if abs(k.GetBox(False,False)[0]*1000-8.03)<0.01][0]
print('comp', prop(c,'Name2')[-50:])
b=D((c.GetBodies2(0))[0])
fs=prop(b,'GetFaces'); print('faces', len(fs))
rows=[]
for f in fs:
fc=D(f); s=prop(fc,'GetSurface'); fb=[round(v*1000,3) for v in prop(fc,'GetBox')]
if fb[1] > -4.9: continue # only faces touching the front region (local y near -5.05)
if prop(s,'IsPlane'):
n=list(prop(s,'PlaneParams'))[:3]
if prop(fc,'FaceInSurfaceSense'): n=[-v for v in n]
kind='PLN n=(%.0f,%.0f,%.0f)' % tuple(n)
elif prop(s,'IsCylinder'):
cp=list(prop(s,'CylinderParams')); kind='CYL R%.3f ax=(%.0f,%.0f,%.0f) c=(%.3f,%.3f)' % (cp[6]*1000, cp[3],cp[4],cp[5], cp[0]*1000, cp[2]*1000)
else:
kind='OTHER'
rows.append((kind, fb, prop(fc,'GetArea')*1e6))
for kind, fb, a in sorted(rows, key=lambda r:(r[1][1], r[0])):
print(' %-48s x %7.3f~%7.3f y %7.3f~%7.3f z %7.3f~%7.3f A=%.2f' % (kind, fb[0],fb[3],fb[1],fb[4],fb[2],fb[5],a))
+79
查看文件
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
u"""按**读回的相机视线方向**挑视图再截图(视图 id 与方向的对应在本机会变,NOTES §35 那张表这次就不成立)。"""
import io
import os
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
import win32com.client as wc
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = [d for d in app.GetDocuments if 'PhoneRail_5blades' in (prop(d, 'GetTitle') or '')][0]
e0 = wc.VARIANT(16387, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
views = {}
for vid in range(0, 9):
try:
doc.ShowNamedView2('', vid)
arr = list(prop(D(prop(doc, 'ActiveView')), 'Transform').ArrayData)
dirv = tuple(round(v, 3) for v in (arr[2], arr[5], arr[8]))
views[vid] = dirv
print(u' vid %d 视线 %s' % (vid, dirv))
except Exception as ex:
print(u' vid %d 失败 %s' % (vid, str(ex)[:40]))
def find(want):
for vid, d in views.items():
if all(abs(d[i] - want[i]) < 0.01 for i in range(3)):
return vid
return None
# 想要的「相机视线指向」:看西端墙 = 从 −x 往 +x 看 ⇒ 视线 (+1,0,0);以此类推
wanted = [(u'v10_west', (1.0, 0.0, 0.0)), (u'v10_south', (0.0, 1.0, 0.0)),
(u'v10_bottom', (0.0, 0.0, 1.0)), (u'v10_top', (0.0, 0.0, -1.0))]
for name, want in wanted:
vid = find(want)
if vid is None:
# 视线可能以相反号记录:再试反向(相机在另一侧)
vid2 = find(tuple(-v for v in want))
print(u' %s:没有视线 %s 的视图%s' % (name, want, u'(反向 vid %s)' % vid2 if vid2 is not None else u''))
continue
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, name + '.bmp'), 1600, 1100)
print(u' %s ← vid %d' % (name, vid))
doc.ShowNamedView2('', 7)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, 'v10_iso.bmp'), 1600, 1100)
vid = find((0.0, 1.0, 0.0))
if vid is not None:
doc.ShowNamedView2('', vid)
# 拉近到南墙 Type-C 与标识那一段(装配坐标,米)
doc.ViewZoomTo2(0.000, -0.005, -0.012, 0.100, -0.005, 0.008)
doc.SaveBMP(os.path.join(OUT, 'v10_south_zoom.bmp'), 1600, 1100)
print(u' v10_south_zoom ← vid %d + ViewZoomTo2' % vid)
vid = find((1.0, 0.0, 0.0))
if vid is not None:
doc.ShowNamedView2('', vid)
doc.ViewZoomTo2(-0.0184, 0.015, 0.002, -0.0184, 0.088, 0.074) # 拉近西端格栅
doc.SaveBMP(os.path.join(OUT, 'v10_west_zoom.bmp'), 1600, 1100)
print(u' v10_west_zoom ← vid %d + ViewZoomTo2' % vid)
+72
查看文件
@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
u"""按**读回的相机视线方向**挑视图再截图(视图 id 与方向的对应在本机会变,NOTES §35 那张表这次就不成立)。"""
import io
import os
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
import win32com.client as wc
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = [d for d in app.GetDocuments if 'PhoneRail_5blades' in (prop(d, 'GetTitle') or '')][0]
e0 = wc.VARIANT(16387, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
views = {}
for vid in range(0, 9):
try:
doc.ShowNamedView2('', vid)
arr = list(prop(D(prop(doc, 'ActiveView')), 'Transform').ArrayData)
dirv = tuple(round(v, 3) for v in (arr[2], arr[5], arr[8]))
views[vid] = dirv
print(u' vid %d 视线 %s' % (vid, dirv))
except Exception as ex:
print(u' vid %d 失败 %s' % (vid, str(ex)[:40]))
def find(want):
for vid, d in views.items():
if all(abs(d[i] - want[i]) < 0.01 for i in range(3)):
return vid
return None
# 想要的「相机视线指向」:看西端墙 = 从 −x 往 +x 看 ⇒ 视线 (+1,0,0);以此类推
wanted = [(u'v7_west', (1.0, 0.0, 0.0)), (u'v7_south', (0.0, 1.0, 0.0)),
(u'v7_bottom', (0.0, 0.0, 1.0)), (u'v7_top', (0.0, 0.0, -1.0))]
for name, want in wanted:
vid = find(want)
if vid is None:
# 视线可能以相反号记录:再试反向(相机在另一侧)
vid2 = find(tuple(-v for v in want))
print(u' %s:没有视线 %s 的视图%s' % (name, want, u'(反向 vid %s)' % vid2 if vid2 is not None else u''))
continue
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, name + '.bmp'), 1600, 1100)
print(u' %s ← vid %d' % (name, vid))
doc.ShowNamedView2('', 7)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, 'v7_iso.bmp'), 1600, 1100)
vid = find((0.0, 1.0, 0.0))
if vid is not None:
doc.ShowNamedView2('', vid)
# 拉近到南墙 Type-C 与标识那一段(装配坐标,米)
doc.ViewZoomTo2(0.000, -0.005, -0.012, 0.100, -0.005, 0.008)
doc.SaveBMP(os.path.join(OUT, 'v7_south_zoom.bmp'), 1600, 1100)
print(u' v7_south_zoom ← vid %d + ViewZoomTo2' % vid)
+79
查看文件
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
u"""按**读回的相机视线方向**挑视图再截图(视图 id 与方向的对应在本机会变,NOTES §35 那张表这次就不成立)。"""
import io
import os
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
import win32com.client as wc
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = [d for d in app.GetDocuments if 'PhoneRail_5blades' in (prop(d, 'GetTitle') or '')][0]
e0 = wc.VARIANT(16387, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
views = {}
for vid in range(0, 9):
try:
doc.ShowNamedView2('', vid)
arr = list(prop(D(prop(doc, 'ActiveView')), 'Transform').ArrayData)
dirv = tuple(round(v, 3) for v in (arr[2], arr[5], arr[8]))
views[vid] = dirv
print(u' vid %d 视线 %s' % (vid, dirv))
except Exception as ex:
print(u' vid %d 失败 %s' % (vid, str(ex)[:40]))
def find(want):
for vid, d in views.items():
if all(abs(d[i] - want[i]) < 0.01 for i in range(3)):
return vid
return None
# 想要的「相机视线指向」:看西端墙 = 从 −x 往 +x 看 ⇒ 视线 (+1,0,0);以此类推
wanted = [(u'v9_west', (1.0, 0.0, 0.0)), (u'v9_south', (0.0, 1.0, 0.0)),
(u'v9_bottom', (0.0, 0.0, 1.0)), (u'v9_top', (0.0, 0.0, -1.0))]
for name, want in wanted:
vid = find(want)
if vid is None:
# 视线可能以相反号记录:再试反向(相机在另一侧)
vid2 = find(tuple(-v for v in want))
print(u' %s:没有视线 %s 的视图%s' % (name, want, u'(反向 vid %s)' % vid2 if vid2 is not None else u''))
continue
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, name + '.bmp'), 1600, 1100)
print(u' %s ← vid %d' % (name, vid))
doc.ShowNamedView2('', 7)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, 'v9_iso.bmp'), 1600, 1100)
vid = find((0.0, 1.0, 0.0))
if vid is not None:
doc.ShowNamedView2('', vid)
# 拉近到南墙 Type-C 与标识那一段(装配坐标,米)
doc.ViewZoomTo2(0.000, -0.005, -0.012, 0.100, -0.005, 0.008)
doc.SaveBMP(os.path.join(OUT, 'v9_south_zoom.bmp'), 1600, 1100)
print(u' v9_south_zoom ← vid %d + ViewZoomTo2' % vid)
vid = find((1.0, 0.0, 0.0))
if vid is not None:
doc.ShowNamedView2('', vid)
doc.ViewZoomTo2(-0.0184, 0.015, 0.002, -0.0184, 0.088, 0.074) # 拉近西端格栅
doc.SaveBMP(os.path.join(OUT, 'v9_west_zoom.bmp'), 1600, 1100)
print(u' v9_west_zoom ← vid %d + ViewZoomTo2' % vid)
+95
查看文件
@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
u"""把螺丝位置算成常量表,供各板建模脚本引用(只算不改模型)。
依据全部来自**实测贴合面**(`contact_map.py` 回读,不是推的):
| 贴合对 | 沿哪个轴 | 重叠区 | 能否打 M3 |
|---|---|---|---|
| 端板 ↔ 南/北板 | y | z 82.0 × x 27.4 | ✅ 宽裕 |
| 端板 ↔ 顶盖 | z | x 27.4 × y 103.7 | ✅ 宽裕 |
| **托盘 ↔ 南/北板** | y | **z 10.0 × x 106.0** | ✅ 10 mm 带够打 |
| 南板 ↔ 顶盖 | z | x 152.8 × **y 2.3** | ⛔ 太窄,不在此处打 |
⇒ 螺丝分三组,全部 M3:
A. **侧板 → 端板**(水平,沿 y):每块侧板两端各 2 颗,共 8 颗。
螺丝穿过侧板拧进端板的 y 端面(端板本体 2.4 厚 + L 脚区,
实际拧入的是端板板厚方向 ⇒ 需在端板端面加 **Ø6×6 凸台**才有肉,
否则 2.4 板边攻不住 M3)。
B. **侧板 → 托盘**(水平,沿 y):沿 x 均布 3 颗 × 2 块 = 6 颗。
打在 z 10.0 那条带的中线 z=−5.0 上,拧进托盘侧端面
(托盘面板 2.4 厚 + 立柱区 ⇒ 同样要在托盘端面加凸台)。
C. **顶盖 → 端板**(竖直,沿 z):每端 2 颗,共 4 颗。
打在端板顶面 x 27.4 宽的带上,位置避开风口(风口中心 y=51.345、Ø60)。
⚠ 3D 打印件不直接攻螺纹:凸台里预留 **Ø4.2×6 热熔螺母孔**(M3),
与托盘立柱顶面用的规格一致,全机统一 M3 + 热熔螺母。
"""
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', write_through=True)
# ── 已实测的几何边界 ──────────────────────────────────────────────────
EP_W = (-27.4, 0.0) # 西端板 x
EP_E = (98.0, 125.4) # 东端板 x
EP_Y = (-1.655, 104.345) # 端板 y(对称)
EP_Z = (-10.0, 72.0) # 端板 z
TRAY_X = (-4.0, 102.0)
TRAY_Y = (-1.655, 104.345)
TRAY_Z = (-12.4, 0.0)
PANEL_T = 2.4
SOUTH_Y = (-4.055, -1.655)
NORTH_Y = (104.345, 106.745)
TOP_Z = (72.0, 74.4)
FAN_CY, VENT_R = 51.345, 30.0
SCREW_D = 3.4 # 过孔
INSERT_D, INSERT_DEPTH = 4.2, 6.0 # 热熔螺母
BOSS_D = 8.0 # 凸台外径
P = print
P(u'【A 组】侧板 → 端板(沿 y 水平,每块侧板两端各 2 颗,共 8 颗)')
P(u' 螺丝轴线沿 y;孔在侧板上,凸台在端板的 y 端面。')
# ⛔ 上一版漏了「南板和北板各要一套」:只按两个端板算了 4 颗,
# 而 A 组是**侧板→端板**,南、北两块侧板各自都要在两端各打 2 颗 ⇒ 实际 8 颗。
a = []
for panel, py in ((u'南板', EP_Y[0]), (u'北板', EP_Y[1])):
for tag, (x0, x1) in ((u'西端', EP_W), (u'东端', EP_E)):
cx = (x0 + x1) / 2.0
for z in (EP_Z[0] + 12.0, EP_Z[1] - 12.0): # 上下各留 12
a.append((panel, tag, round(cx, 3), round(z, 3)))
P(u' %s→%s x=%7.2f z=%6.2f (螺丝穿过 y=%.3f 那块侧板)'
% (panel, tag, cx, z, py))
P(u' ⇒ 端板需在 y=%.3f 与 y=%.3f 两个端面各加 2 个 Ø%.1f 凸台(含 Ø%.1f 螺母孔)'
% (EP_Y[0], EP_Y[1], BOSS_D, INSERT_D))
P(u'\n【B 组】⛔ **已取消**(2026-09-13)')
P(u' 原因一:托盘南北边缘与底板 PCB 之间只剩 1.665 / 6.355 mm,放不下 Ø8 凸台;')
P(u' 原因二:盒子刚性由「两端板 + 南北板 + 顶盖」四面围合已足够,')
P(u' 托盘本身由端板 L 脚的 4 颗 Ø3.4 锁孔固定在托盘面板上。')
P(u' ⇒ 螺丝总数由 18 减到 **12**(A 8 + C 4)。南北板上已打好的 3 个 B 组孔保留无害。')
P(u'\n【B 组·原方案,仅存档】侧板 → 托盘(沿 y 水平,每块 3 颗,共 6 颗)')
P(u' 打在托盘侧端面,贴合带 z −10~0,取中线 z=−5.0。')
b = []
for x in (TRAY_X[0] + 15.0, (TRAY_X[0] + TRAY_X[1]) / 2.0, TRAY_X[1] - 15.0):
b.append((round(x, 3), -5.0))
P(u' x=%7.2f z=%6.2f' % (x, -5.0))
P(u' ⇒ 托盘需在 y=%.3f 与 y=%.3f 端面各加 3 个凸台' % (TRAY_Y[0], TRAY_Y[1]))
P(u'\n【C 组】顶盖 → 端板(沿 z 竖直,每端 2 颗,共 4 颗)')
c = []
for tag, (x0, x1) in ((u'西端', EP_W), (u'东端', EP_E)):
cx = (x0 + x1) / 2.0
for y in (EP_Y[0] + 14.0, EP_Y[1] - 14.0): # 避开风口 Ø60(中心 y=51.345)
d = abs(y - FAN_CY)
c.append((tag, round(cx, 3), round(y, 3)))
P(u' %s x=%7.2f y=%7.2f 离风口中心 %.1f(需 > %.1f)%s'
% (tag, cx, y, d, VENT_R, u'✅' if d > VENT_R else u'⛔'))
P(u' ⇒ 顶盖在这 4 处开 Ø%.1f 过孔;端板顶面 z=%.1f 加 Ø%.1f 凸台'
% (SCREW_D, EP_Z[1], BOSS_D))
# A 组已含南北两块侧板(不再乘 2);B 组是"每块 3 颗 × 2 块"故乘 2;C 组已含两端。
P(u'\n合计 M3 螺丝 **%d 颗**(A %d + C %d;B 组已取消),配 Ø%.1f×%.0f 热熔螺母'
% (len(a) + len(c), len(a), len(c), INSERT_D, INSERT_DEPTH))
P(u'⚠ 托盘立柱顶面原有的 4 个 Ø4.2 螺母孔(锁底板 PCB)与本表无关,另计。')
+284
查看文件
@@ -0,0 +1,284 @@
# -*- 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'⛔ 有项没过'))
+107
查看文件
@@ -0,0 +1,107 @@
# -*- coding: ascii -*-
"""Shroud feasibility: per-channel fan-bore coverage + plenum-depth resistance model.
All geometry read back from the live assembly (_top.json), nothing assumed."""
import json, math
rows = json.load(open('_top.json', encoding='utf-8'))
B = {r['name']: r['box'] for r in rows if r['box']}
blades = sorted([v for k, v in B.items() if 'BLADE' in k.upper()], key=lambda b: b[1])
fanW = [v for k, v in B.items() if 'OD6025' in k.upper() and v[0] < 50][0]
fanE = [v for k, v in B.items() if 'OD6025' in k.upper() and v[0] > 50][0]
tray = [v for k, v in B.items() if 'TRAY' in k.upper()][0]
top = [v for k, v in B.items() if 'TOPPANEL' in k.upper()][0]
ep = [v for k, v in B.items() if 'ENDPLATE' in k.upper()][0]
base = [v for k, v in B.items() if 'BASE-PCB' in k.upper()][0]
BZ0, BZ1 = blades[0][2], blades[0][5] # blade z span
SX0, SX1 = blades[0][0], blades[0][3] # blade x span (stack length)
IY0, IY1 = ep[1], ep[4] # interior y (end-plate span)
IZ0, IZ1 = tray[5], top[2] # interior z: tray top .. top panel inner
CY, CZ, R = 51.345, 37.85, 30.5 # fan bore centre + radius (Orion OD6025 measured)
print('== measured geometry ==')
print(' blade stack x %.2f~%.2f z %.2f~%.2f (h %.2f)' % (SX0, SX1, BZ0, BZ1, BZ1 - BZ0))
print(' interior y %.3f~%.3f z %.2f~%.2f' % (IY0, IY1, IZ0, IZ1))
print(' west fan x %.2f~%.2f exit plane x=%.2f' % (fanW[0], fanW[3], fanW[3]))
print(' east fan x %.2f~%.2f inlet plane x=%.2f' % (fanE[0], fanE[3], fanE[0]))
print(' PLENUM west x %.2f~%.2f = %.2f mm' % (fanW[3], SX0, SX0 - fanW[3]))
print(' PLENUM east x %.2f~%.2f = %.2f mm' % (SX1, fanE[0], fanE[0] - SX1))
print(' base PCB x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f' % (base[0], base[3], base[1], base[4], base[2], base[5]))
# ---- channels: 4 inter-blade + 2 edge -------------------------------------
ch = []
ch.append(('edge-S', IY0, blades[0][1]))
for i in range(4):
ch.append(('C%d (blade%d-%d)' % (i + 1, i + 1, i + 2), blades[i][4], blades[i + 1][1]))
ch.append(('edge-N', blades[4][4], IY1))
def circ_area(y0, y1, z0, z1, n=2000):
"""area of [y0,y1]x[z0,z1] inside the bore circle, by strip integration"""
a = 0.0
dy = (y1 - y0) / n
for i in range(n):
y = y0 + (i + 0.5) * dy
d = R * R - (y - CY) ** 2
if d <= 0:
continue
h = math.sqrt(d)
lo, hi = max(z0, CZ - h), min(z1, CZ + h)
if hi > lo:
a += (hi - lo) * dy
return a
print('\n== per-channel frontal coverage by the fan bore (D61) ==')
print(' %-18s %8s %8s %10s %10s %7s' % ('channel', 'y0', 'y1', 'front mm2', 'in-bore', 'cover'))
tot_f = tot_c = 0.0
for nm, y0, y1 in ch:
z0, z1 = (BZ0, BZ1) if 'edge' not in nm else (IZ0, IZ1)
f = (y1 - y0) * (z1 - z0)
c = circ_area(y0, y1, z0, z1)
tot_f += f
tot_c += c
print(' %-18s %8.3f %8.3f %10.0f %10.0f %6.1f%%' % (nm, y0, y1, f, c, 100 * c / f))
print(' %-18s %8s %8s %10.0f %10.0f %6.1f%%' % ('TOTAL', '', '', tot_f, tot_c, 100 * tot_c / tot_f))
# ---- bypass: air can go around the stack instead of through it ------------
print('\n== bypass paths around the blade stack (unsealed today) ==')
byp = [('south strip', (blades[0][1] - IY0) * (IZ1 - IZ0)),
('north strip', (IY1 - blades[4][4]) * (IZ1 - IZ0)),
('over the top', (blades[4][4] - blades[0][1]) * (IZ1 - BZ1)),
('under (slots)', (blades[4][4] - blades[0][1]) * (BZ0 - IZ0))]
bt = sum(v for _, v in byp)
chan_area = sum((y1 - y0) * (BZ1 - BZ0) for nm, y0, y1 in ch if 'edge' not in nm)
for nm, v in byp:
print(' %-16s %8.0f mm2' % (nm, v))
print(' bypass total %8.0f mm2 vs 4 real channels %.0f mm2 => %.0f%% of the'
' flow area is short-circuit' % (bt, chan_area, 100 * bt / (bt + chan_area)))
# ---- how deep must the plenum be? -----------------------------------------
# Compare dP of the lateral feed run inside the plenum against dP of one channel
# at equal volume flow. dP ~ (L/Dh) * V^2 ; V ~ 1/A.
Lc = SX1 - SX0
wc = ch[3][2] - ch[3][1] # a representative inter-blade gap
Ac = wc * (BZ1 - BZ0)
Dhc = 2 * wc * (BZ1 - BZ0) / (wc + (BZ1 - BZ0))
lat = abs(ch[5][1] - CY) # lateral run from bore centre to the far channel
print('\n== how deep does the distribution plenum have to be? ==')
print(' channel: %.2f wide x %.2f tall x %.0f long, Dh %.1f, A %.0f mm2'
% (wc, BZ1 - BZ0, Lc, Dhc, Ac))
print(' lateral run inside the plenum: %.1f mm (bore centre -> far channel)\n' % lat)
print(' %8s %10s %8s %12s' % ('depth', 'A_plenum', 'Dh', 'dP_lat/dP_ch'))
for d in (4.01, 6, 8, 10, 12, 15, 20, 25):
Ap = d * (BZ1 - BZ0)
Dhp = 2 * d * (BZ1 - BZ0) / (d + (BZ1 - BZ0))
ratio = ((lat / Dhp) / (Lc / Dhc)) * (Ac / Ap) ** 2
flag = ' <= today' if abs(d - 4.01) < 0.01 else (' OK' if ratio < 0.5 else '')
print(' %8.2f %10.0f %8.1f %12.2f%s' % (d, Ap, Dhp, ratio, flag))
print('\n (ratio >> 1 means the far channels are starved no matter what vanes you add;')
print(' ratio < ~0.5 means the plenum equalises pressure and every channel sees the same dP)')
# geometric diffuser check
need = ((IY1 - IY0) - 2 * R) / 2.0
print('\n== a real (vaneless) diffuser would need ==')
for ang in (7, 10, 15):
print(' half-angle %2d deg -> %.0f mm of axial length (have %.2f)'
% (ang, need / math.tan(math.radians(ang)), SX0 - fanW[3]))
+174
查看文件
@@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
u"""南侧板 v4 —— 跟着 15 mm 分风腔加长(2026-09-13)。
与 v3 的差别只有两处,都是「端板外移 11」的下游:
· 长度 x −27.4~125.4(152.8)⇒ **−38.4~136.4(175.0)**
· A 组螺丝孔 x −13.70/111.70 ⇒ **−31/129**(端板角柱中心),z 2.0/60.0 ⇒ **10/60**
⛔ 旧的 x=−13.70 打在端板的**空气**里:端板在 z=72 那层只有 2.4 厚的板本体,
L 脚在底部 z 0~2.4,中间是空的。v10 加的角柱(x −36~−26 / 124~134,截面 8×10、
通高 z 0~72)才是这 8 颗螺丝真正能拧进去的肉。
· B 组 3 个孔(x 30/49/68, z −5)保留 —— B 组已取消,孔留着无害(PROGRESS 已记)。
其余(USB-C 开口位置 13×9、板厚、z 范围)与 v3 逐字相同:开口对准的是底板上的座子,
座子没动 ⇒ 开口不能动。
⛔ 竖板、法线沿 y ⇒ 画在**上视基准面**:草图 (u,v) → 模型 (x, −z),拉伸法线 +y。
⛔ 画草图前必须 `sm.AddToDB = True`。
⛔ 判据:矩形开口用不了圆柱面计数 ⇒ 用面数(6 + 每个通口 4)+ Ø3.4 圆柱面计数。
"""
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\\SouthPanel_v4.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
X0, X1 = -38.4, 136.4
Z0, Z1 = -10.0, 72.0
PANEL_T = 2.4
_J1_CX, _J23_CX, _OP_CZ = 12.50, 84.00, -1.20
OP_W, OP_H = 13.0, 9.0
OPENINGS = [(_J1_CX - OP_W / 2, _J1_CX + OP_W / 2),
(_J23_CX - OP_W / 2, _J23_CX + OP_W / 2)]
OP_Z0, OP_Z1 = _OP_CZ - OP_H / 2, _OP_CZ + OP_H / 2
SCREW_D = 3.4
POST_CX_W, POST_CX_E = -31.0, 129.0 # 端板角柱中心(西 x −36~−26 / 东 124~134)
SCREW_HOLES = [(POST_CX_W, 10.0), (POST_CX_W, 60.0),
(POST_CX_E, 10.0), (POST_CX_E, 60.0),
(30.0, -5.0), (49.0, -5.0), (68.0, -5.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 UZ(z):
u"""上视基准面:草图纵坐标 v → 模型 −z"""
return M(-z)
app = wc.GetActiveObject('SldWorks.Application')
for d in list(app.GetDocuments or []):
try:
t = prop(d, 'GetTitle') or ''
if 'SOUTHPANEL_V4' in t.upper():
app.CloseDoc(t)
P(u'关掉旧文档 %s' % 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 sk_name():
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 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)]
P(u'\n[1] 板本体 %.1f(x) × %.1f(z) × %.1f,含 2 个 USB-C 开口 + %d 个 Ø%.1f 螺丝孔'
% (X1 - X0, Z1 - Z0, PANEL_T, len(SCREW_HOLES), SCREW_D))
pick(u'上视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(M(X0), UZ(Z0), 0.0, M(X1), UZ(Z1), 0.0)
for ox0, ox1 in OPENINGS:
sm.CreateCornerRectangle(M(ox0), UZ(OP_Z0), 0.0, M(ox1), UZ(OP_Z1), 0.0)
for sx, sz in SCREW_HOLES:
sm.CreateCircleByRadius(M(sx), UZ(sz), 0.0, M(SCREW_D / 2))
sm.AddToDB = False
_segs = prop(D(prop(sm, 'ActiveSketch')), 'GetSketchSegments')
want_segs = 4 * (1 + len(OPENINGS)) + len(SCREW_HOLES)
P(u' 草图 segment = %d(期望 %d)' % (len(_segs) if _segs else 0, want_segs))
# 回读每个圆:只数条数查不出自动捕捉的静默篡改
for _s in (_segs or []):
_ss = D(_s)
if prop(_ss, 'GetType') != 1:
continue
_c = D(prop(_ss, 'GetCenterPoint2'))
P(u' 圆 r=%.3f 圆心=(%.3f, %.3f)'
% (prop(_ss, 'GetRadius') * 1000, prop(_c, 'X') * 1000, prop(_c, 'Y') * 1000))
sm.InsertSketch(True)
if pick(sk_name(), 'SKETCH') != 1:
P(u'⛔ 草图没选中'); sys.exit(2)
feat = fm.FeatureExtrusion3(True, False, False, 0, 0, M(PANEL_T), 0.0,
False, False, False, False, 0.0, 0.0,
False, False, False, False, True, True, True, 0, 0.0, False)
P(u' 拉伸 → %r' % (prop(feat, 'Name') if feat else feat))
b = bbox()
P(u' box %s(期望 x %.1f~%.1f, y 0~%.1f, z %.1f~%.1f)' % (b, X0, X1, PANEL_T, Z0, Z1))
if not b:
P(u'⛔ 没出实体(多半开口越界或轮廓自相交),停手'); sys.exit(3)
bd = D(bodies()[0])
nf = len(prop(bd, 'GetFaces'))
want_faces = 6 + 4 * len(OPENINGS)
P(u' 面数 = %d(期望 ≥ %d = 6 + 每个通口 4)%s'
% (nf, want_faces, u'✅' if nf >= want_faces else u'⛔ 开口没成形'))
hist = {}
for f in prop(bd, 'GetFaces'):
s = prop(D(f), 'GetSurface')
if prop(s, 'IsCylinder'):
r = round(list(prop(s, 'CylinderParams'))[6] * 1000, 2)
hist[r] = hist.get(r, 0) + 1
P(u' 圆柱面直方图 %s' % dict(sorted(hist.items())))
n34 = hist.get(round(SCREW_D / 2, 2), 0)
P(u' Ø%.1f 螺丝孔 = %d 个(期望 %d)%s'
% (SCREW_D, n34, len(SCREW_HOLES), u'✅' if n34 >= len(SCREW_HOLES) else u'⛔ 没打全'))
if n34 < len(SCREW_HOLES):
P(u'⛔ 停手:包围盒与面数都看不出漏孔,靠的就是这个计数'); sys.exit(4)
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))
if not os.path.exists(DST):
P(u'⛔ 没落盘,停手'); sys.exit(5)
P(u'⚠ 零件保持打开')
+184
查看文件
@@ -0,0 +1,184 @@
# -*- coding: utf-8 -*-
u"""总装里 EndPlate_v10 → **v12**(多了 A 组 8 + C 组 4 个 Ø4.2×6 螺母孔),并核「螺丝拧得进去」。
摆位与 place_v10.py 完全相同(西:不转、外表面 −38.4;东:绕 z 180°、外表面 136.4、dy=2×51.345)。
判据:
1. 两块端板装配 box 与 v10 时一致;
2. 干涉仍是 360 基线;
3. ⭐ **同轴核对**:在装配坐标下,把端板上 12 个 R2.1 螺母孔的轴线,
与南板/北板/顶盖上 Ø3.4 过孔的轴线逐一配对 —— 同方向、横向偏差 < 0.05 才算拧得进去。
这是「孔打出来了」和「螺丝能穿过去」之间的那一步,包围盒/干涉/直方图都回答不了。
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
DIR = u'D:\\DCDC\\新建文件夹\\5blades\\'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
ROT_Z180 = [-1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0]
FAN_CY = 51.345
WEST_OUT, EAST_OUT = -38.4, 136.4
EP_Y0, EP_Y1 = -1.655, 104.345
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)
app = wc.GetActiveObject('SldWorks.Application')
doc = None
for d in app.GetDocuments or []:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
e0 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e0)
doc = app.ActiveDoc
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
def mk(a16):
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, list(a16))
return wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1, v))
def setx(c, a16):
xf = mk(a16)
try:
c.SetTransformAndSolve2(xf)
except Exception:
c.Transform2 = xf
doc.EditRebuild3
return [round(v * 1000.0, 3) for v in c.GetBox(False, False)]
P(u'[1] 删 v10')
for c in list(doc.GetComponents(True)):
nm = prop(c, 'Name2')
if 'ENDPLATE' in nm.upper():
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
EP = DIR + u'EndPlate_v13.SLDPRT'
P(u'\n[2] 插 v12 ×2')
allok = True
for tag, rot, want in ((u'西', IDENT, [WEST_OUT, EP_Y0, -10.0, 0.0, EP_Y1, 72.0]),
(u'东', ROT_Z180, [98.0, EP_Y0, -10.0, EAST_OUT, EP_Y1, 72.0])):
c = doc.AddComponent4(EP, '', 0.0, 0.0, 0.0)
if c is None:
P(u' ⛔ %s AddComponent4 → None' % tag); sys.exit(4)
b0 = setx(c, rot + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0])
if tag == u'西':
t = (WEST_OUT - b0[0], 0.0, -10.0 - b0[2])
else:
t = (EAST_OUT - b0[3], 2.0 * FAN_CY, 0.0)
b = setx(c, rot + [t[0] / 1000.0, t[1] / 1000.0, t[2] / 1000.0, 1.0, 0.0, 0.0, 0.0])
ok = all(abs(b[i] - want[i]) < 0.05 for i in range(6))
allok &= ok
P(u' %s端板 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f %s'
% (tag, b[0], b[3], b[1], b[4], b[2], b[5], u'✅' if ok else u'⛔ 期望 %s' % want))
# ── [3] 同轴核对 ──────────────────────────────────────────────────────────
def world_holes(comp, radius):
u"""组件上指定半径的圆柱面 → 装配坐标下的 (轴向单位向量, 轴上一点, 面box)"""
xf = list(prop(comp, 'Transform2').ArrayData)
R = xf[0:9]
T = [xf[9] * 1000, xf[10] * 1000, xf[11] * 1000]
out = []
body_list = comp.GetBodies2(0) or []
for bd in body_list:
for f in prop(D(bd), 'GetFaces'):
s = prop(D(f), 'GetSurface')
if not prop(s, 'IsCylinder'):
continue
cp = list(prop(s, 'CylinderParams'))
if abs(cp[6] * 1000 - radius) > 0.01:
continue
p = [cp[0] * 1000, cp[1] * 1000, cp[2] * 1000]
a = cp[3:6]
# 列主序:本地 (x,y,z) → 全局 = x*col0 + y*col1 + z*col2 + T
gp = [p[0] * R[0] + p[1] * R[3] + p[2] * R[6] + T[0],
p[0] * R[1] + p[1] * R[4] + p[2] * R[7] + T[1],
p[0] * R[2] + p[1] * R[5] + p[2] * R[8] + T[2]]
ga = [a[0] * R[0] + a[1] * R[3] + a[2] * R[6],
a[0] * R[1] + a[1] * R[4] + a[2] * R[7],
a[0] * R[2] + a[1] * R[5] + a[2] * R[8]]
out.append((ga, gp))
return out
P(u'\n[3] 同轴核对:端板 R2.1 螺母孔 ↔ 侧板/顶盖 R1.7 过孔')
nuts, thru = [], []
for c in doc.GetComponents(True):
nm = prop(c, 'Name2').upper()
if 'ENDPLATE' in nm:
nuts += [(nm, *h) for h in world_holes(c, 2.1)]
elif any(k in nm for k in ('SOUTHPANEL', 'NORTHPANEL', 'TOPPANEL')):
thru += [(nm, *h) for h in world_holes(c, 1.7)]
P(u' 螺母孔 %d 个(期望 12)/ 板上过孔 %d 个(期望 7+7+4=18,其中 B 组 6 个无对象)'
% (len(nuts), len(thru)))
matched = 0
for nm, ga, gp in nuts:
ai = max(range(3), key=lambda i: abs(ga[i]))
best = None
for tn, ta, tp in thru:
if max(range(3), key=lambda i: abs(ta[i])) != ai:
continue
lat = max(abs(gp[i] - tp[i]) for i in range(3) if i != ai)
if best is None or lat < best[0]:
best = (lat, tn)
ok = best is not None and best[0] < 0.05
matched += ok
o = [i for i in range(3) if i != ai]
P(u' %-16s 轴%s @ (%s=%.2f, %s=%.2f) ↔ %-16s 偏 %.3f %s'
% (nm[:16], 'xyz'[ai], 'xyz'[o[0]], gp[o[0]], 'xyz'[o[1]], gp[o[1]],
best[1][:16] if best else '-', best[0] if best else -1, u'✅' if ok else u'⛔'))
P(u' 同轴 %d / %d %s' % (matched, len(nuts), u'✅' if matched == 12 else u'⛔'))
allok &= (matched == 12)
# ── [4] 干涉 ──────────────────────────────────────────────────────────────
P(u'\n[4] 干涉(基线 360)')
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = True
idm.UseTransform = False
try:
n = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
n = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
idm.Done()
P(u' 干涉 %s %s' % (n, u'✅' if n == 360 else u'⛔'))
allok &= (n == 360)
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'\n存盘 err=%s 总判 %s' % (e3.value, u'✅ 全过' if allok else u'⛔ 有项没过'))
+180
查看文件
@@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
u"""顶盖 v4 —— 加长 + 补上一条实测出来的缝(2026-09-13)。
与 v3 的三处差别:
1. **加长**:x −27.4~125.4(152.8)⇒ **−38.4~136.4(175.0)**,跟 15 mm 分风腔走。
⚠ 泄压缝不用动:缝阵按盒子 x 中心 49 对称排布,而新盒子中心
(−38.4+136.4)/2 = **49.0**,与旧值相同。
2. ⛔ **y 由 −4~102 改成 −4.055~106.745**。这是回读比对查出来的真缝:
北板内表面在 y=104.345,而顶盖只到 102 ⇒ 沿全长(175)开着一条 **2.345 mm** 的口子。
端板改 y 对称那轮,顶盖没跟着改,干涉检查也照不出来(不接触本来就不干涉)。
新范围正好盖住南北两板的**外表面**(南 −4.055 / 北 106.745),成为真正的盖子。
3. **C 组 4 个螺丝过孔挪位**:(−13.70, 12.35/90.34) ⇒ **(−31, 2.345/100.345)** 与
**(129, 2.345/100.345)** —— 端板 v10 四根角柱的中心。
⛔ 旧位打在端板的空气里(端板 z=72 那层只有 2.4 厚板本体,L 脚在底部)。
✅ 新位每个都落在 8(y)×10(x) 的角柱顶面上,Ø4.2 热熔螺母孔(留 GUI 打)有肉可吃。
离 Ø60 风口中心 y=51.345 分别 49.0 / 49.0 > 30 ✅ 不碰风口。
⛔ 水平板、法线沿 z ⇒ **前视基准面**(草图 (u,v) → 模型 (x,y),法线 +z)。
⛔ 画草图前 `sm.AddToDB = True`。
⛔ 判据:缝是矩形 ⇒ 面数(6 + 每条缝 4);螺丝孔是圆 ⇒ 圆柱面计数。两个都要看。
"""
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\\TopPanel_v4.SLDPRT'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
X0, X1 = -38.4, 136.4
Y0, Y1 = -4.055, 106.745 # 盖住南北板外表面
PANEL_T = 2.4
SLOT_W = 1.5
SLOT_L = 60.0
SLOT_N = 11
SLOT_SPAN = 106.0
SLOT_XC = (X0 + X1) / 2.0 # = 49.0,与旧盒子中心相同
SLOT_X0, SLOT_X1 = SLOT_XC - SLOT_SPAN / 2.0, SLOT_XC + SLOT_SPAN / 2.0
SLOT_YC = 51.345 # 刀片堆叠中心(旧版写 49,偏 2.345)
SCREW_D = 3.4
POST_CX_W, POST_CX_E = -31.0, 129.0
POST_CY_S, POST_CY_N = 2.345, 100.345 # 角柱截面 y −1.655~6.345 / 96.345~104.345 的中心
SCREW_HOLES = [(POST_CX_W, POST_CY_S), (POST_CX_W, POST_CY_N),
(POST_CX_E, POST_CY_S), (POST_CX_E, POST_CY_N)]
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')
for d in list(app.GetDocuments or []):
try:
t = prop(d, 'GetTitle') or ''
if 'TOPPANEL_V4' in t.upper():
app.CloseDoc(t)
P(u'关掉旧文档 %s' % 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 sk_name():
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 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)]
step = (SLOT_X1 - SLOT_X0) / (SLOT_N - 1)
slots = [(SLOT_X0 + i * step - SLOT_W / 2.0, SLOT_X0 + i * step + SLOT_W / 2.0)
for i in range(SLOT_N)]
P(u'\n[1] 顶盖 %.1f(x) × %.1f(y) × %.1f,%d 条 %.1f×%.0f 泄压缝 + 4 个 Ø%.1f 螺丝孔'
% (X1 - X0, Y1 - Y0, PANEL_T, SLOT_N, SLOT_W, SLOT_L, SCREW_D))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
sm.AddToDB = True
sm.CreateCornerRectangle(M(X0), M(Y0), 0.0, M(X1), M(Y1), 0.0)
for sx0, sx1 in slots:
sm.CreateCornerRectangle(M(sx0), M(SLOT_YC - SLOT_L / 2), 0.0,
M(sx1), M(SLOT_YC + SLOT_L / 2), 0.0)
for sx, sy in SCREW_HOLES:
sm.CreateCircleByRadius(M(sx), M(sy), 0.0, M(SCREW_D / 2))
sm.AddToDB = False
_segs = prop(D(prop(sm, 'ActiveSketch')), 'GetSketchSegments')
want_segs = 4 * (1 + SLOT_N) + len(SCREW_HOLES)
P(u' 草图 segment = %d(期望 %d)' % (len(_segs) if _segs else 0, want_segs))
for _s in (_segs or []):
_ss = D(_s)
if prop(_ss, 'GetType') != 1:
continue
_c = D(prop(_ss, 'GetCenterPoint2'))
P(u' 圆 r=%.3f 圆心=(%.3f, %.3f)'
% (prop(_ss, 'GetRadius') * 1000, prop(_c, 'X') * 1000, prop(_c, 'Y') * 1000))
sm.InsertSketch(True)
if pick(sk_name(), 'SKETCH') != 1:
P(u'⛔ 草图没选中'); sys.exit(2)
feat = fm.FeatureExtrusion3(True, False, False, 0, 0, M(PANEL_T), 0.0,
False, False, False, False, 0.0, 0.0,
False, False, False, False, True, True, True, 0, 0.0, False)
P(u' 拉伸 → %r' % (prop(feat, 'Name') if feat else feat))
b = bbox()
P(u' box %s(期望 x %.1f~%.1f / y %.3f~%.3f / z 0~%.1f)' % (b, X0, X1, Y0, Y1, PANEL_T))
if not b:
P(u'⛔ 没出实体,停手'); sys.exit(3)
bd = D(bodies()[0])
nf = len(prop(bd, 'GetFaces'))
want_faces = 6 + 4 * SLOT_N
P(u' 面数 = %d(期望 ≥ %d = 6 + 每条缝 4)%s'
% (nf, want_faces, u'✅' if nf >= want_faces else u'⛔ 缝没成形'))
hist = {}
for f in prop(bd, 'GetFaces'):
s = prop(D(f), 'GetSurface')
if prop(s, 'IsCylinder'):
r = round(list(prop(s, 'CylinderParams'))[6] * 1000, 2)
hist[r] = hist.get(r, 0) + 1
P(u' 圆柱面直方图 %s' % dict(sorted(hist.items())))
n34 = hist.get(round(SCREW_D / 2, 2), 0)
P(u' Ø%.1f 螺丝孔 = %d 个(期望 4)%s' % (SCREW_D, n34, u'✅' if n34 >= 4 else u'⛔ 没打全'))
if nf < want_faces or n34 < 4:
P(u'⛔ 停手'); sys.exit(4)
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))
if not os.path.exists(DST):
P(u'⛔ 没落盘,停手'); sys.exit(5)
P(u'⚠ 零件保持打开')
+372
查看文件
@@ -0,0 +1,372 @@
# -*- coding: utf-8 -*-
u"""**一体顶盖** 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_v1.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_V1' 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)
# ── 面上草图切除(带候选映射)────────────────────────────────────────────
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))])
# ── 验收 ──────────────────────────────────────────────────────────────────
P(u'\n[7] 验收')
check(u'最终', [X0, Y0, -H, X1, Y1, 0.0])
h = hist()
P(u' 圆柱面直方图 %s(期望 {1.7: 8, 2.1: 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))
+721
查看文件
@@ -0,0 +1,721 @@
# -*- 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))
+765
查看文件
@@ -0,0 +1,765 @@
# -*- coding: utf-8 -*-
# v11:南墙 Type-C 外侧沉槽 13.4×7.4 R3.2、槽底留 1.0 薄壁(小孔仍 = 母座口),标识右移到 10.5;其余同 v10
# 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_v11.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_V11' 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)
# ── [5a] v11:Type-C 外侧沉槽 —— 护套进墙、槽底留 PK_LEFT 薄壁;贯穿的仍是上面那个母座口小孔 ──
WALL_T = IY0 - Y0 # 南墙厚 2.4
PK_W, PK_H, PK_R, PK_LEFT = 13.40, 7.40, 3.20, 1.00
PK_D = WALL_T - PK_LEFT
A_PORT = USB_W * USB_H - (4 - math.pi) * USB_R ** 2
A_PK = PK_W * PK_H - (4 - math.pi) * PK_R ** 2
P(u'\n[5a] 南墙 Type-C 沉槽 ×2:%.2f × %.2f、R%.2f,深 %.2f(墙 %.2f,留 %.2f)' % (PK_W, PK_H, PK_R, PK_D, WALL_T, PK_LEFT))
_port = (USB_W, USB_H, USB_R)
USB_W, USB_H, USB_R = PK_W, PK_H, PK_R # usb_shapes 读全局;画完就还原
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])
nseg += bool(sm.CreateArc(qc[0], qc[1], 0.0, q1[0], q1[1], 0.0, q2[0], q2[1], 0.0, 1 if cross > 0 else -1))
sm.AddToDB = False
sm.InsertSketch(True)
USB_W, USB_H, USB_R = _port
if nseg != 16 or pick(last_sketch(), 'SKETCH') != 1:
P(u' ⛔ 沉槽草图不对(段 %d)' % nseg); sys.exit(5)
c = fm.FeatureCut4(True, False, False, 0, 0, M(PK_D), 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 + PK_D)) < 0.02 and abs(fb[4] - (Y0 + PK_D)) < 0.02)
want = 2 * (A_PK - A_PORT)
ok = abs(floor - want) < 0.05
P(u' 沉槽 切除 → %-12r 面数 %d→%d 槽底薄壁面积 %.3f(理论 2×(%.3f−%.3f) = %.3f)%s'
% (prop(c, 'Name') if c else c, nbefore, len(faces()), floor, A_PK, A_PORT, want, u'✅' 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, 10.5 # v11:随沉槽右移
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, 3.2: 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))
+437
查看文件
@@ -0,0 +1,437 @@
# -*- 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))
+441
查看文件
@@ -0,0 +1,441 @@
# -*- 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))
+510
查看文件
@@ -0,0 +1,510 @@
# -*- coding: utf-8 -*-
# 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 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_v4.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_V4' 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))
# ── [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)
# ── [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(期望 {0.7: 8, 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 or h.get(0.7, 0) != 8:
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))
+676
查看文件
@@ -0,0 +1,676 @@
# -*- coding: utf-8 -*-
# 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_v5.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.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_V5' 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])
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, 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)))
cut_shapes(u'西端墙', (0, -1, X0), [((X0, cy, L(cz)), r) for cy, cz, r in END_CIRCLES], [], end_verify(X0))
# ── B:东端墙 Ø60 圆孔 → 竖向圆头槽格栅(西端被滤网盖住,保持圆孔)─────────────────
GR_R, GR_W, GR_RIB = 32.0, 4.5, 1.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 east_face_area():
return sum(a_ for ax_, sg, fb, a_, _ in planes()
if ax_ == 0 and sg == 1 and abs(fb[0] - X1) < 0.02 and abs(fb[3] - X1) < 0.02)
GRS = grille_slots()
A_SLOTS = sum(GR_W * (L_ - GR_W) + math.pi * (GR_W / 2) ** 2 for _, L_ in GRS)
A_HOLES = 4 * math.pi * R_FAN ** 2
P(u' 东端格栅:%d 条槽 宽 %.1f 筋 %.1f 外包 R%.0f,槽面积 %.1f(原 Ø60 = %.1f,%.1f%%)'
% (len(GRS), GR_W, GR_RIB, GR_R, A_SLOTS, math.pi * 900, A_SLOTS / (math.pi * 900) * 100))
a_before = east_face_area()
nbefore = len(faces())
X = open_face_sketch((0, +1, X1), u'东端墙')
sm.AddToDB = True
for cy, cz, r in END_CIRCLES[1:]:
q = to_sketch(X, (X1, cy, L(cz)))
sm.CreateCircleByRadius(q[0], q[1], 0.0, M(r))
zc = L(FAN_CZ)
for ys, L_ in GRS:
r, a = GR_W / 2, L_ / 2 - GR_W / 2
for p0, p1 in (((ys + r, zc - a), (ys + r, zc + a)), ((ys - r, zc + a), (ys - r, zc - a))):
q0, q1 = to_sketch(X, (X1, p0[0], p0[1])), to_sketch(X, (X1, p1[0], p1[1]))
sm.CreateLine(q0[0], q0[1], 0.0, q1[0], q1[1], 0.0)
for cz_, sg in ((zc + a, 1), (zc - a, -1)):
qc = to_sketch(X, (X1, ys, cz_))
q1 = to_sketch(X, (X1, ys + r, cz_))
q2 = to_sketch(X, (X1, ys - r, cz_))
qm = to_sketch(X, (X1, ys, cz_ + sg * 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))
sm.AddToDB = False
sm.InsertSketch(True)
c = do_cut(T + 0.2)
a_after = east_face_area()
d_area = a_before - a_after
ok_area = abs(d_area - (A_SLOTS + A_HOLES)) < 0.5
ends = sum(1 for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0)
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] - X1), abs(fb[3] - X1)) < 0.05
for rr, ax_, fb in cyls()) for cy, cz, _ in END_CIRCLES[1:])
P(u' 东端墙 切除 → %-12r 面数 %d→%d' % (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' 槽端半圆柱 R%.2f = %d(期望 %d)%s;风扇孔 4 个在位 %s'
% (GR_W / 2, ends, 2 * len(GRS), u'✅' if ends == 2 * len(GRS) else u'⛔', u'✅' if fan_ok else u'⛔'))
if not (ok_area and ends == 2 * len(GRS) and fan_ok):
P(u' ⛔⛔ 格栅不对,停手、不存盘'); sys.exit(5)
# ── [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)
# ── [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()
WANT_H = {0.7: 8, 0.75: 2 * SLOT_N, 0.8: 1, 1.5: 4, 2.1: 4, round(GR_W / 2, 2): 2 * len(GRS), 2.6: 8, 3.0: 4, 30.0: 1}
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))
+684
查看文件
@@ -0,0 +1,684 @@
# -*- coding: utf-8 -*-
# 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_v6.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.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_V6' 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])
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, 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:两端墙 Ø60 圆孔 → 竖向圆头槽格栅(2026-09-13 用户:两侧都改格栅)──────────────
GR_R, GR_W, GR_RIB = 32.0, 4.5, 1.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
GRS = grille_slots()
A_SLOTS = sum(GR_W * (L_ - GR_W) + math.pi * (GR_W / 2) ** 2 for _, L_ in GRS)
A_HOLES = 4 * math.pi * R_FAN ** 2
P(u' 格栅:%d 条槽 宽 %.1f 筋 %.1f 外包 R%.0f,槽面积 %.1f(原 Ø60 = %.1f,%.1f%%)'
% (len(GRS), GR_W, GR_RIB, GR_R, A_SLOTS, math.pi * 900, A_SLOTS / (math.pi * 900) * 100))
def grille_cut(tag, xw, sg):
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)
ends0 = sum(1 for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0)
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)
for ys, L_ in GRS:
r, a = GR_W / 2, L_ / 2 - GR_W / 2
for p0, p1 in (((ys + r, zc - a), (ys + r, zc + a)), ((ys - r, zc + a), (ys - r, zc - a))):
q0, q1 = to_sketch(X, (xw, p0[0], p0[1])), to_sketch(X, (xw, p1[0], p1[1]))
sm.CreateLine(q0[0], q0[1], 0.0, q1[0], q1[1], 0.0)
for cz_, s_ in ((zc + a, 1), (zc - a, -1)):
qc = to_sketch(X, (xw, ys, cz_))
q1 = to_sketch(X, (xw, ys + r, cz_))
q2 = to_sketch(X, (xw, ys - r, cz_))
qm = to_sketch(X, (xw, ys, cz_ + 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))
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
ends = sum(1 for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0) - ends0
caps_ok = all(abs((fb[4] - fb[1]) - GR_W) < 0.02 and abs((fb[5] - fb[2]) - GR_W / 2) < 0.02
for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0)
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),全部「y 跨槽宽、z 跨半径」= 竖槽 %s;风扇孔 4 个在位 %s'
% (ends, 2 * len(GRS), u'✅' if caps_ok else u'⛔', u'✅' if fan_ok else u'⛔'))
if not (ok_area and ends == 2 * len(GRS) and caps_ok 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)
# ── [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()
WANT_H = {0.7: 8, 0.75: 2 * SLOT_N, 0.8: 1, 1.5: 4, 2.1: 4, round(GR_W / 2, 2): 4 * len(GRS), 2.6: 8, 3.0: 4, 30.0: 0}
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))
+656
查看文件
@@ -0,0 +1,656 @@
# -*- coding: utf-8 -*-
# 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_v7.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.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_V7' 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])
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, 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:两端墙 Ø60 圆孔 → 竖向圆头槽格栅(2026-09-13 用户:两侧都改格栅)──────────────
GR_R, GR_W, GR_RIB = 32.0, 4.5, 1.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
GRS = grille_slots()
A_SLOTS = sum(GR_W * (L_ - GR_W) + math.pi * (GR_W / 2) ** 2 for _, L_ in GRS)
A_HOLES = 4 * math.pi * R_FAN ** 2
P(u' 格栅:%d 条槽 宽 %.1f 筋 %.1f 外包 R%.0f,槽面积 %.1f(原 Ø60 = %.1f,%.1f%%)'
% (len(GRS), GR_W, GR_RIB, GR_R, A_SLOTS, math.pi * 900, A_SLOTS / (math.pi * 900) * 100))
def grille_cut(tag, xw, sg):
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)
ends0 = sum(1 for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0)
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)
for ys, L_ in GRS:
r, a = GR_W / 2, L_ / 2 - GR_W / 2
for p0, p1 in (((ys + r, zc - a), (ys + r, zc + a)), ((ys - r, zc + a), (ys - r, zc - a))):
q0, q1 = to_sketch(X, (xw, p0[0], p0[1])), to_sketch(X, (xw, p1[0], p1[1]))
sm.CreateLine(q0[0], q0[1], 0.0, q1[0], q1[1], 0.0)
for cz_, s_ in ((zc + a, 1), (zc - a, -1)):
qc = to_sketch(X, (xw, ys, cz_))
q1 = to_sketch(X, (xw, ys + r, cz_))
q2 = to_sketch(X, (xw, ys - r, cz_))
qm = to_sketch(X, (xw, ys, cz_ + 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))
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
ends = sum(1 for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0) - ends0
caps_ok = all(abs((fb[4] - fb[1]) - GR_W) < 0.02 and abs((fb[5] - fb[2]) - GR_W / 2) < 0.02
for rr, ax_, fb in cyls() if abs(rr - GR_W / 2) < 0.01 and ax_ == 0)
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),全部「y 跨槽宽、z 跨半径」= 竖槽 %s;风扇孔 4 个在位 %s'
% (ends, 2 * len(GRS), u'✅' if caps_ok else u'⛔', u'✅' if fan_ok else u'⛔'))
if not (ok_area and ends == 2 * len(GRS) and caps_ok 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, 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, round(GR_W / 2, 2): 4 * len(GRS), 2.6: 8, 3.0: 4, 30.0: 0}
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))
+711
查看文件
@@ -0,0 +1,711 @@
# -*- coding: utf-8 -*-
# v8:格栅竖槽加 3 条横撑(最长悬空筋 64 → 16),其余同 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_v8.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.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_V8' 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])
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, 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, 4.5, 1.2
XB_Z, XB_W = (-16.0, 0.0, 16.0), 1.2 # 横撑中心(相对风扇中心 z)与宽度
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=该端是半圆槽头"""
r = GR_W / 2
h, a_ = L_ / 2, L_ / 2 - r
cuts = []
for c in XB_Z:
lo, hi = c - XB_W / 2, c + XB_W / 2
if hi <= -h or lo >= h:
continue # 横撑碰不到这条槽
if lo < -a_ or hi > a_:
raise SystemExit(u'⛔ 横撑 z=%.1f 切进了槽头半圆(槽半长 %.2f),调整 XB_Z' % (c, h))
cuts.append((lo, hi))
cuts.sort()
segs, z = [], -h
for lo, hi in cuts:
segs.append((z, lo, z == -h, False))
z = hi
segs.append((z, h, z == -h, True))
return segs, len(cuts)
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
for _ys, _L in GRS:
_segs, _nc = slot_segments(_L)
A_SLOTS += sum(seg_area(*sg) for sg in _segs)
N_FLAT += 2 * _nc
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)+ 3 条横撑 %.1f,开孔 %.1f mm²(原 Ø60 的 %.1f%%),最长槽段 %.1f'
% (len(GRS), GR_W, GR_RIB, GR_R, XB_W, A_SLOTS, A_SLOTS / (math.pi * 900) * 100, LONGEST))
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, 2 * len(GRS), u'✅' if caps == 2 * len(GRS) else u'⛔',
flats, N_FLAT, u'✅' if flats == N_FLAT else u'⛔', u'✅' if fan_ok else u'⛔'))
if not (ok_area and caps == 2 * len(GRS) 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, 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, round(GR_W / 2, 2): 4 * len(GRS), 2.6: 8, 3.0: 4, 30.0: 0}
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))
+718
查看文件
@@ -0,0 +1,718 @@
# -*- coding: utf-8 -*-
# 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_v9.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.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_V9' 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])
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, 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, 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))
+423
查看文件
@@ -0,0 +1,423 @@
# -*- coding: utf-8 -*-
u"""托盘 v2:重建 + 装进总装。
⛔ 为什么不用 heredoc 跑:**bash heredoc 会打碎脚本里的中文路径**。
上一轮实测报错里路径变成了 `D:\\DCDC\\u65b0建文件夹\x05blades\\...`,
随后 `SaveAs3` 返回 err=1、文件没落盘。⇒ 含中文路径的脚本一律用 Write 落成 .py 再跑。
⛔ 旧的 `Tray_base.SLDPRT` 是**第一版躺错轴**的错版(box `x −4~102 / y 0~2.4 / z −102~4`),
已弃用;本脚本换名 `Tray_v2.SLDPRT`,并在开头关掉所有旧的 Tray 文档。
零件本地坐标(与底板局部坐标同向,装配时整体平移 z −POST_H 即可对上):
z = 0 托盘顶面(支柱根部)
z = −2.4 托盘底面
z = +15.0 支柱顶面(= 装配后 PCB 底面)
"""
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
OUT = r'C:\Users\35138\.claude\jobs\11836093\tmp'
# v7:南边加两个 USB-C 缺口。实测托盘面板 z −2.40~0 与 USB-C 插入通道 z −3.25~0.85
# **重叠 2.40 mm**(两段 x 内各有 5 / 3 个面的板材)⇒ J1 与 J23 现在插不进去。
# v8:y 外扩到 −4.055~106.745,给南北板落脚(v7 时两板悬空、螺丝无处可打)。
# ⛔ 换名不覆盖:v7 正开在 SolidWorks 里(`~$Tray_v7` 锁文件),同名 SaveAs3 会 err=1。
# v9:托盘 y 改成与侧板**内表面**齐平(−1.655~104.345),让侧板从外侧包住托盘端面、
# 形成真正的面接触(v8 与外表面齐平 ⇒ 只是擦过,贴合面分析里一处接触都没有)。
# ⛔ 必须换名:v8 正开在 SolidWorks 里(锁文件在),同名 SaveAs3 已实测 err=1 且不写盘。
# v10:加 B 组螺丝凸台(每侧 3 个 Ø8×6,内嵌 Ø4.2 热熔螺母),
# 供南北板水平锁进托盘侧端面(贴合带 z −10~0,孔心取中线 z=−5.0)。
# v11:B 组凸台改用「遍历选端面 + 面上建草图」(v10 画在 y=0 平面,凸台位置全错,
# 只成了 3 个且悬在板外);B 组孔位同步挪到 x 30/49/68 避开 USB-C 开口。
DST = u'D:\\DCDC\\新建文件夹\\5blades\\Tray_v11.SLDPRT'
ASM = 'PhoneRail_5blades'
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
BW = BH = 98.0
# ⛔ 托盘原来 y −4~102(按 PCB + 4 外扩)。端板改 y 对称(−1.655~104.345)、南北板跟着
# 贴到端板外侧之后,南板落在 y −4.055~−1.66、北板 104.345~106.745 —— **全在托盘之外**,
# 贴合面分析里 Tray↔SouthPanel / Tray↔NorthPanel **一处接触都没有** ⇒ 两块板悬空、
# 螺丝无处可打。⚠ 干涉检查当时是 360 基线"零冲突",因为不接触本来就不干涉 ——
# **干涉为零不等于装配成立**。
# ✅ 托盘 y 外扩到与南北板外表面齐平:−4.055 ~ 106.745。
# ⛔ v8 把托盘 y 外扩到 −4.055~106.745(与侧板**外表面**齐平)——**没解决问题**:
# 贴合面分析里 Tray↔South/North 依旧一处接触都没有。因为侧板内表面在
# y=−1.655 / 104.345,托盘边缘却跑到了它们**外面**,两者只是侧面擦过、并不面接触。
# ✅ v9 改成与侧板**内表面**齐平:托盘 y = −1.655 ~ 104.345。
# 这样侧板从外侧正好包住托盘端面,形成真正的面接触,螺丝可从侧板水平拧进托盘侧棱。
TRAY_Y0, TRAY_Y1 = -1.655, 104.345
# ⛔ POST_H 由 15.0 降到 10.0(2026-09-13 用户要求「降低底部立柱高度」)。
# 15 的来历是 R2 决策 D15 那句「背面最高 12 mm(C2/C3 直立电解),底板下要 ≥15 mm 铜柱」——
# **这个前提已经失效**:C2/C3 早已挪到顶面(`pcb_base.TOP_EXTRA` = C2/C3/J26/J27/J33),
# 3D 实测底板背面最低只到 **−6.30**(SRP1265A 电感)。
# ⇒ 10.0 对背面件余量 **3.70**,同时给底板下的风扇线 / USB 线留走线空间。
# 改这一个常数即可(装配平移与断言都从它算,不许再写死)。
MARGIN, FLOOR_T, POST_H, POST_D = 4.0, 2.4, 10.0, 8.0
INSERT_D, INSERT_DEPTH = 4.2, 6.0
HOLES = [(4.0, 4.0), (94.0, 4.0), (4.0, 94.0), (94.0, 94.0)]
# ── 端板锁孔(2026-09-13 用户选 B:端板独立、螺丝锁到托盘)──────────────
# 端板在风扇外侧(西 x −27.4~−25 / 东 x 123~125.4),底部带 25 长的 L 脚伸回来,
# 在 x −4~0(西)与 98~102(东)这两段压在托盘面板上 ⇒ 锁孔打在这两段里。
# y 取 51.345 ± 30 = 21.345 / 81.345(风扇螺孔投影在 26.345/76.345,错开 5)。
# ⛔ 第一版孔心放在 x=−2.0 / 100.0:孔半径 2.1 ⇒ 圆占 x −4.1~0.1,**越过面板边界 −4**,
# 轮廓自相交 ⇒ `FeatureExtrusion3` 返回 None、整个零件建不出来(回读 box 为 None)。
# ⇒ 内移到 x=1.0 / 97.0:离边 5.0,孔外侧还剩 2.9 壁厚。端板 L 脚相应加长到 28。
# ⛔⛔ x=1.0 / 97.0 也是错的:实测底板 PCB 占 x **0.01~97.99**,这两排孔正好**落在 PCB 底下**,
# 螺丝拧不进去。托盘露在 PCB 之外的环只有 **4.01 mm**(西 −4.00~0.01 / 东 97.99~102.00)。
# ⇒ 锁孔退到环中线 **x = −2.0 / 100.0**。
# (最早那版也用过 −2.0 并"破边"失败,但那是草图自动捕捉把 r=2.1 吸成 3.1 所致;
# 现在 `AddToDB=True` 已根治,−2.0 离边 2.0、孔半径 2.1 —— 仍会破边!
# ⇒ 取环中线偏内一点:**x = −1.9 / 99.9**,离托盘边 2.1 正好等于孔半径,
# 再往内就压到 PCB。孔壁与托盘边**相切**,所以把孔径改小到 Ø3.4(M3 过孔,
# 螺母改用底面沉入的 M3 螺母或自攻螺丝),孔半径 1.7 ⇒ 离边 2.3、离 PCB 1.7,两侧都有料。
END_HOLES = [(-1.9, 21.345), (-1.9, 81.345), (99.9, 21.345), (99.9, 81.345)]
# ⛔ 由 Ø4.2 热熔螺母孔改成 **Ø3.4 过孔**:环只有 4.01 宽,Ø4.2 的孔(半径 2.1)
# 放在环中线上会与托盘外边相切/破边。Ø3.4 的孔离边 2.3、离 PCB 1.7,两侧都有料。
# ⇒ 端板用 M3 螺丝穿过托盘,从**底面**上螺母(或用自攻螺丝)。
END_INSERT_D = 3.4
END_INSERT_DEPTH = 2.4 # 打穿 2.4 厚的面板
# ⛔ y 不再用 ±MARGIN:端板改 y 对称后南北板贴到端板外侧(−4.055 / 106.745),
# 托盘若仍是 −4~102,两块板整个落在托盘之外 ⇒ 悬空、螺丝无处可打
# (贴合面分析里 Tray↔South/North 一处接触都没有,而干涉却是"零冲突")。
# ✅ y 外扩到与南北板外表面齐平。x 方向不动(端板 L 脚已在 x 上锁住托盘)。
X0, X1, Y0, Y1 = -MARGIN, BW + MARGIN, TRAY_Y0, TRAY_Y1
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')
P(u'目标目录存在=%s' % os.path.isdir(os.path.dirname(DST)))
for d in list(app.GetDocuments):
try:
t = prop(d, 'GetTitle') or ''
if 'TRAY' in t.upper():
app.CloseDoc(t)
P(u'关掉旧文档 %s' % 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 sk_name():
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 extrude(depth, rev=False):
if pick(sk_name(), '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 bodies():
return part.GetBodies2(0, True) or []
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 check(tag, want, tol=0.02):
got = bbox()
ok = got and all(abs(got[i] - want[i]) <= tol for i in range(6))
P(u' %s box %s 期望 %s %s' % (tag, got, want, u'✅' if ok else u'⛔'))
return ok
P(u'\n[1] 托盘底板 %.0f×%.0f×%.1f + 4 个端板锁孔(同一张草图,一次拉伸成形)'
% (X1 - X0, Y1 - Y0, FLOOR_T))
# ⛔ 端板锁孔不再用 FeatureCut4:托盘面板 z=0 那一面的法线是 (0,0,−1)(建模时从 z=0 往 −z 拉),
# z=0 处没有朝上的自由面,按坐标选面 / 前视基准面草图、rev 两个方向**全部返回 None**
# (用「Ø4.2 圆柱面计数」这个有分辨力的判据复验过:始终只有 4 个 = 只有柱顶孔)。
# ✅ 改成把 4 个圆和外框矩形放进**同一张草图**,拉伸时圆自然成为通孔,不需要切除特征。
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
# ⛔⛔ 必须开 AddToDB:否则 SolidWorks 的草图**自动捕捉会静默改掉几何** ——
# 实测 (1.0, 21.345) r=2.1 被吸成 **(0.0, 21.345) r=3.1**(圆心挪到原点纵轴、半径 +1.0),
# 于是锁孔要么尺寸不对、要么越界让整个轮廓失效。AddToDB=True 时几何按精确坐标写库、
# 不做任何推理,四个圆回读全部精确。
sm.AddToDB = True
# 外框改成带两个南侧缺口的折线(缺口从南边 Y0 往里挖到 y=0.20,正好让开 USB-C 插入通道)
# 缺口 x 区间实测:J1 6.53~18.47、J23 78.03~89.97(各含每侧 1.5 装配余量)
NOTCH_Y = 0.20
_pts = [(X0, Y0), (6.53, Y0), (6.53, NOTCH_Y), (18.47, NOTCH_Y), (18.47, Y0),
(78.03, Y0), (78.03, NOTCH_Y), (89.97, NOTCH_Y), (89.97, Y0),
(X1, Y0), (X1, Y1), (X0, Y1), (X0, Y0)]
for _i in range(len(_pts) - 1):
_a, _b = _pts[_i], _pts[_i + 1]
sm.CreateLine(M(_a[0]), M(_a[1]), 0.0, M(_b[0]), M(_b[1]), 0.0)
for hx, hy in END_HOLES:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(END_INSERT_D / 2))
sm.AddToDB = False
_sk = prop(sm, 'ActiveSketch')
_segs = prop(D(_sk), 'GetSketchSegments') if _sk else None
P(u' 草图 segment = %d(期望 4 线 + 4 圆 = 8)' % (len(_segs) if _segs else 0))
sm.InsertSketch(True)
extrude(FLOOR_T, rev=True)
if not check(u'底板', [X0, Y0, -FLOOR_T, X1, Y1, 0.0]):
sys.exit(2)
P(u'\n[2] 四支柱 Ø%.0f 高 %.0f' % (POST_D, POST_H))
pick(u'前视基准面', 'PLANE')
sm.InsertSketch(True)
for hx, hy in HOLES:
sm.CreateCircleByRadius(M(hx), M(hy), 0.0, M(POST_D / 2))
sm.InsertSketch(True)
extrude(POST_H, rev=False)
if not check(u'底板+支柱', [X0, Y0, -FLOOR_T, X1, Y1, POST_H]):
sys.exit(3)
P(u'\n[3] 柱顶热熔螺母孔 Ø%.1f 深 %.1f(FeatureCut4 = 27 参)' % (INSERT_D, INSERT_DEPTH))
part.ClearSelection2(True)
ok = part.Extension.SelectByID2('', 'FACE', M(HOLES[0][0]), M(HOLES[0][1]), M(POST_H),
False, 0, EMPTY, 0)
n = part.SelectionManager.GetSelectedObjectCount2(-1)
P(u' 按坐标选柱顶面 返回=%s 选中数=%s' % (ok, n))
if n == 1:
sm.InsertSketch(True)
for hx, hy in HOLES:
sm.CreateCircleByRadius(M(hx), M(hy), M(POST_H), M(INSERT_D / 2))
sm.InsertSketch(True)
pick(sk_name(), 'SKETCH')
try:
cut = fm.FeatureCut4(True, False, False, 0, 0, M(INSERT_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)
P(u' FeatureCut4 → %r' % (prop(cut, 'Name') if cut else cut))
except Exception as ex:
P(u' ⛔ FeatureCut4 失败: %s' % str(ex)[:110])
check(u'切孔后', [X0, Y0, -FLOOR_T, X1, Y1, POST_H])
else:
P(u' ⚠ 选不到柱顶面,跳过孔')
# ── [4] 已作废:端板锁孔改在第 1 步的草图里一次成形(见那里的说明)────────
# 这里原本用 FeatureCut4 切孔,两个方向都恒返回 None,已删除,免得重复打孔或污染日志。
# ── [3b] B 组螺丝凸台:托盘两侧端面各 3 个 Ø8×6,供南北板水平锁进来 ──────
# 2.4 的面板边攻不住 M3 ⇒ 加凸台内嵌 Ø4.2 热熔螺母。孔心在贴合带中线 z=−5.0。
BOSS_D, BOSS_H = 8.0, 6.0
# ⛔ B 组孔位与南板同步改成两个 USB-C 开口之间的实心区(原 11/49/87 会撞开口)
B_X = (30.0, 49.0, 68.0)
B_Z = -5.0
P(u'\n[3b] B 组凸台:两侧端面各 3 个 Ø%.0f×%.0f(孔心 z=%.1f)' % (BOSS_D, BOSS_H, B_Z))
# ⛔ 上一版画在**上视基准面**:草图在 y=0 平面,拉伸从 y=0 起算而不是从端面起算 ⇒
# 南侧凸台长到 y −6~0(悬在板外)、北侧 3 个整个埋进板体,只成了 3 个(R4.0 从 4→7)。
# `T0/StartOffset` 本会话已实测静默失效,不能用。
# ✅ 改走「遍历选端面 → 面上建草图 → 拉伸」——托盘柱顶孔已验证这条路可行,
# 面上草图接受**模型坐标**(那次传的就是 (x, y, z=柱顶高))。
for tag, ynom, rev in ((u'南侧', Y0, True), (u'北侧', Y1, False)):
# 遍历找法线沿 ±y、且位置等于该端面的平面
tgt, tarea = None, -1.0
for _f in prop(D(bodies()[0]), 'GetFaces'):
_fc = D(_f)
_s = prop(_fc, 'GetSurface')
if not prop(_s, 'IsPlane'):
continue
_n = list(prop(_s, 'PlaneParams'))
if abs(abs(_n[1]) - 1.0) > 0.01: # 法线必须沿 y
continue
_fb = [round(v * 1000, 3) for v in prop(_fc, 'GetBox')]
if abs(_fb[1] - ynom) > 0.01 or abs(_fb[4] - ynom) > 0.01:
continue
_a = prop(_fc, 'GetArea') * 1e6
if _a > tarea:
tgt, tarea = _fc, _a
if tgt is None:
P(u' ⛔ %s 没找到 y=%.3f 的端面' % (tag, ynom))
continue
part.ClearSelection2(True)
tgt.Select4(False, EMPTY)
if part.SelectionManager.GetSelectedObjectCount2(-1) != 1:
P(u' ⛔ %s 端面没选中' % tag)
continue
sm.InsertSketch(True)
sm.AddToDB = True
# ⛔ 面上草图**不接受模型坐标**(我误以为接受,托盘柱顶孔那次是巧合:
# 那是 z 常量面,草图 xy 恰好与模型 xy 一致)。
# y 端面实测映射:草图 u → 模型 x、草图 v → 模型 **−z**、法线 → +y。
# 传 (bx, ynom, B_Z) 会把 ynom 当成 v ⇒ 凸台被甩到 z=−104,还吞掉已有特征。
# ✅ 正确写法:第三参恒 0,v 传 −z。
for bx in B_X:
sm.CreateCircleByRadius(M(bx), M(-B_Z), 0.0, M(BOSS_D / 2))
sm.AddToDB = False
sm.InsertSketch(True)
if pick(sk_name(), 'SKETCH') != 1:
P(u' ⛔ %s 草图没选中' % tag)
continue
f = fm.FeatureExtrusion3(True, False, rev, 0, 0, M(BOSS_H), 0.0,
False, False, False, False, 0.0, 0.0,
False, False, False, False, True, True, True,
0, 0.0, False)
P(u' %s 端面(面积 %.0f mm²) 凸台 → %r' % (tag, tarea, prop(f, 'Name') if f else f))
P(u' 加凸台后 box %s' % bbox())
P(u' 圆柱面直方图 %s(R4.0 应为 4 支柱 + 6 凸台 = 10)'
% {r: n for r, n in sorted({}.items())} if False else u'')
P(u'\n[4] 复验孔(两种孔径分开数 —— 上一版把两者混成一个 8 的期望值,误报了)')
# ⛔ 柱顶热熔螺母孔是 **Ø4.2**,端板锁孔是 **Ø3.4**,半径不同不能合并计数。
# 上一版写「Ø3.4 应有 8 个 = 4 柱顶 + 4 端板」⇒ 柱顶那 4 个根本不是 3.4,
# 于是永远只数到 4,把已经成形的锁孔误判成"没成形"并提前退出。
_bd = D(bodies()[0])
_hist = {}
for _f in prop(_bd, 'GetFaces'):
_s = prop(D(_f), 'GetSurface')
if prop(_s, 'IsCylinder'):
_r = round(list(prop(_s, 'CylinderParams'))[6] * 1000, 2)
_hist[_r] = _hist.get(_r, 0) + 1
P(u' 圆柱面直方图 = %s' % dict(sorted(_hist.items())))
_n_end = _hist.get(round(END_INSERT_D / 2, 2), 0) # Ø3.4 ⇒ R1.7:端板锁孔
_n_post = _hist.get(round(INSERT_D / 2, 2), 0) # Ø4.2 ⇒ R2.1:柱顶螺母孔
P(u' 端板锁孔 Ø%.1f = %d 个(期望 4)%s' % (END_INSERT_D, _n_end, u'✅' if _n_end >= 4 else u'⛔'))
P(u' 柱顶孔 Ø%.1f = %d 个(期望 4)%s' % (INSERT_D, _n_post, u'✅' if _n_post >= 4 else u'⛔'))
if _n_end < 4 or _n_post < 4:
P(u'⛔ 停手:包围盒断言对通孔没有分辨力,必须靠这个计数判'); sys.exit(4)
e1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
r = part.Extension.SaveAs3(DST, 0, 1, EMPTY, EMPTY, e1, w1)
# ⚠ NOTES §11 的固定动作:不信返回值,看文件真的在不在
P(u'\n另存 返回=%s err=%s 存在=%s %.0f KB'
% (r, e1.value, os.path.exists(DST), os.path.getsize(DST) / 1024.0 if os.path.exists(DST) else 0))
if not os.path.exists(DST):
P(u'⛔ 文件没落盘,停手(零件留着便于排查)')
sys.exit(5)
# ⛔ 不要在插入前 CloseDoc:本会话三次成功的 AddComponent4(风扇占位体 / OD6025 / STEP 临时件)
# 共同点都是**被插入的文档当时还开着**;唯一一次关掉零件再插,就恒返回 None
# (文件明明存在、86 KB)。⇒ 保持零件打开。
P(u'零件保持打开(AddComponent4 疑似要求目标文档在打开状态)')
# ── 4) 插进总装 ───────────────────────────────────────────────────────
doc = None
for d in app.GetDocuments:
if ASM in (prop(d, 'GetTitle') or ''):
doc = d
break
if doc is None:
P(u'⛔ 没找到总装'); sys.exit(6)
e2 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
app.ActivateDoc3(prop(doc, 'GetTitle'), False, 0, e2)
doc = app.ActiveDoc
for c in list(doc.GetComponents(True)):
if 'TRAY' in prop(c, 'Name2').upper():
doc.ClearSelection2(True)
doc.Extension.SelectByID2(prop(c, 'Name2'), 'COMPONENT', 0, 0, 0, False, 0, EMPTY, 0)
doc.Extension.DeleteSelection2(0)
P(u'删掉旧托盘零部件')
doc.EditRebuild3
c = doc.AddComponent4(DST, '', 0.0, 0.0, 0.0)
P(u'AddComponent4 → %s' % (prop(c, 'Name2') if c else u'⛔ None'))
if c is None:
sys.exit(7)
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
xf = wc.Dispatch(mu._oleobj_.Invoke(d2, 0, pythoncom.DISPATCH_METHOD, 1,
wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
[1.0, 0, 0, 0, 1.0, 0, 0, 0, 1.0,
0.0, 0.0, -M(POST_H), 1.0, 0.0, 0.0, 0.0])))
try:
c.SetTransformAndSolve2(xf)
except Exception:
c.Transform2 = xf
doc.EditRebuild3
b = [round(x * 1000, 3) for x in c.GetBox(False, False)]
want = [X0, Y0, -(POST_H + FLOOR_T), X1, Y1, 0.0]
P(u'托盘装配 box %s 期望 %s %s'
% (b, want, u'✅' if all(abs(b[i] - want[i]) < 0.05 for i in range(6)) else u'⛔'))
for nm2, vid in ((u'tray_iso', 7), (u'tray_front', 1), (u'tray_right', 4)):
try:
doc.ShowNamedView2('', vid)
doc.ViewZoomtofit2
doc.SaveBMP(os.path.join(OUT, '%s.bmp' % nm2), 1600, 1100)
except Exception:
pass
e3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
w3 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
doc.Extension.SaveAs3(prop(doc, 'GetPathName'), 0, 1, EMPTY, EMPTY, e3, w3)
P(u'截图+存盘 err=%s' % e3.value)
P(u'\n干涉(fast):')
try:
idm = doc.InterferenceDetectionManager
idm.TreatCoincidenceAsInterference = False
idm.IncludeMultibodyPartInterferences = False
idm.TreatSubAssembliesAsComponents = True
idm.IgnoreHiddenBodies = True
idm.UseTransform = False
try:
nn = idm.GetInterferenceCount
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferenceCount')
nn = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
P(u' 干涉 %s 处(基线 360)' % nn)
hits = []
if nn:
try:
items = idm.GetInterferences
except AttributeError:
dd = idm._oleobj_.GetIDsOfNames('GetInterferences')
items = idm._oleobj_.Invoke(dd, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
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('TRAY' in y.upper() for y in nms):
hits.append((o.Volume * 1e9, nms))
if hits:
P(u' ⚠ 托盘相关 %d 处:' % len(hits))
for vv, nms in sorted(hits, reverse=True)[:5]:
P(u' %.3f mm³ %s ↔ %s' % (vv, nms[0][:24], nms[1][:24]))
else:
P(u' ✅ 托盘与底板/刀片/风扇零干涉')
idm.Done()
except Exception as ex:
P(u' 干涉失败 %s' % str(ex)[:80])
try:
doc.InterferenceDetectionManager.Done()
except Exception:
pass