302 行
12 KiB
Python
302 行
12 KiB
Python
# -*- coding: utf-8 -*-
|
||
u"""端板 v11 = v10 的几何 + **试着把 A/C 两组螺母孔做出来**。
|
||
|
||
v10 把「肉」给够了(4 根角柱),但孔一直留给 GUI,因为 `FeatureCut4` 在本机
|
||
连续 7 次返回 None。⇒ 本脚本按 NOTES 里**唯一成功过的那个配方**再试一次:
|
||
|
||
✅ 成功的那次:托盘柱顶孔 —— 草图建在**实体的面**上(而且是 z 常量面),沿面法线切。
|
||
⛔ 失败的那些:草图建在**基准面**上(前视/右视)去切。
|
||
|
||
C 组(沿 z,打进角柱顶面 z=72)与那次**构型完全相同**,成功概率最高。
|
||
A 组(沿 y,打进角柱外侧面)是同一招换个面,一并试。
|
||
|
||
⚠ 两条必须守住的纪律:
|
||
1. **不动 v10**。v10 已在总装里验过(干涉 360、贴合 10 组),v11 没通过就原样丢掉。
|
||
2. **判据是圆柱面半径直方图,而且要双向看**:
|
||
· 新增:R2.1 应该 +6(4 个 A + 2 个 C);
|
||
· **没丢**:R1.7 仍是 4(风扇螺孔)、R30 仍是 1(风口)。
|
||
NOTES 记过一次面上草图的异常拉伸**把已有 8 个孔全吞掉**,只看"新增"是抓不到的。
|
||
|
||
局部坐标(西端板;装配 x = 本地 x − 38.4,z 与装配同值):
|
||
· 角柱 本地 x 2.4~12.4(中心 **7.4** = 装配 −31)、通高 z 0~72
|
||
南柱 y −1.655~6.345(中心 **2.345**)/北柱 y 96.345~104.345(中心 **100.345**)
|
||
· C 组 2 孔:柱顶面 z=72,(x,y) = (7.4, 2.345) / (7.4, 100.345),Ø4.2 深 6
|
||
· A 组 4 孔:柱外侧面 y=−1.655 与 y=104.345,(x,z) = (7.4, 10) / (7.4, 60),Ø4.2 深 6
|
||
"""
|
||
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
|
||
|
||
DST = u'D:\\DCDC\\新建文件夹\\5blades\\EndPlate_v11.SLDPRT'
|
||
EMPTY = wc.VARIANT(pythoncom.VT_DISPATCH, None)
|
||
|
||
PLATE_T = 2.4
|
||
Y0, Y1 = 51.345 - 53.0, 51.345 + 53.0
|
||
Z0, Z1 = -10.0, 72.0
|
||
FAN_CY, FAN_CZ = 51.345, 37.85
|
||
VENT_D = 60.0
|
||
FAN_SCREW = 50.0
|
||
SCREW_D = 3.4
|
||
FOOT_LEN = 36.0
|
||
FOOT_T = 2.4
|
||
POST_W = 8.0
|
||
POST_LEN = 10.0
|
||
POST_Z0, POST_Z1 = 0.0, Z1
|
||
POST_Y = [(Y0, Y0 + POST_W), (Y1 - POST_W, Y1)]
|
||
POST_CX = PLATE_T + POST_LEN / 2.0 # 7.4
|
||
|
||
INSERT_D, INSERT_DEPTH = 4.2, 6.0
|
||
C_HOLES = [(POST_CX, (POST_Y[0][0] + POST_Y[0][1]) / 2.0),
|
||
(POST_CX, (POST_Y[1][0] + POST_Y[1][1]) / 2.0)] # (x, y) @ z=72
|
||
A_Z = (10.0, 60.0)
|
||
|
||
|
||
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
|
||
|
||
|
||
def RZ(z):
|
||
return M(-z)
|
||
|
||
|
||
app = wc.GetActiveObject('SldWorks.Application')
|
||
for d in list(app.GetDocuments or []):
|
||
try:
|
||
t = prop(d, 'GetTitle') or ''
|
||
if 'ENDPLATE_V11' 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 faces():
|
||
b = bodies()
|
||
return prop(D(b[0]), 'GetFaces') if b else []
|
||
|
||
|
||
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 cyl_hist():
|
||
rs = {}
|
||
for f in faces():
|
||
s = prop(D(f), 'GetSurface')
|
||
if prop(s, 'IsCylinder'):
|
||
r = round(list(prop(s, 'CylinderParams'))[6] * 1000, 2)
|
||
rs[r] = rs.get(r, 0) + 1
|
||
return dict(sorted(rs.items()))
|
||
|
||
|
||
def pick_face(axis, sign, coord, tag):
|
||
u"""遍历选面:法线沿 ±axis、且该面整体落在 coord 上;取面积最大的那个。
|
||
⛔ 绝不按坐标 SelectByID2 撞面 —— 实测会静默选中邻面(NOTES §34)。"""
|
||
ai = {'x': 0, 'y': 1, 'z': 2}[axis]
|
||
best, barea, bbx = None, -1.0, None
|
||
for f in faces():
|
||
fc = D(f)
|
||
s = prop(fc, 'GetSurface')
|
||
if not prop(s, 'IsPlane'):
|
||
continue
|
||
# ⛔ PlaneParams 是**曲面**法线,不是面的朝外法线。本机实测(14 个平面逐一核过):
|
||
# FaceInSurfaceSense == True ⇒ 朝外法线 = −PlaneParams
|
||
# FaceInSurfaceSense == False ⇒ 朝外法线 = +PlaneParams
|
||
# v11 第一次跑就栽在这:三个面一个都没选到(FeatureCut4 根本没被调用)。
|
||
n = list(prop(s, 'PlaneParams'))[ai]
|
||
if prop(fc, 'FaceInSurfaceSense'):
|
||
n = -n
|
||
if sign * n < 0.99:
|
||
continue
|
||
fb = [round(v * 1000, 3) for v in prop(fc, 'GetBox')]
|
||
if abs(fb[ai] - coord) > 0.02 or abs(fb[ai + 3] - coord) > 0.02:
|
||
continue
|
||
a = prop(fc, 'GetArea') * 1e6
|
||
if a > barea:
|
||
best, barea, bbx = fc, a, fb
|
||
if best is None:
|
||
P(u' ⛔ %s:没找到法线 %s%s、位于 %.3f 的平面' % (tag, '+' if sign > 0 else '-', axis, coord))
|
||
return None
|
||
part.ClearSelection2(True)
|
||
best.Select4(False, EMPTY)
|
||
n = part.SelectionManager.GetSelectedObjectCount2(-1)
|
||
P(u' %s:选中面 面积 %.1f mm² box x %.2f~%.2f y %.2f~%.2f z %.2f~%.2f 选中数=%d'
|
||
% (tag, barea, bbx[0], bbx[3], bbx[1], bbx[4], bbx[2], bbx[5], n))
|
||
return best if n == 1 else None
|
||
|
||
|
||
def cut(depth, tag):
|
||
if pick(sk_name(), 'SKETCH') != 1:
|
||
P(u' ⛔ %s 草图没选中' % tag)
|
||
return None
|
||
try:
|
||
c = fm.FeatureCut4(True, False, False, 0, 0, M(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)
|
||
except Exception as ex:
|
||
P(u' ⛔ %s FeatureCut4 异常 %s' % (tag, str(ex)[:90]))
|
||
return None
|
||
P(u' %s FeatureCut4 → %r' % (tag, prop(c, 'Name') if c else c))
|
||
return c
|
||
|
||
|
||
# ══ 1~3:与 v10 逐字相同的几何 ═══════════════════════════════════════════
|
||
P(u'\n[1] 板本体 + Ø60 风口 + 4 风扇螺孔')
|
||
pick(u'右视基准面', 'PLANE')
|
||
sm.InsertSketch(True)
|
||
sm.AddToDB = True
|
||
sm.CreateCornerRectangle(RZ(Z0), M(Y0), 0.0, RZ(Z1), M(Y1), 0.0)
|
||
sm.CreateCircleByRadius(RZ(FAN_CZ), M(FAN_CY), 0.0, M(VENT_D / 2))
|
||
for dy in (-FAN_SCREW / 2, FAN_SCREW / 2):
|
||
for dz in (-FAN_SCREW / 2, FAN_SCREW / 2):
|
||
sm.CreateCircleByRadius(RZ(FAN_CZ + dz), M(FAN_CY + dy), 0.0, M(SCREW_D / 2))
|
||
sm.AddToDB = False
|
||
sm.InsertSketch(True)
|
||
extrude(PLATE_T)
|
||
P(u' box %s 直方图 %s' % (bbox(), cyl_hist()))
|
||
|
||
P(u'\n[2] L 脚 长 %.0f' % FOOT_LEN)
|
||
pick(u'右视基准面', 'PLANE')
|
||
sm.InsertSketch(True)
|
||
sm.AddToDB = True
|
||
sm.CreateCornerRectangle(RZ(0.0), M(Y0), 0.0, RZ(FOOT_T), M(Y1), 0.0)
|
||
sm.AddToDB = False
|
||
sm.InsertSketch(True)
|
||
extrude(PLATE_T + FOOT_LEN)
|
||
|
||
P(u'\n[3] 角柱 ×2')
|
||
pick(u'右视基准面', 'PLANE')
|
||
sm.InsertSketch(True)
|
||
sm.AddToDB = True
|
||
for py0, py1 in POST_Y:
|
||
sm.CreateCornerRectangle(RZ(POST_Z0), M(py0), 0.0, RZ(POST_Z1), M(py1), 0.0)
|
||
sm.AddToDB = False
|
||
sm.InsertSketch(True)
|
||
extrude(PLATE_T + POST_LEN)
|
||
base_box = bbox()
|
||
base_hist = cyl_hist()
|
||
P(u' box %s 直方图 %s' % (base_box, base_hist))
|
||
if base_hist.get(1.7, 0) != 4 or base_hist.get(30.0, 0) != 1:
|
||
P(u'⛔ 基础几何就不对,停手'); sys.exit(2)
|
||
|
||
# ══ 4:C 组 —— 柱顶面(z 常量面)沿 z 切,与托盘柱顶孔同款配方 ═══════════
|
||
P(u'\n[4] C 组 2 × Ø%.1f 深 %.1f(沿 z,柱顶面 z=%.1f)' % (INSERT_D, INSERT_DEPTH, Z1))
|
||
f = pick_face('z', +1, Z1, u'柱顶面')
|
||
if f is not None:
|
||
sm.InsertSketch(True)
|
||
sm.AddToDB = True
|
||
# ⚠ z 常量面上草图的 uv 恰好 = 模型 xy(托盘那次的成功是因为这个,不是"面草图接受模型坐标")
|
||
for hx, hy in C_HOLES:
|
||
sm.CreateCircleByRadius(M(hx), M(hy), M(Z1), M(INSERT_D / 2))
|
||
sm.AddToDB = False
|
||
_g = prop(D(prop(sm, 'ActiveSketch')), 'GetSketchSegments')
|
||
P(u' 草图 segment = %d(期望 2)' % (len(_g) if _g else 0))
|
||
sm.InsertSketch(True)
|
||
cut(INSERT_DEPTH, u'C 组')
|
||
P(u' 直方图 %s' % cyl_hist())
|
||
|
||
# ══ 5:A 组 —— 柱外侧面(y 常量面)沿 y 切 ═══════════════════════════════
|
||
# ⛔ y 端面草图的映射:u → 模型 x、v → 模型 **−z**,第三参恒 0(NOTES §35 实测)。
|
||
# 直接把模型 z 塞进 v 会把孔甩到 z 的镜像位置。
|
||
P(u'\n[5] A 组 4 × Ø%.1f 深 %.1f(沿 y,柱外侧面 y=%.3f / %.3f)'
|
||
% (INSERT_D, INSERT_DEPTH, Y0, Y1))
|
||
for ynom, sign, tag in ((Y0, -1, u'南侧面'), (Y1, +1, u'北侧面')):
|
||
f = pick_face('y', sign, ynom, tag)
|
||
if f is None:
|
||
continue
|
||
sm.InsertSketch(True)
|
||
sm.AddToDB = True
|
||
for az in A_Z:
|
||
sm.CreateCircleByRadius(M(POST_CX), M(-az), 0.0, M(INSERT_D / 2))
|
||
sm.AddToDB = False
|
||
_g = prop(D(prop(sm, 'ActiveSketch')), 'GetSketchSegments')
|
||
P(u' 草图 segment = %d(期望 2)' % (len(_g) if _g else 0))
|
||
sm.InsertSketch(True)
|
||
cut(INSERT_DEPTH, tag)
|
||
P(u' 直方图 %s' % cyl_hist())
|
||
|
||
# ══ 6:验收 —— 双向判据 ═════════════════════════════════════════════════
|
||
P(u'\n[6] 验收')
|
||
h = cyl_hist()
|
||
b = bbox()
|
||
n_new = h.get(round(INSERT_D / 2, 2), 0)
|
||
n_fan = h.get(1.7, 0)
|
||
n_vent = h.get(30.0, 0)
|
||
box_ok = b and all(abs(b[i] - base_box[i]) < 0.02 for i in range(6))
|
||
P(u' 包围盒 %s %s' % (b, u'✅ 未变' if box_ok else u'⛔ 变了,说明切歪了'))
|
||
P(u' Ø%.1f 螺母孔 = %d 个(期望 6 = 4 A + 2 C)%s'
|
||
% (INSERT_D, n_new, u'✅' if n_new == 6 else (u'⚠ 少了 %d 个,剩下的留 GUI' % (6 - n_new))))
|
||
P(u' Ø3.4 风扇孔 = %d(必须仍是 4)%s' % (n_fan, u'✅' if n_fan == 4 else u'⛔ 被吃掉了'))
|
||
P(u' Ø60 风口 = %d(必须仍是 1)%s' % (n_vent, u'✅' if n_vent == 1 else u'⛔ 被吃掉了'))
|
||
|
||
if n_fan != 4 or n_vent != 1 or not box_ok:
|
||
P(u'\n⛔⛔ 已有特征被破坏 —— **不存盘**,v10 保持可用。')
|
||
sys.exit(3)
|
||
if n_new == 0:
|
||
P(u'\n⛔ 一个孔都没切出来 ——**不存盘**,孔仍留给 GUI。'
|
||
u'(先看上面:是选面失败还是 FeatureCut4 返回 None,别混为一谈)')
|
||
sys.exit(4)
|
||
|
||
e1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
|
||
w1 = wc.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)
|
||
part.Extension.SaveAs3(DST, 0, 1, EMPTY, EMPTY, e1, w1)
|
||
P(u'\n另存 err=%s 存在=%s %.0f KB'
|
||
% (e1.value, os.path.exists(DST), os.path.getsize(DST) / 1024.0 if os.path.exists(DST) else 0))
|
||
P(u'⚠ 零件保持打开')
|