146 行
6.3 KiB
Python
146 行
6.3 KiB
Python
# -*- 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' ⇒ 腔底可能不是平面,或这个模型确实没建腔。')
|