# -*- coding: utf-8 -*- """跨件共用的刀法与统一配方:X 向基础体、减重腔 helper、电机前端压盖、止口 hub、pitch->roll 转接座骨架。""" from arm.cad import arm_build as AB import cadquery as cq from arm import kinematics as HL import math from arm.cad.helpers import cyl, place from arm.cad.arm_build import COVER_Z, R, T, Ycyl, bx, prism from arm.kinematics import X_ARM, Y_AXIS, Y_COVER 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) # ------------------------------------------------------------------ 减重(镂空)-------- # 三个手法件(P04/P06/P07)在关节根部都留着一大块"实心过渡料":壁厚最深到 15~17.5mm, # 而 gyroid 填充只有 25~35%,等于用整块实心去抗一个安全系数 19~122 的截面。 # 下面两个 helper 把这块料掏掉,规则与 P03 的"帐篷形减重窗"一致: # 1) 不留封闭空腔 —— 减重腔要么朝上敞开,要么是有进出口的窗,打印不憋气、不积料; # 2) 斜面自支撑 —— 斜面与水平面夹角 >= 52°(与竖直面夹角 <= 38°),免支撑; # 3) 内角 R4 圆角 —— 挖出来的洞会把圆截面变成尖角,Kt 从 ~1.2 抬到 ~2.5, # 圆角把应力集中压回去; # 4) 只吃"距表面 > 8mm"的厚料,四周至少留 8mm 壁 —— 受力纤维一根不碰。 HOLLOW_WALL = 8.0 # 减重腔与原外表面的最小间距(= 留下的最小壁厚) HOLLOW_FILLET = 4.0 # 减重腔内角圆角,与 P03 竖板减重窗一致 def hollow_channel(x0, x1, y0, y1, z_floor, z_open, tag="", fil=HOLLOW_FILLET): """朝上敞开的减重槽:从 z_open 往下切到 z_floor,槽底水平(朝上受支撑)、侧壁竖直。 竖打(模型 Z = 打印 Z)下这个形状是零支撑的:槽底是水平面、槽壁全是竖直面, 顶部完全敞开因而不构成封闭空腔。切完该件变成 U 形槽钢而不是实心料: 抗弯靠两侧 X 向腹板(正好落在弯矩拉压区),中间中性层掏空几乎不损失 I。 x0/x1/y0/y1 用世界坐标;z_open 取一个保证贯通顶面的值。 """ tool = bx(x0, x1, y0, y1, z_floor, z_open) if fil > 0: try: tool = cq.Workplane(obj=tool).edges("|Z").fillet(fil).val() except Exception as e: print(" [减重槽] %s 内角圆角跳过: %s" % (tag, e), flush=True) print(" [减重槽] %s: X%.1f..%.1f Y%.1f..%.1f 自 Z%.1f 向上敞开 (四周留壁 >=%.0fmm)" % (tag, x0, x1, y0, y1, z_floor, HOLLOW_WALL), flush=True) return tool def hollow_tent_window(pts_xz, y0, y1, tag="", fil=HOLLOW_FILLET): """帐篷形减重窗:XZ 平面画轮廓,沿 Y 双向切穿。 轮廓下宽上窄(拱形)——窗顶上方的料形成一个尖顶,斜面与水平夹角 >= 52° 自支撑, 只有顶部一小段平顶需要桥接(<=20mm,与 P03 同)。轮廓在 X 方向不能吃满整个截面, 两侧必须留墙,否则该高度上零件会被切断、失去单实体。 """ # XZ 工作平面的法线是 -Y,extrude 会朝 -Y 走,所以起点放在 y1 才能覆盖 y0..y1 tool = (cq.Workplane("XZ", origin=(0, y1, 0)).polyline(pts_xz).close() .extrude(y1 - y0).val()) if fil > 0: try: tool = cq.Workplane(obj=tool).edges("|Y").fillet(fil).val() except Exception as e: print(" [减重窗] %s 内角圆角跳过: %s" % (tag, e), flush=True) print(" [减重窗] %s: XZ 轮廓 %d 点, 沿 Y %.1f..%.1f 切穿 (帐篷形, 斜面自支撑)" % (tag, len(pts_xz), y0, y1), flush=True) return cq.Shape(tool.wrapped) # 各关节"电机前端夹紧"统一配方(J2/J3/J4/J5 通用,J1 由 P03 自己那套 S04 凸台实现)。 # 电机前端盖外端面:Y 轴关节(J2/J4/J5)在 Y_COVER=156,压盖朝 +Y 生长; # Z 轴关节(J3,roll 电机,轴向 -Z)在 Z_R-56,压盖朝 -Z 生长(-Z 侧是从动件)。 # 压盖背面与之 0.0 间隙面压,螺钉只提供压紧力。 # 沉孔相位按【关节局部相位】定义(BCD55,72.2°+90k):换算到全局后 Y 轴关节是 # 17.8°+90k(与 P03→M1 的 S04 一致),Z 轴关节是同一组数落在全局 (x,y) 平面, # 两种轴向的物理指向完全一致 —— 全机同一套孔位约定,改一处必须同步另一处。 CLAMP_BCD = 27.5 CLAMP_CLOCK = (17.8, 107.8, 197.8, 287.8) # Y 轴关节的全局 (x,z) 相位 CLAMP_CLOCK_LOCAL = tuple((360.0 - a) % 360.0 for a in CLAMP_CLOCK) # = 72.2+90k CLAMP_ROOT = 0.5 # 根部薄环厚度:与前端定位环前端面共面,保证熔接成一体 CLAMP_T = 5.0 # 压盖本体厚度 CLAMP_CLR = 1.0 # 从动件让位间隙:与 Y 轴关节 hub_gap=6.0 同一个道理 CLAMP_BACKOFF = CLAMP_T + CLAMP_CLR - CLAMP_ROOT # =5.5 从动件毂面整体后移量 # 【2026-10-05e】"电机前端围挡"统一整改开关,作用于全机 5 处电机前端(J2/J3/J4/J5/J6)。 # 围挡本体 = 各零件前端的 **前端定位环**(`front_collar_pocket` 把座孔从 R30.15 扩到 # R30.25 时留下的那圈 r30.25..35、6mm 高的环墙)。它比电机前端盖 Ø60.3 凸出 4.75mm, # 把电机前端盖整圈围住;J2/J3/J4/J5 上还骑着一枚 R35 前端压盖(Ø70 再凸 3.2mm)。 # 用户 2026-10-05 先在 J3/M3 尾部指出,2026-10-05d 只切敞口那一圈解决;本次把同一刀 # 推广到其余四处(J2/M2、J4/M4、J5/M5、J6/M6),几何完全同构。 # 为什么不整圈削到与电机齐平(R35 -> R31.8): # 1. 那圈壁与本体在两个方位与外壁**相切**,整圈切完那两片切向楔形会脱空成孤立碎块 # (实测 1.32mm3),破坏单实体拓扑; # 2. 压盖根部只有 0.5mm 熔接带,环削窄后连接面从 2.37mm2 掉到 0.78mm2, # 4xM3x8 的夹紧力会把压盖从本体上拉下来。 # 所以只切**敞口那一半**:背脊侧定位环继续定位电机前端盖、并作为压盖唯一且足够强的 # 支撑/熔接面;压盖本体、Ø37 让位孔、4xM3x8 沉孔、腔口下沿全部不动。 # 【2026-10-05e / 2026-10-07 修复:电机装配敞口通道贯通,消除两端阻碍围挡】 # 参考 P05 大臂旋转座设计,电机两头都不要有围挡挡住,电机可顺畅装配入座: # 1. 承压面段通过 R35 圆腔切平,彻底消除尾端阻碍电机的环形围挡; # 2. 前端定位环通过 cut_motor_fence 切除敞口那一半,消除前端阻碍装配的围挡; # 3. 背脊侧整圈定位环完整保留,继续定位电机前端盖并作为压盖唯一且足够强的支撑/熔接面。 FENCE_OPEN_CUT = True def cut_motor_fence(shape, j, tag, name3, z0=49.4, z1=55.5): """只切掉电机前端定位环在敞口侧那一圈环墙。""" if not FENCE_OPEN_CUT: return shape """【2026-10-05e / 2026-10-07 整改】只切掉电机前端围挡(前端定位环)在**敞口那一半**的环墙。 z1 设为 55.5(等于前端定位环外缘 Y_COVER-0.5,不啃压盖本体): 原取 56.1 导致切削盒切入前端压盖内侧贴合面 0.1mm(本应在 56.0/Y156.0), 造成敞口侧下陷 0.1mm,与背脊侧在关节中心面产生错位台阶与分界线。 取 55.5 后定位环完整切除,压盖贴合面在整圈 360° 完整保留为共面的平圆环(Y156.0),分界线彻底消除。 与 2026-10-05d 给 P05/M3 那一刀完全同源 —— 当时那记全局盒切 (X_ARM±40, Y_AXIS-40..Y_AXIS+3, Z388.9..395.6) 换算到 J3 局部系正好是 x±40 / y -3..+40 / z 49.4..56.1。本函数把它从"P05 专用全局盒"泛化成 "按关节局部坐标切半个环",J2/J4/J5/J6 于是同一刀通用。 局部坐标约定(见 trs/place):z 沿电机轴、指向电机前端盖(外端面在 z=COVER_Z=56), x/y 在垂直于轴的平面内。**局部 +y 一律是半包敞口侧** —— 实测 5 处电机舱的 本体(环外 r=37/40 有料处 = 背脊侧)方位角全部落在局部角 180..360 = 局部 -y 侧: J2/M2 背脊 240..315 J3/M3 背脊 225..315 J4/M4 背脊 210..330 J5/M5 背脊 210..330 J6/M6 背脊 195..345 故敞口方向无需逐件配置,统一取局部 +y。 为什么一对 z 参数能覆盖全机:5 处电机的前端定位环都恰好落在**局部 z 49.5..56** (Y 轴电机前端环 Y150..156 -> z50..56;roll 电机 Z395.5..389 -> z49.5..56)。 被切掉的只有 r30.25..35 那一圈环墙在敞口侧的部分;压盖本体(4xM3x8 沉孔段)、 Ø37 让位孔、腔口下沿、背脊侧整圈定位环全部不动。 切口边界取在局部 y=-3.0 而不是 0:环壁在局部 y=-3 处仍与背脊侧实体连通, 切出来的是真正的缺口而不是零厚度缝,单实体拓扑天然保持 (P05 实测切完 solids=1;2026-10-05c 整圈削 R31.8 那版会掉出 1.32mm3 碎块)。 """ if not FENCE_OPEN_CUT: return shape ymin = -3.0 if name3 in ('P05', 'P08') else 0.0 shape = shape.cut(j.L(bx(-40.0, 40.0, ymin, 40.0, z0, z1))) print(" [围挡] %s: 只切敞口侧那一圈前端定位环(局部 y %.1f..+40, z %.1f..%.1f);" "背脊侧整圈保留定位前端盖并支撑压盖,压盖本体/沉孔/腔口下沿不变" % (name3, ymin, z0, z1), flush=True) return shape def _clamp_cyl(axis, px, pz, py, z_face): """压盖刀具坐标系:C(r, s0, s1, du, dv) = 沿电机轴 s 区间、偏离轴线 (du,dv) 的圆柱。 s>0 一律"背离电机",两个轴向同号(s=0 = 电机前端盖外端面)。""" if axis == "Y": return lambda r, s0, s1, du=0.0, dv=0.0: Ycyl(r, Y_COVER + s0, Y_COVER + s1, px + du, pz + dv) if axis == "Z": return lambda r, s0, s1, du=0.0, dv=0.0: cyl(r, z_face - s1, z_face - s0, px + du, py + dv) raise ValueError("motor_front_clamp: axis 只能是 Y 或 Z,收到 %r" % (axis,)) def clamp_hole_drills(px, pz, plate_t=CLAMP_T, axis="Y", py=0.0, z_face=None): """4xM3x8 压盖钻模:Ø6.0 沉孔(s=2..plate_t,深 3.0)+ Ø3.4 通孔(s=-1.0..2)。 与 motor_front_clamp 共用同一组半径/相位/深度,孔位只有这一个事实源。 单独导出是因为**压盖上的孔在预钻阶段可能被本体填回来**:本体若有料占住沉孔方位, `fuse(压盖)` 取的是并集,不会把本体那部分料挖掉,于是并集上孔就消失了 (P04 立柱整体化后 Y158..161 的 +Z 半侧被填成实心,4 个孔只剩 2 个)。 所以对"熔接之后的最终并集"必须再钻一次;本体没料的方位上这一刀是空操作。 """ C = _clamp_cyl(axis, px, pz, py, z_face) s0, s1 = -CLAMP_ROOT, plate_t drills = [] for deg in CLAMP_CLOCK_LOCAL: rad = math.radians(-deg) # 全局相位 = -局部相位 fu, fv = CLAMP_BCD * math.cos(rad), CLAMP_BCD * math.sin(rad) drills.append(C(3.0, 2.0, s1 + 0.5, fu, fv)) # Ø6.0 沉孔 drills.append(C(1.7, s0 - 0.5, 2.0, fu, fv)) # Ø3.4 通孔 return cq.Compound.makeCompound(drills) def motor_front_clamp(px, pz, plate_t=CLAMP_T, tag="", axis="Y", py=0.0, z_face=None): """电机前端压盖:把"只被后端安装板单面锁死"改成前后双向面压夹紧。 原设计里每个关节的电机都只有后端板 4xM3x10(S02)锁在尾端面上,前端盖完全裸露, 大扭矩下电机绕自身轴在 Ø60.3 座孔里点头/翘起。这里在电机前端盖外端面加一枚 与后端板同径的 R35 压盖,用 4xM3x8(BCD55)在前端盖上再夹一次 —— 电机被前后两块 同心 Ø60.3 端面双向夹死,倾斜自由度归零;夹紧力全部走面压,螺钉不参与定位。 两个轴向共用同一套尺寸,只换刀具体系(不重复实现任何半径/相位): axis="Y"(J2/J4/J5):关节轴过 (px, pz),前端盖外端面在 Y_COVER,压盖朝 +Y 长; axis="Z"(J3 roll,轴向 -Z):关节轴过 (px, py),前端盖外端面在 z_face,压盖朝 -Z 长。 局部坐标(s 从前端盖外端面起算,s>0 一律"背离电机",两个轴向同号): - 压盖 R35,s 从 -CLAMP_ROOT(与前端定位环前端面共面,保证熔接成一体)到 plate_t; - 背面精确落在 s=0,不让根部那 0.5mm 啃到端盖 R30.15 的外圆; - Ø37 中央让位孔(R18.5)让开从动件的直锁凸台 R17.8 与 3xS01 输出螺钉头; - 4xM3x8:Ø3.4 通孔 + Ø6.0 沉孔深 3.0(自前端面沉到 s=2),圆柱头全沉入, 头面压孔口台阶,中间 2.0mm 压盖本体受压。 """ C = _clamp_cyl(axis, px, pz, py, z_face) s0, s1 = -CLAMP_ROOT, plate_t fp = C(35.0, s0, s1) fp = fp.cut(C(18.5, s0 - 0.5, s1 + 0.5)) # Ø37 中央让位孔 # 【2026-10-03h 接缝整改】根部让位槽原来切 R30.15,比腔壁 BAY_R=30.50 小 0.35, # 于是压盖根部那圈 r30.15..35 的定位唇内壁比腔壁**内凸 0.35**,从腔里看就是 # 腔口一圈 0.5mm 宽、0.35mm 深的环槽(P04/P05/P06/P07 各有一圈,实测台阶面 # 31.2mm2 + R30.15 环带 44.4mm2,四件完全相同)。这刀是腔还是 R30.15/R30.25 # 那个年代留下的:当时必须让到电机实测外圆,压盖根部才啃不到电机。 # 现在主腔已统一到 BAY_R,电机实测 R30.15,让位槽取 BAY_R 即可: # 定位唇内壁与腔壁同一根正圆,两张圆柱面首尾相接,中间不再有任何台阶, # 对电机的单边间隙仍是 0.35(唇内径 Ø61.0 > 电机 Ø60.3)。 fp = fp.cut(C(AB.BAY_R, s0 - 0.5, 0.0)) # Ø61.0 根部让位槽(= 腔径) fp = fp.cut(clamp_hole_drills(px, pz, plate_t, axis, py, z_face)) where = ("Y%.1f..%.1f" % (Y_COVER - CLAMP_ROOT, Y_COVER + plate_t)) if axis == "Y" \ else ("Z%.1f..%.1f" % (z_face - plate_t, z_face + CLAMP_ROOT)) print(" [前端压盖] %s: R35 %s, 4xM3x8 BCD55@%s (%s 轴)" % (tag, where, "/".join("%.1f" % d for d in CLAMP_CLOCK), axis), flush=True) return fp def hub_m2_holes(shape, j, cb_z1, th_z1=58.0, th_z0=54.0, cb_z0=58.0, th_r=1.4, cb_r=2.8, clock=None): """在从动毂上开 3x M2 直锁螺钉孔:Ø2.8 通孔 + Ø5.6 沉孔,BCD 见 AB.M["m2_bcd"]。 全机 5 处(J1 叉板 / J2+J3 直连毂 / J4+J5 直连毂)统一走这里,各调用点只传自己那两个 z。 【2026-10-07 修复】J1 直连电机输出转子,使用转子真实孔位 AB.M["m2_clock"](随 j1.t 旋转), 确保肩叉 P04 保持垂直位姿且沉孔与 M1 转子螺孔完全同心。 """ if clock is None: clock = AB.M["m2_clock"] if j.jid == "J1" else AB.HUB_M2_CLOCK for a in 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) shape = shape.cut(j.L(cyl(th_r, th_z0, th_z1, u, v))) shape = shape.cut(j.L(cyl(cb_r, cb_z0, cb_z1, u, v))) return shape def make_direct_hub(shape, j, r_disc=35.0, z_out=74.0, backoff=0.0): """优化直连法兰:增加导向倒角 backoff = 毂面沿电机轴**远离电机**的让位量(2026-10-03 新增,目前只有 P09 用)。 场景:J6/M6 补上前端压盖后(见 p_wrist_bracket 的 roll_clamp=True),压盖占 局部 z 55.5..61.0(= 全局 Z 120.5..115),而毂面原来在局部 56.5,两者直接打架。 按 P06 给 J3 让位的同一套办法,把毂面整体后移 CLAMP_BACKOFF=5.5: 毂面 56.5 -> 62.0,压盖前端面在 61.0,中间正好留 CLAMP_CLR=1.0。 只动"外端"坐标,不动"电机端"坐标,否则咬合量就变了: 直锁凸台起点 54.942 不动、长度 54.942..56.5 -> ..62.0 加长 5.5, 进入电机输出毂的深度与改前完全一致(与 p_humerus_bone 的 J3 处理同源); 导向锥 54.5..54.942 不动; 3xS01 输出螺钉让位通道与 4xM2 直锁沉孔的**内**端(54.0 / 58.0)不动, 只把外端(z_out+30 / z_out+40)跟着放长。 """ disc = cyl(r_disc, 56.5 + backoff, z_out + backoff) boss = cyl(17.8, 54.942, 56.5 + backoff) 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 + backoff))) return hub_m2_holes(shape, j, cb_z1=z_out + 40.0 + backoff) def make_pitch_to_roll_bracket(j_pitch, j_roll, z_pitch, z_r, name="bracket", hub_gap=1.0, roll_clamp=False, fillet_r=1.5, flank_fillet_r=3.0): """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)。 hub_gap = 从动法兰盘背面与上级电机前端盖之间的轴向让位间隙。 默认 1.0(贴合防磨)。P05 传 6.0:J2 处 P04 新增了 M2 前端压盖 (Y 155.5..161.0),必须让出这 6.0mm 才能装下;见 p_shoulder_roll_base。 roll_clamp = 本件作为 roll 电机【前端夹紧件】时为 True(J3/P05 夹 M3)。 roll 电机(J3/J6)轴向是 -Z,前端盖外端面在 z_r-56,-Z 侧是从动件: 压盖朝 -Z 长(Z_COVER-5..Z_COVER),从动件毂面必须整体后移 CLAMP_BACKOFF=5.5 让位(与 Y 轴关节 hub_gap=6.0 同一个道理,只是基准间隙 0.5 而非 1.0)。 P08 自 2026-10-03 起也传 True:M6 前端压盖补齐,对侧 P09 让位 CLAMP_BACKOFF (见 make_direct_hub 的 backoff),五处电机腔至此全部是「后端板 + 前端压盖」 双向面压,配方同源。 fillet_r = 立柱顶面与竖直臂交汇处的 L 型内转角过渡圆角半径(mm,默认 1.5)。 半径调小为 1.5mm 确保在 OCC 中稳定边倒圆成型(小于临界高度 2.0mm,彻底避免算法异常), 同时不生成多余外凸台阶,平滑消除 90° 锐角应力集中。 flank_fillet_r = 侧壁平直立面与正面 Loft 放样曲面交汇处空间斜向折棱圆角半径(mm,默认 3.0)。 【2026-10-07】消除侧壁与正面交界处长约 72mm 的尖锐硬折棱,使侧腹部外轮廓更加饱满圆润。 """ z_bot = z_r - 56.0 # roll 电机底端 (front collar) z_plate = z_r + 8.0 # roll 电机顶端安装板 (rear plate top);见第 8 步 t_plate 说明 y_out = HL.Y_HUB + HL.PLATE_T # 174.0 (俯仰直连法兰外侧面) y_hub_in = HL.Y_COVER + hub_gap # 157.0 / (P05) 162.0 从动法兰内侧面 z_hub_in = y_hub_in - HL.Y_REAR # 法兰盘背面的电机局部 z(凸台长度跟着它走) 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侧背脊壳体加强 # 切削高度上限直通承压面(z_r + AB.REAR_SEAT),消除侧边悬垂突耳,使侧壁边缘与下方完全平直对齐 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 + AB.REAR_SEAT) 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. 下方电机安装孔位与定位止口 # t_plate=8.0:Joint.pocket 的尾端面承压面让空了 MOTOR_TOL=1.0,承压面到螺钉沉孔 # 台阶为 t_plate-cb_depth-REAR_SEAT = 8-4-1.6 = 2.4mm 膜厚(结构强度大幅提升)。 s = j_roll.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) # 【2026-10-03i 接缝整改】front_collar_pocket 已删。 # 它切的是 cyl(BAY_R, z0=50.0, z1=56.5),而紧接着的 Joint.pocket 切的是 # cyl(BAY_R, -REAR_SEAT, COVER_Z+front_pad) —— **同一根圆柱、同一个半径**, # 前者完全是后者的子集。两次半径相同的圆柱布尔相减,等于让 OCC 在腔壁上 # 留下一条 s=50.0 的零宽棱:实测 P05 的 R30.500 腔壁被切成 5090.5 + 493.9 # 两张面(-2.6..50.0 与 50.0..55.5),P06 同样在 50.0 处裂开。着色时那条 # 棱就是腔里横穿的一道线。P04/P08 本来就没调它,腔壁是完整一张面。 s = j_roll.pocket(s) # 9. 电机定子配合空腔 # 【2026-10-05f】原 cyl(30.5, ...) 定子配合空腔已删,同上:已被主腔 BAY_R 覆盖。 # 10. 俯仰轴直连法兰结构(凸台延伸至法兰盘背面之后 2.5mm,跨过间隙与法兰盘牢固熔接, # 保持单实体)。凸台长度跟着 y_hub_in 走:法兰盘后移时凸台同步加长, # 3xS01 输出螺钉的头面台阶(局部 z=58)、杆长、咬合量与改前完全一致。 boss = cyl(17.8, 54.942, z_hub_in + 2.5) s = s.fuse(j_pitch.L(boss)) s = s.cut(j_pitch.L(cyl(3.5, 54.0, 74.0 + 30.0))) s = hub_m2_holes(s, j_pitch, cb_z1=74.0 + 40.0) # 11. L 型转角连接处圆角过渡(圆角半径调小为 1.5mm): # 平滑消除立柱顶面与竖直臂交汇处的 90° 尖角与应力集中, # 半径调小至 1.5mm 确保在 OCC 中稳定成型(小于临界高度 2.0mm,彻底避免算法异常), # 局部圆角不切入两侧轮廓,完全避免任何外凸台阶,流线自然过渡。 if fillet_r > 0: 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(fillet_r).val() except Exception as e: print(f"[{name}] L型转角圆角异常: {e}") # 12. roll 电机(M3)前端压盖:与 P04/P06/P07 的 M2/M4/M5 压盖同一套配方, # 只是 roll 电机轴向为 -Z,压盖朝 -Z 长(见 motor_front_clamp 的 axis="Z")。 # 熔接必须放在最后:第 6/7/8/9 步所有 R30.15~30.5 圆柱刀具都走完之后才接, # 否则压盖根部那 0.5mm 薄环会被 front_collar_pocket / 定子孔啃掉一圈。 if roll_clamp: # tag 里的电机号从 j_roll.jid 取(P05->M3 / P08->M6),别再写死 M3 —— # 写死的话 P08 的构建日志会谎报自己夹的是 M3,排查时容易被带偏。 s = s.fuse(motor_front_clamp(HL.X_ARM, 0.0, tag="%s/M%d" % (name[:3], int(j_roll.jid[1])), axis="Z", py=HL.Y_AXIS, z_face=z_r - COVER_Z)) # 13.【2026-10-05d】只切"围挡"那一圈:敞口(-Y)侧的前端定位环 + 压盖根部薄环。 # 用户 2026-10-05 指出这圈环墙把 M3 前端盖和 P06 的 J3 凸台围在中间;我第一版 # 把整个前端(定位环 + 压盖 + 腔口下沿 389->395)都切了,电机前端没了支撑面 —— # 过度。现在只切敞口这一圈,Y 界限与第 6 步 cut_open 完全一致 # (Y_AXIS-40 .. Y_AXIS+3),保证 -Y 半圈从 Z=395.5 一路开到压盖背面 Z=389; # 背脊(+Y)侧整圈定位环保留: # - 继续定位 M3 前端盖(内孔 r30.25,前端盖 Ø60.3 -> 单边 0.10mm); # - 继续当 R35 压盖唯一且足够强的支撑面(4.75mm 宽 x 0.5mm 熔接带,本次改动 # 完全不碰)→ M3 仍是后端板 4xM3x10 + 前端压盖 4xM3x8 双向面压夹紧; # - 压盖本体 / Ø37 让位孔 / 4xM3x8 沉孔 / 腔口下沿 z_bot=389 全部不动, # 压盖敞口侧的端面从 Z=389.5 降到 389 与背面齐平,视觉上不再凸出。 # 为什么不能整圈削到与电机齐平(R35->R31.8,见 FENCE_OPEN_CUT 处的说明): # ① 这圈壁与第 3 步 Loft 在 X=±35 处与 Ø70 柱面**相切**,整圈切完那两片切向楔形 # (实测 1.32mm3)会脱空成孤立碎块,破坏单实体拓扑; # ② 压盖根部只有 0.5mm 熔接带,环削到 1.55mm 宽后连接面从 2.37mm2 掉到 0.78mm2, # 4xM3x8 的夹紧力会把压盖从本体上拉下来。 # 附带效果:盒切把 Loft 在 X=±35 与 Ø70 柱面相切的那两片切向楔形一起带走 # (楔形位于 Y_AXIS 附近 < Y_AXIS+3),不会留下孤立碎块 -> 单实体拓扑保持。 # 13.【2026-10-05d / 2026-10-07】删掉 P05/P08 前端定位环在敞口侧那半圈环墙(见 FENCE_OPEN_CUT)。 if (roll_clamp or FENCE_OPEN_CUT) and name.startswith(('P05', 'P08')): s = cut_motor_fence(s, j_roll, "%s/M%d" % (name[:3], int(j_roll.jid[1])), name[:3]) # 14.【2026-10-07 表面流线优化】侧壁与有机放样曲面交汇处斜向脊线圆角(flank_fillet_r): # 平滑消除侧壁平直立面与正面放样弧面交汇处的 71.6mm 空间硬折棱, # 沿双侧各倒 R3.0 平滑过渡圆角,使大臂旋转座/腕偏转座外表面流线浑然一体、手感圆润饱满 if flank_fillet_r > 0: try: wp_s = cq.Workplane(obj=s) e_flank = [ e for e in s.Edges() if e.geomType() == 'BSPLINE' and (abs(e.BoundingBox().xmin - (HL.X_ARM - 35.0)) < 0.2 or abs(e.BoundingBox().xmax - (HL.X_ARM + 35.0)) < 0.2) and (e.BoundingBox().ymax - e.BoundingBox().ymin) > 20.0 and (e.BoundingBox().zmax - e.BoundingBox().zmin) > 30.0 ] if len(e_flank) == 2: s = wp_s.newObject(e_flank).fillet(flank_fillet_r).val() except Exception as e: print(f"[{name}] 侧壁放样斜棱圆角异常: {e}") return s def make_surface_tie_bridge(x, y_surf, z, w_x=10.0, l_z=8.0, h_y=3.2, slot_w=3.2, slot_h=1.8, tunnel_dir='X'): """实体表面外凸式扎带拱桥 (Cable Tie Saddle Bridge)。 在 3D 打印件外壁表面一体化成型微型拱桥,扎带完全在外部穿通锁紧,不挖穿外壳壁厚、不进入内部空腔。 """ total_y = h_y + 1.0 cy = (y_surf - h_y) + total_y / 2.0 body = cq.Workplane('XY', origin=(x, cy, z)).box(w_x, total_y, l_z) try: body = body.edges('|X and