767 lines
36 KiB
Python
767 lines
36 KiB
Python
# -*- 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
|
|||
|
|
|
|||
|
|
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():
|
|||
|
|
"""Torso mast the arm is bolted to: a 12 mm plate with lightening and a cable window."""
|
|||
|
|
s = bx(-X_CHEST, X_CHEST, -Y_CHEST, Y_CHEST, Z_CHEST0, Z_CHEST1)
|
|||
|
|
s = s.cut(bx(-18, 18, -48, 48, 130, 220)) # cable window
|
|||
|
|
for k in range(6):
|
|||
|
|
s = s.cut(bx(-X_CHEST - .01, -26, -54, 54, 48 + 60 * k, 76 + 60 * k))
|
|||
|
|
s = s.cut(bx(26, X_CHEST + .01, -54, 54, 48 + 60 * k, 76 + 60 * k))
|
|||
|
|
for (xx, yy) in [(-38, -44), (38, -44), (-38, 44), (38, 44)]: # 4x M5 through
|
|||
|
|
s = s.cut(cyl(2.75, Z_CHEST0 - .01, Z_CHEST1 + .01, xx, yy))
|
|||
|
|
for yy in (-44, 44):
|
|||
|
|
s = s.cut(Ycyl(1.7, -20, 20, yy, Z_CHEST0 + 28))
|
|||
|
|
return s
|
|||
|
|
|
|||
|
|
|
|||
|
|
def p_base_foot():
|
|||
|
|
s = bx(-84, 84, -Y_CHEST - 24, Y_H + 44, 0, Z_CHEST0)
|
|||
|
|
s = s.cut(bx(-X_CHEST - .01, X_CHEST + .01, -Y_CHEST - .01, Y_CHEST + .01,
|
|||
|
|
Z_CHEST0 - .01, Z_CHEST0 + 5))
|
|||
|
|
for (xx, yy) in [(-70, -86), (70, -86), (-70, 86), (70, 86)]:
|
|||
|
|
s = s.cut(cyl(2.75, -.01, Z_CHEST0 + .01, xx, yy))
|
|||
|
|
s = s.cut(cyl(5.5, -.01, 3.0, xx, yy))
|
|||
|
|
return s
|
|||
|
|
|
|||
|
|
|
|||
|
|
def p_girdle(j1):
|
|||
|
|
"""P03_肩部基座 (Chest Shoulder Base):
|
|||
|
|
Mounted directly to the chest mast, holds M1 horizontally with fore-aft roll axis.
|
|||
|
|
Fixes M1 strictly via two ends (4x rear bolts @ BCD 53.7mm + front collar).
|
|||
|
|
Stator middle cylinder is 100% exposed for cooling.
|
|||
|
|
"""
|
|||
|
|
zb0 = Z_CHEST1 - 34.0
|
|||
|
|
# Mast mount bracket (Y: 40..66, Z: zb0..Z_CHEST1)
|
|||
|
|
s = bx(-35.0, 35.0, 40.0, 66.0, zb0, Z_CHEST1)
|
|||
|
|
s = s.cut(bx(-X_CHEST - 0.01, X_CHEST + 0.01, -Y_CHEST - 1, Y_CHEST + 1, zb0 - 0.01, Z_CHEST1 + 1))
|
|||
|
|
|
|||
|
|
# Diagonal arm slanting up from (Y: 60, Z: 493) to (Y: 128, Z: 610)
|
|||
|
|
# Stays at Y <= 80 for Z <= 535, so it completely clears M2 (at Y: 92..156, Z <= 525) and P04
|
|||
|
|
diag_pts = [
|
|||
|
|
(45.0, Z_CHEST1),
|
|||
|
|
(66.0, Z_CHEST1),
|
|||
|
|
(80.0, 535.0),
|
|||
|
|
(Y_AXIS + 32.0, Z_RL - 22.0),
|
|||
|
|
(Y_AXIS + 32.0, Z_RL + 22.0),
|
|||
|
|
(Y_AXIS - 34.0, Z_RL + 22.0),
|
|||
|
|
(Y_AXIS - 34.0, Z_RL - 10.0),
|
|||
|
|
(70.0, 540.0),
|
|||
|
|
(45.0, Z_CHEST1 + 10.0),
|
|||
|
|
]
|
|||
|
|
diag = Xplate(diag_pts, Z_R1 - 8.0, 4.0)
|
|||
|
|
s = s.fuse(diag)
|
|||
|
|
|
|||
|
|
# M1 rear plate at Z_R1 (X: -59..-51)
|
|||
|
|
rear_m1 = Xcyl(35.0, Z_R1 - 8.0, Z_R1, Y_AXIS, Z_RL)
|
|||
|
|
s = s.fuse(rear_m1)
|
|||
|
|
|
|||
|
|
# M1 front locating collar at X: 0..5 (before hub plane X=14)
|
|||
|
|
front_m1 = Xcyl(35.0, 0.0, 5.0, Y_AXIS, Z_RL)
|
|||
|
|
s = s.fuse(front_m1)
|
|||
|
|
|
|||
|
|
# Cut M1 rear bolt holes and front collar pocket (Two-End Mounting!)
|
|||
|
|
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, z0=50.0, z1=56.5)
|
|||
|
|
s = j1.pocket(s)
|
|||
|
|
|
|||
|
|
# Stator middle zone exposed
|
|||
|
|
s = s.cut(Xcyl(30.5, Z_R1 + 0.5, -0.5, Y_AXIS, Z_RL))
|
|||
|
|
|
|||
|
|
# Mast bolt holes
|
|||
|
|
for yy in (50.0, 60.0):
|
|||
|
|
for xx in (-28.0, 28.0):
|
|||
|
|
s = s.cut(cyl(2.2, zb0 - 0.01, Z_CHEST1 + 0.01, xx, yy))
|
|||
|
|
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. 后端安装法兰盘:全封闭 Ø70mm (R=35) 刚性圆盘 (Y: 92.0 .. 100.0),高刚度固定 4x M2 电机安装螺钉
|
|||
|
|
rear_plate = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_SH)
|
|||
|
|
|
|||
|
|
# 2. XZ 平面有机连续流线轮廓(严格依照用户手绘红线数字化拟合):
|
|||
|
|
# 右侧:以 R=111.56mm 大直径切线圆弧从 (39.0, 545.0) 顺滑外扩至右侧极值点 (53.5, 490.0)
|
|||
|
|
# 左侧:严格依据用户手绘红线,构建三次 Hermite C1 切线连续曲线:
|
|||
|
|
# - 顶端 (5.5, 545.0) 与立柱壁面完全垂直相切 (0, 1)
|
|||
|
|
# - 底端 (-12.65, 506.0) 与 Ø70mm J2 回转圆盘以切线 (-0.514, -1.0) 完全同相相切自然过渡!
|
|||
|
|
# 底端水平切线维持于 Z_SH = 490.0,电机下半部分 (Z < 490.0) 100% 敞开镂空,极大强化电机散热!
|
|||
|
|
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))
|
|||
|
|
|
|||
|
|
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
|
|||
|
|
|
|||
|
|
xz_wire = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0))
|
|||
|
|
.moveTo(5.5, 545.0)
|
|||
|
|
.lineTo(5.5, 610.0)
|
|||
|
|
.lineTo(23.0, 610.0)
|
|||
|
|
.lineTo(23.0, 622.0)
|
|||
|
|
.lineTo(34.5, 622.0)
|
|||
|
|
.radiusArc((40.5, 616.0), 6.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)
|
|||
|
|
|
|||
|
|
# 3. YZ 平面包络:顶端顺接 J1 从动盘,底端收于 Z_SH = 490.0,使电机下半部完全敞开散热
|
|||
|
|
yz_pts = [
|
|||
|
|
(Y_AXIS - 38.0, Z_RL + 12.0),
|
|||
|
|
(Y_AXIS + 38.0, Z_RL + 12.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)
|
|||
|
|
|
|||
|
|
# 双向曲面高精度布尔求交 (BRepAlgoAPI_Common)
|
|||
|
|
c_int = BRepAlgoAPI_Common(yz_solid.wrapped, xz_shape.wrapped)
|
|||
|
|
c_int.Build()
|
|||
|
|
column = cq.Shape(c_int.Shape())
|
|||
|
|
|
|||
|
|
s = rear_plate.fuse(column)
|
|||
|
|
|
|||
|
|
# J1 从动圆盘结构(从 5.5 到 23.0,与电机 boss 紧密贴合并保留独立圆盘外轮廓)
|
|||
|
|
disc_j1 = Xcyl(38.0, 5.5, 23.0, Y_AXIS, Z_RL)
|
|||
|
|
s = s.fuse(disc_j1)
|
|||
|
|
|
|||
|
|
# 4. 电机完全容纳于一体化实心包络壳体内,中间不镂空保持全侧面连续实心造型
|
|||
|
|
|
|||
|
|
# 5. 减重与走线窗
|
|||
|
|
s = s.cut(Xplate([(Y_AXIS - 16.0, Z_SH + 74.0), (Y_AXIS + 16.0, Z_SH + 74.0),
|
|||
|
|
(Y_AXIS + 16.0, Z_RL - 22.0), (Y_AXIS - 16.0, Z_RL - 22.0)],
|
|||
|
|
20.0, 34.0))
|
|||
|
|
|
|||
|
|
# 6. 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):
|
|||
|
|
借鉴 P07 紫色骨架的高品质融合处理:
|
|||
|
|
1. 顶部从动法兰仅保留上半圆 (top_semi),下半部完全融入顺延曲面,消除独立下半圆凸棱与接缝;
|
|||
|
|
2. 内侧过渡采用有机凹弧 (.radiusArc) 从后安装螺孔平滑顺弧直达从动法兰根部 (156.5),
|
|||
|
|
配合上级电机同心圆弧避让 (Ycyl 35.5),彻底消除生硬的水平桌面与 90° L型折角;
|
|||
|
|
3. 外侧肩部采用切线转折加 R=10mm 平滑倒角过渡,以大直径圆弧顺滑连接至下方电机外圆;
|
|||
|
|
4. 下方电机实行半包设计:-Y侧完全敞开露空散热,+Y侧全深连续背脊壳体与圆柱完全填实为一个融合整体;
|
|||
|
|
5. 严格满足 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 + 0.5 # 156.5 (从动法兰全厚度直接延伸自电机间隙)
|
|||
|
|
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, X_ARM, Y_AXIS)
|
|||
|
|
|
|||
|
|
# 2. 俯仰轴直连法兰盘:全圆柱实体 (Y: y_hub_in .. y_out, R=35mm),彻底填实下方悬空!
|
|||
|
|
disc = Ycyl(35.0, y_hub_in, y_out, X_ARM, z_pitch)
|
|||
|
|
s = s.fuse(disc)
|
|||
|
|
|
|||
|
|
# 3. 后侧/内侧结构骨架 (Y: Y_AXIS .. y_hub_in, Z: z_plate .. z_pitch)
|
|||
|
|
rear_spine = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
|||
|
|
.moveTo(Y_AXIS, z_plate)
|
|||
|
|
.lineTo(y_hub_in, z_plate)
|
|||
|
|
.lineTo(y_hub_in, z_pitch)
|
|||
|
|
.lineTo(Y_AXIS, z_pitch)
|
|||
|
|
.close()
|
|||
|
|
.extrude(35.0, both=True))
|
|||
|
|
rear_spine = rear_spine.edges(cq.NearestToPointSelector((X_ARM, y_hub_in, z_plate))).fillet(8.0)
|
|||
|
|
s = s.fuse(rear_spine.val())
|
|||
|
|
|
|||
|
|
# 4. 前侧从底端 Ø70mm 圆柱到顶端法兰的有机相切平滑渐变过渡体 (Loft Morph)
|
|||
|
|
# 彻底消除底端两侧突出方壳与尖角,自底端 Ø70mm 圆柱平滑渐变扩展至顶端法兰!
|
|||
|
|
# t=0 (Z=z_bot): 截面完全贴合 Ø70mm 电机外圆弧 (u^2 + (Y - Y_AXIS)^2 <= 35^2),零突出尖角,与下级法兰无缝对齐!
|
|||
|
|
# t=1 (Z=Z_top): 截面完全贴合顶端法兰平面 (Y = y_out),平整相切
|
|||
|
|
# 导数在两端皆为 0 (Hermite S-curve 连续),实现 G1 自然相切无折痕过渡
|
|||
|
|
Z_top = z_pitch - 35.0
|
|||
|
|
z_start = z_bot
|
|||
|
|
N = 12
|
|||
|
|
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 = Y_AXIS + st * (y_out - Y_AXIS)
|
|||
|
|
|
|||
|
|
v_c = y_center - Y_AXIS
|
|||
|
|
v_s = y_side - Y_AXIS
|
|||
|
|
v_mid = max(v_c, v_s + 0.02)
|
|||
|
|
|
|||
|
|
w = (cq.Workplane("XY", origin=(X_ARM, 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())
|
|||
|
|
|
|||
|
|
# 5. 上下平滑连续包裹:在顶端 Z_top 到 z_pitch 之间填满 Disc 与 Loft 的连接
|
|||
|
|
top_fill = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
|||
|
|
.moveTo(Y_AXIS, Z_top)
|
|||
|
|
.lineTo(y_out, Z_top)
|
|||
|
|
.lineTo(y_out, z_pitch)
|
|||
|
|
.lineTo(Y_AXIS, z_pitch)
|
|||
|
|
.close()
|
|||
|
|
.extrude(35.0, both=True))
|
|||
|
|
s = s.fuse(top_fill.val())
|
|||
|
|
|
|||
|
|
# 4. 电机半包处理 (Half-Enclosure):-Y侧敞开散热,+Y侧背脊壳体加强
|
|||
|
|
cut_open = bx(X_ARM - 40.0, X_ARM + 40.0, Y_AXIS - 40.0, Y_AXIS + 3.0, z_bot + 6.0, z_r - 1.0)
|
|||
|
|
s = s.cut(cut_open)
|
|||
|
|
|
|||
|
|
# 5. 上级电机同心圆弧避让(同 P07 line 457 处理,同心平滑包裹并留出 0.5mm 间隙)
|
|||
|
|
clearance = Ycyl(35.5, Y_AXIS - 40.0, HL.Y_COVER + 0.1, X_ARM, z_pitch)
|
|||
|
|
s = s.cut(clearance)
|
|||
|
|
|
|||
|
|
# 6. 下方电机安装孔位与定位止口
|
|||
|
|
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)
|
|||
|
|
|
|||
|
|
# 7. 电机定子配合空腔
|
|||
|
|
s = s.cut(cyl(30.5, z_r - 49.5, z_r - 0.5, X_ARM, Y_AXIS))
|
|||
|
|
|
|||
|
|
# 8. 俯仰轴直连法兰结构(定位止口 + 走线通孔 + 3x M2 贯通沉头孔,取消多余底凸圆盘)
|
|||
|
|
boss = cyl(17.8, 54.942, 56.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))
|
|||
|
|
|
|||
|
|
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_小臂骨架 (Forearm Bone):
|
|||
|
|
受 J4 (肘部俯仰, +Y轴, 轴线 Z_EL=310.0) 驱动,
|
|||
|
|
向下延伸 135mm 形成修长轻盈的小臂骨架,底端抱紧 M5 (腕部俯仰电机, +Y轴, 轴线 Z_WR=175.0)。
|
|||
|
|
1. 彻底消除独立立柱与断差,材料完全填实为一体化平滑曲面;
|
|||
|
|
2. 从侧面切线位置(全法兰厚度 Y: 156.5..174.0)顺滑向下延伸收窄(R=169.1mm 连续切线圆弧);
|
|||
|
|
3. 彻底消除圆盘底部的悬空圆弧与阶梯,侧向全厚度一体化延伸顺接;
|
|||
|
|
4. 消除所有水平搁台与L型折角,全向流线型高刚度流形实体。
|
|||
|
|
"""
|
|||
|
|
y_out = Y_HUB + PLATE_T # 174.0
|
|||
|
|
y_hub_in = HL.Y_COVER + 0.5 # 156.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. M5 底部抱紧套筒与后端安装板 (R=35mm)
|
|||
|
|
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, HL.X_ARM, Z_WR)
|
|||
|
|
s = collar_front.fuse(rear_plate)
|
|||
|
|
|
|||
|
|
# 2. J4 顶部从动法兰盘:全圆柱实体 (Y: y_hub_in .. y_out, R=35mm),同 P08 架构彻底填实圆盘正面与侧方
|
|||
|
|
disc = Ycyl(35.0, y_hub_in, y_out, X_ARM, Z_EL)
|
|||
|
|
s = s.fuse(disc)
|
|||
|
|
|
|||
|
|
# 3. XZ 平面有机流线轮廓(全高单一大直径圆弧顺滑收腰,彻底消除中段折线与折痕断差):
|
|||
|
|
# 从顶端 Z_EL=332 到低端 Z_WR=222,单侧仅由一条完整的 R=305mm 大圆弧平滑顺接,中段零折痕
|
|||
|
|
xz_w = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0))
|
|||
|
|
.moveTo(-16.5, Z_WR)
|
|||
|
|
.threePointArc((-11.5, 277.0), (-16.5, Z_EL))
|
|||
|
|
.lineTo(53.5, Z_EL)
|
|||
|
|
.threePointArc((48.5, 277.0), (53.5, Z_WR))
|
|||
|
|
.close())
|
|||
|
|
xz_solid = xz_w.extrude(100.0, both=True).val()
|
|||
|
|
xz_shape = cq.Shape(xz_solid.wrapped)
|
|||
|
|
|
|||
|
|
# 4. YZ 平面有机骨架(全深双向相切连续 S 弯顺接,彻底消除后侧下端 70° 折痕与棱角):
|
|||
|
|
Z_top = Z_EL - 35.0 # 297.0 (完整覆盖 J4 从动圆盘与螺钉孔区域)
|
|||
|
|
Z_end = z_top_housing # 257.0 (M5 电机套筒顶端)
|
|||
|
|
dz_total = Z_top - Z_end # 40.0 mm
|
|||
|
|
dz_arc = dz_total / 2.0 # 20.0 mm
|
|||
|
|
dy_total = y_out - y_front # 18.5 mm
|
|||
|
|
dy_arc = dy_total / 2.0 # 9.25 mm
|
|||
|
|
R_scurve = (dy_arc**2 + dz_arc**2) / (2.0 * dy_arc) # 26.247 mm
|
|||
|
|
|
|||
|
|
Z_mid = Z_top - dz_arc # 277.0
|
|||
|
|
|
|||
|
|
C1 = (y_out - R_scurve, Z_top)
|
|||
|
|
theta_mid = math.asin(dz_arc / R_scurve)
|
|||
|
|
theta_half1 = theta_mid / 2.0
|
|||
|
|
p_half1 = (C1[0] + R_scurve * math.cos(theta_half1), C1[1] - R_scurve * math.sin(theta_half1))
|
|||
|
|
|
|||
|
|
C2 = (y_front + R_scurve, Z_end)
|
|||
|
|
theta_half2 = math.pi - theta_mid / 2.0
|
|||
|
|
p_half2 = (C2[0] + R_scurve * math.cos(theta_half2), C2[1] + R_scurve * math.sin(theta_half2))
|
|||
|
|
|
|||
|
|
# 后侧平滑相切 S 弯过渡:自 M5 顶端 Z=257 垂直相切起步,精确相切融入 J4 从动法兰
|
|||
|
|
z_rear_start = z_top_housing # 257.0 mm
|
|||
|
|
dz_r = (Z_EL - z_rear_start) / 2.0 # 37.5 mm
|
|||
|
|
dy_r = (y_hub_in - y_rear) / 2.0 # 32.25 mm
|
|||
|
|
R_r = (dy_r**2 + dz_r**2) / (2.0 * dy_r) # 37.927 mm
|
|||
|
|
|
|||
|
|
theta_r = math.asin(dz_r / R_r)
|
|||
|
|
C_r1 = (y_rear + R_r, z_rear_start)
|
|||
|
|
p_r_half1 = (C_r1[0] - R_r * math.cos(theta_r / 2.0), C_r1[1] + R_r * math.sin(theta_r / 2.0))
|
|||
|
|
C_r2 = (y_hub_in - R_r, Z_EL)
|
|||
|
|
p_r_half2 = (C_r2[0] + R_r * math.cos(theta_r / 2.0), C_r2[1] - R_r * math.sin(theta_r / 2.0))
|
|||
|
|
p_r_mid = (y_rear + dy_r, z_rear_start + dz_r)
|
|||
|
|
|
|||
|
|
yz_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
|||
|
|
.moveTo(y_hub_in, Z_EL)
|
|||
|
|
.lineTo(y_out, Z_EL)
|
|||
|
|
.lineTo(y_out, Z_top)
|
|||
|
|
.threePointArc(p_half1, (y_out - dy_arc, Z_mid))
|
|||
|
|
.threePointArc(p_half2, (y_front, Z_end))
|
|||
|
|
.lineTo(y_front, Z_WR)
|
|||
|
|
.lineTo(y_rear, Z_WR)
|
|||
|
|
.lineTo(y_rear, z_rear_start)
|
|||
|
|
.threePointArc(p_r_half1, p_r_mid)
|
|||
|
|
.threePointArc(p_r_half2, (y_hub_in, Z_EL))
|
|||
|
|
.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)
|
|||
|
|
|
|||
|
|
# 5. 走线槽直接贯穿一体化圆润实体腰身(无任何水平方顶折角)
|
|||
|
|
z_mid = (Z_EL + Z_WR) / 2.0
|
|||
|
|
slot = Ycyl(8.0, Y_AXIS - 15.0, y_out + 2.0, X_ARM, z_mid - 10.0)
|
|||
|
|
slot = slot.fuse(Ycyl(8.0, Y_AXIS - 15.0, y_out + 2.0, X_ARM, z_mid + 10.0))
|
|||
|
|
slot = slot.fuse(XB(-8.0, 8.0, Y_AXIS - 15.0, y_out + 2.0, z_mid - 10.0, z_mid + 10.0))
|
|||
|
|
s = s.cut(slot)
|
|||
|
|
|
|||
|
|
# 6. 切除静止电机 M4 与 P06 套筒干涉安全裕度(Y <= 156.0, R=35.5mm)
|
|||
|
|
clearance = Ycyl(35.5, 80.0, HL.Y_COVER + 0.1, HL.X_ARM, HL.Z_EL)
|
|||
|
|
s = s.cut(clearance)
|
|||
|
|
|
|||
|
|
# 7. M5 接口成型
|
|||
|
|
s = j5.front_collar_pocket(s, r_bore=30.25, 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, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_WR))
|
|||
|
|
|
|||
|
|
# 8. J4 直连法兰结构(定位止口 + 走线通孔 + 3x M2 贯通沉头孔)
|
|||
|
|
boss = cyl(17.8, 54.942, 56.5)
|
|||
|
|
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))
|
|||
|
|
|
|||
|
|
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 ----------------
|
|||
|
|
def build():
|
|||
|
|
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
|
|||
|
|
|
|||
|
|
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 = {}
|
|||
|
|
|
|||
|
|
add("P01_胸板", "躯干/胸腔安装板", p_chest(), 0x4b5563,
|
|||
|
|
note="手臂根部;12 厚板,顶面与肩吊臂桥板用 4x M4 连接,另有走线窗")
|
|||
|
|
add("P02_底脚", "站立底脚", p_base_foot(), 0x374151,
|
|||
|
|
note="0..%d,与机架安装面连接" % Z_CHEST0)
|
|||
|
|
add("P03_肩吊臂", "肩部基座(对标灵犀X1,胸部直出)", p_girdle(j1), 0x4b5563,
|
|||
|
|
note="胸腔顶部直出基座;抱紧M1定子")
|
|||
|
|
add("P04_肩叉", "正交肩肘(取消独立联轴器,3×M2螺钉直锁M1输出轴,贯通沉头孔)", p_fork(j1, j2), 0x2563eb,
|
|||
|
|
note="受 J1 驱动,一体化 3x M2 螺钉直锁 M1 电机输出轴,贯通沉头孔;90°一体化高刚度正交转向肘,抱紧 M2 电机")
|
|||
|
|
add("P05_大臂旋转座", "大臂旋转座(取消独立联轴器,3×M2螺钉直锁M2输出轴,贯通沉头孔)", p_shoulder_roll_base(j2, j3, None), 0x059669,
|
|||
|
|
note="受 J2 驱动,彻底取消黄色独立联轴器;基座直接贴合 M2 输出轴,3 颗 M2 贯通沉头螺钉直锁输出轴;90°正交抱紧 M3 电机,中间 100% 敞开散热")
|
|||
|
|
add("P06_大臂骨架", "大臂骨架(取消独立联轴器,3×M2螺钉直锁M3输出轴,贯通沉头孔)", p_humerus_bone(j3, j4), 0x0d9488,
|
|||
|
|
note="受 J3 驱动,彻底取消黄色独立联轴器;一体化 3x M2 贯通沉头螺钉直锁 M3 输出轴;流线型工字骨架+走线槽,底端抱紧 M4 肘部电机")
|
|||
|
|
add("P07_小臂骨架", "小臂骨架(取消独立联轴器,3×M2螺钉直锁M4输出轴,贯通沉头孔)", p_forearm_bone(j4, j5, None), 0x7c3aed,
|
|||
|
|
note="受 J4 驱动,彻底取消黄色独立联轴器;从动盘直接贴合 M4 输出轴,3 颗 M2 贯通沉头螺钉直锁输出轴;轻量高刚度骨架,底端抱紧 M5 电机")
|
|||
|
|
add("P08_腕偏转座", "腕部转接座(取消独立联轴器,3×M2螺钉直锁M5输出轴,贯通沉头孔)", p_wrist_bracket(j5, j6), 0xd97706,
|
|||
|
|
note="受 J5 驱动,彻底取消黄色独立联轴器;一体化 3x M2 贯通沉头螺钉直锁 M5 输出轴;两端刚性固定 M6 立式电机,中间 100% 敞开散热")
|
|||
|
|
add("P09_工具法兰", "末端工具法兰(取消独立联轴器,3×M2螺钉直锁M6输出轴,贯通沉头孔)", p_tool_flange(j6), 0xdc2626,
|
|||
|
|
note="受 J6 驱动,彻底取消黄色独立联轴器;一体化 3x M2 贯通沉头螺钉直锁 M6 输出轴;标准工业法兰接口,4x M4 螺孔 BCD 40mm + 定位止口与走线孔")
|
|||
|
|
|
|||
|
|
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")
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
Exploding each part along its own joint axis is physically honest but useless to read on
|
|||
|
|
this arm: four of the six axes are parallel lateral Y lines, so everything piles up
|
|||
|
|
sideways and the outlines overlap. Here each link steps DOWN the limb by one SEP per
|
|||
|
|
joint outboard, and that joint's motor + coupling kit then pulls out along its own axis -
|
|||
|
|
so the drawing reads top-down in the same order you assemble it.
|
|||
|
|
"""
|
|||
|
|
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))
|
|||
|
|
print(" fillet total: %d rounded corners" % tot)
|
|||
|
|
print("HUMANOID %s roll %.0f shoulder %.0f elbow %.0f wrist %.0f tip %.0f L1 %.0f"
|
|||
|
|
% (VERSION, Z_RL, Z_SH, Z_EI, Z_WR, Z_TIP, L1))
|
|||
|
|
bad = 0
|
|||
|
|
for p in PARTS:
|
|||
|
|
if not p["printed"]:
|
|||
|
|
continue
|
|||
|
|
n = len(solids_of(p["shape"]))
|
|||
|
|
if n != 1:
|
|||
|
|
bad += 1
|
|||
|
|
print(" %-12s %8.1f cm3 solids=%d%s" % (p["name"], p["vol"] / 1000.0, n,
|
|||
|
|
"" if n == 1 else " <<< NOT ONE SOLID"))
|
|||
|
|
print("single-solid failures: %d" % bad)
|
|||
|
|
AB.explode_vec = explode_chain
|
|||
|
|
bom = AB.export(JS)
|
|||
|
|
cl = AB.collide()
|
|||
|
|
print("COLLISION pairs=%d" % len(cl))
|
|||
|
|
for r in cl[:30]:
|
|||
|
|
print(" %-14s x %-14s %9.1f mm3 %s" % r)
|
|||
|
|
sys.stdout.flush()
|
|||
|
|
os._exit(0)
|