# -*- coding: utf-8 -*- """Humanoid arm: 6 x the 60 mm gearmotor module on a torso mast. J1 肩抬举 roll +X fore-aft, in the shoulder pod -> drives P04 肩叉 J2 肩俯仰 pitch -Y outboard in the fork -> drives P05 大臂 J3 肘俯仰 pitch +Y inboard in the humerus -> drives P06 小臂 J4 前臂旋转 roll -Z coaxial inside the forearm -> drives P07 腕帽 J5 腕俯仰 pitch -Y outboard in the cap -> drives P08 手掌 J6 手部 -Z down the hand, crank + jaws -> drives P12 曲柄 Stations all come from human_layout.py, which derives them from the one hard fact about this joint recipe: a module is a 60 mm drum lying ALONG its own axis for 65 mm before the plate it drives. Consecutive pitch motors alternate inboard / outboard and share the one hub plane Y_H, which keeps the limb a single tube wide. Four rules the earlier passes broke, now enforced in code: * no vertical yaw column - the arm has to hang through it. J1 is a shoulder roll about a fore-aft axis 92 mm above the pitch axis, and the pod keeps its cheeks at |x| >= 64 while the limb lives in the plane X_ARM with a +-42 envelope. * a motor boss ends at COVER_Z + 0.2, because the split clamp band (local 56.6..64.4, out to R38) belongs to the neighbouring link and needs that space free. * a driven hub disc stops short of the next joint's clamp envelope instead of running through it. * every bolted pair shares a plane whose normal IS the bolt axis. The hand therefore presents two faces: the J5 hub disc (normal Y, spline-driven) and a neck plate at the shell flange (normal Z, 3x M3). That 90 deg turn is what makes the hand a structure instead of an orange disc floating in front of the arm. """ import math import os import sys import time try: sys.stdout.reconfigure(encoding="utf-8", line_buffering=True) except Exception: pass 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, Y_AXIS, Y_HUB, Y_REAR, Y_COVER, Y_H, Y_R_IN, Y_R_OUT, Y_M6, Y_CHEST, X_CHEST) VERSION = os.environ.get("ARMVER", "h1") ITER = int(os.environ.get("ARMITER", "1")) AB.VERSION = VERSION AB.ITER = ITER Y_MID = Y_AXIS # arm lateral centre plane BOSS = COVER_Z + 0.2 # a motor boss stops here; the clamp lives beyond DISC_R = 40.0 # driven hub disc (shroud OD is 38) SLEEVE_R = 35.0 # motor mount collar outer (OD 70, wall 4.85mm over Ø60.3 bore) def Xcyl(r, x0, x1, py, pz): """Cylinder of radius r along +X from x0..x1, axis at (y,z) = (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): """Plate bounded by a (y,z) profile, extruded along 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): """Box in the limb frame: x measured from X_ARM.""" 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): """Direct motor output interface for joint j: - Completely eliminates separate couplings & clamps across the arm. - Fits directly to motor rotating output flange at local z = 54.942. - Outer disc starts at local z = 56.5 (0.5mm clearance from stationary motor cover at 56.0). - Centering pilot boss Ø35.6mm (R17.8mm) spans local z: 54.942 to 56.5 into motor neck. - Central through-hole Ø7.0mm (R3.5mm) penetrates all the way through for wire passing. - 3 M2 counterbored screw holes at BCD 29.0mm (angles 8.6°, 127.7°, 247.9°): Through hole Ø2.8mm (local z: 54.0 to 58.0, leaves 3.06mm solid clamping flange). Counterbore Ø5.6mm (local z: 58.0 to z_out + 30.0) penetrates ALL THE WAY THROUGH to the outside! """ disc = cyl(r_disc, 56.5, z_out) boss = cyl(17.8, 54.942, 56.5) shape = shape.fuse(j.L(disc)).fuse(j.L(boss)) 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 # ---------------------------------------------------------------- torso ------------------- def p_chest(): """P01_胸腔支撑立柱 (High-Rigidity Box Mast Tower): 针对 3D 打印与高抗扭刚度彻底重构: 1. 66x66mm 封闭中空厚壁箱型柱(壁厚 8mm),中心在 (-27.0, 47.0),与 P03 底座完美贴合无阶梯、无突起; 2. 顶部接收 P03 48x48x15mm 榫头插槽与 4x M5 安装螺栓; 3. 底部插入 P02 沉槽(深度 12mm),剪切力由槽壁全周承载; 4. 前向开设两组大圆角走线工艺窗口(Z=60~150mm 与 Z=330~420mm); 5. 外棱角全部采用 R=6mm 圆角。 """ z_bot = 22.0 # P02 底座厚度 22mm z_top = Z_CHEST1 # 470.0 # 外部实心箱梁 col = (cq.Workplane("XY", origin=(-27.0, 47.0, z_bot)) .rect(66.0, 66.0) .extrude(z_top - z_bot) .edges("|Z").fillet(6.0)).val() s = cq.Shape(col.wrapped) # 中空中部腔体 (50x50mm,留 8mm 强韧厚壁) cav = (cq.Workplane("XY", origin=(-27.0, 47.0, z_bot + 10.0)) .rect(50.0, 50.0) .extrude(z_top - z_bot + 1.0) .edges("|Z").fillet(4.0)).val() s = s.cut(cav) # 底部 4x M5 安装孔 for xx in (-48.0, -6.0): for yy in (26.0, 68.0): s = s.cut(cyl(2.75, z_bot - 0.01, z_bot + 15.0, xx, yy)) # 顶部 4x M5 安装孔 for xx in (-48.0, -6.0): for yy in (26.0, 68.0): s = s.cut(cyl(2.75, z_top - 20.0, z_top + 0.01, xx, yy)) # 走线工艺窗口(带大圆角) w_bot = (cq.Workplane("XZ", origin=(-27.0, 80.5, 105.0)) .rect(34.0, 70.0) .extrude(-20.0)).val() s = s.cut(w_bot) w_top = (cq.Workplane("XZ", origin=(-27.0, 80.5, 375.0)) .rect(34.0, 70.0) .extrude(-20.0)).val() s = s.cut(w_top) return s def split_p01_stack(p01): """将 P01 拆分为适合 250mm 桌面 3D 打印机的两段(P01_A 与 P01_B),带 16mm 阶梯互锁榫卯""" z_split = 246.0 h_lap = 16.0 # 下段 P01_A (Z=22 to 246, 包含向上的 16mm 榫头插舌) cut_b = bx(-70.0, 20.0, 0.0, 100.0, z_split, 500.0) tongue_outer = (cq.Workplane("XY", origin=(-27.0, 47.0, z_split)) .rect(64.8, 64.8) .extrude(h_lap) .edges("|Z").fillet(5.5)).val() tongue_cav = (cq.Workplane("XY", origin=(-27.0, 47.0, z_split - 1.0)) .rect(51.0, 51.0) .extrude(h_lap + 2.0)).val() tongue = cq.Shape(tongue_outer.wrapped).cut(tongue_cav) p01_a = p01.cut(cut_b).fuse(tongue) # 上段 P01_B (Z=246 to 470, 底部包含接收榫头的凹槽) cut_a = bx(-70.0, 20.0, 0.0, 100.0, 0.0, z_split) p01_b = p01.cut(cut_a) pocket = (cq.Workplane("XY", origin=(-27.0, 47.0, z_split - 0.1)) .rect(65.6, 65.6) .extrude(h_lap + 0.5) .edges("|Z").fillet(6.0)).val() p01_b = p01_b.cut(pocket) # 对锁螺栓孔 (4x M4 对锁孔在 Z=z_split + h_lap/2) z_bolt = z_split + h_lap / 2.0 for xx in (-46.0, -8.0): y_bolt = Ycyl(2.2, 10.0, 85.0, xx, z_bolt) p01_a = p01_a.cut(y_bolt) p01_b = p01_b.cut(y_bolt) return p01_a, p01_b def p_base_foot(): """P02_测试台架底座 (Ultra-Stable 320mm Extended Stabilizer Base): 1. 200x320x22mm 重型防倾翻大底盘(长 320mm,向后延伸至 Y=-90,向前延伸至 Y=+230); 2. 彻底保证机械臂在最大延伸(前伸 300mm+)及动态制动时的重心稳定度; 3. 中央设计 12mm 深度立柱抗剪嵌套凹槽 (Socket Pocket),精密容纳 P01 下端; 4. 四个边角各设有一组 28x7mm 的 M6 夹持/桌面压板安装腰槽; 5. 凹槽底面设有 4x M5 沉头紧固螺栓孔; 6. 底面设有 28x6mm 隐蔽过线通槽; 7. 四周棱角全部采用 R=16mm 大圆角过渡与 2.5mm 上边缘导角。 """ z0 = 0.0 z1 = 22.0 base_plate = (cq.Workplane("XY", origin=(-20.0, 70.0, z0)) .rect(200.0, 320.0) .extrude(z1) .edges("|Z").fillet(16.0) .edges(">Z").fillet(2.5)).val() s = cq.Shape(base_plate.wrapped) # 中央立柱嵌装定位槽 (深度 12mm, Z: 10~22mm) socket_box = (cq.Workplane("XY", origin=(-27.0, 47.0, z1 - 12.0)) .rect(67.0, 67.0) .extrude(13.0) .edges("|Z").fillet(6.5)).val() s = s.cut(socket_box) # 底部 4x M5 贯通螺栓孔及沉头沉孔 (从底座下方向上紧固立柱) for xx in (-48.0, -6.0): for yy in (26.0, 68.0): th = cyl(2.75, -0.01, z1 - 11.0, xx, yy) cb = cyl(5.0, -0.01, 6.0, xx, yy) s = s.cut(th).cut(cb) # 4 处边角 M6 桌面压板/台钳固定长腰槽 (28mm x 7mm) for cx in (-100.0, 60.0): for cy in (-60.0, 200.0): 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) # 底部隐蔽走线凹槽 (宽 28mm, 深 6mm, 从立柱槽直通底座后侧 Y=-90) wire_ch = (cq.Workplane("XY", origin=(-27.0, -20.0, -0.01)) .rect(28.0, 145.0) .extrude(6.0)).val() s = s.cut(wire_ch) return s def p_girdle(j1): """P03_肩部基座 浑然一体高刚度规整风道机壳 (Monolithic Aerodynamic Shoulder Housing): 彻底重构并消除旧版所有缺陷: 1. 彻底消除不对称悬空凸台与多余阶梯(消除 X>5.0 处突出的无用耳朵与螺栓孔); 2. 全件采用统一的 X 跨度 X in [-59.0, 5.0] (宽 64mm),外侧平齐,零多余外凸; 3. 斜挑支撑柱 (Pylon) 自 Z=470mm 安装基底浑然一体平滑延伸至 Ø73mm 电机外壳舱,无上下断层或拼接触痕; 4. 底面设置 48x48x15mm 高强度整体榫头与 4x M5 对锁安装沉孔,隐藏于腔体内部; 5. 7 组轴向圆角散热通风百叶窗 (长34mm, 宽4.6mm) + 尾部双排气对流窗口。 """ R_POD = 36.5 X_REAR = Z_R1 - 8.0 # -59.0 (8mm 后端安装板) X_FRONT = 5.0 # +5.0 (平齐电机输出端面,留 0.5mm 间隙至 P04) # 1. 规整同轴电机外壳舱 (Coaxial Nacelle, Ø73mm) pod_outer = (cq.Workplane("YZ", origin=(X_REAR, Y_AXIS, Z_RL)) .circle(R_POD) .extrude(X_FRONT - X_REAR) .edges(">X").chamfer(2.0) .edges("= 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. 竖直臂前侧外角倒角(5mm):平滑消除圆柱体以外的两侧垂直尖角 c_size = 5.0 c_left = (cq.Workplane("XY", origin=(HL.X_ARM - 35.0, y_hub_in, z_plate)) .moveTo(-0.1, -0.1) .lineTo(c_size, -0.1) .lineTo(-0.1, c_size) .close() .extrude(z_pitch - z_plate + 10.0)) c_right = (cq.Workplane("XY", origin=(HL.X_ARM + 35.0, y_hub_in, z_plate)) .moveTo(0.1, -0.1) .lineTo(-c_size, -0.1) .lineTo(0.1, c_size) .close() .extrude(z_pitch - z_plate + 10.0)) s = s.cut(c_left.val()).cut(c_right.val()) # 12. L 型转角连接处倒角过渡(倒角 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).chamfer(2.0).val() except Exception: pass return s def p_shoulder_roll_base(j2, j3, half=None): """P05_大臂旋转座 (Shoulder Pitch-to-Roll Base)""" return make_pitch_to_roll_bracket(j2, j3, Z_SH, Z_R3, name="P05_大臂旋转座") def p_humerus_bone(j3, j4): """P06_大臂骨架 (Humerus Bone): 受 J3 (大臂自转, -Z轴, hub@Z_HUB3=380) 驱动, 向下延伸构成大臂主体骨架,底端抱紧 M4 (肘部俯仰电机, +Y轴, 轴线位于 Z_EL=332.0)。 1. XZ 视图满宽 Ø70mm 直通过渡:左右两侧(X: -16.5..53.5)全宽平滑垂直到达 Z_EL,严格相切融入 M4 下圆柱,彻底消除腰身台阶断差; 2. YZ 视图连续相切双 S 弯有机顺接:顶端 Ø70mm 圆柱与底端前端套筒(Y=155.5)及后端安装板(Y=92.0)严格切线顺接,彻底消除水平方顶阶梯平台; 3. 取消侧壁贯穿开孔,消除镂空,保持骨架为浑然一体的高强度流线型实体。 """ # 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) # 顶端 Disc 结合面及底端过渡高度 z_top = 388.5 z_bot = Z_EL + 35.0 # 367.0 # 2. XZ 平面满宽流线轮廓(左右两侧严格相切于底端 M4 轴线圆柱,无收腰台阶): xz_w = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0)) .moveTo(-16.5, Z_EL) .lineTo(-16.5, z_top) .lineTo(53.5, z_top) .lineTo(53.5, Z_EL) .close()) xz_solid = xz_w.extrude(100.0, both=True).val() xz_shape = cq.Shape(xz_solid.wrapped) # 3. YZ 平面双向精确切线 S 弯顺接(前后平滑连续切线顺接,严控与 P07 动间隙): dz_f = (z_top - z_bot) / 2.0 # 10.75 mm dy_f = (163.0 - (Y_COVER - 0.5)) / 2.0 # 3.75 mm R_f = (dy_f**2 + dz_f**2) / (2.0 * dy_f) # 17.283 mm p_mid_f = (163.0 - dy_f, z_top - dz_f) theta_max_f = math.asin(dz_f / R_f) C_f1 = (Y_COVER - 0.5 + R_f, z_bot) p_quarter_f1 = (C_f1[0] - R_f * math.cos(theta_max_f / 2.0), C_f1[1] + R_f * math.sin(theta_max_f / 2.0)) C_f2 = (163.0 - R_f, z_top) p_quarter_f2 = (C_f2[0] + R_f * math.cos(theta_max_f / 2.0), C_f2[1] - R_f * math.sin(theta_max_f / 2.0)) dz_r = (z_top - z_bot) / 2.0 # 10.75 mm dy_r = (93.0 - (Y_REAR - 8.0)) / 2.0 # 0.5 mm R_r = (dy_r**2 + dz_r**2) / (2.0 * dy_r) # 115.8125 mm p_mid_r = (93.0 - dy_r, z_top - dz_r) theta_max_r = math.asin(dz_r / R_r) C_r1 = (Y_REAR - 8.0 - R_r, z_bot) p_quarter_r1 = (C_r1[0] + R_r * math.cos(theta_max_r / 2.0), C_r1[1] + R_r * math.sin(theta_max_r / 2.0)) C_r2 = (93.0 + R_r, z_top) p_quarter_r2 = (C_r2[0] - R_r * math.cos(theta_max_r / 2.0), C_r2[1] - R_r * math.sin(theta_max_r / 2.0)) yz_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0)) .moveTo(Y_REAR - 8.0, Z_EL) .lineTo(Y_COVER - 0.5, Z_EL) .lineTo(Y_COVER - 0.5, z_bot) .threePointArc(p_quarter_f1, p_mid_f) .threePointArc(p_quarter_f2, (163.0, z_top)) .lineTo(93.0, z_top) .threePointArc(p_quarter_r2, p_mid_r) .threePointArc(p_quarter_r1, (Y_REAR - 8.0, z_bot)) .close()) yz_solid = yz_w.extrude(50.0, both=True).val() yz_shape = cq.Shape(yz_solid.wrapped) c_int = BRepAlgoAPI_Common(yz_shape.wrapped, xz_shape.wrapped) c_int.Build() body = cq.Shape(c_int.Shape()) s = s.fuse(body) # 4. 电机中间定子散热裸露切除 (100% 裸露) s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_EL)) # 5. 保持腰身为坚固完整实体,移除侧向镂空贯穿槽 # 6. 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) s = make_direct_hub(s, j3, r_disc=35.0, z_out=58.0) return s def p_forearm_bone(j4, j5, half=None): """P07_小臂骨架 L型高刚度无干涉版(深度圆角优化版) 设计要点: 1. 改为与 P05/P08 统一的 L 型结构(L-bracket): - +Y侧垂直背脊立柱(Y: y_hub_in..y_out),避开上级大臂骨架 P06(Y <= 155.5) - 垂直立柱与下方 M5 电机水平座之间采用平滑双相切内凹大圆弧 (R=28.0mm) 过渡(用户要求圆弧过渡取代折角) - 彻底消除侵入下方旋转副的悬空小方块/阶梯(用户要求"把这个小的这块都给消掉") 2. 下方 M5 电机实行半包散热结构,底部与内腔开窗散热 3. J4/J5 轴俯仰大范围自由运动零干涉 4. 满足单实体 (solids=1) 与装配零碰撞。 """ CLEARANCE_GAP = 2.0 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 + 35.0 # 257.0 # 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_top_housing) .lineTo(y_front, z_top_housing) .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_top_housing),精确相切至立柱内侧面 (y_hub_in, z_arc_end) # 严控全域 Z >= z_top_housing,彻底消除侵入下方旋转副的悬空小方块与多余突起! R_arc = 28.0 y_arc_start = y_hub_in - R_arc z_arc_end = z_top_housing + 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_top_housing) .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 旋转圆盘动态避让切除(避让下级 J5 旋转部件) 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, 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) return s def p_wrist_bracket(j5, j6): """P08_腕偏转座 (Wrist Pitch-to-Roll Bracket)""" return make_pitch_to_roll_bracket(j5, j6, Z_WR, Z_R6, name="P08_腕偏转座") def p_tool_flange(j6): """P09_末端工具法兰 (Tool Output Flange): 受 J6 (工具自转, -Z轴, hub@Z_HUB6=64.0) 驱动,直接集成 3x M2 直锁电机输出轴, 彻底取消黄色独立联轴器与抱箍。 提供工业标准法兰接口,集成 4x M4 螺栓孔(BCD 40mm)、Ø20mm 定位止口及 Ø10mm 贯通走线孔, 可随时直接快拆安装各类微型夹爪或末端工具。 外轮廓统一 Ø70mm,与腕部整体平顺过渡。 """ z_bot = Z_HUB6 - PLATE_T - 9.0 s = cyl(35.0, z_bot, Z_HUB6, X_ARM, Y_AXIS) # Standard 4x M4 bolt holes on BCD 40mm 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) # Central locating pilot hole (Ø20mm x 2.5mm deep) + wire pass hole (Ø10mm) 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) # Direct motor output interface (3x M2 screws through to outside face) s = make_direct_hub(s, j6, r_disc=35.0, z_out=74.0) return s # ---------------------------------------------------------------- assembly ---------------- # ---------------------------------------------------------------- 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_肩叉", "J2": "P05_大臂旋转座", "J3": "P06_大臂骨架", "J4": "P07_小臂骨架", "J5": "P08_腕偏转座", "J6": "P09_工具法兰"} 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] # the motor body is the proximal side AB.GRP[driven[t]] = t AB.GRP.update({"P01_胸板": "root", "P02_底脚": "root", "P03_肩吊臂": "root"}) AB.JAW = {} parts_to_build = [ ("P01_胸板", "躯干/胸腔安装板", lambda: p_chest(), 0x4b5563, "手臂根部;12 厚板,顶面与肩吊臂桥板用 4x M4 连接,另有走线窗"), ("P02_底脚", "站立底脚", lambda: p_base_foot(), 0x374151, "0..%d,与机架安装面连接" % Z_CHEST0), ("P03_肩吊臂", "肩部基座(对标灵犀X1,胸部直出)", lambda: p_girdle(j1), 0x4b5563, "胸腔顶部直出基座;抱紧M1定子"), ("P04_肩叉", "正交肩肘(取消独立联轴器,3×M2螺钉直锁M1输出轴,贯通沉头孔)", lambda: p_fork(j1, j2), 0x2563eb, "受 J1 驱动,一体化 3x M2 螺钉直锁 M1 电机输出轴,贯通沉头孔;90°一体化高刚度正交转向肘,抱紧 M2 电机"), ("P05_大臂旋转座", "大臂旋转座(取消独立联轴器,3×M2螺钉直锁M2输出轴,贯通沉头孔)", lambda: p_shoulder_roll_base(j2, j3, None), 0x059669, "受 J2 驱动,彻底取消黄色独立联轴器;基座直接贴合 M2 输出轴,3 颗 M2 贯通沉头螺钉直锁输出轴;90°正交抱紧 M3 电机,中间 100% 敞开散热"), ("P06_大臂骨架", "大臂骨架(取消独立联轴器,3×M2螺钉直锁M3输出轴,贯通沉头孔)", lambda: p_humerus_bone(j3, j4), 0x0d9488, "受 J3 驱动,彻底取消黄色独立联轴器;一体化 3x M2 贯通沉头螺钉直锁 M3 输出轴;流线型工字骨架+走线槽,底端抱紧 M4 肘部电机"), ("P07_小臂骨架", "小臂骨架(L型圆弧加强版,R=28mm双相切平滑圆弧,消除悬空块)", lambda: p_forearm_bone(j4, j5, None), 0x7c3aed, "受 J4 驱动,彻底取消黄色独立联轴器;从动盘直接贴合 M4 输出轴,3 颗 M2 贯通沉头螺钉直锁输出轴;轻量高刚度骨架,底端抱紧 M5 电机"), ("P08_腕偏转座", "腕部转接座(取消独立联轴器,3×M2螺钉直锁M5输出轴,贯通沉头孔)", lambda: p_wrist_bracket(j5, j6), 0xd97706, "受 J5 驱动,彻底取消黄色独立联轴器;一体化 3x M2 贯通沉头螺钉直锁 M5 输出轴;两端刚性固定 M6 立式电机,中间 100% 敞开散热"), ("P09_工具法兰", "末端工具法兰(取消独立联轴器,3×M2螺钉直锁M6输出轴,贯通沉头孔)", lambda: p_tool_flange(j6), 0xdc2626, "受 J6 驱动,彻底取消黄色独立联轴器;一体化 3x M2 贯通沉头螺钉直锁 M6 输出轴;标准工业法兰接口,4x M4 螺孔 BCD 40mm + 定位止口与走线孔"), ] for idx, (name, cn, fn, color, note) in enumerate(parts_to_build, 1): print(f" [{idx}/9] 正在构建 {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 # mm of separation per joint in the exploded view def explode_chain(p, JS, cents, gmean): """Explode as a readable stack, not along the joint axes.""" 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, -SEP * k] j = JS.get(grp) if j: nm = p["name"] 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 def solids_first(path): return solids_of(cq.importers.importStep(path).val())[0].wrapped FILLET_R = float(os.environ.get("FILLET_R", "0")) if __name__ == "__main__": JS = build() if FILLET_R > 0: from arm_build import vol_of, bbox_str import filletlib as FL tot = 0 for p in PARTS: if not p["printed"]: continue others = [(FL._bb(q["shape"]), q["shape"]) for q in PARTS if q is not p and q["vol"] > 1.0] sh, n, sk = FL.round_internal(p["shape"], FILLET_R, others) dv = (vol_of(sh) - p["vol"]) / 1000.0 tot += n p["shape"], p["vol"], p["bb"] = sh, vol_of(sh), bbox_str(sh) print(" fillet %-10s %2d corners @R%.1f %+.2f cm3 (%d skipped: clearance)" % (p["name"], n, FILLET_R, dv, sk), flush=True) print(" fillet total: %d rounded corners" % tot, flush=True) print("=" * 80, flush=True) print("【单实体拓扑完整性校验】验证所有打印构件流形实体状态...", flush=True) print("=" * 80, flush=True) bad = 0 printed_list = [p for p in PARTS if p["printed"]] 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}/9] {p['name']:<14} : 体积 {p['vol']/1000.0:6.1f} cm³ | {status}", flush=True) if bad == 0: print("-> 单实体检查全部通过!9/9 构件均为完美单一流形拓扑 ✓\n", flush=True) else: print(f"-> 发现 {bad} 个非单实体构件!\n", flush=True) AB.explode_vec = explode_chain bom = AB.export(JS) cl = AB.collide() try: from build_viewer import rebuild_viewer rebuild_viewer() except Exception as e: print("rebuild_viewer error:", e, flush=True) print("=" * 80, flush=True) print("✅ 机器人手臂编译、导出与查看器发布全流程完成!", flush=True) print("=" * 80, flush=True) sys.stdout.flush() os._exit(0)