refactor(layout): src/ 收成顶层包 arm/,入口统一 python -m;提升承担构建的 scratch 脚本

单文件约束取消后,Python 侧没必要再靠 16 处 sys.path.insert 维持扁平模块互认。
现在按职责分包:
  arm/paths.py 路径唯一来源(含新增 CACHE、TPL)
  arm/constants.py 实测接口数值(原 armcore 的 M/C/CLR/FIT/MOTOR_TOL 段)
  arm/cad/{helpers,arm_build,part_tools,parts/} 几何与装配
  arm/kinematics.py(原 human_layout.py,唯一运动学事实源)
  arm/analysis/{sweep_v2,strength_check,torque_balance,finalize_travel,ws_sweep,gen_env_mesh}
  arm/dataio/{screw_mesh,draw_sheet}   arm/viewer/{build_viewer,shot}
  arm/build_model.py(原 arm_model.py)

顺带修掉的既有缺陷(都是实测撞出来的,不是预防性改动):
- arm_model 第二次 sys.path 跳到 gitignore 的 scratch/ 去 import 构建必需的包络脚本:
  ws_sweep/gen_env_mesh 提升进 arm/analysis/,缓存挪到 data/cache/(gitignore,可重生成)。
- finalize_travel / draw_sheet 顶层就是"算完写文件",一次 importlib 冒烟测试就把
  docs/travel.json 重写了一遍;现在被 import 直接抛错。
- strength_check 用 OUT.replace("/out","/web") 靠字符串形状推路径,改成直接用 WEB。
- build_viewer 里 WORKSPACE 使用点漏改导致 NameError —— 已由构建关卡抓到并修掉。

os._exit 的处置与计划里的假设相反:实测光是 `python -c "import arm.cad.helpers"`
就会在解释器退出时段错误(exit 139,OCC 静态析构),全流程跑完也一样。所以入口
脚本尾部保留 flush + os._exit,并把原因写进注释;被 import 时不生效。

关卡(tests/):
- verify.py 一条命令跑三层:产物 md5、IK 数值 exact 段、截图。
- 截图基线重做并分级:同一构建连跑两轮实测 102 张逐字节可复现(要求 md5 相同),
  3 张线束 x-ray 不可复现(mean<=0.0017、无像素差>8)改为与参考像素比容差;
  这 3 张不再入库,免得每次跑关卡都脏 git。
- 新增 ?view=sheet / ?sheet=N / ?theme / ?guide=N 四个 URL 参数:工程图与装配向导
  占了约一半 CSS 和 700 行 JS,原先没有任何截图覆盖,拆它们等于没有关卡。
- 关卡灵敏度已验:改坏 IK 基线一个数 -> FAIL;换旧版求解器 -> 5 处 exact 差异。

实测:python -m arm.build_model 退出码 0,全部产物 md5 与基线一致,
105 视图截图全过,IK exact 段逐字节一致。
This commit is contained in:
cbb
2026-10-15 03:31:20 +08:00
parent b32d44efe0
commit 649bd69810
57 changed files with 843 additions and 453 deletions
+8
View File
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
"""每个零部件一个模块:几何、件号/配色/说明、装配帧归属都在这里。
arm_model.build() 只按 PART_MODULES 顺序收集 specs();改哪件就开哪个文件。
"""
from arm.cad.parts import p01_column, p02_base, p03_girdle, p04_fork, p05_roll_base, p06_humerus, p07_forearm, p08_wrist, p09_flange
PART_MODULES = [p01_column, p02_base, p03_girdle, p04_fork, p05_roll_base, p06_humerus, p07_forearm, p08_wrist, p09_flange]
+187
View File
@@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
"""P01 立柱三段(A/B/C):躯干承力柱 + 阶梯榫卯与防反装对锁孔。"""
import cadquery as cq
from arm.cad.helpers import cyl
from arm.cad.arm_build import Ycyl, bx
from arm.kinematics import Y_AXIS, Z_CHEST1
from arm.cad.part_tools import Xcyl
# ========== 躯干部分 ==========
# ========== 躯干/测试台架部分 (后置防倾翻高刚性机架) ==========
X_COL = -115.0
Y_COL = Y_AXIS # 128.0
Z_LOCK = 463.0 # 横向防脱锁紧螺钉高度(立柱后壁 → 榫头,M4x16 自攻)
def p_chest():
"""P01_胸腔支撑立柱 (High-Rigidity Box Mast Tower - 后置台架立柱):
1. 66x66mm 封闭中空厚壁箱型柱(壁厚 11mm,内腔 44x44),中心在 (-115.0, 128.0),位于手臂正后方;
2. 顶部接收 P03 43.4x43.4x15mm 定位榫头(44x44 R4 内腔)、4x 竖向安装孔 + 1x 横向防脱锁紧孔
(全部 Ø3.9 直孔:M4x16 从外部直接拧入自攻成型,无过孔/无沉孔/无预埋);
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))
# 顶部 4x 安装孔:统一 Ø3.9 直孔(M4x16 直接拧入自攻成型;只打这一个孔,无过孔/无沉孔),深 20mm
# 位于 66x66 顶环 11mm 实体壁内(内腔 22 → 外壁 33),避开 44x44 内腔与 R4 内腔圆角;
for xx in (X_COL - 27.5, X_COL + 27.5):
for yy in (Y_COL - 13.5, Y_COL + 13.5):
s = s.cut(cyl(1.95, z_top - 20.0, z_top + 0.01, xx, yy))
# 横向防脱锁紧孔(后壁 → 榫头):Ø3.9 贯穿 11mm 后壁 + 榫头同径(M4x16 拧入自攻)
s = s.cut(Xcyl(1.95, X_COL - 34.0, X_COL - 20.0, Y_COL, Z_LOCK))
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)、以及统一 Ø3.9 直孔的分段对锁孔(M4x16 从外面直接拧入自攻成型,
母口壁与公榫壁同径,无过孔/沉孔/预埋,前后两面对称各一颗);
孔位相对立柱中心 −16 / +22 非对称(物理防反装防呆);本体 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)
# 分段对锁孔:统一 Ø3.9 直孔(M4x16 从外面直接拧入自攻成型;母口壁与公榫壁同径,
# 没有过孔/沉孔等其它孔),前后两面对称;杆尖伸过榫壁的一段在内腔侧留 Ø4.5 让位。
def _seam_holes(part, xx, z):
return (part.cut(Ycyl(1.95, Y_COL - 33.6, Y_COL - 21.4, xx, z))
.cut(Ycyl(1.95, Y_COL + 21.4, Y_COL + 33.6, xx, z))
.cut(Ycyl(2.25, Y_COL - 21.7, Y_COL - 15.5, xx, z))
.cut(Ycyl(2.25, Y_COL + 15.5, Y_COL + 21.7, xx, z)))
# --- 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 = _seam_holes(p01_a, xx, z_bolt1) # A-B 缝对锁孔(Ø3.9,前后两面)
# 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 = _seam_holes(p01_b, xx, z_bolt1) # A-B 缝对锁孔(Ø3.9,前后两面)
p01_b = _seam_holes(p01_b, xx, z_bolt2) # B-C 缝对锁孔(Ø3.9,前后两面)
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 = _seam_holes(p01_c, xx, z_bolt2) # B-C 缝对锁孔(Ø3.9,前后两面)
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
JOINT = 'root'
def specs(JS):
"""本件的 (件号, 中文名, 构建 thunk, 配色, 说明) 清单;顺序即 BOM 顺序。"""
p01_a, p01_b, p01_c = split_p01_poka_yoke(p_chest())
return [
("P01_A_立柱下段_三角形30%_墙4底5_竖打", "立柱下段", lambda: p01_a, 0x0284c7,
"200mm适配分段A:高166mm+16mm阶梯公榫+顶端 2x Ø3.9 对锁孔(M4x16 自攻)+底部50x50x12榫头+P01-A 竖排TOP刻字"),
("P01_B_立柱中段_三角形30%_墙4底5_竖打", "立柱中段", lambda: p01_b, 0x2563eb,
"200mm适配分段B:高165mm+双端公母榫+两端各 2x Ø3.9 对锁孔(M4x16 自攻,非对称孔位防反装)+P01-B 竖排TOP刻字"),
("P01_C_立柱上段_三角形30%_墙4底5_竖打", "立柱上段", lambda: p01_c, 0x1d4ed8,
"200mm适配分段C:高149mm+底端母榫+底端 2x Ø3.9 对锁孔(非对称孔位防反装)+顶面P03安装台(4x Ø3.9 安装孔 + 1x Ø3.9 横向锁紧孔)+P01-C 竖排TOP刻字"),
]
+293
View File
@@ -0,0 +1,293 @@
# -*- coding: utf-8 -*-
"""P02 底座三段(中核心 / 前向力臂 / 后向力臂):后置防倾翻机架与防呆母榫。"""
import cadquery as cq
from arm.cad.helpers import cyl
from arm.cad.parts.p01_column import X_COL, Y_COL
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 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(front_mortise)
p02_c = p02_c.cut(cyl(2.75, -0.01, z1 + 1.0, -10.0, yy))
p02_c = p02_c.cut(cyl(5.0, z1 - 5.0, z1 + 1.0, -10.0, yy))
# Rear mortise pockets
for sign in (-1, 1):
yy = Y_COL + sign * r_tenon_y_span
rear_mortise = (cq.Workplane("XY", origin=(-185.0, yy, 4.8))
.rect(21.0, r_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 <X").fillet(2.5).edges("<X").chamfer(1.5)).val()
p02_f = p02_f.fuse(cq.Shape(tongue.wrapped))
p02_f = p02_f.cut(cyl(2.75, -0.01, z1 + 1.0, -10.0, yy))
nut_pocket = (cq.Workplane("XY", origin=(-10.0, yy, -0.01)).polygon(6, 9.2).extrude(5.0)).val()
p02_f = p02_f.cut(nut_pocket)
# Top-face engraving rotated 90 deg to run across the plate along +Y (user
# request): letter tops face -X, so it reads upright from the front (+X).
# "▲" points local +Y = world -X, i.e. toward the centre joint.
txt_f = (cq.Workplane(cq.Plane(origin=(x_f_mid, Y_COL, z1), xDir=(0, 1, 0),
normal=(0, 0, 1)))
.text("P02-FRONT ▲ JOIN", 8.0, -0.8)).val()
p02_f = p02_f.cut(cq.Shape(txt_f.wrapped))
# 3. P02_Rear (X = -240.0 .. -195.0, 45x190mm)
x_r_len = 45.0
x_r_mid = -217.5
r_plate = (cq.Workplane("XY", origin=(x_r_mid, Y_COL, z0))
.rect(x_r_len, y_width).extrude(z1)
.edges("|Z and <X").fillet(16.0).edges(">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
JOINT = 'root'
def specs(JS):
"""本件的 (件号, 中文名, 构建 thunk, 配色, 说明) 清单;顺序即 BOM 顺序。"""
p02_c, p02_f, p02_r = make_p02_poka_yoke()
return [
("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刻字"),
]
+417
View File
@@ -0,0 +1,417 @@
# -*- coding: utf-8 -*-
"""P03 肩吊臂:板式桁架 + 侧开式电机托架 + M1 后置法兰。"""
import cadquery as cq
import math
from arm.cad.helpers import cyl
from arm.cad.arm_build import Yplate, bx
from arm.kinematics import X_BAY_EXT, Y_AXIS, Z_CHEST1, Z_R1, Z_RL
from arm.cad.part_tools import Xcyl, make_surface_tie_bridge
from arm.cad.parts.p01_column import X_COL, Y_COL, Z_LOCK
def p_girdle(j1):
"""P03_肩关节安装悬臂 (轻量化板式桁架版 + 侧开式电机托架):
1. 10mm 后端重载法兰盘 (X: -61.0..-51.0, 74x74mm);
2. 主干由 66mm 实心块改为双侧 6mm 桁架竖板 (Y = Y_ARM ± 27..33),外轮廓保持旧版
"主干 ∪ 侧筋"并集不变 —— 只减材料不加体积,零新增干涉风险;实心 728 -> 286 cm³ (-61%);
3. 竖板帐篷形减重窗 (顶/底斜面 ≥52°,自支撑;仅 20mm 平顶可桥接);
4. 底部 9mm 承力鞍座 + 43.4x43.4 定位榫头 (配 P01_C 44x44 内腔,单边 0.3mm 间隙);
鞍座底面 66x66 与立柱顶环带承压面积 2403mm² —— 竖向载荷走面压,螺栓只防上抬/防滑;
5. 4x 安装孔 (X_COL±27.5, Y_COL±13.5):Ø3.9 直孔,M4x16 从上方直接拧入自攻成型
(鞍座与立柱顶环同径,无过孔/沉孔/预埋),可拆装;
6. 下腹中央龙骨筋 (12mm 中位腹板,自鞍座斜拉至法兰下缘) + 顶部横向锁紧环抱筋 (66x9x10mm,
与两侧竖板外面齐平、顶面压在筋板脊线上,仅顶前棱一道 2mm 倒角);
7. 侧开式电机托架 (+Y侧全开, X: -51.0..11.5,前端随 X_BAY_EXT 前延 3mm),
M1 电机可从侧方斜插卡入装配(插槽比电机长 3mm),由后端法兰 4xM3x16 锁死;
电机舱 Ø60.7 从后端直通到前端面(2026-10-02 用户裁定"去掉台阶改成平的":无环形台阶、
无喇叭锥;原前置止脱锁紧法兰 4xM3 随之取消,电机轴向由后端 4xM3x16 约束);
9. 两侧大面积散热舷窗 (34x20mm) + 顶部双斜向对流排热槽 (30x6.5mm),确保散热畅通;
10. Ø26mm 中央走线孔;竖板间为开放腔体(无封闭空腔),M3 螺丝刀通道直接走腔体,拧紧更顺手。
11. 三处外凸扎带拱桥(10x8mm、站出 3.2mm、桥下 3.2x1.8 穿带隧道):①桁架竖板面 Y=95、
②后置法兰面 Y=91、③Ø73 舱体 −Y 母线 Y=91.5 —— 安装面按各处真实表面取;原来统一按
Y=95 放,②③ 被筒壁吞成露出 0mm、且压在 34x20 散热舷窗的贯通孔上,等于没有固定点。
"""
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
Y_P_IN = 27.0 # 桁架竖板内表面 (Y_ARM ± 27)
Y_P_OUT = 33.0 # 桁架竖板外表面 (与旧主干齐平)
T_SADDLE = 9.0 # 底部鞍座厚度
TENON = 43.4 # 定位榫头边长(P01_C 内腔 44x44 R4,单边留 0.3mm)
WIN_R = 4.0 # 竖板减重窗内角圆角(消除内尖角应力集中,Kt 由 ~2.5 降到 ~1.2)
BOLT_DX, BOLT_DY = 27.5, 13.5 # 4x 安装孔相对立柱中心偏移:落在顶环 11mm 实体壁内
# 1. 主安装法兰盘 (74x74mm, 四角倒圆 R=8mm)
# 背面那圈 2mm 倒角已去掉:板料只伸进法兰 0.1mm,倒角面与板背面相交会在
# Z≈574.9 挤出一批 2mm 短棱,正是这批短棱让整条交接内棱网络倒不出圆角;
# 同时它跨过 R8 角部圆弧,就是角上那道"卷起来"的怪倒角。
flange = (cq.Workplane("YZ", origin=(X_BACK, Y_ARM, Z_ARM))
.rect(74.0, 74.0)
.extrude(T_FLANGE)
.edges("|X").fillet(8.0)).val()
s = cq.Shape(flange.wrapped)
# 2. 双侧桁架竖板 (6mm):旧"斜挑支撑主干"与"大三角加强筋"轮廓同段挤出 → 自动并集成单块侧板
# 主干的后缘两点取在加强筋斜边的延长线上,使外轮廓顶部成为一整条直线
# (否则后缘 (-148,550) 处有 5° 折点、留下 5mm 台阶)。
# Z_CREST = 侧筋脊线高度,同时是顶筋 2mm 顶前倒角的下沿:倒角从 (X-71,Z_CREST)
# 斜上到 (X-69,Z_CREST+2) = 顶筋顶面,斜坡面与倒角面几乎相切,顶部读起来是一整张面。
Z_CREST = Z_ARM + 33.0 # 643(初版 642;顶筋整体上移 3mm 让出扳手空间后跟着抬)
RIB_SLOPE = (Z_CREST - 575.0) / ((X_BACK - 10.0) - (X_COL - 15.0))
def rib_line_z(x):
return Z_CREST + RIB_SLOPE * (x - (X_BACK - 10.0))
trunk_pts = [
(X_COL - 33.0, Z_CHEST1),
(X_COL + 33.0, Z_CHEST1),
(X_COL + 33.0, 505.0),
(X_BACK + 0.1, 565.0),
(X_BACK + 0.1, Z_ARM - 5.0),
(X_COL - 10.0, rib_line_z(X_COL - 10.0)),
(X_COL - 33.0, rib_line_z(X_COL - 33.0)),
]
rib_pts = [
(X_COL - 33.0, Z_CHEST1), # 底边两端与主干/鞍座对齐 (±33):原来收进 3mm,
(X_COL + 33.0, Z_CHEST1), # 会在鞍座顶面上留下 0.7/3mm 短棱,卡住整条底棱圆角
(X_BACK + 0.1, 565.0),
(X_BACK + 0.1, Z_CREST),
(X_BACK - 10.0, Z_CREST), # 脊线 (-71,643):正好落在顶筋前面与 2mm 倒角下沿的交点
(X_COL - 15.0, 575.0),
(X_COL - 30.0, 530.0),
]
for sign in (-1, 1):
ya = Y_ARM + sign * Y_P_IN
yb = Y_ARM + sign * Y_P_OUT
y0, y1 = min(ya, yb), max(ya, yb)
for prof in (trunk_pts, rib_pts):
s = s.fuse(cq.Shape(Yplate(prof, y0, y1).wrapped))
# 3. 竖板减重窗 (帐篷形:斜面 ≥52° 自支撑、仅 20mm 平顶可桥接;避开鞍座与 M5 螺母柱)
# 6 个内角全部 R4 圆角:把内尖角应力集中 (Kt≈2.5) 降到 ≈1.2,同时圆角面仍是竖直/自支撑面
win_pts = [
(X_COL - 24.0, 498.0), (X_COL + 23.0, 498.0), (X_COL + 25.0, 522.0),
(X_COL + 10.0, 545.0), (X_COL - 10.0, 545.0), (X_COL - 25.0, 524.0),
]
for sign in (-1, 1):
yc = Y_ARM + sign * 0.5 * (Y_P_IN + Y_P_OUT)
tool = (cq.Workplane("XZ", origin=(0, yc, 0)).polyline(win_pts).close()
.extrude(Y_P_OUT - Y_P_IN + 1.0, both=True)
.edges("|Y").fillet(WIN_R).val())
s = s.cut(cq.Shape(tool.wrapped))
# 4. 底部承力鞍座:坐实立柱顶面,把两竖板、龙骨与榫头连成整体
s = s.fuse(bx(X_COL - 33.0, X_COL + 33.0, Y_ARM - Y_P_OUT, Y_ARM + Y_P_OUT,
Z_CHEST1, Z_CHEST1 + T_SADDLE))
# 5. 下腹部纵向抗弯龙骨筋 (12mm 中位腹板,自鞍座斜拉至法兰背面;交接内棱在融合后做真 3D 圆角)
keel_pts = [
(X_COL + 33.0, Z_CHEST1), # (-82, 470)
(X_COL + 33.0, 495.0), # (-82, 495)
(X_BACK + 0.1, 573.0), # (-60.9, 573) 斜拉至法兰背面
(X_BACK + 0.1, 594.4), # (-60.9, 594.4) 顶端贴法兰
(X_COL + 42.1, 585.0), # (-72.9, 585)
(X_COL + 20.0, Z_CHEST1), # (-95, 470) 背面一路斜到底:原来在 (-95,480) 有个折点,
] # 折点离鞍座顶面只 1mm,R2.5 圆角爬不上去 → 龙骨底棱整组倒不出
s = s.fuse(cq.Shape(Yplate(keel_pts, Y_ARM - 6.0, Y_ARM + 6.0).wrapped))
# 7. 顶部横向锁紧环抱横筋:Y 向与两竖板外面齐平 (Y_ARM±33)、前面与筋板脊线共面 (X=-71)、
# 顶面 Z=645 压住脊线 (Z=643) 并与 2mm 顶前倒角相切 → 顶筋与两侧脊线合成一整块。
# 整体比初版抬高 3mm (底面 633→636):4x M3 后端沉孔 Ø6.5 跨在 Z 627.2..633.7 上,
# 底面 633 会压住孔口上沿 0.7mm、扳手让位只剩负间隙;抬到 636 后退到孔口上方 2.3mm、
# 离螺钉轴线 5.55mm。顶面 645 不越过法兰 R8 角部圆弧在 Y=95 处的 645.9 上界。
tie_rib = (cq.Workplane("YZ", origin=(X_BACK - 10.0, Y_ARM, Z_ARM + 30.5))
.rect(66.0, 9.0)
.extrude(10.0)
.edges(cq.selectors.BoxSelector((X_BACK - 10.5, Y_ARM - 34.0, Z_ARM + 34.5),
(X_BACK - 9.5, Y_ARM + 34.0, Z_ARM + 35.5)))
.chamfer(2.0)).val()
s = s.fuse(cq.Shape(tie_rib.wrapped))
# 6.y 体-体交接凹棱:融合完成后对"凹棱"直接做真 3D 圆角。
# 原先这里是手工沿棱 fuse 的 10 条"正方形−四分之一圆"焊珠小体(_cove),已整体删除:
# 焊珠端面是平的、悬在半空,两条互相垂直的焊珠在角上互不搭接 → 圆角"缺一块",
# 并与相邻体的棱互相穿插留 1~3mm² 碎面。真圆角在面边界自然收口、多棱相交处由 OCC
# 生成角部补片。
from cadquery import selectors as _sel
from OCP.BRepAdaptor import BRepAdaptor_Surface
from OCP.GeomAbs import GeomAbs_Plane
from OCP.TopAbs import TopAbs_REVERSED
def _outward(f, sh):
"""平面面的外法向:先按 Orientation 修正,再用"面中心外移 1mm 是否仍在实体内"兜方向。"""
ad = BRepAdaptor_Surface(f.wrapped)
if ad.GetType() != GeomAbs_Plane:
return None
ax = ad.Plane().Axis().Direction()
n = cq.Vector(ax.X(), ax.Y(), ax.Z())
if f.wrapped.Orientation() == TopAbs_REVERSED:
n = n * -1.0
c = f.Center()
if cq.Solid(sh.wrapped).isInside(cq.Vector(c.x + n.x, c.y + n.y, c.z + n.z), 1e-6):
n = n * -1.0
return n
def _fillet_reflex(lo, hi, radii, tag, eps=0.9):
"""倒出 lo~hi 区域内的凹棱(两面夹 270° 材料)。判据:从棱中点沿 (n1-n2) 外探 eps,
落在实体内 = 凹、落在外 = 凸。区域内凹棱先整批一次倒(半径逐级降级,且倒完验拓扑:
OCC 会给出"没抛异常但面已坏"的结果),建不出时按相连棱分组逐组倒。"""
nonlocal s
def _pick(sh):
edges = sh.Edges()
idx = {}
for i, e in enumerate(edges):
idx[e.hashCode()] = i
adj = [[] for _ in edges]
for f in sh.Faces():
for ee in f.Edges():
k = idx.get(ee.hashCode())
if k is not None:
adj[k].append(f)
box = _sel.BoxSelector(lo, hi)
out = []
for i, e in enumerate(edges):
if e.Length() <= 6.0 or len(adj[i]) != 2 or not box.filter([e]):
continue
n1 = _outward(adj[i][0], sh)
n2 = _outward(adj[i][1], sh)
if n1 is None or n2 is None:
continue
dx, dy, dz = n1.x - n2.x, n1.y - n2.y, n1.z - n2.z
ln = math.sqrt(dx * dx + dy * dy + dz * dz)
if ln < 1e-6:
continue
p = e.Center()
if cq.Solid(sh.wrapped).isInside(
cq.Vector(p.x + dx / ln * eps, p.y + dy / ln * eps,
p.z + dz / ln * eps), 1e-6):
out.append(e)
return out
es0 = _pick(s)
if not es0:
print(" [P03 真圆角] %-14s 区域内无凹棱,跳过" % tag, flush=True)
return
def _groups(ee):
"""按共端点把凹棱连成链/网络:一组一次倒,OCC 才能在交汇处生成角部补片;
逐条倒会在交汇处留下断口(看着就像圆角倒一半停了)。"""
pts = [e.Vertices() or [e.Center()] for e in ee]
par = list(range(len(ee)))
def find(a):
while par[a] != a:
par[a] = par[par[a]]
a = par[a]
return a
for i in range(len(ee)):
for j in range(i + 1, len(ee)):
if any(pi.distance(pj) < 0.05 for pi in pts[i] for pj in pts[j]):
par[find(i)] = find(j)
g = {}
for i in range(len(ee)):
g.setdefault(find(i), []).append(ee[i])
return list(g.values())
def _do(edges_, rr):
"""倒完立刻验拓扑:OCC 会给出"没抛异常但面已坏"的结果,必须挡下来。"""
try:
ns = cq.Shape(cq.Solid(s.wrapped).fillet(rr, edges_).wrapped)
except Exception:
return None
return ns if cq.Solid(ns.wrapped).isValid() else None
# 先试"区域内全部凹棱一次倒"(探针实测这样能整批成型,且只重建一次,
# 不会像分批那样把同一张面反复重算坏掉);不行再按相连棱分组逐组倒。
for rr in radii:
ns = _do(es0, rr)
if ns is not None:
s = ns
print(" [P03 真圆角] %-14s %d 条凹棱 R%.1f ✓ 整批一次成型"
% (tag, len(es0), rr), flush=True)
return
n_ok, n_grp, left = 0, 0, len(es0)
for _ in range(24):
es = _pick(s)
if not es:
break
got = False
for g in sorted(_groups(es), key=lambda g: -sum(e.Length() for e in g)):
for rr in radii:
ns = _do(g, rr)
if ns is None:
continue
s = ns
n_ok += len(g)
n_grp += 1
print(" [P03 真圆角] %-14s 第%d组 %d 条 R%.1f ✓"
% (tag, n_grp, len(g), rr), flush=True)
got = True
break
if got:
break
if not got:
left = len(es)
break
print(" [P03 真圆角] %-14s 共 %d 组 %d 条凹棱倒圆,剩 %d 条保持尖棱"
% (tag, n_grp, n_ok, left), flush=True)
XR, YC, ZS = X_BACK, Y_ARM, Z_CHEST1 + T_SADDLE # -61, 128, 479
# —— 鞍座顶面 (Z=479) 上的交接内棱:龙骨三条 + 两竖板内侧两条,一次真圆角 ——
# 半径上限 2.0:龙骨侧面 Y=122 与 M5 螺钉头外缘 Y=118.75 只隔 3.25mm,
# 原来的 R3 焊珠会顶到螺帽承压边,R2.0 留 1.25mm。
_fillet_reflex((X_COL - 34.0, Y_ARM - 40.0, ZS - 3.0),
(X_COL + 34.0, Y_ARM + 40.0, ZS + 7.0),
(2.0, 1.5), "鞍座顶面交接")
# —— 法兰背面 (X=-61) 上的交接内棱:只倒竖板/龙骨的竖直棱 ——
# 顶筋底面那条水平棱 (Z=636) 不倒:4x M3 沉孔 Ø6.5 在 Z 627.2..633.7,
# 倒上 R2.5 会把螺钉上方 5.55mm 的扳手让位吃掉 2.5mm。
# 上界取 631:竖板内棱被顶筋底面切成 573..636 / 636..643 两段,只倒下面长的那段
# ——把 636..643 的 7mm 短段并进来,整批就从"一次成型"退化成逐组、且少倒 6 条。
_fillet_reflex((X_BACK - 3.0, Y_ARM - 40.0, 570.0),
(X_BACK + 3.0, Y_ARM + 40.0, 631.0),
(2.5, 2.0, 1.5), "竖板/龙骨↔法兰")
_fillet_reflex((X_BACK - 3.0, Y_ARM - 40.0, Z_ARM + 34.5),
(X_BACK + 3.0, Y_ARM + 40.0, Z_ARM + 35.5),
(1.5, 1.0), "顶筋顶↔法兰")
# 与立柱顶部配合的定位榫头 (43.4x43.4x15mm):配 P01_C 44x44 R4 内腔,单边 0.3mm 滑配
boss = (cq.Workplane("XY", origin=(X_COL, Y_COL, Z_CHEST1 - 15.0))
.rect(TENON, TENON)
.extrude(15.0)
.edges("|Z").fillet(4.2)).val()
s = s.fuse(cq.Shape(boss.wrapped))
# 4x 安装孔:统一 Ø3.9 直孔(M4x16 从腔体正上方拧入自攻成型;鞍座与立柱顶环同径、无沉孔)
# 孔位 (X_COL±27.5, Y_COL±13.5) 同时满足:①在立柱顶环实体壁内(内腔 22 → 外壁 33);
# ②在 P03 两竖板之间、避开中间龙骨,从腔体正上方用扳手可直接拧紧。
for xx in (X_COL - BOLT_DX, X_COL + BOLT_DX):
for yy in (Y_COL - BOLT_DY, Y_COL + BOLT_DY):
s = s.cut(cyl(1.95, Z_CHEST1 - 0.5, Z_CHEST1 + T_SADDLE + 0.5, xx, yy))
# 1x 横向防脱锁紧:榫头 −X 侧向内 Ø3.9x14 直孔(与立柱后壁 Ø3.9 孔同轴,M4x16 拧入自攻)
s = s.cut(Xcyl(1.95, X_COL - TENON / 2.0 - 1.0, X_COL - TENON / 2.0 + 14.0, Y_COL, Z_LOCK))
# 【线束过线孔】榫头/鞍座中央 22x22mm 竖向贯通方孔 (直通 P01 立柱 44x44mm 内腔,容纳 20x20mm 接插件)
saddle_wire = (cq.Workplane("XY", origin=(X_COL, Y_COL, Z_CHEST1 - 16.0))
.rect(22.0, 22.0)
.extrude(T_SADDLE + 17.0)
.edges("|Z").fillet(3.0)).val()
s = s.cut(cq.Shape(saddle_wire.wrapped))
# 4x M3 连接螺孔(螺丝刀通道直接走两竖板之间的开放腔体,无需再钻 Ø8.5 直通隧道)
# 4x M3 连接螺孔(螺丝刀通道直接走两竖板之间的开放腔体,无需再钻 Ø8.5 直通隧道)
# 孔位 = 电机尾端 4x M3 螺纹径向槽实测位(电机.STEP):BCD55。
# 【2026-10-07 整改】M1 电机沿 +X 轴旋转 5.35° 后,4x M3 后端孔完全规整对齐 45°/135°/225°/315° 正四角
# (dy, dz = ±19.45, ±19.45),水平与竖直完全齐平规整,且与旋转后的 M1 尾端 4xM3 槽完全同心
for dy, dz in ((19.45, 19.45), (-19.45, 19.45), (-19.45, -19.45), (19.45, -19.45)):
py = Y_ARM + dy
pz = Z_ARM + dz
th = Xcyl(1.7, X_BACK - 0.5, X_MOUNT + 4.0, py, pz)
cb = Xcyl(3.25, X_BACK - 0.5, X_BACK + 4.5, py, pz)
s = s.cut(th).cut(cb)
# 6. 前向电机全包托架 (X: -51.0..8.5+X_BAY_EXT=11.5,Ø60.7 舱孔平直贯通到端面)
# 端面随插槽加长前延 X_BAY_EXT;叉板背面(10.5+X_FORK_GAP=11.5)与端面 0.0 硬贴平
# (用户两次要求"搞成平的";J1 转动摩擦由用户知悉,发涩时可微松 M2)
X_FRONT = 8.5 + X_BAY_EXT
R_POD = 36.5
R_BORE = 30.35
cradle_outer = (cq.Workplane("YZ", origin=(X_MOUNT - 0.1, Y_ARM, Z_ARM))
.circle(R_POD)
.extrude(X_FRONT - X_MOUNT + 0.1)
.edges(">X").chamfer(1.5)).val()
s = s.fuse(cq.Shape(cradle_outer.wrapped))
# 电机定子配合内腔(最终状态,2026-10-02 用户裁定"去掉台阶改成平的+保留固定电机的面"):
# Ø60.7 圆柱孔壁从前端面平直贯通到舱底(无窄颈、无台阶、无喇叭锥);
# 舱底 = 一个完整的平坦环面(X=5.06+X_BAY_EXT=8.06,与插槽末端墙共面,中开 Ø39 凸台过孔)
# ——即电机前限位面。电机轴向由后端法兰 4xM3x16 约束;
# 前端锁紧由 4 个 Ø10 局部凸台上的 M3 圆柱头螺钉承担(见下方凸台段)。
motor_pocket = Xcyl(R_BORE, X_MOUNT - 0.2, 5.06 + X_BAY_EXT, Y_ARM, Z_ARM)
s = s.cut(motor_pocket)
# 4x 前置 M3 锁紧螺钉凸台(BCD=55mm,跟随 M1 电机旋转 5.35° -> 22.82°/112.82°/202.82°/292.82°):
# 平直舱孔的沉头座位由 4 个 Ø10 局部凸台承载——凸台位于舱孔前段 8.06..11.5,
# 电机前端面(5.0)之前 3mm,电机斜插路径不经过;M3x8 圆柱头坐在凸台沉孔
# 底(x=9.5)旋入电机前端面,咬合 3.5mm,与旋转后的 M1 前端盖实测 4xM3 螺孔精确同心
for deg in (22.82, 112.82, 202.82, 292.82):
rad = math.radians(deg)
fy = Y_ARM + 27.5 * math.cos(rad)
fz = Z_ARM + 27.5 * math.sin(rad)
s = s.fuse(Xcyl(5.0, 5.06 + X_BAY_EXT, X_FRONT, fy, fz))
th = Xcyl(1.6, 5.06 + X_BAY_EXT, X_FRONT, fy, fz)
cb = Xcyl(3.0, X_FRONT - 2.0, X_FRONT, fy, fz)
s = s.cut(th).cut(cb)
# J1 输出凸台过孔 (Ø39 × 3.44——J1 凸台与 3xM2 输出螺钉的必经孔,非台阶;
# 原 6.7mm 长颈与半圆台肩已随舱底前移一并消除)
hub_clearance = Xcyl(19.5, 4.8, X_FRONT + 0.5, Y_ARM, Z_ARM)
s = s.cut(hub_clearance)
# 8. 电机托架 +Y 侧全开(侧开式半包,替代原封闭全包筒):
# M1 电机可从 +Y 侧方直线滑入托架完成装配——原封闭筒电机物理上无法装入。
# 轴向约束:后端法兰 4xM3(电机尾端面)单向锁死(前置止脱法兰随平直舱孔要求取消);
# 径向约束:Ø60.7 精密滑配托架底 + 重力贴底。开口前端至 5.06+X_BAY_EXT=8.06
# (比电机前端面多 3mm 斜插余量,电机可倾斜卡入),止脱法兰环随之前移、环宽保持 3.44mm。
s = s.cut(bx(X_MOUNT, 5.06 + X_BAY_EXT, 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)
# 8. 实体表面外凸式扎带拱桥 (10x8mm 拱形穿带座,一体成型,扎带从桥下 3.2x1.8 隧道穿过):
# 安装面一律取"该处的真实表面"——原来三处全按竖板面 Y=95 放,而 ②③ 落在 Ø73 舱体上,
# 舱面母线只到 Y=91.5,桥体 3.2mm 被筒壁整段吞掉(露出 0.0mm、穿带孔被壁填死)= 没有固定点。
# 组①:桁架竖板平面段 X=-85, Z=570, 贴 Y=95 平面,竖直穿带 —— 与 P04 三处同配方,不动
# 组②:后置法兰 −Y 平面 X=-56, Z=610, 贴 Y=91 (法兰 74 宽 → 面在 128-37)
# 选法兰面而不是舱面:法兰是 10mm 整实料;且线束过了 X=-61 必须绕开 Y=91 这道 4mm 台阶,
# 贴法兰面后法兰带内线皮净空 2.78mm,走舱面径向则线要外拱、只剩 0.33mm。
# X 向留 1.0mm 不越法兰前后脸,Z 向 606..614 在 R8 圆角之间 (581..639) 全平面。
# 组③:舱体 −Y 母线实料段 X=+5.5, Z=610, 贴 Y=91.5 切点,动静接缝前锚固点
# 离舷窗前缘 (最外到 X=-8.3) 10.3mm、离前端 1.5 倒角 (X=10 起) 1.0mm;
# 穿带孔由沿 X 改成沿 Z —— 此处线束沿 X 走,孔轴必须垂直于线才抱得住。
Y_FACE_FLANGE = Y_ARM - 37.0 # 91.0 法兰 −Y 承面
Y_TAN = Y_ARM - R_POD # 91.5 舱体最外的 −Y 母线(切点)
for tx, ys, tz, wx in ((-85.0, Y_ARM - Y_P_OUT, 570.0, 10.0),
(-56.0, Y_FACE_FLANGE, 610.0, 8.0),
(5.5, Y_TAN, 610.0, 7.0)):
s = s.fuse(make_surface_tie_bridge(tx, ys, tz, w_x=wx, l_z=8.0, h_y=3.2,
slot_w=3.2, slot_h=1.8, tunnel_dir='Z'))
return s
JOINT = 'root'
def specs(JS):
"""本件的 (件号, 中文名, 构建 thunk, 配色, 说明) 清单;顺序即 BOM 顺序。"""
j1 = JS["J1"]
return [
("P03_肩吊臂_螺旋体35%_墙5底5_竖打", "肩关节安装悬臂", lambda: p_girdle(j1), 0x4b5563,
"轻量化板式桁架版:主干由 66mm 实心块改为双侧 6mm 桁架竖板+帐篷减重窗(斜面≥52°自支撑)+R4内角,外轮廓与旧版并集完全一致,实心 728->286cm³;底部 9mm 承力鞍座(Ø3.9 安装孔)+12mm 中位龙骨筋(自鞍座斜拉至法兰下缘)+40~45°外扩斜撑;保留 10mm 后置法兰、侧开式电机托架、Ø60.7 直通电机舱(平孔无台阶)(M1 从+Y滑入,后端 4xM3x16 锁死)、大散热窗与对流槽;【2026-10-07 电机旋转整改】M1 电机旋转 5.35°:后置法兰 4xM3 连接孔完全规整对齐 45°/135°/225°/315° 正四角(dy,dz=±19.45mm),前端 4xM3 锁紧凸台同步旋转至 22.82°+90k,消除原有倾斜,与 M1 前后端盖螺孔精确同心;无封闭空腔、免支撑"),
]
File diff suppressed because one or more lines are too long
+38
View File
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
"""P05 大臂旋转座:J2 俯仰 -> J3 滚转(复用 part_tools 的 pitch->roll 骨架)。"""
import cadquery as cq
from arm.kinematics import Z_R3, Z_SH
from arm.cad.part_tools import make_pitch_to_roll_bracket
def p_shoulder_roll_base(j2, j3, half=None):
"""P05_大臂旋转座_螺旋体30%_墙4底5_竖打(深度圆角优化版)
本件在链路上有两重身份,两处让位都是为前端压盖:
1. hub_gap=6.0(从动侧):J2 处 P04 新增了 M2 前端压盖(前端夹紧电机,消除大扭矩下的
倾斜),压盖占 Y 155.5..161.0,所以从动法兰盘背面必须从 Y=157 后移到 Y=162
(留 1.0mm 动间隙)。
2. roll_clamp=True(前端夹紧侧):P05 自己抱着 M3(J3,-Z 轴),原来也只有后端板
4xM3x10 单面锁死;现在按 motor_front_clamp 的统一配方在 M3 前端盖外端面
(Z=389)加一枚 R35 压盖(Z 384..389,4xM3x8 BCD55),与后端板前后双向面压,
倾斜自由度归零。代价是对侧从动件 P06 的 J3 毂面整体后移 CLAMP_BACKOFF=5.5
(Z 388.5→383.0),见 p_humerus_bone。
P08 保持默认 —— J5 处 P07 的前端压盖已做(只需 6.0 的 hub_gap),而 J6 的 M6
前端压盖尚未做,故 roll_clamp 传 False。
"""
s = make_pitch_to_roll_bracket(j2, j3, Z_SH, Z_R3,
name="P05_大臂旋转座_螺旋体30%_墙4底5_竖打",
hub_gap=6.0, roll_clamp=True)
return s
JOINT = 'J2'
def specs(JS):
"""本件的 (件号, 中文名, 构建 thunk, 配色, 说明) 清单;顺序即 BOM 顺序。"""
j2 = JS["J2"]
j3 = JS["J3"]
return [
("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "大臂旋转座", lambda: p_shoulder_roll_base(j2, j3, None), 0x059669,
"V3圆角+ J2前端夹紧配合:Loft外轮廓R=10mm,后缘凸包两角90°竖棱磨R2圆角(结构/后缘高度不变,打印无悬挂);【2026-10-07 侧壁放样斜棱圆角】侧壁与有机放样交汇处斜向硬折棱倒R3圆角,消除侧立面与正面放样弧面接缝硬棱,表面光影与触感圆润流线;【2026-10-07】L型内转角R1.5精细圆角(将圆角半径精调至1.5mm,完全杜绝算法异常与外凸台阶,平滑过渡并消除直角应力集中,与螺钉沉孔/运动间隙保持充裕余量);【2026-10-07 电机座内凹槽与边缘突起消除】彻底消除承压面内侧 4.5mm 环形内凹槽(全腔单一 BAY_R 贯通无接缝),同时 cut_open 敞口高度上限延伸至承压面 (z_r + REAR_SEAT),侧壁完全平直对齐下方壁面,彻底消除侧沿悬垂突耳;J2 从动法兰盘背面由 Y=157 后移至 Y=162(hub_gap=6.0,给 P04 新增的 M2 前端压盖让位,J2 直锁凸台同步加长到 Y=165.5,3xS01 输出螺钉咬合量不变);保证 J2 全角度 0 干涉;【2026-10-05d 只切敞口那一圈围挡】用户指出 M3 电机前端(另一头)也有一圈围挡:Z389.0~395.5 的 r30.25~35 前端定位环(6mm 高环墙) + 骑在它上面的 R35 压盖外圈(Ø70 比电机 Ø63.6 凸 3.2mm),把 M3 前端盖连同穿过 Ø37 孔的 P06 J3 凸台围在中间。第一版把整个前端(定位环+压盖+腔口下沿 389->395)都切了属过度(电机前端失去支撑面、M3 退回单面锁死),已回退;现只切敞口(-Y)那一圈(盒切,Y 界限与 cut_open 一致),-Y 半圈从 Z=395.5 一路开到压盖背面 Z=389,背脊(+Y)侧整圈保留作前端盖定位与压盖唯一且足够强的支撑面(4.75mm 宽 x 0.5mm 熔接带);压盖本体/Ø37 孔/4xM3x8 沉孔/腔口下沿 389.0 全部不动,P05 只少 3.2cm3(154.8->151.6),仍单实体。不整圈削到 R31.8 的原因:那圈壁与 Loft 在 X=±35 处与 Ø70 柱面相切,整圈切完会脱空出 1.32mm3 碎块破坏单实体;且压盖根部熔接面会从 2.37mm2 缩到 0.78mm2,4xM3x8 夹紧力会把压盖拉下来。开关 FENCE_OPEN_CUT(全机 5 处电机前端定位环敞口侧统一切除,无围挡阻碍装配)。【2026-10-03】M3 前端压盖(R35, Z384.0..389.0):J3 roll 电机原来也只有后端板 4xM3x10 单面锁死(S02),大扭矩下绕 J3 轴在 Ø60.3 座孔里点头;现按 P04/P06/P07 同一配方在 M3 前端盖外端面(Z=389)加 4xM3x8(BCD55)沉孔压盖,与后端板前后双向面压,倾斜自由度归零(配方唯一差别是 roll 电机轴向 -Z,压盖朝 -Z 长,见 motor_front_clamp axis=\"Z\");代价是对侧 P06 的 J3 毂面整体后移 5.5mm 让位;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R<R35 的必然产物。现 SEAT_R=35.0、SEAT_TOP=+1.0(越过尾端面 1mm,吃掉半包开口处那截0.052cm3 残片):承压面段整圈挖到外壁,承压面成为完整 R35 平圆盘,凸台与围挡一起消失,电机尾端面与承压面之间再无凸起。承力改由臂身本体承担(band 段 r36..58 有料方位 P04 10/36、其余各 15/36,其余方位靠 z>+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"),
]
+156
View File
@@ -0,0 +1,156 @@
# -*- coding: utf-8 -*-
"""P06 大臂骨架:J3 滚转 -> J4 俯仰的 Loft 空心梁。"""
from arm.cad import arm_build as AB
import cadquery as cq
from arm.cad.helpers import cyl
from arm.cad.arm_build import COVER_Z, Ycyl, bx
from arm.kinematics import X_ARM, Y_AXIS, Y_COVER, Y_REAR, Z_EL, Z_R3
from arm.cad.part_tools import (
CLAMP_BACKOFF, CLAMP_CLR, CLAMP_T, cut_motor_fence, hub_m2_holes,
make_surface_tie_bridge_neg_x, motor_front_clamp,
)
P06_WIN_XZ = [(3.0, 362.0), (37.0, 362.0), (37.0, 371.0),
(28.0, 374.0), (12.0, 374.0), (3.0, 371.0)] # 大臂过渡块
P06_HOLLOW_WIN = False # 2026-10-03 起关闭:拱形窗正面看是贯通黑洞,不雅观(详见 p_upper_bone 5c)
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 = 383.0mm):与 J3 轴径严格相切的 Ø70mm 正圆形轮廓,彻底消除原先外突的方形水平阶梯;
- 底端结合面 (Z = 367.0mm):平滑展开融入 M4 电机套筒的 70x63.5mm 方形截面;
- 过渡区 (Z: 367.0..383.0mm):采用 Hermite s(t) = 3t² - 2t³ 驱动 20 层放样切片连续渐变;
2. 下部抱紧套筒与安装板 (R=35mm),电机定子 100% 裸露散热开窗;
3.【2026-10-03】J3 毂面整体后移 CLAMP_BACKOFF=5.5(顶端 388.5→383.0,直锁凸台
59.5→65.0):P05 新增 M3 前端压盖,占 Z 384..389,原先 388.5 的毂面只剩 0.5mm
让位放不下压盖。配方与 Y 轴三处(J2/J4/J5 令从动法兰盘 Y157→162)完全同源:
压盖 5.0 + 动间隙 1.0 - 原基准 0.5 = 5.5。凸台同步加长,3xS01 输出螺钉的头面
台阶(局部 s=58)、杆长、咬合量与改前完全一致,J3 直锁与零干涉不受影响。
压盖中央 Ø37 让位孔(R18.5)也让开 R17.8 凸台,单边余 0.7mm。
4.【2026-10-09】−X 侧线束走廊加一座外凸扎带拱桥 (y=102.5, Z=345):站出 3.2mm、桥下
3.2×1.8 穿带隧道沿 Y 贯通(与 P03/P04 同配方)。走廊线心随之由 x=-17.5 提到 -20.7:
原线皮离本件 −X 面只剩 0.2mm,扎带没有容身处。
5. 严格单实体 (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 由 M3 前端盖外端面反推:压盖占 [Z_COVER-CLAMP_T, Z_COVER],再留 CLAMP_CLR
# 动间隙 -> 383.0(改前 388.5,与前端盖只有 0.5mm,放不下压盖)。
z_cover3 = Z_R3 - COVER_Z # 389.0 M3 前端盖外端面
z_top = z_cover3 - CLAMP_T - CLAMP_CLR # 383.0 J3 毂面(= 388.5 - CLAMP_BACKOFF)
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% 裸露)
# 【2026-10-05f】原 Ycyl(30.5, ...) 定子散热切除已删:主腔统一 BAY_R=30.50
# 且从承压面挖到前端,这刀完全落在主腔内,属同段腔被两个半径各挖一次的残留。
# 通风由敞口盒切实现,不依赖它。
# 5. M4 接口成型
# 【2026-10-03i 接缝整改】front_collar_pocket 已删,理由同 p_shoulder_roll_base:
# 它与 Joint.pocket 切的是同一根 BAY_R 圆柱,是后者的子集,两次同半径布尔
# 相减会在腔壁上留下 s=50.0 的零宽棱(本件实测 R30.500 裂成 5168.4 + 493.9 两张面)。
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)
# 【2026-10-07 修复:仅切除敞口侧承压面环墙,保护侧壁杜绝缺口漏螺丝】
# 仿照 P05 设计,只在敞口半侧(局部 y>=0)将承压面段(z: -REAR_SEAT..0)切平,
# 彻底消除阻碍电机装配的凸起围挡;臂身侧壁(局部 y<0)绝对不动,杜绝切穿侧壁留下缺口!
s = s.cut(j4.L(bx(-40.0, 40.0, 0.0, 40.0, -AB.REAR_SEAT, 0.0)))
# 5b. 【2026-10-02 新增】M4 前端压盖 4xM3x8(BCD55):M4 原来只被后端板单面锁死,
# 大扭矩下会绕 J4 轴在 Ø60.3 座孔里点头。现在前端也面压夹一次(见 motor_front_clamp)。
# 熔接放在所有 R30.15~30.5 圆柱刀具之后,理由同 p_fork 7b。P07 的 J4 从动侧
# 已相应后移到 Y>=162(见 p_forearm_bone 的 J4_CLEAR),与压盖前端面留 1.0mm。
s = s.fuse(motor_front_clamp(X_ARM, Z_EL, tag="P06/M4"))
# 5b2.【2026-10-05e / 2026-10-07 修复】删掉前端"围挡"在敞口侧的那半圈环墙(见 FENCE_OPEN_CUT)。
s = cut_motor_fence(s, j4, "P06/M4", "P06")
# 5c. 【2026-10-02 新增】电机上方过渡块减重窗(见 hollow_tent_window)。
# Z360..385 这段 Loft 过渡块壁厚最深 15.8mm,是 P06 唯一一块可掏的厚料
# (Z332..367 的下部本体已经被 Ø61 定子孔掏成 4.5mm 薄壁,没得再掏)。
# 但它正压在 J3 毂下面,所以三条红线:
# 1) 窗顶最高只到 Z374 —— J3 的 Ø70 毂面在 Z380..388.5,窗顶留 6mm 整块
# 实心托住毂面承压,绝不碰毂面和它后面的 3xS01 直锁螺钉;
# 2) 窗的 X 范围 3..37 —— 截面本体 X-15..55,两侧各留 18mm 竖墙,
# 该高度上零件才不会被切成两半(必须单实体);
# 3) 拱形(下宽上窄)轮廓,斜面与水平 >= 52° 自支撑,只有顶部 X12..28 的
# 16mm 平顶需要桥接(与 P03 同量级)。
# 窗底取 Z362 而不是更低:Z360 附近是全段安全系数最低的截面,
# 抬高窗底 4mm 比横向收窄 8mm 更划算(减重量只少 3cm3,安全系数从 92% 回到 100%)。
# 窗沿 Y 双向切穿(Y87..172 超出本体,前后都贯通),顺带把这段的定子散热口
# 一起打开。
# 【2026-10-03 关闭】用户实机视点确认这个拱形窗在正面看就是一个贯通的矩形黑洞,
# 位于J4 关节正上方、整机最显眼的落脚点,视觉上像"零件破了个洞",非常不雅观。
# 权衡后恢复实心:代价只有 161->183cm3(+22cm3 / +27g PLA),而强度本来就已经是
# 无窗基线的 100%(这段本来就不缺强度),减重收益纯属锦上添花。
# 6. J3 直连法兰结构(凸台延伸至 65.0 跨过结合面牢固熔接,保持单实体与零干涉)
# 凸台随 J3 毂面一起后移 CLAMP_BACKOFF=5.5(59.5→65.0):进入电机输出毂的深度
# 仍是 s54.942..56.0 那 1.06mm,3xS01 的头面台阶(s=58)/杆长/咬合量一律不变。
boss = cyl(17.8, 54.942, 59.5 + CLAMP_BACKOFF)
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)))
s = hub_m2_holes(s, j3, cb_z1=58.0 + 40.0)
# 9. −X 侧线束走廊扎带拱桥(与 P03/P04 同一配方:站出 3.2mm、桥下 3.2×1.8 穿带隧道、孔沿 Y)
# 走廊原来贴 x=-17.5 走,离本件 −X 面 (X_ARM-35 = -15.5) 只剩 2.0mm —— 线皮净空 0.2mm,
# 扎带没有容身处。桥站出后线心改到 x=-20.5(桥顶 -18.7 + 线半径 1.8),与 P03 舱段同一处理。
# 落点 Z=345 在 lower_block 的 −X 平面上 (Z332..367.5 / Y92..155.5):足迹 Z341..349 离块体
# 下缘 9.0mm、Y97.5..107.5 离 Y 边 5.5mm,实测平面度 0.00mm、背后 10.35mm 整实料(未打穿内腔)。
s = s.fuse(make_surface_tie_bridge_neg_x(X_ARM - 35.0, 102.5, 345.0,
w_y=10.0, l_z=8.0, h_x=3.2))
return s
JOINT = 'J3'
def specs(JS):
"""本件的 (件号, 中文名, 构建 thunk, 配色, 说明) 清单;顺序即 BOM 顺序。"""
j3 = JS["J3"]
j4 = JS["J4"]
return [
("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "大臂骨架", lambda: p_humerus_bone(j3, j4), 0x0d9488,
"Loft有机过渡版:借鉴P05圆弧变方形放样算法,顶端Ø70正圆与J3平齐无台阶,底端渐变融入M4抱紧套筒与壳体;【2026-10-03】取消电机上方Loft过渡块(Z360~385)的拱形减重窗,恢复实心:该窗沿Y贯通,正面看是整机最显眼处的矩形黑洞(用户视觉整改),代价仅 +22cm3/+27g,强度仍为无窗基线100%;【2026-10-03】J3 毂面整体后移 5.5mm(顶端 388.5→383.0,直锁凸台 59.5→65.0):给 P05 新增的 M3 前端压盖(Z384..389)让位,留 1.0mm 动间隙——与 Y 轴三处(J2/J4/J5 令从动法兰盘 Y157→162)完全同源,换算式同为「压盖 5.0 + 动间隙 1.0 - 原基准 0.5」;凸台同步加长,3xS01 头面台阶(s=58)/杆长/咬合量与 J3 直锁关系一律不变;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R<R35 的必然产物。现 SEAT_R=35.0、SEAT_TOP=+1.0(越过尾端面 1mm,吃掉半包开口处那截0.052cm3 残片):承压面段整圈挖到外壁,承压面成为完整 R35 平圆盘,凸台与围挡一起消失,电机尾端面与承压面之间再无凸起。承力改由臂身本体承担(band 段 r36..58 有料方位 P04 10/36、其余各 15/36,其余方位靠 z>+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"),
]
+198
View File
@@ -0,0 +1,198 @@
# -*- coding: utf-8 -*-
"""P07 小臂骨架:J4 -> J5 的 L 型结构 + 顶板减重槽。"""
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
from arm.cad.arm_build import Ycyl, bx
from arm.kinematics import PLATE_T, X_ARM, Y_COVER, Y_HUB, Y_REAR, Z_EL, Z_WR
from arm.cad.part_tools import (
FILLET_SMALL, cut_motor_fence, hollow_channel, hub_m2_holes,
make_surface_tie_bridge_neg_x, motor_front_clamp,
)
P07_CH = dict(x0=-3.0, x1=43.0, y0=103.0, y1=153.0, z_floor=255.0) # 小臂顶板
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) 与装配零碰撞。
5.【2026-10-02】M5 前端压盖(4xM3x8 BCD55)消除"电机只被后端板单面锁死"的倾斜隐患;
J4 从动侧整体让位:y_hub_in 由 158.5 后移到 J4_CLEAR=162.0,让出 P06 的 M4 前端压盖
(前端面 Y=161.0),留 1.0mm 动间隙。J4 直连凸台同步加长,3xS01 输出螺钉咬合量不变。
6.【2026-10-09】−X 侧线束走廊加一座外凸扎带拱桥 (y=102.5, Z=235):站出 3.2mm、桥下
3.2×1.8 穿带隧道沿 Y 贯通(与 P06/P03/P04 同配方)。走廊线心随之由 x=-17.5 提到 -20.7:
原线皮离本件 −X 面只剩 0.2mm,扎带没有容身处。
"""
y_out = Y_HUB + PLATE_T # 174.0
# J4 从动侧让位基准:必须 >= P06 的 M4 前端压盖前端面(Y_COVER+5.0=161.0) + 1.0mm
J4_CLEAR = Y_COVER + 6.0 # 162.0
y_hub_in = J4_CLEAR
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)
# 6b. 【2026-10-02 新增】顶板根部减重槽(见 hollow_channel)。
# 顶板在 Z255..280 一整段是壁厚最深 15mm 的实心料,而 J4 在 z=245 的安全系数
# 高达 122、z=195 也有 24。挖成朝上敞开的 U 形槽,顶板变成槽钢:
# 槽底 Z255 以下保留整块实心到电机座,抗弯的两片 X 向腹板(X-15..-7 / X47..55)
# 留在弯矩拉压区。Y 上限取 156 而非核心的 166,是为了避开 y_hub_in=158.5 起的
# 竖直立柱本体(立柱是 J4 从动法兰的承力柱,不能掏)。
# 槽顶 Z280 就是顶板上表面,敞口向上,免支撑、非封闭腔。
s = s.cut(hollow_channel(P07_CH["x0"], P07_CH["x1"], P07_CH["y0"], P07_CH["y1"],
P07_CH["z_floor"], 300.0, tag="P07顶板"))
# 7. M5 电机安装接口与定子空腔
# 【2026-10-05f 接缝整改】本件原先是全机唯一给腔径加 CLEARANCE_GAP(2.0) 的地方:
# front_collar_pocket(r_bore=32.25) + 一刀 Ycyl(32.5, Y_REAR-0.5, y_front-6)。
# 结果内壁是 35 -> 32.5 -> 30.2 -> 32.3 -> 30.2,比兄弟件多两道台阶
# (实测 scratch/bay_step_profile.py:z=5.5 处 32.5、z=49.5 处掉到 30.2、
# z=50.25 又回到 32.3)。
# 这 2.0mm 是「关节间隙」,但关节间隙是**轴向**让位(见 hub_gap / CLAMP_CLR),
# 径向把腔径撑大 2mm 只会让电机在腔里晃,且与 P05/P06/P08 不一致。
# 现统一走 BAY_R(30.50),与兄弟件同径,接缝归零。
# 【2026-10-03i】front_collar_pocket 随之删除:它切 cyl(BAY_R, 50.0..56.5),
# 是 Joint.pocket 那根 cyl(BAY_R, -2.6..61.6) 的子集,同半径重复相减
# 只会在腔壁上多出一条 s=50.0 的零宽棱。
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)
# 【2026-10-07 修复:仅切除敞口侧承压面环墙,消除用户圈出的围挡】
# 仿照 P05 设计,只在敞口半侧(局部 y>=0)将承压面段(z: -REAR_SEAT..0)切平,
# 彻底消除用户红圈指出的承压面凸起围挡;顶板背脊侧(局部 y<0)绝对不动,杜绝切穿本体!
s = s.cut(j5.L(bx(-40.0, 40.0, 0.0, 40.0, -AB.REAR_SEAT, 0.0)))
# 【2026-10-05f】原 Ycyl(30.5+CLEARANCE_GAP=32.5, ...) 整刀删除:
# 它既撑大了腔径(接缝主因),又已被主腔 BAY_R 完全覆盖(纯冗余)。
# 半包散热设计:-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)
# 7b. 【2026-10-02 新增】M5 前端压盖 4xM3x8(BCD55):M5 原来只被后端板单面锁死,
# 大扭矩下会绕 J5 轴在座孔里点头。现在前端也面压夹一次(见 motor_front_clamp)。
# 必须在第 6 步 p08_clearance(R35.5 让位孔,Y>=156)之后熔接,否则压盖会被削掉。
# P08 的 J5 从动侧已相应后移到 Y>=162(make_pitch_to_roll_bracket 的 hub_gap=6.0)。
s = s.fuse(motor_front_clamp(X_ARM, Z_WR, tag="P07/M5"))
# 7c2.【2026-10-05e / 2026-10-07 修复】删掉前端"围挡"在敞口侧的那半圈环墙(见 FENCE_OPEN_CUT)。
s = cut_motor_fence(s, j5, "P07/M5", "P07")
# 8. J4 直连法兰结构(凸台延伸到法兰盘背面之后 2.0mm 跨过间隙与法兰盘牢固熔接,
# 保持单实体)。凸台长度跟着 y_hub_in 走:法兰盘后移时凸台同步加长,
# 3xS01 输出螺钉的头面台阶(局部 z=58)、杆长、咬合量与改前完全一致。
boss = cyl(17.8, 54.942, (y_hub_in - HL.Y_REAR) + 2.0)
s = s.fuse(j4.L(boss))
s = s.cut(j4.L(cyl(3.5, 54.0, 74.0 + 30.0)))
s = hub_m2_holes(s, j4, cb_z1=74.0 + 30.0)
# ========== 关键圆角优化 ==========
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}")
try:
# 垂直立柱下部外缘圆角(消除立柱垂直外壁与下方悬空水平底面交汇处的 90° 尖角,用户截图红圈指定)
wp_s = cq.Workplane(obj=s)
sel_leg_bot = cq.selectors.BoxSelector(
(X_ARM - 40, y_out - 1.0, z_top_housing - 1.0),
(X_ARM + 40, y_out + 1.0, z_top_housing + 1.0)
)
s = wp_s.edges(sel_leg_bot).fillet(FILLET_SMALL).val()
except Exception as e:
print(f"P07 立柱下底外缘圆角异常: {e}")
# 9. −X 侧线束走廊扎带拱桥(与 P06/P03/P04 同一配方:站出 3.2mm、桥下 3.2×1.8 隧道、孔沿 Y)
# 线心随桥由 x=-17.5 改到 -20.5(桥顶 -18.7 + 线半径 1.8);本件 −X 面离走廊同样只有
# 2.0mm,原来线皮净空 0.2mm,扎带无从下手。
# 落点 Z=235 在 M5 壳体顶板的 −X 平面 (x=-15.5, Z222..280) 上:足迹 Z231..239 离顶板下缘
# 9.0mm、Y97.5..107.5 离 Y 边 5.5mm,实测平面度 0.00mm、背后 10.35mm 整实料;顶板减重槽
# 在 X≥-3 / Z≥255,与本桥不相交。
s = s.fuse(make_surface_tie_bridge_neg_x(X_ARM - 35.0, 102.5, 235.0,
w_y=10.0, l_z=8.0, h_x=3.2))
return s
JOINT = 'J4'
def specs(JS):
"""本件的 (件号, 中文名, 构建 thunk, 配色, 说明) 清单;顺序即 BOM 顺序。"""
j4 = JS["J4"]
j5 = JS["J5"]
return [
("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "小臂骨架", lambda: p_forearm_bone(j4, j5, None), 0x7c3aed,
"V2 L型结构 + 顶板减重槽:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶板和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,顶板前后外缘2mm倒角改R3圆角,立柱底端外下缘90°直棱倒R3圆角(平滑消除立柱外壁与水平底面交汇处直角尖角);顶板整条加高19mm(z261->280)加强连接且gusset弧R28->R14重定相切融入,J4圆盘外沿与立柱外棱不倒圆保持原始尖角,下方M5电机半包散热,俯仰大范围自由运动零干涉;【2026-10-02】顶板根部Z255~280开朝上敞开的减重槽(X-3~43 / Y103~153, Y上限取153避开y_hub_in=158.5起的竖直立柱本体):顶板由实心变槽钢,288->230cm3(-20%),槽底以下保留整块实心到电机座,抗弯靠两侧各10mm的X向腹板;槽底水平朝上、侧壁竖直、顶部敞开故零支撑且非封闭腔;开度按「减重区最差安全系数 >= 无槽基线 95%」选定(X-3~43 比最初的 X-7~47 收窄 4mm,强度从 88% 回到 96%);【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R<R35 的必然产物。现 SEAT_R=35.0、SEAT_TOP=+1.0(越过尾端面 1mm,吃掉半包开口处那截0.052cm3 残片):承压面段整圈挖到外壁,承压面成为完整 R35 平圆盘,凸台与围挡一起消失,电机尾端面与承压面之间再无凸起。承力改由臂身本体承担(band 段 r36..58 有料方位 P04 10/36、其余各 15/36,其余方位靠 z>+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"),
]
+33
View File
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
"""P08 腕偏转座:J5 俯仰 -> J6 滚转(复用 part_tools 的 pitch->roll 骨架)。"""
from arm.kinematics import Z_R6, Z_WR
from arm.cad.part_tools import make_pitch_to_roll_bracket
def p_wrist_bracket(j5, j6):
"""P08_腕偏转座_螺旋体25%_墙4底5_竖打(深度圆角优化版)
hub_gap=6.0:J5 处 P07 新增了 M5 前端压盖(Y 155.5..161.0),从动法兰盘背面必须
从 Y=157 后移到 Y=162 让位;J5 直锁凸台同步加长,3xS01 输出螺钉咬合量不变。
roll_clamp=True【2026-10-03】:M6 补上前端压盖,与 P04/P05/P06/P07 的
M2/M3/M4/M5 同一套配方(R35、4xM3x8 BCD55 @17.8°+90k、Ø6.0 沉孔、Ø61.0 根部
让位槽),轴向 -Z,压盖占全局 Z 120.5..115。此前本件是五处电机腔里**唯一**
只被后端板 4xM3x10 单面锁死的,电机大扭矩下会在 Ø60.3 座孔里点头;对侧
P09 让位 CLAMP_BACKOFF=5.5(见 make_direct_hub 的 backoff),工具端点不动。
"""
return make_pitch_to_roll_bracket(j5, j6, Z_WR, Z_R6,
name="P08_腕偏转座_螺旋体25%_墙4底5_竖打",
hub_gap=6.0, roll_clamp=True)
JOINT = 'J5'
def specs(JS):
"""本件的 (件号, 中文名, 构建 thunk, 配色, 说明) 清单;顺序即 BOM 顺序。"""
j5 = JS["J5"]
j6 = JS["J6"]
return [
("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "腕部转接座", lambda: p_wrist_bracket(j5, j6), 0xd97706,
"V2圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,彻底清除立壁保证J5全角度0干涉;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R<R35 的必然产物。现 SEAT_R=35.0、SEAT_TOP=+1.0(越过尾端面 1mm,吃掉半包开口处那截0.052cm3 残片):承压面段整圈挖到外壁,承压面成为完整 R35 平圆盘,凸台与围挡一起消失,电机尾端面与承压面之间再无凸起。承力改由臂身本体承担(band 段 r36..58 有料方位 P04 10/36、其余各 15/36,其余方位靠 z>+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"),
]
+95
View File
@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
"""P09 工具法兰:末端法兰盘与 J6 直锁 hub。"""
import math
from arm.cad.helpers import cyl
from arm.cad.arm_build import D2R
from arm.kinematics import PLATE_T, X_ARM, Y_AXIS, Z_HUB6
from arm.cad.part_tools import CHAMFER_EDGE, CLAMP_BACKOFF, FILLET_SMALL, FILLET_TINY, make_direct_hub
def p_tool_flange(j6):
"""P09_工具法兰_螺旋体30%_墙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)
P09_M4 = []
# 4x M4螺栓孔
#
# 分布圆半径 20 -> 22(BCD 40 -> 44):R20 与 3xS01 输出螺钉的 Ø5.6 螺丝刀通道
# (make_direct_hub 里 cyl(2.8, 58.0, z_out+40) 在 BCD29=R14.5、相位 351.4/232.3/112.1°)
# 咬穿沉孔。0° 那个孔中心 (39.5,128) 到 351.4° 通道中心 (33.84,125.83) 只有 6.06mm,
# 而两孔半径之和是 3.5+2.8=6.30 —— 差 0.24mm,Ø7.0 沉孔壁被啃掉一块,
# 完整圆柱面(88mm²)裂成 45.7+35.4 两片,即用户截图里的月牙孔。
# 该相位差只有 8.6°,转动 M4 孔相位无法躲开(0/90/180/270 与 45 度错开全部仍 <6.30),
# 只能加大分布圆半径:R22 时最小中心距 7.96mm,余量 1.66mm。
# 边壁仍充足:R35 法兰外径 - (R22 + 沉孔 3.5) = 9.5mm。
for a in (0, 90, 180, 270):
rad = 22.0
u = rad * math.cos(a * D2R)
v = rad * math.sin(a * D2R)
# 实际切割推迟到 make_direct_hub 之后(见下)。这里只算出 4 个孔位。
P09_M4.append((X_ARM + u, Y_AXIS + v))
# 定位止口 + 走线孔
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)
# 直连法兰
# 【2026-10-03】M6 补上前端压盖后,J6 毂面必须让位 CLAMP_BACKOFF=5.5
# (详见 make_direct_hub 的 backoff)。P09 的法兰本体 z_bot/Z_HUB6 一个字没动,
# 让位的只是伸进 J6 腔里的那圈毂面 —— 圆盘下移后仍与法兰本体在局部 65..79.5
# 大面积交叠,熔接不受影响;工具端点 Z_TIP=93 也随之不变。
s = make_direct_hub(s, j6, r_disc=35.0, z_out=74.0, backoff=CLAMP_BACKOFF)
# 4x M4 孔(必须在 make_direct_hub 之后切)
#
# 直连法兰那步 fuse 了 R35 圆盘(局部 z=56.5..74 -> 全局 z=102..121 一侧),
# 会把先切的孔重新填死:分段求交显示 z=97..102 无干涉、z=102 以上整根杆都埋在料里
# (每段 12.57 mm^3 = pi*2^2*1,全截面被埋)。
#
# 孔径与深度同时修正:旧值 Ø3.7 比 M4 杆 Ø4.0 还细,且只到 z=105,
# 而 M4x16 头面在 z=97、杆要走到 z=113 —— 杆尾 8mm 无处可去(干涉 147.3 mm^3)。
# Ø4.4 是 M4 标准间隙孔(ISO 273 medium ≈ Ø4.3)。
# 孔顶要盖过 M4x16 的杆尾:头面 z=97 + 杆长 16 = z=113,故开到 Z_HUB6+4=115,
# 比杆尾多留 2mm(实测开到 112 时最后 1mm 杆尾仍撞料 37.7mm³)。
for (fx, fy) in P09_M4:
s = s.cut(cyl(2.2, z_bot - 1.0, Z_HUB6 + 4.0, fx, fy))
s = s.cut(cyl(3.5, z_bot - 1.0, z_bot + 4.0, fx, fy))
# ========== 关键圆角优化 ==========
try:
# 外轮廓圆角
s = s.edges("|Z").fillet(FILLET_SMALL)
except:
pass
try:
# 法兰面倒角
s = s.edges("<Z").chamfer(CHAMFER_EDGE)
except:
pass
try:
# 顶部边缘圆角
s = s.edges(">Z").fillet(FILLET_TINY)
except:
pass
return s
JOINT = 'J6'
def specs(JS):
"""本件的 (件号, 中文名, 构建 thunk, 配色, 说明) 清单;顺序即 BOM 顺序。"""
j6 = JS["J6"]
return [
("P09_工具法兰_螺旋体30%_墙4底5_竖打", "末端工具法兰", lambda: p_tool_flange(j6), 0xdc2626,
"V2圆角:外轮廓R=3mm,法兰面倒角2mm;【2026-10-02】打印填充由100%降到30%:该截面安全系数288是全机裕度最大的件,满填充纯属浪费(件号同步改为P09_工具法兰_螺旋体30%)"),
]