# -*- 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'⚠ 零件保持打开')