# -*- coding: utf-8 -*- u"""直接量 PCIE 插槽模型的**槽腔**尺寸(不靠碰撞反推)。 做法:枚举塑料体的所有平面面,挑法线沿 ±Z 的,按 z 分层列出 x/y 跨度与面积。 · 顶面 = z 最高的朝上平面 · 槽腔底 = z 明显低于顶面、x 跨度大、y 跨度窄 的朝上平面 ⇒ 一次读出**腔长**(对比舌头 36.0)与**腔宽**(对比 STEP 板厚 1.67 / 标称 1.6)。 ⛔ 全程不碰 IInterferenceDetectionMgr —— 重度用它之后会丢 ROT 注册(NOTES §24)。 """ import io, re, subprocess, sys, time sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') import pythoncom import win32com.client as wc BASE_STEP = r'D:\DCDC\新建文件夹\3D_BASE-PCB_2026-09-12.step' def prop(o, name): u"""取 0 参成员。动态派发下多数是属性,但有例外(§17 `GetMathUtility`、 §25 `GetFaces`)——取回来若是个 **python 方法对象**就再调一次。 ⛔ 不能用 `callable()` 判断:COM 对象一律 callable(§2),那样会把 子对象当函数调,报"找不到成员"。只认 python 侧的方法类型。""" v = getattr(o, name) if type(v).__name__ in ('method', 'function', 'builtin_function_or_method'): return v() return v def sw_pids(): out = subprocess.run(['tasklist', '/FI', 'IMAGENAME eq SLDWORKS.EXE', '/FO', 'CSV', '/NH'], capture_output=True, text=True, encoding='gbk', errors='ignore').stdout return sorted(re.findall(r'"SLDWORKS\.exe","(\d+)"', out, re.I)) app = None try: app = wc.GetActiveObject('SldWorks.Application') print(u'✅ 复用现有实例(文档数 %s)' % app.GetDocumentCount) except Exception as e: print(u'✗ 连不上 (%s) → 自愈' % str(e)[:50]) if sw_pids(): subprocess.run(['taskkill', '/F', '/IM', 'SLDWORKS.exe'], capture_output=True) time.sleep(4) for k in range(20): try: app = wc.Dispatch('SldWorks.Application'); app.Visible = True _ = app.RevisionNumber; break except Exception: if k == 19: raise time.sleep(5) if app.GetDocumentCount == 0: print(u'导入底板 STEP…') t0 = time.time() data = app.GetImportFileData(BASE_STEP) err = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0) app.LoadFile4(BASE_STEP, 'r', data, err) print(u' err=%s %.1fs 文档数 %s' % (err.value, time.time() - t0, app.GetDocumentCount)) part = None for d in app.GetDocuments: try: if d.GetType == 1 and 'PCIE' in d.GetTitle.upper() and '64P11L' in d.GetTitle.upper(): part = d; break except Exception: pass if part is None: print(u'⛔ 没找到插槽零件'); sys.exit(1) print(u'\n零件: %s' % part.GetTitle) bb = [v * 1000 for v in part.GetPartBox(True)] print(u'包围盒 x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f' % (bb[0], bb[3], bb[1], bb[4], bb[2], bb[5])) bodies = part.GetBodies2(0, True) print(u'实体 %d 个' % len(bodies)) rows = [] for bi, bd in enumerate(bodies): body = bd if hasattr(bd, '_oleobj_') else wc.Dispatch(bd) try: faces = prop(body, 'GetFaces') except Exception as e: print(u' 实体%d GetFaces 失败 %s' % (bi, str(e)[:70])); continue try: vol = body.GetMassProperties(1.0)[3] * 1e9 except Exception: vol = float('nan') try: bx = [v * 1000 for v in prop(body, 'GetBodyBox')] bxs = u'x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f' % (bx[0], bx[3], bx[1], bx[4], bx[2], bx[5]) except Exception: bxs = u'?' print(u' 实体%d: %d 个面, 体积 %.1f mm^3, %s' % (bi, len(faces), vol, bxs)) for f in faces: face = f if hasattr(f, '_oleobj_') else wc.Dispatch(f) try: surf = prop(face, 'GetSurface') if not prop(surf, 'IsPlane'): continue p = list(prop(surf, 'PlaneParams')) nz = p[2] if abs(nz) < 0.99: continue fb = [v * 1000 for v in prop(face, 'GetBox')] rows.append((round(fb[5], 3), nz, prop(face, 'GetArea') * 1e6, fb[0], fb[3], fb[1], fb[4], bi)) except Exception: continue print(u'\n=== 法线沿 ±Z 的平面(z 从高到低)===') print(u' z 朝向 面积mm^2 x 跨度 y 跨度') rows.sort(reverse=True) for z, nz, area, x0, x1, y0, y1, bi in rows[:30]: print(u' %7.3f %+5.1f %9.2f %7.2f~%7.2f(%5.2f) %6.2f~%6.2f(%5.2f) 体%d' % (z, nz, area, x0, x1, x1 - x0, y0, y1, y1 - y0, bi)) print(u' …共 %d 个朝 ±Z 的平面' % len(rows)) up = [r for r in rows if r[1] > 0] if not up: print(u'\n⛔ 一个朝上的平面都没有'); sys.exit() top = up[0] print(u'\n塑料体顶面 z=%.3f(面积 %.1f mm^2)' % (top[0], top[2])) # ⛔⛔ 下面这个"挑面积最大的候选面当腔底"的启发式**不可靠**,实测选出了 # x -4.11~17.54 这种既不对称也不居中的局部面,并据此打印出"舌头要收窄 14.35" # 的错误结论。真正的开口尺寸要用 `slot_opening_measure.py`(量顶面内环)。 # 这里保留只为看分层概况,**不要拿它的数当结论**。 print(u'\n⚠ 以下"腔底"是启发式猜的,已知会选错面 —— 只作参考,' u'开口尺寸以 slot_opening_measure.py 为准。') cav = [r for r in up if r[0] < top[0] - 0.5 and (r[4] - r[3]) > 10 and (r[6] - r[5]) < 4] if cav: c = max(cav, key=lambda r: r[2]) L, W, D = c[4] - c[3], c[6] - c[5], top[0] - c[0] print(u'\n🎯 槽腔底:z=%.3f' % c[0]) print(u' 腔深 = %.2f − %.2f = **%.2f mm** ← 这就是一直缺的 d' % (top[0], c[0], D)) print(u' 腔长 x %.2f~%.2f = **%.2f mm** 舌头 36.00 ⇒ %s' % (c[3], c[4], L, u'✅ 插得进(余量 %.2f)' % (L - 36.0) if L > 36.0 else u'⛔ **插不进,舌头要收窄 %.2f 以上**' % (36.0 - L))) print(u' 腔宽 y %.2f~%.2f = **%.2f mm** STEP 板厚 1.67 ⇒ %s' % (c[5], c[6], W, u'✅ 进得去' if W > 1.67 else u'⚠ 模型腔宽 < 1.67,这多半是**模型按 1.6 标称建的**,实物无碍')) else: print(u'\n⚠ 没有符合"腔底"特征的朝上平面(z 低于顶面、x 跨度 >10、y 跨度 <4)') print(u' ⇒ 腔底可能不是平面,或这个模型确实没建腔。')