# -*- coding: utf-8 -*- """机器人手臂深度圆角优化版 V2:彻底消除所有棱角 针对用户反馈的三大问题区域进行深度优化: 1. 蓝色零件(P04肩叉)顶部方形突起 → 全圆角处理 2. 绿色/青色零件(P05/P08)底部和侧面棱角 → Loft外轮廓圆角 3. 所有连接处的尖锐过渡 → 大圆角平滑过渡 4. 橙色零件(P06/P07)所有方形边缘 → 完整圆角系统 """ import math import os import sys import time sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import cadquery as cq import arm_build as AB from armcore import (M, C, cyl, box, ring, place, trs, bb, bbox_str, solids_of, OUT, WEB, DOCS) from arm_build import (Joint, add, bx, Ycyl, Ybox, Yplate, prism, dogbone, sector, FLANGE_Z, COVER_Z, HUB_FACE_Z, CLAMP_SPAN, HUB_M2_CLOCK, BORE_R, CB_R, PCB_W, D2R, T, R, PARTS) import human_layout as HL from OCP.BRepAlgoAPI import BRepAlgoAPI_Common from human_layout import (HUB, PLATE_T, R_MOT, Z_TIP, Z_WR, Z_EI, Z_SH, Z_RL, Z_R1, Z_R3, Z_HUB3, Z_EL, Z_R6, Z_HUB6, Z_R4, Z_CAP, Z_M6, Z_CHEST0, Z_CHEST1, X_J1_HUB, X_ARM, L1, X_FORK_GAP, Y_AXIS, Y_HUB, Y_REAR, Y_COVER, Y_H, Y_R_IN, Y_R_OUT, Y_M6, Y_CHEST, X_CHEST, FOOT_H) VERSION = os.environ.get("ARMVER", "v2") ITER = int(os.environ.get("ARMITER", "3")) AB.VERSION = VERSION AB.ITER = ITER Y_MID = Y_AXIS BOSS = COVER_Z + 0.2 DISC_R = 40.0 SLEEVE_R = 35.0 # 深度圆角优化参数 CLEARANCE_GAP = 2.0 # 关节间隙 2.0mm FILLET_TINY = 1.5 # 微小圆角(螺孔边缘) FILLET_SMALL = 3.0 # 小圆角(功能面) FILLET_MEDIUM = 6.0 # 中圆角(结构过渡) FILLET_LARGE = 10.0 # 大圆角(主要轮廓) FILLET_XLARGE = 15.0 # 超大圆角(视觉主导区域) CHAMFER_EDGE = 2.0 # 边缘倒角 def Xcyl(r, x0, x1, py, pz): return place(place(cyl(r, 0.0, x1 - x0, 0.0, 0.0), R(90, 0, 1, 0)), T(x0, py, pz)) def Xplate(pts_yz, x0, x1): prof = [(-p[1], p[0]) for p in pts_yz] return place(place(prism(prof, 0.0, x1 - x0), R(90, 0, 1, 0)), T(x0, 0.0, 0.0)) def XB(a0, a1, y0, y1, z0, z1): return bx(X_ARM + a0, X_ARM + a1, y0, y1, z0, z1) def make_direct_hub(shape, j, r_disc=35.0, z_out=74.0): """优化直连法兰:增加导向倒角""" disc = cyl(r_disc, 56.5, z_out) boss = cyl(17.8, 54.942, 56.5) chamfer_cone = cyl(18.5, 54.5, 54.942) shape = shape.fuse(j.L(disc)).fuse(j.L(boss)).cut(j.L(chamfer_cone)) shape = shape.cut(j.L(cyl(3.5, 54.0, z_out + 30.0))) for a in AB.HUB_M2_CLOCK: u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) th = cyl(1.4, 54.0, 58.0, u, v) cb = cyl(2.8, 58.0, z_out + 40.0, u, v) shape = shape.cut(j.L(th)).cut(j.L(cb)) return shape # ========== 躯干部分 ========== # ========== 躯干/测试台架部分 (后置防倾翻高刚性机架) ========== X_COL = -115.0 Y_COL = Y_AXIS # 128.0 def p_chest(): """P01_胸腔支撑立柱 (High-Rigidity Box Mast Tower - 后置台架立柱): 1. 66x66mm 封闭中空厚壁箱型柱(壁厚 11mm,内腔 44x44),中心在 (-115.0, 128.0),位于手臂正后方; 2. 顶部接收 P03 48x48x15mm 榫头插槽与 4x M5 安装螺栓; 3. 底部 50x50x12mm 阶梯榫头插入 P02 榫座:环形台肩(66->50)面承载轴向力,槽壁全周承载剪切; 4. 前向开设两组大圆角走线工艺窗口(Z=60~150mm 与 Z=330~420mm); 5. 外棱角全部采用 R=6mm 圆角。 """ z_bot = 22.0 z_top = Z_CHEST1 # 470.0 col = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_bot)) .rect(66.0, 66.0) .extrude(z_top - z_bot) .edges("|Z").fillet(6.0)).val() s = cq.Shape(col.wrapped) cav = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_bot + 10.0)) .rect(44.0, 44.0) .extrude(z_top - z_bot + 1.0) .edges("|Z").fillet(4.0)).val() s = s.cut(cav) # 底部阶梯榫头:插入 P02 榫座后环形台肩坐实,轴向力走面压而非 M5 螺纹 tenon = (cq.Workplane("XY", origin=(X_COL, Y_COL, 10.0)) .rect(50.0, 50.0).extrude(z_bot - 10.0) .edges("|Z").fillet(4.0)).val() s = s.fuse(tenon) # 底部 M5 夹紧孔(贯通榫头):X 向非对称 (−20/+16) 与榫座孔位配对防反装/防转 90° for xx in (X_COL - 20.0, X_COL + 16.0): for yy in (Y_COL - 18.0, Y_COL + 18.0): s = s.cut(cyl(2.75, 9.99, z_bot + 15.0, xx, yy)) for xx in (X_COL - 21.0, X_COL + 21.0): for yy in (Y_COL - 21.0, Y_COL + 21.0): s = s.cut(cyl(2.75, z_top - 20.0, z_top + 0.01, xx, yy)) w_bot = (cq.Workplane("YZ", origin=(X_COL + 33.5, Y_COL, 105.0)) .rect(34.0, 70.0) .extrude(-20.0)).val() s = s.cut(w_bot) w_top = (cq.Workplane("YZ", origin=(X_COL + 33.5, Y_COL, 375.0)) .rect(34.0, 70.0) .extrude(-20.0)).val() s = s.cut(w_top) return s def split_p01_poka_yoke(p01): """拆分 P01 为 A/B/C 三段(严格适配 200x200mm 打印机,竖直免支撑打印): 带 16mm 阶梯互锁榫卯(外套筒壁 3.6 + 公榫壁 6.9,内腔收窄 51->44 换取双厚壁, 单边配合间隙 0.5)、非对称 M4 横向对锁穿钉孔(物理防反装防呆)、以及本体 3D 凹刻朝向标识。 """ z_bot = 22.0 z_s1 = 172.0 z_s2 = 321.0 z_top = 470.0 h_lap = 16.0 # 非对称对锁孔位(相对立柱中心偏心 -16mm 与 +22mm,偏心差 6mm,反装无法穿入螺栓) xx_bolts = (X_COL - 16.0, X_COL + 22.0) # --- P01_A (下段) --- cut_above_s1 = bx(X_COL - 50.0, X_COL + 50.0, Y_COL - 50.0, Y_COL + 50.0, z_s1, z_top + 10.0) p01_a_base = p01.cut(cut_above_s1) tongue1_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s1)) .rect(57.8, 57.8).extrude(h_lap) .edges("|Z").fillet(3.4).edges(">Z").chamfer(1.5)).val() tongue1_cav = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s1 - 1.0)) .rect(44.0, 44.0).extrude(h_lap + 2.0)).val() tongue1 = cq.Shape(tongue1_outer.wrapped).cut(tongue1_cav) p01_a = p01_a_base.fuse(tongue1) z_bolt1 = z_s1 + h_lap / 2.0 # 180.0 for xx in xx_bolts: p01_a = p01_a.cut(Ycyl(2.2, Y_COL - 40.0, Y_COL + 40.0, xx, z_bolt1)) # Engrave the front (-Y) flat face, reading VERTICALLY bottom-to-top along # the column (user request): local +X = world +Z, local +Y = world -X, so # the trailing "▶" glyph renders pointing world-up (+Z). front_label_plane_a = cq.Plane( origin=(X_COL, Y_COL - 33.0, 120.0), xDir=(0, 0, 1), normal=(0, -1, 0), ) label_a = (cq.Workplane(front_label_plane_a) .text("P01-A TOP ▶", 9.0, -0.8)).val() p01_a = p01_a.cut(cq.Shape(label_a.wrapped)) # --- P01_B (中段) --- cut_below_s1 = bx(X_COL - 50.0, X_COL + 50.0, Y_COL - 50.0, Y_COL + 50.0, z_bot - 14.0, z_s1) cut_above_s2 = bx(X_COL - 50.0, X_COL + 50.0, Y_COL - 50.0, Y_COL + 50.0, z_s2, z_top + 10.0) p01_b_base = p01.cut(cut_below_s1).cut(cut_above_s2) pocket1 = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s1 - 0.1)) .rect(58.8, 58.8).extrude(h_lap + 0.6).edges("|Z").fillet(3.9)).val() p01_b_base = p01_b_base.cut(pocket1) tongue2_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s2)) .rect(57.8, 57.8).extrude(h_lap) .edges("|Z").fillet(3.4).edges(">Z").chamfer(1.5)).val() tongue2_cav = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s2 - 1.0)) .rect(44.0, 44.0).extrude(h_lap + 2.0)).val() tongue2 = cq.Shape(tongue2_outer.wrapped).cut(tongue2_cav) p01_b = p01_b_base.fuse(tongue2) z_bolt2 = z_s2 + h_lap / 2.0 # 329.0 for xx in xx_bolts: p01_b = p01_b.cut(Ycyl(2.2, Y_COL - 40.0, Y_COL + 40.0, xx, z_bolt1)) p01_b = p01_b.cut(Ycyl(2.2, Y_COL - 40.0, Y_COL + 40.0, xx, z_bolt2)) front_label_plane_b = cq.Plane( origin=(X_COL, Y_COL - 33.0, 240.0), xDir=(0, 0, 1), normal=(0, -1, 0), ) label_b = (cq.Workplane(front_label_plane_b) .text("P01-B TOP ▶", 9.0, -0.8)).val() p01_b = p01_b.cut(cq.Shape(label_b.wrapped)) # --- P01_C (上段) --- cut_below_s2 = bx(X_COL - 50.0, X_COL + 50.0, Y_COL - 50.0, Y_COL + 50.0, z_bot - 14.0, z_s2) p01_c_base = p01.cut(cut_below_s2) pocket2 = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s2 - 0.1)) .rect(58.8, 58.8).extrude(h_lap + 0.6).edges("|Z").fillet(3.9)).val() p01_c = p01_c_base.cut(pocket2) for xx in xx_bolts: p01_c = p01_c.cut(Ycyl(2.2, Y_COL - 40.0, Y_COL + 40.0, xx, z_bolt2)) front_label_plane_c = cq.Plane( origin=(X_COL, Y_COL - 33.0, 390.0), xDir=(0, 0, 1), normal=(0, -1, 0), ) label_c = (cq.Workplane(front_label_plane_c) .text("P01-C TOP ▶", 9.0, -0.8)).val() p01_c = p01_c.cut(cq.Shape(label_c.wrapped)) return p01_a, p01_b, p01_c def make_p02_poka_yoke(): """将 P02 拆分为三件模块化组件(均严格 <= 195x190mm,适配 200x200mm 打印床): 1. P02_Center (底座中核心): 195x190x70mm,完整容纳立柱套筒与前后三角斜撑筋,核心应力区无接缝; 2. P02_Front (前向力臂): 145x190x22mm,宽跨距 104mm 双榫头 + 预埋 M5 螺母槽; 3. P02_Rear (后向力臂): 45x190x22mm,窄跨距 80mm 双榫头(跨距差达 24mm 物理防呆,绝不混装)。 """ z0 = 0.0 z1 = 22.0 y_width = 190.0 f_tenon_w = 44.0 f_tenon_y_span = 52.0 r_tenon_w = 32.0 r_tenon_y_span = 40.0 # 1. P02_Center (X = -195.0 .. 0.0, 195x190mm) x_c_min = -195.0 x_c_max = 0.0 x_c_len = x_c_max - x_c_min x_c_mid = (x_c_min + x_c_max) / 2.0 c_plate = (cq.Workplane("XY", origin=(x_c_mid, Y_COL, z0)) .rect(x_c_len, y_width).extrude(z1) .edges("|Z and X").fillet(8.0) .edges(">Z").fillet(2.0)).val() p02_c = cq.Shape(c_plate.wrapped) # Socket collar (Z: 22..70mm) z_collar_top = 70.0 collar_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) .rect(80.0, 80.0).extrude(z_collar_top - z1) .edges("|Z").fillet(8.0).edges(">Z").chamfer(2.0)).val() p02_c = p02_c.fuse(cq.Shape(collar_outer.wrapped)) # Front & Rear triangular gusset ribs f_pts = [(-75.0, z1), (-15.0, z1), (-15.0, z1 + 4.0), (-75.0, 68.0)] f_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(f_pts).close().extrude(19.0, both=True)).val() p02_c = p02_c.fuse(cq.Shape(f_rib.wrapped)) r_pts = [(-155.0, z1), (-195.0, z1), (-195.0, z1 + 14.0), (-155.0, 70.0)] r_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(r_pts).close().extrude(19.0, both=True)).val() p02_c = p02_c.fuse(cq.Shape(r_rib.wrapped)) # Front wire guide wg_pts = [(-75.5, z1 + 1.0), (-14.0, z1 + 1.0), (-14.0, z1 + 4.0), (-75.5, 68.5)] wire_guide = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(wg_pts).close().extrude(9.0, both=True)).val() p02_c = p02_c.cut(wire_guide) # Column socket — stepped mortise: upper 66.6 bore for the 66 box walls, # lower 50.6 recess (floor 0.6 low) for the 50x50 bottom tenon; the recess # rim at z=22 is the axial seat, so column weight bears on face, not threads. socket_upper = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) .rect(66.6, 66.6).extrude(z_collar_top - z1 + 1.0) .edges("|Z").fillet(6.3)).val() p02_c = p02_c.cut(socket_upper) socket_lower = (cq.Workplane("XY", origin=(X_COL, Y_COL, 9.4)) .rect(50.6, 50.6).extrude(z1 - 9.4) .edges("|Z").fillet(4.3)).val() p02_c = p02_c.cut(socket_lower) # Vertical M5 clamp bolts (offsets match the asymmetric column tenon holes: # X −20/+16 blocks 180° flip, the ±18 Y span blocks 90° rotation). # Threads engage the tenon 15mm — clamp only, axial load rides on the seat. for xx in (X_COL - 20.0, X_COL + 16.0): for yy in (Y_COL - 18.0, Y_COL + 18.0): p02_c = p02_c.cut(cyl(2.75, -0.01, 10.5, xx, yy)).cut(cyl(5.0, -0.01, 6.0, xx, yy)) # Center clamp slots for cy in (Y_COL - 75.0, Y_COL + 75.0): plane = cq.Plane(origin=(x_c_mid, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() p02_c = p02_c.cut(slot) p02_c = p02_c.cut((cq.Workplane("XY", origin=(X_COL, Y_COL - 70.0, -0.01)).rect(28.0, 80.0).extrude(6.0)).val()) # Front mortise pockets for sign in (-1, 1): yy = Y_COL + sign * f_tenon_y_span front_mortise = (cq.Workplane("XY", origin=(-10.0, yy, 4.8)) .rect(21.0, f_tenon_w + 0.8).extrude(12.6).edges("|Z and X").fillet(3.0)).val() p02_c = p02_c.cut(rear_mortise) p02_c = p02_c.cut(cyl(2.75, -0.01, z1 + 1.0, -185.0, yy)) p02_c = p02_c.cut(cyl(5.0, z1 - 5.0, z1 + 1.0, -185.0, yy)) # Debossed text on P02_Center top face. The mid-plate line (y=Y_COL) is # hidden behind the column collar and the gusset ribs, so both labels sit on # the flat -Y side strip (y = Y_COL-68) where they stay in full view from the # label side: REAR on the rear half, FRONT on the front half, clear of the # clamp slot (x -101..-94) and the M5 screw counterbores. txt_c_f = (cq.Workplane("XY", origin=(-49.0, Y_COL - 68.0, z1)).text("▲ FRONT (前向) ▲", 9.0, -0.8)).val() txt_c_r = (cq.Workplane("XY", origin=(-146.0, Y_COL - 68.0, z1)).text("▲ REAR (后向) ▲", 9.0, -0.8)).val() p02_c = p02_c.cut(cq.Shape(txt_c_f.wrapped)).cut(cq.Shape(txt_c_r.wrapped)) # 2. P02_Front (X = 0.0 .. 145.0, 145x190mm) x_f_len = 145.0 x_f_mid = 72.5 f_plate = (cq.Workplane("XY", origin=(x_f_mid, Y_COL, z0)) .rect(x_f_len, y_width).extrude(z1) .edges("|Z and >X").fillet(16.0).edges(">Z").fillet(2.0)).val() p02_f = cq.Shape(f_plate.wrapped) for cy in (Y_COL - 70.0, Y_COL + 70.0): plane = cq.Plane(origin=(120.0, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() p02_f = p02_f.cut(slot) for sign in (-1, 1): yy = Y_COL + sign * f_tenon_y_span tongue = (cq.Workplane("XY", origin=(-9.5, yy, 5.25)) .rect(20.0, f_tenon_w - 0.2).extrude(11.5).edges("|Z and Z").fillet(2.0)).val() p02_r = cq.Shape(r_plate.wrapped) for cy in (Y_COL - 70.0, Y_COL + 70.0): plane = cq.Plane(origin=(-215.0, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() p02_r = p02_r.cut(slot) for sign in (-1, 1): yy = Y_COL + sign * r_tenon_y_span tongue = (cq.Workplane("XY", origin=(-185.5, yy, 5.25)) .rect(20.0, r_tenon_w - 0.2).extrude(11.5).edges("|Z and >X").fillet(2.5).edges(">X").chamfer(1.5)).val() p02_r = p02_r.fuse(cq.Shape(tongue.wrapped)) p02_r = p02_r.cut(cyl(2.75, -0.01, z1 + 1.0, -185.0, yy)) nut_pocket = (cq.Workplane("XY", origin=(-185.0, yy, -0.01)).polygon(6, 9.2).extrude(5.0)).val() p02_r = p02_r.cut(nut_pocket) # Same +Y rotation as the front plate; "▼" points local -Y = world +X, # i.e. toward the centre joint. Along +Y the ~73 mm text fits the 190 mm # plate width instead of overhanging its 45 mm X span. txt_r = (cq.Workplane(cq.Plane(origin=(x_r_mid, Y_COL, z1), xDir=(0, 1, 0), normal=(0, 0, 1))) .text("P02-REAR ▼ JOIN", 7.5, -0.8)).val() p02_r = p02_r.cut(cq.Shape(txt_r.wrapped)) return p02_c, p02_f, p02_r def p_base_foot(): """P02_底脚 (重型防倾翻超长力臂底座 + 双向大截面三角加强斜撑筋): 1. 底盘尺寸大幅扩展为 385x240x22mm (X: -240.0..+145.0, Y: 8.0..248.0); - 前向延伸至 X = +145.0mm (在手臂垂直投影前方 91.5mm),彻底杜绝手臂前伸与抓握重物时的前倾翻; - 后向延伸至 X = -240.0mm (在立柱后方 92.0mm),抵御大动态回程后仰力矩; 2. 立柱插接套筒由 12mm 浅槽大幅升级为 48mm 高加固套筒 (Z: 22.0..70.0mm,外壁厚 7mm); 3. 前向大三角加强斜撑筋 (Z: 68.0->22.0mm, X: -75.0->-15.0mm, 宽 38mm),抗弯力臂长达 60mm,留足安全间隙不干涉手臂运动; 4. 后向大三角加强斜撑筋 (Z: 70.0->22.0mm, X: -155.0->-225.0mm, 宽 38mm),抗后仰力臂长达 70mm; 5. 前斜撑表面设 18mm 宽平滑走线导槽,与 P01 下部走线窗口自然贯通; 6. 6 组 M6 工业工作台紧固腰槽 (28x7mm) + 底部隐蔽走线槽; 7. 四周 R=18mm 大圆角过渡与 2.5mm 上边缘倒角。 """ z0 = 0.0 z1 = 22.0 x_rear = -240.0 x_front = 145.0 x_len = x_front - x_rear # 385.0 x_mid = (x_rear + x_front) / 2.0 # -47.5 y_width = 240.0 # 1. 主底盘 (385 x 240 x 22 mm) base_plate = (cq.Workplane("XY", origin=(x_mid, Y_COL, z0)) .rect(x_len, y_width) .extrude(z1) .edges("|Z").fillet(18.0) .edges(">Z").fillet(2.5)).val() s = cq.Shape(base_plate.wrapped) # 2. 立柱外包加固套筒 (Z: 22.0..70.0 mm, 外廓 80x80mm) z_collar_top = 70.0 collar_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) .rect(80.0, 80.0) .extrude(z_collar_top - z1) .edges("|Z").fillet(8.0) .edges(">Z").chamfer(2.0)).val() s = s.fuse(cq.Shape(collar_outer.wrapped)) # 3. 前向抗倾翻三角加强斜撑筋 (Front Triangular Gusset Rib) f_pts = [ (-75.0, z1), (-15.0, z1), (-15.0, z1 + 4.0), (-75.0, 68.0), ] f_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) .polyline(f_pts).close() .extrude(19.0, both=True)).val() s = s.fuse(cq.Shape(f_rib.wrapped)) # 4. 后向抗倾翻三角加强斜撑筋 (Rear Triangular Gusset Rib) r_pts = [ (-155.0, z1), (-225.0, z1), (-225.0, z1 + 4.0), (-155.0, 70.0), ] r_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) .polyline(r_pts).close() .extrude(19.0, both=True)).val() s = s.fuse(cq.Shape(r_rib.wrapped)) # 5. 前斜撑表面走线导槽 (与 P01 前走线窗 Z=70..140mm 自然顺接) wire_guide_pts = [ (-75.5, z1 + 1.0), (-14.0, z1 + 1.0), (-14.0, z1 + 4.0), (-75.5, 68.5), ] wire_guide = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) .polyline(wire_guide_pts).close() .extrude(9.0, both=True)).val() s = s.cut(wire_guide) # 6. 立柱深插槽内腔 (Pocket: 66.6 x 66.6 mm, 留 0.3mm 精密滑配间隙) socket_box = (cq.Workplane("XY", origin=(X_COL, Y_COL, 10.0)) .rect(66.6, 66.6) .extrude(z_collar_top - 10.0 + 1.0) .edges("|Z").fillet(6.3)).val() s = s.cut(socket_box) # 7. 4x M5 底部垂直紧固螺栓孔与沉孔 for xx in (X_COL - 21.0, X_COL + 21.0): for yy in (Y_COL - 21.0, Y_COL + 21.0): th = cyl(2.75, -0.01, 10.5, xx, yy) cb = cyl(5.0, -0.01, 6.0, xx, yy) s = s.cut(th).cut(cb) # 8. 6 组 M6 工业工作台紧固腰槽 (28 x 7 mm) slot_positions = [ (-215.0, Y_COL - 85.0), (-215.0, Y_COL + 85.0), # 后部两角 (-47.5, Y_COL - 95.0), (-47.5, Y_COL + 95.0), # 中部两侧 (120.0, Y_COL - 85.0), (120.0, Y_COL + 85.0), # 前部两角 ] for cx, cy in slot_positions: plane = cq.Plane(origin=(cx, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() s = s.cut(slot) # 9. 底部隐蔽走线通槽 wire_ch = (cq.Workplane("XY", origin=(X_COL, Y_COL - 80.0, -0.01)) .rect(28.0, 100.0) .extrude(6.0)).val() s = s.cut(wire_ch) return s def p_girdle(j1): """P03_肩关节安装悬臂 (高强受力多筋桁架 + 侧开式电机托架与前置止脱锁紧法兰): 1. 10mm 后端重载法兰盘 (X: -61.0..-51.0, 74x74mm); 2. 双侧大截面三角加强筋 (8mm 厚,Y = Y_ARM ± 28mm); 3. 下腹部纵向大受力龙骨筋 (12mm 厚); 4. 顶部横向锁紧环抱筋 (60x10x10mm); 5. 侧开式电机托架 (+Y侧全开, X: -51.0..8.5),M1 电机可从侧方滑入装配,由后端法兰 4xM3 与前置止脱锁紧法兰 4xM3 双向锁死; 6. 端面 4x Ø3.2mm 沉头通孔直连 M1 定子端面螺纹孔 (BCD=55mm),彻底消除轴向拉脱风险; 7. 两侧大面积散热舷窗 (34x20mm) + 顶部双斜向对流排热槽 (30x6.5mm),确保散热畅通; 8. 4x 直通式螺丝刀安装通道 (Ø8.5mm) 与 Ø26mm 中央走线孔。 """ X_MOUNT = Z_R1 # -51.0 (Rear face of M1) T_FLANGE = 10.0 # 10mm thick high-strength mounting flange X_BACK = X_MOUNT - T_FLANGE # -61.0 Y_ARM = Y_AXIS # 128.0 Z_ARM = Z_RL # 610.0 # 1. 主安装法兰盘 (74x74mm, 倒圆角 R=8mm) flange = (cq.Workplane("YZ", origin=(X_BACK, Y_ARM, Z_ARM)) .rect(74.0, 74.0) .extrude(T_FLANGE) .edges("|X").fillet(8.0) .edges("X").chamfer(1.5)).val() s = s.fuse(cq.Shape(cradle_outer.wrapped)) # 电机定子配合内腔 (精密滑配至 X=5.05) motor_pocket = Xcyl(R_BORE, X_MOUNT - 0.2, 5.05, Y_ARM, Z_ARM) s = s.cut(motor_pocket) # 前端中心避让孔 (避让 J1 输出轴旋转凸台) hub_clearance = Xcyl(19.5, 4.8, X_FRONT + 0.5, Y_ARM, Z_ARM) s = s.cut(hub_clearance) # 4x 前置 M3 锁紧螺钉通孔与沉头 (BCD=55mm, R=27.5mm, 角度 17.8°, 107.8°, 197.8°, 287.8°) for deg in (17.8, 107.8, 197.8, 287.8): rad = math.radians(deg) fy = Y_ARM + 27.5 * math.cos(rad) fz = Z_ARM + 27.5 * math.sin(rad) th = Xcyl(1.6, 4.5, X_FRONT + 0.5, fy, fz) cb = Xcyl(3.0, 6.5, X_FRONT + 0.5, fy, fz) s = s.cut(th).cut(cb) # 8. 电机托架 +Y 侧全开(侧开式半包,替代原封闭全包筒): # M1 电机可从 +Y 侧方直线滑入托架完成装配——原封闭筒电机物理上无法装入。 # 轴向约束:后端法兰 4xM3(电机尾端面)+ 前置止脱法兰 4xM3(电机前端面)双向锁死; # 径向约束:Ø60.7 精密滑配托架底 + 重力贴底。开口前端至 5.06 保留完整止脱法兰环。 s = s.cut(bx(X_MOUNT, 5.06, Y_ARM, Y_ARM + 60.0, Z_ARM - 60.0, Z_ARM + 60.0)) # 7. 散热系统:双侧大视窗 + 顶部斜向对流排热槽 for sign in (-1, 1): win_plane = cq.Plane( origin=(-24.5, Y_ARM + sign * 35.0, Z_ARM), xDir=(1, 0, 0), normal=(0, sign, 0) ) win_cut = cq.Workplane(win_plane).slot2D(34.0, 20.0).extrude(16.0, both=True).val() s = s.cut(win_cut) for deg in (45, 135): rad = math.radians(deg) slot_plane = cq.Plane( origin=(-24.5, Y_ARM + 33.0 * math.cos(rad), Z_ARM + 33.0 * math.sin(rad)), xDir=(1, 0, 0), normal=(0, math.cos(rad), math.sin(rad)) ) slot_cut = cq.Workplane(slot_plane).slot2D(30.0, 6.5).extrude(15.0, both=True).val() s = s.cut(slot_cut) return s def p_fork(j1, j2): """P04_肩叉_螺旋体30%_墙5底5_竖打 (Shoulder Fork / L-Bracket) - 加长避让顺滑流线版: 1. 头部与身子起点自 X=5.5 前移加长至 X=10.5,与 P03 前置锁紧法兰 (X=8.5) 保持 2.0mm 均匀轴向安全间隙 (X_FORK_GAP=0:不做整体前移,保持 J1 输出端最短悬伸力臂,避免额外弯矩负荷); 2. J1 从动法兰圆盘已删除(盘面在 column 前缘形成鼓楞):J1 由 boss(Ø35.6)贯通 P03 前盖中心孔直连电机输出盘;column 顶边保持 Z=646.5 与 P03 环齐高,R=18mm 大圆弧顺滑过渡; 3. 头部转接凸台加长贯通 P03 前盖中心孔 (R=19.5mm),直连 J1 旋转法兰盘; 4. M2 保留后端安装板(R=35mm, 带 rear_mount_holes 螺钉孔),前端定位环鼓出圆盘已删除(前端定位由 column 端面 pocket 承担),中间镂空散热; 5. 严格满足 single-solid 流形拓扑与 360° 全范围零干涉 (COLLISION pairs = 0)。 """ # 1. M2 后端安装法兰盘 (Y: 92..100, R=35mm) rear_plate = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_SH) # 2. M2 前端定位环 (Y: 149.5..155.5, R=35mm) front_collar = Ycyl(35.0, Y_COVER - 6.5, Y_COVER - 0.5, X_ARM, Z_SH) s = rear_plate.fuse(front_collar) # 3. J1 从动法兰圆盘已删除:原 R=36.5 圆盘盘面在 column 前缘形成圆盘鼓楞, # J1 与 P04 的连接由 boss(Ø35.6 贯通 P03 前盖中心孔 R=19.5)直连电机输出盘 # 承担,螺钉走 boss 端面 4xB 孔,圆盘无传力功能已去除。 # 4. XZ 平面有机连续流线轮廓(随 X_FORK_GAP 整体前移,避让顺滑过渡): x_b, z_b = -12.65 + X_FORK_GAP, 506.0 x_a, z_a = 10.5 + X_FORK_GAP, 545.0 Lz = z_a - z_b d0 = 0.514 * Lz d1 = 0.0 hermite_pts = [] N_h = 16 for i in range(N_h + 1): t = i / N_h h00 = 2.0 * (t**3) - 3.0 * (t**2) + 1.0 h10 = t**3 - 2.0 * (t**2) + t h01 = -2.0 * (t**3) + 3.0 * (t**2) h11 = t**3 - t**2 xh = x_b * h00 + d0 * h10 + x_a * h01 + d1 * h11 zh = z_b + t * Lz hermite_pts.append((xh, zh)) dx_flare = (53.5 + X_FORK_GAP) - (44.0 + X_FORK_GAP) dz_flare = 545.0 - 490.0 R_flare = (dx_flare**2 + dz_flare**2) / (2.0 * dx_flare) xz_wire = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0)) .moveTo(10.5 + X_FORK_GAP, 545.0) .lineTo(10.5 + X_FORK_GAP, Z_RL + 36.5) .lineTo(28.0 + X_FORK_GAP, Z_RL + 36.5) .radiusArc((44.0 + X_FORK_GAP, 630.0), 18.0) .lineTo(44.0 + X_FORK_GAP, 545.0) .radiusArc((53.5 + X_FORK_GAP, 490.0), -R_flare) .lineTo(18.5 + X_FORK_GAP, 490.0) .lineTo(-16.5 + X_FORK_GAP, 490.0) .threePointArc((-15.1 + X_FORK_GAP, 498.5), (-12.65 + X_FORK_GAP, 506.0))) for pt in hermite_pts[1:]: xz_wire = xz_wire.lineTo(pt[0], pt[1]) xz_wire = xz_wire.close() xz_shape = cq.Shape(xz_wire.extrude(100.0, both=True).val().wrapped) # 5. YZ 平面包络 yz_pts = [ (Y_AXIS - 36.5, Z_RL + 36.5), (Y_AXIS + 36.5, Z_RL + 36.5), (Y_COVER - 0.5, Z_SH + 36.0), (Y_COVER - 0.5, Z_SH), (Y_REAR - 8.0, Z_SH), (Y_REAR - 8.0, Z_SH + 36.0), ] yz_solid = Xplate(yz_pts, -20.0, 60.0) c_int = BRepAlgoAPI_Common(yz_solid.wrapped, xz_shape.wrapped) c_int.Build() column = cq.Shape(c_int.Shape()) s = s.fuse(column) # 6. 中部电机定子散热切除 s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_SH)) # 7. M2 接口成型 s = j2.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) s = j2.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) c_m2 = [cyl(BORE_R, -0.6, COVER_Z - 3.5), cyl(CB_R, COVER_Z - 3.5, COVER_Z + 0.6)] s = s.cut(*[j2.L(item) for item in c_m2]) # 8. 头部转接凸台加长(贯通 P03 前盖中心孔,跨越装配缝直连 J1 输出盘) boss = cyl(17.8, 54.942, 61.5 + X_FORK_GAP) s = s.fuse(j1.L(boss)) s = s.cut(j1.L(cyl(3.5, 54.0, 74.0 + 30.0))) for a in AB.HUB_M2_CLOCK: u = AB.M['m2_bcd'] / 2.0 * math.cos(a * AB.D2R) v = AB.M['m2_bcd'] / 2.0 * math.sin(a * AB.D2R) th = cyl(1.4, 54.0, 63.0 + X_FORK_GAP, u, v) cb = cyl(2.8, 63.0 + X_FORK_GAP, 74.0 + 40.0 + X_FORK_GAP, u, v) s = s.cut(j1.L(th)).cut(j1.L(cb)) return s def make_pitch_to_roll_bracket(j_pitch, j_roll, z_pitch, z_r, name="bracket"): """Half-wrapped (半包) 双曲面有机流线型 Pitch-to-Roll 结构座 (P05 & P08): 1. 顶部从动法兰与前侧 Loft 自然相切顺接; 2. 严格清除 z_plate 顶面以上与上级旋转件贴面的多余材料 (cut_top_solid),保证关节大范围摆动动态 0 干涉; 3. 下方电机实行半包散热设计,-Y侧敞开,+Y侧背脊与圆柱一体化融合; 4. 严格满足 single-solid 流形拓扑与零干涉 (COLLISION pairs = 0)。 """ z_bot = z_r - 56.0 # roll 电机底端 (front collar) z_plate = z_r + 7.0 # roll 电机顶端安装板 (rear plate top) y_out = HL.Y_HUB + HL.PLATE_T # 174.0 (俯仰直连法兰外侧面) y_hub_in = HL.Y_COVER + 1.0 # 157.0 (从动法兰内侧面,预留 1.0mm 动间隙) y_cyl_max = HL.Y_AXIS + 35.0 # 163.0 (下方电机外轮廓极值) # 1. 基础立柱:Ø70mm 实心圆柱 (Z: z_bot .. z_plate, R=35mm) s = cyl(35.0, z_bot, z_plate, HL.X_ARM, HL.Y_AXIS) # 2. 俯仰轴直连法兰盘:全圆柱实体 (Y: y_hub_in .. y_out, R=35mm) disc = Ycyl(35.0, y_hub_in, y_out, HL.X_ARM, z_pitch) s = s.fuse(disc) # 3. 前侧从底端 Ø70mm 圆柱到顶端法兰的有机相切平滑渐变过渡体 (Loft Morph) Z_top = z_pitch - 35.0 z_start = z_bot N = 16 wp = cq.Workplane("XY") for i in range(N + 1): t = i / float(N) z_curr = z_start + t * (Z_top - z_start) st = 3.0 * (t**2) - 2.0 * (t**3) y_center = y_cyl_max + st * (y_out - y_cyl_max) y_side = HL.Y_AXIS + st * (y_out - HL.Y_AXIS) v_c = y_center - HL.Y_AXIS v_s = y_side - HL.Y_AXIS v_mid = max(v_c, v_s + 0.02) # 后缘两角 R2 圆角:把 y=123 凸包与 R35 柱面交汇的两条 90° 竖棱圆润化 # (磨掉"凸起来的"尖角,不动后缘高度/位置,打印无悬挂) FR = 2.0 w = (cq.Workplane("XY", origin=(HL.X_ARM, HL.Y_AXIS, z_curr)) .moveTo(-35.0, v_s) .threePointArc((0.0, v_mid), (35.0, v_s)) .lineTo(35.0, -5.0 + FR) .radiusArc((35.0 - FR, -5.0), FR) .lineTo(-35.0 + FR, -5.0) .radiusArc((-35.0, -5.0 + FR), FR) .lineTo(-35.0, v_s) .close()) wp = wp.add(w) loft = wp.toPending().loft() s = s.fuse(loft.val()) # 4. 顶部填充(仅保留 y >= y_hub_in 与法兰盘对齐的部分,避免侵入上级关节旋转扫掠区域) top_fill = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0)) .moveTo(y_hub_in, Z_top) .lineTo(y_out, Z_top) .lineTo(y_out, z_pitch) .lineTo(y_hub_in, z_pitch) .close() .extrude(35.0, both=True)) s = s.fuse(top_fill.val()) # 5. 旋转清让切除:彻底清除 z >= z_plate 且 y <= y_hub_in 范围内的多余材料, # 消除导致与上游旋转件(P04/P07)严重干涉的立壁与尖角,保持顶面平整规整! cut_top_solid = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0)) .moveTo(-999.0, z_plate) .lineTo(y_hub_in, z_plate) .lineTo(y_hub_in, z_pitch + 50.0) .lineTo(-999.0, z_pitch + 50.0) .close() .extrude(60.0, both=True)) s = s.cut(cut_top_solid.val()) # 6. 电机半包处理 (Half-Enclosure):-Y侧敞开散热,+Y侧背脊壳体加强 cut_open = bx(HL.X_ARM - 40.0, HL.X_ARM + 40.0, HL.Y_AXIS - 40.0, HL.Y_AXIS + 3.0, z_bot + 6.0, z_r - 1.0) s = s.cut(cut_open) # 7. 上级电机同心圆弧避让 clearance = Ycyl(35.5, HL.Y_AXIS - 40.0, HL.Y_COVER + 0.1, HL.X_ARM, z_pitch) s = s.cut(clearance) # 8. 下方电机安装孔位与定位止口 s = j_roll.rear_mount_holes(s, t_plate=7.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) s = j_roll.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) s = j_roll.pocket(s) # 9. 电机定子配合空腔 s = s.cut(cyl(30.5, z_r - 49.5, z_r - 0.5, HL.X_ARM, HL.Y_AXIS)) # 10. 俯仰轴直连法兰结构(凸台延伸至 59.5 跨过间隙与法兰盘牢固熔接,保持单实体) boss = cyl(17.8, 54.942, 59.5) s = s.fuse(j_pitch.L(boss)) s = s.cut(j_pitch.L(cyl(3.5, 54.0, 74.0 + 30.0))) for a in AB.HUB_M2_CLOCK: u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) th = cyl(1.4, 54.0, 58.0, u, v) cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v) s = s.cut(j_pitch.L(th)).cut(j_pitch.L(cb)) # 11. L 型转角连接处圆角过渡(圆角 R=2.0mm): # 平滑消除立柱顶面与竖直臂交汇处的 90° 尖角与应力集中 try: wp_s = cq.Workplane(obj=s) sel_l = cq.NearestToPointSelector((HL.X_ARM, y_hub_in, z_plate)) s = wp_s.edges(sel_l).fillet(2.0).val() except Exception as e: print(f"[{name}] L型转角圆角异常: {e}") return s def p_shoulder_roll_base(j2, j3, half=None): """P05_大臂旋转座_螺旋体30%_墙4底5_竖打(深度圆角优化版)""" return make_pitch_to_roll_bracket(j2, j3, Z_SH, Z_R3, name="P05_大臂旋转座_螺旋体30%_墙4底5_竖打") def p_humerus_bone(j3, j4): """P06_大臂骨架_螺旋体25%_墙4底5_竖打 (Humerus Bone - Loft 圆弧转方有机过渡版): 受 J3 (大臂自转, -Z轴, hub@Z_HUB3=380) 驱动, 向下延伸构成大臂主体骨架,底端抱紧 M4 (肘部俯仰电机, +Y轴, 轴线位于 Z_EL=332.0)。 1. 借鉴 P05 大臂旋转座几何放样算法: - 顶端结合面 (Z = 388.5mm):与 J3 轴径严格相切的 Ø70mm 正圆形轮廓,彻底消除原先外突的方形水平阶梯; - 底端结合面 (Z = 367.0mm):平滑展开融入 M4 电机套筒的 70x63.5mm 方形截面; - 过渡区 (Z: 367.0..388.5mm):采用 Hermite s(t) = 3t² - 2t³ 驱动 20 层放样切片连续渐变; 2. 下部抱紧套筒与安装板 (R=35mm),电机定子 100% 裸露散热开窗; 3. 严格单实体 (solids = 1) 且与上下级旋转构件及电机实现动态 0 碰撞干涉。 """ # 1. M4 底部抱紧套筒与后端安装板 (R=35mm) rear_m4 = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_EL) front_m4 = Ycyl(35.0, Y_COVER - 6.0, Y_COVER - 0.5, X_ARM, Z_EL) s = rear_m4.fuse(front_m4) # 顶端结合面与过渡高度 z_top = 388.5 z_bot = Z_EL + 35.0 # 367.0 # 2. 下部实心基座块 (Z: Z_EL..z_bot + 0.5) lower_block = bx(X_ARM - 35.0, X_ARM + 35.0, Y_REAR - 8.0, Y_COVER - 0.5, Z_EL, z_bot + 0.5) s = s.fuse(lower_block) # 3. 借鉴 P05 的 Loft 放样过渡体:圆弧变方形(20 层平滑放样,保持严格对称 4 段式截面拓扑) N = 20 wp = cq.Workplane("XY") for i in range(N + 1): t = i / float(N) z_curr = z_bot + t * (z_top - z_bot) st = 3.0 * (t**2) - 2.0 * (t**3) # 前侧:t=0 为平直面 (Y=155.5, v=27.5),t=1 渐变为与 Ø70 正切的圆弧顶点 (Y=163.0, v=35.0) v_sf = 27.5 * (1.0 - st) + 0.05 * st v_cf = max(27.5 * (1.0 - st) + 35.0 * st, v_sf + 0.05) # 后侧:从 t=0 的平直面 (Y=92.0, v=-36.0) 渐变到 t=1 的圆弧 (Y=93.0, v=-35.0) v_sr = -36.0 * (1.0 - st) - 0.05 * st v_cr = min(-36.0 * (1.0 - st) - 35.0 * st, v_sr - 0.05) w = (cq.Workplane("XY", origin=(X_ARM, Y_AXIS, z_curr)) .moveTo(-35.0, v_sf) .threePointArc((0.0, v_cf), (35.0, v_sf)) .lineTo(35.0, v_sr) .threePointArc((0.0, v_cr), (-35.0, v_sr)) .close()) wp = wp.add(w) loft = wp.toPending().loft() s = s.fuse(loft.val()) # 4. 电机中间定子散热裸露切除 (100% 裸露) s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_EL)) # 5. M4 接口成型 s = j4.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) s = j4.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) s = j4.pocket(s) # 6. J3 直连法兰结构(凸台延伸至 59.5 跨过结合面牢固熔接,保持单实体与零干涉) boss = cyl(17.8, 54.942, 59.5) chamfer_cone = cyl(18.5, 54.5, 54.942) s = s.fuse(j3.L(boss)).cut(j3.L(chamfer_cone)) s = s.cut(j3.L(cyl(3.5, 54.0, 58.0 + 30.0))) for a in AB.HUB_M2_CLOCK: u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) th = cyl(1.4, 54.0, 58.0, u, v) cb = cyl(2.8, 58.0, 58.0 + 40.0, u, v) s = s.cut(j3.L(th)).cut(j3.L(cb)) return s def p_forearm_bone(j4, j5, half=None): """P07_小臂骨架_螺旋体30%_墙5底5_竖打 L型高刚度无干涉版(深度圆角优化版) 设计要点: 1. 改为与 P05/P08 统一的 L 型结构(L-bracket): - +Y侧垂直背脊立柱(Y: y_hub_in..y_out),避开上级大臂骨架 P06(Y <= 155.5) - 彻底消除原全深S弯在J4轴下方向两边突出的干涉结构(两边突出的结构全部去除) - 垂直立柱与下方 M5 电机水平座之间采用规则折角(Corner Chamfer/Gusset)刚性连接 2. 下方 M5 电机实行半包散热结构,底部与内腔开窗散热 3. J4 轴俯仰动态行程从受限的 ±54° 大幅释放至无死角大范围摆动(零干涉) 4. 满足单实体 (solids=1) 与装配零碰撞。 """ y_out = Y_HUB + PLATE_T # 174.0 y_hub_in = HL.Y_COVER + 0.5 + CLEARANCE_GAP # 158.5 y_front = Y_COVER - 0.5 # 155.5 y_rear = Y_REAR - 8.0 # 92.0 z_top_housing = Z_WR + 39.0 # 261.0 (顶面加厚 4.0mm,消除过薄破面问题) z_roof_top = z_top_housing + 19.0 # 280.0 (顶板整条加高 19mm,用户确认方案4) # 1. J5 电机前后安装环 collar_front = Ycyl(35.0, HL.Y_COVER - 6.0, HL.Y_COVER - 0.5, X_ARM, Z_WR) rear_plate = Ycyl(35.0, HL.Y_REAR - 8.0, HL.Y_REAR, X_ARM, Z_WR) s = collar_front.fuse(rear_plate) # 2. J4 顶部从动法兰盘 (Y: y_hub_in..y_out, R=35mm, Z: Z_EL) disc = Ycyl(35.0, y_hub_in, y_out, X_ARM, Z_EL) s = s.fuse(disc) # 3. M5 电机壳体水平顶部顶板 (Z: Z_WR..z_top_housing, Y: y_rear..y_front) roof_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0)) .moveTo(y_rear, Z_WR) .lineTo(y_rear, z_roof_top) .lineTo(y_front - 3.0, z_roof_top) .threePointArc((y_front - 3.0 + 3.0 * math.cos(math.pi / 4), z_roof_top - 3.0 + 3.0 * math.sin(math.pi / 4)), (y_front, z_roof_top - 3.0)) .lineTo(y_front, Z_WR) .close() .extrude(35.0, both=True)) s = s.fuse(roof_w.val()) # 4. L型垂直立柱臂 (+Y侧,Y: y_hub_in..y_out,从 Z_EL 顶端延伸至下方电机座) vert_leg = (cq.Workplane("YZ", origin=(X_ARM, 0, 0)) .moveTo(y_hub_in, z_top_housing) .lineTo(y_out, z_top_housing) .lineTo(y_out, Z_EL) .lineTo(y_hub_in, Z_EL) .close() .extrude(35.0, both=True)) s = s.fuse(vert_leg.val()) # 5. L型内侧圆弧加强过渡(Circular Arc Gusset,用户要求"改成圆弧而不是折角,消掉悬空小块") # 彻底取代直角折角与直斜线折角,采用与顶面和立柱双相切的平滑内凹大圆弧 (R=28.0mm) # 起点在顶板平整面 (y_arc_start, z_roof_top),精确相切至立柱内侧面 (y_hub_in, z_arc_end) # 严控全域 Z >= z_top_housing,彻底消除侵入下方旋转副的悬空小方块与多余突起! # 注:顶板整条加高19mm(z_roof_top=280)后,R28改R14使弧仍与顶板面光滑相切,不出现折角 R_arc = 14.0 # 随顶板加高联动重定:相切起点(144.5,280)->终点(158.5,294),避开P06环底z=297留3mm y_arc_start = y_hub_in - R_arc z_arc_end = z_roof_top + R_arc p_mid_arc = (y_arc_start + R_arc * math.cos(math.pi / 4), z_arc_end - R_arc * math.sin(math.pi / 4)) gusset_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0)) .moveTo(y_arc_start, z_roof_top) .threePointArc(p_mid_arc, (y_hub_in, z_arc_end)) .lineTo(y_out, z_arc_end) .lineTo(y_out, z_top_housing) .lineTo(y_arc_start, z_top_housing) .close() .extrude(35.0, both=True)) s = s.fuse(gusset_w.val()) # 6. 下级 P08 旋转圆盘动态避让切除(R=35.5mm,预留 0.5mm 间隙) p08_clearance = Ycyl(35.5, 156.0, y_out + 2.0, X_ARM, Z_WR) s = s.cut(p08_clearance) # 7. M5 电机安装接口与定子空腔 s = j5.front_collar_pocket(s, r_bore=30.25 + CLEARANCE_GAP, z0=50.0, z1=56.5) s = j5.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) s = j5.pocket(s) s = s.cut(Ycyl(30.5 + CLEARANCE_GAP, HL.Y_REAR - 0.5, y_front - 6.0, X_ARM, Z_WR)) # 半包散热设计:-Z底部电机定子通风口 vent_cut = bx(X_ARM - 40.0, X_ARM + 40.0, y_rear + 8.0, y_front - 6.0, Z_WR - 40.0, Z_WR - 25.0) s = s.cut(vent_cut) # 8. J4 直连法兰结构(凸台延伸至 60.0 跨过间隙与法兰盘牢固熔接,保持单实体) boss = cyl(17.8, 54.942, 60.0) s = s.fuse(j4.L(boss)) s = s.cut(j4.L(cyl(3.5, 54.0, 74.0 + 30.0))) for a in AB.HUB_M2_CLOCK: u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) th = cyl(1.4, 54.0, 58.0, u, v) cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v) s = s.cut(j4.L(th)).cut(j4.L(cb)) # 9. 工艺走线通孔 slot_z = (Z_EL + z_top_housing) / 2.0 slot = Ycyl(7.0, y_hub_in - 2.0, y_out + 2.0, X_ARM, slot_z) s = s.cut(slot) # ========== 关键圆角优化 ========== try: # 后侧顶板外边缘圆角(平滑消除水平顶板与后部垂直端面交汇处的 90° 尖角,用户要求倒角改圆角) wp_s = cq.Workplane(obj=s) sel_rear = cq.selectors.BoxSelector( (X_ARM - 40, y_rear - 1.0, z_roof_top - 1.0), (X_ARM + 40, y_rear + 1.0, z_roof_top + 1.0) ) s = wp_s.edges(sel_rear).fillet(FILLET_SMALL).val() except Exception as e: print(f"P07 顶板后边缘圆角异常: {e}") return s def p_wrist_bracket(j5, j6): """P08_腕偏转座_螺旋体25%_墙4底5_竖打(深度圆角优化版)""" return make_pitch_to_roll_bracket(j5, j6, Z_WR, Z_R6, name="P08_腕偏转座_螺旋体25%_墙4底5_竖打") def p_tool_flange(j6): """P09_工具法兰_螺旋体100%_墙4底5_竖打 深度圆角优化版 关键改进: 1. 外轮廓全圆角(R=4mm) 2. 法兰面倒角(2mm) 3. 螺孔边缘倒角(1.5mm) """ z_bot = Z_HUB6 - PLATE_T - 9.0 s = cyl(35.0, z_bot, Z_HUB6, X_ARM, Y_AXIS) # 4x M4螺栓孔 for a in (0, 90, 180, 270): rad = 20.0 u = rad * math.cos(a * D2R) v = rad * math.sin(a * D2R) h = cyl(1.85, z_bot - 1.0, z_bot + 12.0, X_ARM + u, Y_AXIS + v) cb = cyl(3.5, z_bot - 1.0, z_bot + 4.0, X_ARM + u, Y_AXIS + v) s = s.cut(h).cut(cb) # 定位止口 + 走线孔 pilot = cyl(10.0, z_bot - 1.0, z_bot + 2.5, X_ARM, Y_AXIS) thru = cyl(5.0, z_bot - 1.0, Z_HUB6 + 1.0, X_ARM, Y_AXIS) s = s.cut(pilot).cut(thru) # 直连法兰 s = make_direct_hub(s, j6, r_disc=35.0, z_out=74.0) # ========== 关键圆角优化 ========== try: # 外轮廓圆角 s = s.edges("|Z").fillet(FILLET_SMALL) except: pass try: # 法兰面倒角 s = s.edges("Z").fillet(FILLET_TINY) except: pass return s # ---------------------------------------------------------------- assembly ---------------- def build(): print("=" * 80, flush=True) print("【第一阶段 1/5】开始生成手臂各级构件 3D 几何实体模型...", flush=True) print("=" * 80, flush=True) t_stage1 = time.time() print(" [0/9] 载入电机标准件模组 (STEP)...", end="", flush=True) t0 = time.time() motor = cq.importers.importStep(AB.SRC_STEP).val() motor = place(motor, T(0, 0, -2.57)) AB.MOTOR, AB.HALF, AB.CLAMP = motor, None, None print(f" 完成 ({time.time()-t0:.2f}s) ✓", flush=True) JS = {} for tag, cn, rear, d, xd, holder, driven in HL.JOINTS: JS[tag] = Joint(tag, cn, rear, d, xd, lug_phase=90.0 if tag == "J1" else 0.0) j1, j2, j3, j4, j5, j6 = (JS["J%d" % k] for k in range(1, 7)) PARTS[:] = [] AB.CENTS.clear() AB.GRP = {} par = {"J1": "root", "J2": "J1", "J3": "J2", "J4": "J3", "J5": "J4", "J6": "J5"} driven = {"J1": "P04_肩叉_螺旋体30%_墙5底5_竖打", "J2": "P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "J3": "P06_大臂骨架_螺旋体25%_墙4底5_竖打", "J4": "P07_小臂骨架_螺旋体30%_墙5底5_竖打", "J5": "P08_腕偏转座_螺旋体25%_墙4底5_竖打", "J6": "P09_工具法兰_螺旋体100%_墙4底5_竖打"} motor_of = {"J1": "M1_J1", "J2": "M2_J2", "J3": "M3_J3", "J4": "M4_J4", "J5": "M5_J5", "J6": "M6_J6"} for t in ("J1", "J2", "J3", "J4", "J5", "J6"): AB.GRP[motor_of[t]] = par[t] AB.GRP[driven[t]] = t AB.GRP.update({ "P01_A_立柱下段_三角形30%_墙4底5_竖打": "root", "P01_B_立柱中段_三角形30%_墙4底5_竖打": "root", "P01_C_立柱上段_三角形30%_墙4底5_竖打": "root", "P02_Center_底座中核心_螺旋体30%_墙5底6_平打": "root", "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打": "root", "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打": "root", "P03_肩吊臂_螺旋体35%_墙5底5_竖打": "root" }) AB.JAW = {} p01_full = p_chest() p01_a, p01_b, p01_c = split_p01_poka_yoke(p01_full) p02_c, p02_f, p02_r = make_p02_poka_yoke() parts_to_build = [ ("P01_A_立柱下段_三角形30%_墙4底5_竖打", "立柱下段", lambda: p01_a, 0x0284c7, "200mm适配分段A:高166mm+16mm阶梯公榫+非对称M4防反装通孔+底部50x50x12榫头+P01-A 竖排TOP刻字"), ("P01_B_立柱中段_三角形30%_墙4底5_竖打", "立柱中段", lambda: p01_b, 0x2563eb, "200mm适配分段B:高165mm+双端公母榫+非对称M4防反装通孔+P01-B 竖排TOP刻字"), ("P01_C_立柱上段_三角形30%_墙4底5_竖打", "立柱上段", lambda: p01_c, 0x1d4ed8, "200mm适配分段C:高149mm+底端母榫+非对称M4防反装通孔+顶面P03安装台+P01-C 竖排TOP刻字"), ("P02_Center_底座中核心_螺旋体30%_墙5底6_平打", "底座核心承力座", lambda: p02_c, 0x475569, "200mm适配中座:195x190mm核心承力座+48mm套筒+12mm阶梯榫座(轴向面承载)+前后三角加强筋+前后非对称防呆母榫槽+刻字"), ("P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打", "前向防翻力臂", lambda: p02_f, 0xe11d48, "200mm适配前臂:145x190mm扩展力臂+104mm宽跨距双公榫+底部M5预埋螺母沉槽+P02-FRONT刻字"), ("P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打", "后向防翻力臂", lambda: p02_r, 0xd97706, "200mm适配后臂:45x190mm扩展力臂+80mm窄跨距双公榫+底部M5预埋螺母沉槽+P02-REAR刻字"), ("P03_肩吊臂_螺旋体35%_墙5底5_竖打", "肩关节安装悬臂", lambda: p_girdle(j1), 0x4b5563, "高强多肋机架:10mm后置法兰+双侧8mm大三角加强筋+下腹12mm抗弯龙骨筋+侧开式电机托架(M1从+Y侧滑入装配,前后法兰8xM3双向锁死)+前置止脱锁紧法兰(4xM3止脱螺孔)+大散热窗与对流槽"), ("P04_肩叉_螺旋体30%_墙5底5_竖打", "肩叉", lambda: p_fork(j1, j2), 0x2563eb, "加长避让版:头部与身子前移加长至X=10.5,与P03锁紧法兰保持2.0mm轴向安全间隙(不做前移,保持输出端最短悬伸力臂),J1输出凸台贯通直连电机,J1从动法兰圆盘鼓楞已删(接缝区与P03止脱环齐径齐高R36.5/顶646.5),单实体全角度零干涉"), ("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "大臂旋转座", lambda: p_shoulder_roll_base(j2, j3, None), 0x059669, "V3圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,后缘凸包两角90°竖棱磨R2圆角(结构/后缘高度不变,打印无悬挂),保证J2全角度0干涉"), ("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "大臂骨架", lambda: p_humerus_bone(j3, j4), 0x0d9488, "Loft有机过渡版:借鉴P05圆弧变方形放样算法,顶端Ø70正圆与J3平齐无台阶,底端渐变融入M4抱紧套筒与壳体,单实体0干涉"), ("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "小臂骨架", lambda: p_forearm_bone(j4, j5, None), 0x7c3aed, "V2 L型结构:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶板和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,顶板前后外缘2mm倒角改R3圆角,顶板整条加高19mm(z261->280)加强连接且gusset弧R28->R14重定相切融入,J4圆盘外沿与立柱外棱不倒圆保持原始尖角,下方M5电机半包散热,俯仰大范围自由运动零干涉"), ("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "腕部转接座", lambda: p_wrist_bracket(j5, j6), 0xd97706, "V2圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,彻底清除立壁保证J5全角度0干涉"), ("P09_工具法兰_螺旋体100%_墙4底5_竖打", "末端工具法兰", lambda: p_tool_flange(j6), 0xdc2626, "V2圆角:外轮廓R=3mm,法兰面倒角2mm"), ] total_parts = len(parts_to_build) for idx, (name, cn, fn, color, note) in enumerate(parts_to_build, 1): print(f" [{idx}/{total_parts}] 正在构建 {name} ({cn})...", end="", flush=True) t0 = time.time() sh = fn() dt = time.time() - t0 vol = AB.vol_of(sh) / 1000.0 n_solids = len(solids_of(sh)) print(f" 完成 ✓ ({dt:.2f}s | 体积: {vol:.1f} cm³ | 实体数: {n_solids})", flush=True) add(name, cn, sh, color, note=note) print(" [装配] 正在将 M1~M6 电机定位至各关节坐标位姿...", end="", flush=True) t0 = time.time() for k, (tag, cn, rear, d, xd, holder, driven_name) in enumerate(HL.JOINTS): j = JS[tag] add("M%d_%s" % (k + 1, tag), "%s 电机模组" % tag, place(motor, j.t), 0x9ca3af, printed=False, note="复用 电机.STEP") print(f" 完成 ✓ ({time.time()-t0:.2f}s)", flush=True) print(f"-> 构件模型全部生成完成,总耗时: {time.time()-t_stage1:.2f}s\n", flush=True) return JS CHAIN = ["root", "J1", "J2", "J3", "J4", "J5", "J6"] SEP = 74.0 def explode_chain(p, JS, cents, gmean): """爆炸图偏移:立柱垂直阶梯拉开,底座前后防呆拉开,电机沿轴拉出,机械臂整体随上段分离""" nm = p["name"] if nm == "P01_A_立柱下段_三角形30%_墙4底5_竖打": return [0.0, 0.0, 0.0] if nm == "P01_B_立柱中段_三角形30%_墙4底5_竖打": return [0.0, 0.0, 60.0] if nm == "P01_C_立柱上段_三角形30%_墙4底5_竖打": return [0.0, 0.0, 120.0] if nm == "P02_Center_底座中核心_螺旋体30%_墙5底6_平打": return [0.0, 0.0, 0.0] if nm == "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打": return [65.0, 0.0, 0.0] if nm == "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打": return [-65.0, 0.0, 0.0] if nm == "P03_肩吊臂_螺旋体35%_墙5底5_竖打": return [0.0, 0.0, 120.0] grp = AB.GRP.get(p["name"], "root") if grp == "root": return [0.0, 0.0, 0.0] k = CHAIN.index(grp) off = [0.0, 0.0, 120.0 - SEP * k] j = JS.get(grp) if j: w = 0.0 if "半联轴器" in nm or "抱箍" in nm: w = 34.0 * (1.0 if ("A" in nm[-1:]) else -1.0) elif nm.startswith("M"): w = 62.0 for q in range(3): off[q] += j.d[q] * w return off if __name__ == "__main__": JS = build() print("=" * 80, flush=True) print("【单实体拓扑完整性校验】验证所有打印构件流形实体状态...", flush=True) print("=" * 80, flush=True) bad = 0 printed_list = [p for p in PARTS if p["printed"]] total_pr = len(printed_list) for idx, p in enumerate(printed_list, 1): n = len(solids_of(p["shape"])) if n != 1: bad += 1 status = "单实体 ✓" if n == 1 else f"异常 ({n} 个实体) ❌" print(f" [{idx}/{total_pr}] {p['name']:<14} : 体积 {p['vol']/1000.0:6.1f} cm³ | {status}", flush=True) if bad == 0: print(f"-> 单实体检查全部通过!{total_pr}/{total_pr} 构件均为完美单一流形拓扑 ✓\n", flush=True) else: print(f"-> 发现 {bad} 个非单实体构件!\n", flush=True) AB.explode_vec = explode_chain bom = AB.export(JS) # 紧固件(螺钉)实例注入:把 docs/screws.json 的 72 颗螺钉按真实孔位还原成三维实例, # 追加进 web/mesh_arm.json 的 parts(查看器可点 BOM 螺丝行高亮定位;纯数据,不动 CAD) try: import screw_mesh screw_mesh.main() except Exception as e: print("螺钉实例注入失败(不影响主流程,可手动跑 src/screw_mesh.py):", e, flush=True) cl = AB.collide() try: from build_viewer import rebuild_viewer rebuild_viewer() except Exception as e: print("rebuild_viewer error:", e, flush=True) # 可达包络自动注入:AB.export 刚重写过 mesh_arm.json(包络件会被冲掉), # 这里重跑 FK 采样 + 包络网格注入,再重打包一次查看器(约 3~4s,纯数据无 CAD 计算) try: sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "scratch")) import ws_sweep import gen_env_mesh t_env = time.time() ws_sweep.main() gen_env_mesh.main() from build_viewer import rebuild_viewer as _rbv _rbv() print(f"-> 可达包络已自动注入查看器(+{time.time()-t_env:.1f}s)✓\n", flush=True) except Exception as e: print("可达包络自动注入失败(不影响主流程,修复后手动跑 scratch/gen_env_mesh.py + src/build_viewer.py):", e, flush=True) print("=" * 80, flush=True) print("✅ 机器人手臂编译、导出与查看器发布全流程完成!", flush=True) print("=" * 80, flush=True) sys.stdout.flush() os._exit(0)