R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -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~~\`,废掉总装引用)。
|
||||
@@ -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
|
||||
@@ -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'⚠ 零件保持打开')
|
||||
@@ -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
|
||||
@@ -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})
|
||||
@@ -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})
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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)))
|
||||
@@ -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)把贴合面加宽。')
|
||||
@@ -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 要求目标文档在打开状态)')
|
||||
@@ -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'⚠ 零件保持打开')
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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')
|
||||
@@ -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)
|
||||
@@ -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]))
|
||||
@@ -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')
|
||||
@@ -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))
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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'残留检查通过')
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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'⚠ 零件保持打开')
|
||||
@@ -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')
|
||||
@@ -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 已换成「读草图变换 + 先验平面」版')
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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))
|
||||
@@ -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]))
|
||||
@@ -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)
|
||||
@@ -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')
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)与本表无关,另计。')
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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]))
|
||||
@@ -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'⚠ 零件保持打开')
|
||||
@@ -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'⛔ 有项没过'))
|
||||
@@ -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'⚠ 零件保持打开')
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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
|
||||
在新工单中引用
屏蔽一个用户