R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -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))
|
||||
在新工单中引用
屏蔽一个用户