Files
Robot_Arm/src/legacy/arm_model_h1_legacy.py
T

943 lines
42 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 -*-
"""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("<X").fillet(3.0)).val()
s = cq.Shape(pod_outer.wrapped)
# 2. 一体化斜挑支撑柱 (Monolithic Pylon, 从 Z=470 直通电机机舱)
pylon_pts = [
(16.0, Z_CHEST1),
(78.0, Z_CHEST1),
(78.0, 542.0),
(Y_AXIS - 5.0, Z_RL - R_POD + 4.0),
(Y_AXIS, Z_RL - R_POD),
(Y_AXIS - R_POD, Z_RL + 10.0),
(Y_AXIS - R_POD - 6.0, Z_RL + 6.0),
(70.0, 542.0),
(16.0, Z_CHEST1 + 8.0),
]
pylon = Xplate(pylon_pts, X_REAR, X_FRONT)
s = s.fuse(pylon)
# 3. 底部与立柱对接的精密定位榫头 (Z=455~470mm)
boss = (cq.Workplane("XY", origin=(-27.0, 47.0, Z_CHEST1 - 15.0))
.rect(48.0, 48.0)
.extrude(15.0)
.edges("|Z").fillet(4.0)).val()
s = s.fuse(cq.Shape(boss.wrapped))
# 4. 4x M5 坚固连接螺孔 (隐蔽分布于基底,不露在外部侧面)
for xx in (-48.0, -6.0):
for yy in (26.0, 68.0):
s = s.cut(cyl(2.75, Z_CHEST1 - 16.0, Z_CHEST1 + 25.0, xx, yy))
# 5. 电机安装接口与内腔
s = j1.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0)
s = j1.front_collar_pocket(s, r_bore=30.25 + 0.5, z0=50.0, z1=56.5)
s = j1.pocket(s)
s = s.cut(Xcyl(30.6, Z_R1 + 0.5, -0.5, Y_AXIS, Z_RL))
# 6. 7组轴向散热百叶窗 (Axial Louvers)
slot_angles = [-15, 12, 38, 64, 90, 116, 142]
slot_w = 4.6
slot_l = 34.0
slot_x_mid = -24.0
for ang in slot_angles:
rad = ang * D2R
plane = cq.Plane(
origin=(slot_x_mid, Y_AXIS + 28.0 * math.cos(rad), Z_RL + 28.0 * math.sin(rad)),
xDir=(1, 0, 0),
normal=(0, math.cos(rad), math.sin(rad))
)
slot_solid = cq.Workplane(plane).slot2D(slot_l, slot_w).extrude(16.0).val()
s = s.cut(slot_solid)
# 7. 尾部 PCBA 驱动板散热窗口 (PCBA Vents)
for z_off in (-10.0, 10.0):
vent_plane = cq.Plane(
origin=(X_REAR - 1.0, Y_AXIS, Z_RL + z_off),
xDir=(0, 1, 0),
normal=(1, 0, 0)
)
vent_solid = cq.Workplane(vent_plane).slot2D(16.0, 5.0).extrude(12.0).val()
s = s.cut(vent_solid)
return s
def p_fork(j1, j2):
"""P04_肩叉 (Shoulder Fork / L-Bracket):
Driven by J1 roll output, drops directly to hold M2 (shoulder pitch motor).
Aligend along Y_AXIS = 128.0 mm (both M1 and M2 centered at Y=128!).
Fixes M2 strictly via two ends (4x rear bolts @ BCD 53.7mm + front collar).
Stator middle cylinder is 100% exposed for cooling. ZERO pinch screws!
严格依照用户绘制红线进行 XZ 双曲面有机流线塑形:
1. 右侧由 J1 凸台下方向内收腰 (x=35.0, z=550.0),底端以 R=106.5mm 大直径切线圆弧顺滑外扩,
以严格数学相切连续顺接下方 M2 圆柱最右侧极值点 (x=53.5, z=490.0),彻底消除原先 13mm 生硬台阶;
2. 左侧从 J1 从动盘下端 (x=5.5, z=572.0) 顺滑斜向下顺接至立柱 (x=14.0, z=515.0),填实原先悬空凹槽并大幅强化连接刚度;
3. 采用高精度双向曲面求交 (BRepAlgoAPI_Common) 构建单实体高刚度流线型结构。
"""
x0 = X_J1_HUB # 14.0
# 1. M2 后端安装法兰盘 (Y: 92..100, R=35mm)
rear_plate = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_SH)
# 2. M2 前端定位环 (Y: 149.5..155.5, R=35mm) - 恢复并加固蓝色的环!
front_collar = Ycyl(35.0, Y_COVER - 6.5, Y_COVER - 0.5, X_ARM, Z_SH)
s = rear_plate.fuse(front_collar)
# 3. J1 从动法兰盘 (X: 5.5..23.0, R=38mm)
disc_j1 = Xcyl(38.0, 5.5, 23.0, Y_AXIS, Z_RL)
s = s.fuse(disc_j1)
# 4. XZ 平面有机连续流线轮廓(加固并彻底抹平顶部 26mm 生硬阶梯):
# 顶部:自 (5.5, 648.0) 保持与 J1 从动盘同高延伸至 (23.0, 648.0),完全抹平阶梯!
# 右侧:自 (23.0, 648.0) 以 R=18mm 顺滑倒角过渡到 (39.0, 630.0),再以 R=111.56mm 切线大圆弧顺接下方 (53.5, 490.0)
# 左侧:维持 Hermite C1 切线连续自然过渡
dx_flare = 53.5 - 39.0
dz_flare = 545.0 - 490.0
R_flare = (dx_flare**2 + dz_flare**2) / (2.0 * dx_flare) # 111.56 mm
x_b, z_b = -12.65, 506.0
x_a, z_a = 5.5, 545.0
Lz = z_a - z_b
d0 = 0.514 * Lz
d1 = 0.0
hermite_pts = []
N_h = 16
for i in range(N_h + 1):
t = i / N_h
h00 = 2.0 * (t**3) - 3.0 * (t**2) + 1.0
h10 = t**3 - 2.0 * (t**2) + t
h01 = -2.0 * (t**3) + 3.0 * (t**2)
h11 = t**3 - t**2
xh = x_b * h00 + d0 * h10 + x_a * h01 + d1 * h11
zh = z_b + t * Lz
hermite_pts.append((xh, zh))
xz_wire = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0))
.moveTo(5.5, 545.0)
.lineTo(5.5, Z_RL + 38.0) # 648.0: 与 J1 圆盘顶部平齐共顶!
.lineTo(23.0, Z_RL + 38.0) # 抹平 X=23 处的 26mm 巨型台阶!
.radiusArc((39.0, 630.0), 18.0) # 自然圆角顺滑过渡到外侧肩部
.lineTo(39.0, 545.0)
.radiusArc((53.5, 490.0), -R_flare)
.lineTo(18.5, 490.0)
.lineTo(-16.5, 490.0)
.threePointArc((-15.1, 498.5), (-12.65, 506.0)))
for pt in hermite_pts[1:]:
xz_wire = xz_wire.lineTo(pt[0], pt[1])
xz_wire = xz_wire.close()
xz_solid = xz_wire.extrude(100.0, both=True).val()
xz_shape = cq.Shape(xz_solid.wrapped)
# YZ 平面包络:
# 顶部顺接 J1 圆盘 (Y_AXIS - 38 .. Y_AXIS + 38, Z=648)
# 底端全深顺接 M2 前后安装端 (Y: 92.0 .. 155.5)
yz_pts = [
(Y_AXIS - 38.0, Z_RL + 38.0),
(Y_AXIS + 38.0, Z_RL + 38.0),
(Y_COVER - 0.5, Z_SH + 36.0),
(Y_COVER - 0.5, Z_SH),
(Y_REAR - 8.0, Z_SH),
(Y_REAR - 8.0, Z_SH + 36.0),
]
yz_solid = Xplate(yz_pts, -20.0, 60.0)
c_int = BRepAlgoAPI_Common(yz_solid.wrapped, xz_shape.wrapped)
c_int.Build()
column = cq.Shape(c_int.Shape())
s = s.fuse(column)
# 6. 中部电机定子散热切除(R=30.5,保证电机定子散热,同时保留外侧与上下坚固承力壁)
s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_SH))
# 7. M2 接口成型
s = j2.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5)
s = j2.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0)
c_m2 = [cyl(BORE_R, -0.6, COVER_Z - 3.5), cyl(CB_R, COVER_Z - 3.5, COVER_Z + 0.6)]
s = s.cut(*[j2.L(item) for item in c_m2])
# 7. J1 直连法兰结构(定位止口 + 走线通孔 + 3x M2 贯通沉头孔)
boss = cyl(17.8, 54.942, 56.5)
s = s.fuse(j1.L(boss))
s = s.cut(j1.L(cyl(3.5, 54.0, 74.0 + 30.0)))
for a in AB.HUB_M2_CLOCK:
u = AB.M['m2_bcd'] / 2.0 * math.cos(a * AB.D2R)
v = AB.M['m2_bcd'] / 2.0 * math.sin(a * AB.D2R)
th = cyl(1.4, 54.0, 58.0, u, v)
cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v)
s = s.cut(j1.L(th)).cut(j1.L(cb))
return s
# ---------------------------------------------------------------- limbs -------------------
def make_pitch_to_roll_bracket(j_pitch, j_roll, z_pitch, z_r, name="bracket"):
"""Half-wrapped (半包) 双曲面有机流线型 Pitch-to-Roll 结构座 (P05 & P08):
1. 顶部从动法兰与前侧 Loft 自然相切顺接;
2. 严格清除 z_plate 顶面以上与上级旋转件贴面的多余材料 (cut_top_solid),保证关节大范围摆动动态 0 干涉;
3. 下方电机实行半包散热设计,-Y侧敞开,+Y侧背脊与圆柱一体化融合;
4. 严格满足 single-solid 流形拓扑与零干涉 (COLLISION pairs = 0)。
"""
z_bot = z_r - 56.0 # roll 电机底端 (front collar)
z_plate = z_r + 7.0 # roll 电机顶端安装板 (rear plate top)
y_out = HL.Y_HUB + HL.PLATE_T # 174.0 (俯仰直连法兰外侧面)
y_hub_in = HL.Y_COVER + 1.0 # 157.0 (从动法兰内侧面,预留 1.0mm 动间隙)
y_cyl_max = HL.Y_AXIS + 35.0 # 163.0 (下方电机外轮廓极值)
# 1. 基础立柱:Ø70mm 实心圆柱 (Z: z_bot .. z_plate, R=35mm)
s = cyl(35.0, z_bot, z_plate, HL.X_ARM, HL.Y_AXIS)
# 2. 俯仰轴直连法兰盘:全圆柱实体 (Y: y_hub_in .. y_out, R=35mm)
disc = Ycyl(35.0, y_hub_in, y_out, HL.X_ARM, z_pitch)
s = s.fuse(disc)
# 3. 前侧从底端 Ø70mm 圆柱到顶端法兰的有机相切平滑渐变过渡体 (Loft Morph)
Z_top = z_pitch - 35.0
z_start = z_bot
N = 16
wp = cq.Workplane("XY")
for i in range(N + 1):
t = i / float(N)
z_curr = z_start + t * (Z_top - z_start)
st = 3.0 * (t**2) - 2.0 * (t**3)
y_center = y_cyl_max + st * (y_out - y_cyl_max)
y_side = HL.Y_AXIS + st * (y_out - HL.Y_AXIS)
v_c = y_center - HL.Y_AXIS
v_s = y_side - HL.Y_AXIS
v_mid = max(v_c, v_s + 0.02)
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)
.lineTo(-35.0, -5.0)
.close())
wp = wp.add(w)
loft = wp.toPending().loft()
s = s.fuse(loft.val())
# 4. 顶部填充(仅保留 y >= y_hub_in 与法兰盘对齐的部分,避免侵入上级关节旋转扫掠区域)
top_fill = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0))
.moveTo(y_hub_in, Z_top)
.lineTo(y_out, Z_top)
.lineTo(y_out, z_pitch)
.lineTo(y_hub_in, z_pitch)
.close()
.extrude(35.0, both=True))
s = s.fuse(top_fill.val())
# 5. 旋转清让切除:彻底清除 z >= z_plate 且 y <= y_hub_in 范围内的多余材料,
# 消除导致与上游旋转件(P04/P07)严重干涉的立壁与尖角,保持顶面平整规整!
cut_top_solid = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0))
.moveTo(-999.0, z_plate)
.lineTo(y_hub_in, z_plate)
.lineTo(y_hub_in, z_pitch + 50.0)
.lineTo(-999.0, z_pitch + 50.0)
.close()
.extrude(60.0, both=True))
s = s.cut(cut_top_solid.val())
# 6. 电机半包处理 (Half-Enclosure):-Y侧敞开散热,+Y侧背脊壳体加强
cut_open = bx(HL.X_ARM - 40.0, HL.X_ARM + 40.0, HL.Y_AXIS - 40.0, HL.Y_AXIS + 3.0, z_bot + 6.0, z_r - 1.0)
s = s.cut(cut_open)
# 7. 上级电机同心圆弧避让
clearance = Ycyl(35.5, HL.Y_AXIS - 40.0, HL.Y_COVER + 0.1, HL.X_ARM, z_pitch)
s = s.cut(clearance)
# 8. 下方电机安装孔位与定位止口
s = j_roll.rear_mount_holes(s, t_plate=7.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0)
s = j_roll.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5)
s = j_roll.pocket(s)
# 9. 电机定子配合空腔
s = s.cut(cyl(30.5, z_r - 49.5, z_r - 0.5, HL.X_ARM, HL.Y_AXIS))
# 10. 俯仰轴直连法兰结构(凸台延伸至 59.5 跨过间隙与法兰盘牢固熔接,保持单实体)
boss = cyl(17.8, 54.942, 59.5)
s = s.fuse(j_pitch.L(boss))
s = s.cut(j_pitch.L(cyl(3.5, 54.0, 74.0 + 30.0)))
for a in AB.HUB_M2_CLOCK:
u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R)
v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R)
th = cyl(1.4, 54.0, 58.0, u, v)
cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v)
s = s.cut(j_pitch.L(th)).cut(j_pitch.L(cb))
# 11. 竖直臂前侧外角倒角(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)