# -*- 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