154 行
6.4 KiB
Python
154 行
6.4 KiB
Python
# -*- coding: utf-8 -*-
|
||
u"""从两份 STEP 全自动重建「底板 + 刀片」总装,供 place_and_probe.py 接手。
|
||
|
||
用法:
|
||
python rebuild_assembly.py # 全自动:起 SolidWorks → 导两份 STEP → 建总装 → 插两块板
|
||
|
||
为什么要有这个脚本:SolidWorks 的运行对象表注册一旦被撤销(见 NOTES §22),
|
||
客户端侧无解,只能重启。重启后 `%TEMP%\\swx<旧PID>\\` 里的临时件全没了,
|
||
原装配体的底板零部件会丢引用 ⇒ 与其修,不如按 NOTES §15 那条**已验证的链路**重建。
|
||
|
||
━━ 关键约定 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
||
· 被插的必须是**临时目录里的顶层 SLDASM**(`GetDocuments` 遍历着找),
|
||
不能另存(§11 那两个是废件),也不能猜文件名(真名是 `xxx**.step**.SLDASM`)。
|
||
· 用 `AddComponent4`(5 参),不是 `AddComponent5`(8 参恒返回 None,§15)。
|
||
· ⛔ `AddComponent4` 插进来的件会被**重心归零**(§14)⇒ 板坐标公式全失效。
|
||
对策:插完**强制把底板零部件的变换设成单位阵**,回读包围盒确认变回
|
||
`0.013~97.987`(= 原点回到板左下角),这样 place_and_probe 的公式可原样用。
|
||
· ⛔ `CreateTransform` 只能走 dispid + `VARIANT(VT_ARRAY|VT_R8)`;传 tuple 会**静默返回单位阵**。
|
||
"""
|
||
import io
|
||
import os
|
||
import sys
|
||
import time
|
||
|
||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||
|
||
import pythoncom
|
||
import win32com.client as wc
|
||
|
||
SRC = r'D:\DCDC\新建文件夹'
|
||
BASE_STEP = os.path.join(SRC, '3D_BASE-PCB_2026-09-12.step')
|
||
BLADE_STEP = os.path.join(SRC, 'PhoneRail_blade.step')
|
||
|
||
IDENT = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]
|
||
|
||
|
||
def mk_xform(app, arr16):
|
||
did = app._oleobj_.GetIDsOfNames('GetMathUtility')
|
||
mu = wc.Dispatch(app._oleobj_.Invoke(did, 0, pythoncom.DISPATCH_METHOD, 1))
|
||
d2 = mu._oleobj_.GetIDsOfNames('CreateTransform')
|
||
v = wc.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, list(arr16))
|
||
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), arr16)) > 1e-9:
|
||
raise RuntimeError(u'CreateTransform 回读不符(多半被静默降成单位阵)')
|
||
return xf
|
||
|
||
|
||
def launch(tries=20):
|
||
u"""Dispatch 在没有实例时会**起一个新的**——这里正是我们要的。"""
|
||
for k in range(tries):
|
||
try:
|
||
app = wc.Dispatch('SldWorks.Application')
|
||
app.Visible = True
|
||
_ = app.RevisionNumber
|
||
return app
|
||
except Exception as e:
|
||
if k == tries - 1:
|
||
raise
|
||
print(u' 启动重试 %d/%d (%s)' % (k + 1, tries, str(e)[:60]))
|
||
time.sleep(5)
|
||
|
||
|
||
def import_step(app, path, tag):
|
||
t0 = time.time()
|
||
data = app.GetImportFileData(path)
|
||
err = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
|
||
doc = app.LoadFile4(path, 'r', data, err)
|
||
print(u' %s 导入完成 err=%s 用时 %.1fs 文档数 %s'
|
||
% (tag, err.value, time.time() - t0, app.GetDocumentCount))
|
||
return doc
|
||
|
||
|
||
def find_top_sldasm(app, keyword):
|
||
u"""在打开的文档里找临时目录下的顶层 SLDASM。⛔ 别用 glob 猜名字。"""
|
||
best = None
|
||
for d in app.GetDocuments:
|
||
try:
|
||
p, t = d.GetPathName, d.GetType
|
||
except Exception:
|
||
continue
|
||
if not p or t != 2 or not p.upper().endswith('.SLDASM'):
|
||
continue
|
||
if keyword.lower() in os.path.basename(p).lower():
|
||
best = p
|
||
return best
|
||
|
||
|
||
def main():
|
||
for f in (BASE_STEP, BLADE_STEP):
|
||
if not os.path.exists(f):
|
||
print(u'⛔ 源文件不存在: %s' % f); return
|
||
|
||
print(u'=== 启动 SolidWorks ===')
|
||
app = launch()
|
||
print(u' Revision %s 初始文档数 %s' % (app.RevisionNumber, app.GetDocumentCount))
|
||
|
||
print(u'\n=== 导入 STEP(底板 54 MB ≈ 20s,刀片 31 MB ≈ 11s)===')
|
||
import_step(app, BASE_STEP, u'底板')
|
||
import_step(app, BLADE_STEP, u'刀片')
|
||
|
||
base_asm = find_top_sldasm(app, '3D_BASE-PCB')
|
||
blade_asm = find_top_sldasm(app, 'PhoneRail_blade')
|
||
print(u'\n 底板顶层: %s' % base_asm)
|
||
print(u' 刀片顶层: %s' % blade_asm)
|
||
if not base_asm or not blade_asm:
|
||
print(u'⛔ 没找齐顶层 SLDASM,停手'); return
|
||
|
||
print(u'\n=== 新建总装 ===')
|
||
tmpl = None
|
||
for pref in (9, 10, 8): # swDefaultTemplateAssembly 通常是 9
|
||
try:
|
||
v = app.GetUserPreferenceStringValue(pref)
|
||
if v and v.lower().endswith('.asmdot'):
|
||
tmpl = v; print(u' 模板(pref=%d): %s' % (pref, v)); break
|
||
except Exception:
|
||
pass
|
||
if not tmpl:
|
||
print(u'⛔ 取不到装配体模板路径,停手'); return
|
||
asm = app.NewDocument(tmpl, 0, 0.0, 0.0)
|
||
asm = app.ActiveDoc
|
||
print(u' 新建: %s type=%s' % (asm.GetTitle, asm.GetType))
|
||
|
||
print(u'\n=== 插入两块板(AddComponent4,5 参)===')
|
||
for tag, p in ((u'底板', base_asm), (u'刀片', blade_asm)):
|
||
c = asm.AddComponent4(p, '', 0.0, 0.0, 0.0)
|
||
print(u' %s → %s' % (tag, c.Name2 if c else u'⛔ None(换 AddComponent4 也不行才是真问题)'))
|
||
asm.EditRebuild3
|
||
|
||
comps = asm.GetComponents(True)
|
||
print(u'\n 顶层零部件 %d 个' % len(comps))
|
||
base = [c for c in comps if 'blade' not in c.Name2.lower()][0]
|
||
|
||
print(u'\n=== 把底板变换归为单位阵(撤销 AddComponent4 的重心归零,§14)===')
|
||
print(u' 归零前 box %s' % [round(v * 1000, 3) for v in base.GetBox(False, False)])
|
||
try:
|
||
base.SetTransformAndSolve2(mk_xform(app, IDENT))
|
||
except Exception:
|
||
base.Transform2 = mk_xform(app, IDENT)
|
||
asm.EditRebuild3
|
||
b = [round(v * 1000, 3) for v in base.GetBox(False, False)]
|
||
print(u' 归零后 box %s' % b)
|
||
ok = abs(b[0] - 0.013) < 0.2 and abs(b[3] - 97.987) < 0.2
|
||
print(u' %s' % (u'✅ 原点回到板左下角,place_and_probe 的公式可直接用'
|
||
if ok else u'⛔ 与预期 0.013~97.987 不符,公式不能直接用,先查'))
|
||
|
||
for c in comps:
|
||
print(u' %-36s box %s' % (c.Name2[:36], [round(v * 1000, 2) for v in c.GetBox(False, False)]))
|
||
print(u'\n完成。接着跑:python place_and_probe.py sweep 2')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|