Files
Robot_Arm/arm/cad/parts/p02_base.py
T
cbb 649bd69810 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 段逐字节一致。
2026-10-15 03:31:20 +08:00

294 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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刻字"),
]