Files
cadSet/text-to-cad/models/robots/lyra/lyra_parts/palm.py
T
2026-07-22 19:38:36 +08:00

302 lines
9.8 KiB
Python

"""lyra palm: wrist flange, carpal core, palm slab, knuckle row, thumb turret.
Local frame = robot root frame: origin at the wrist-flange mount face
center, +Z distal, +Y palmar, +X radial. The palm owns all MCP clevis
cheeks, knuckle pins, the thumb CMC turret pedestal/spindle, and the
tactile palm pad.
Articulation clearances baked into the shape:
- Knuckle slots are open from y -14 to +15 so proximal phalanges sweep
-15..+95 deg without touching the covers.
- The palmar plate's top edge stays >= 8 mm proximal of each MCP axis,
outside the swept envelope of the flexing proximal shaft.
- A 15 mm radius relief is turned around the thumb turret so the yawing
thumb base clevis never clips the radial palm corner.
"""
from __future__ import annotations
from build123d import (
Axis,
Box,
Plane,
Polyline,
Pos,
Rot,
RectangleRounded,
extrude,
loft,
make_face,
)
from .chain import FINGERS, MCP_ORIGIN_MM, THUMB_CMC_ORIGIN_MM
from .digits import (
FINGER_SCALE,
MCP_PIN_R,
PALM_LOBE_R,
PROX_TANG_W,
TB_TANG_R,
TB_TANG_H,
)
from .lib import (
ALU_COLOR,
AMBER_COLOR,
GRAPHITE_COLOR,
PAD_COLOR,
PEARL_COLOR,
SENSOR_COLOR,
part_compound,
pin,
safe_chamfer,
safe_fillet,
styled,
xcyl,
ycyl,
zcyl,
)
SLAB_HALF_Y = 9.5 # graphite core half-depth
COVER_Y0, COVER_Y1 = -12.1, -9.5 # pearl dorsal cover
PLATE_Y0, PLATE_Y1 = 9.5, 11.5 # pearl palmar plate
MCP_PIN_LEN = 17.0
# Thumb turret (axis +Z through chain.THUMB_CMC_ORIGIN_MM).
TURRET_R = 10.5
TURRET_RELIEF_R = 15.0
SPINDLE_R = 2.7
CX, CY, CZ = THUMB_CMC_ORIGIN_MM
# Palm silhouette in the XZ plane (clockwise; +X radial, +Z distal). The
# knuckle arch follows the MCP line (index 99 / middle 103 / ring 99 /
# pinky 90); lobes added per knuckle crown above it.
CORE_PTS = [
(-25.0, 33.0), (-33.0, 50.0), (-36.5, 72.0), (-36.5, 87.0),
(-30.0, 90.0), (-19.0, 95.0), (-9.5, 99.0), (0.0, 101.0),
(9.5, 103.0), (19.0, 101.0), (28.5, 99.0), (34.0, 97.5),
(37.5, 95.5), (37.5, 70.0), (30.0, 46.0), (25.0, 33.0),
]
COVER_PTS = [
(-23.5, 35.0), (-31.3, 51.0), (-34.8, 72.0), (-34.8, 85.6),
(-29.0, 88.4), (-18.6, 93.0), (-9.5, 97.0), (0.0, 99.0),
(9.5, 101.0), (18.6, 99.0), (28.5, 97.0), (33.0, 95.6),
(35.8, 93.8), (35.8, 71.0), (28.6, 47.5), (23.4, 35.0),
]
PLATE_PTS = [
(-23.5, 35.0), (-31.0, 51.0), (-34.5, 72.0), (-34.5, 80.0),
(-28.6, 82.5), (-18.6, 86.0), (-9.5, 91.0), (0.0, 93.0),
(9.5, 95.0), (19.0, 93.0), (28.5, 91.0), (35.5, 87.5),
(35.5, 71.0), (28.6, 47.5), (23.4, 35.0),
]
def _outline_solid(pts, y0, depth):
pts3 = [(x, y0, z) for x, z in pts]
face = make_face(Polyline(*pts3, close=True))
return extrude(face, amount=depth, dir=(0, 1, 0))
def _slot_gap(finger: str) -> float:
return PROX_TANG_W * FINGER_SCALE[finger] + 0.7
def _knuckle_cuts(solid):
"""Open the four MCP clevis slots and pin bores in a palm solid."""
for finger in FINGERS:
kx, _, kz = MCP_ORIGIN_MM[finger]
gap = _slot_gap(finger)
solid -= Pos(kx, 0.5, kz + 2.0) * Box(gap, 29.0, 26.0)
solid -= Pos(kx, 0, kz) * xcyl(MCP_PIN_R + 0.4, 26.0)
return solid
def _thumb_relief(solid):
"""Turned clearance around the thumb turret for the yawing base clevis."""
disc_z0 = CZ - TB_TANG_H / 2.0
return solid - Pos(CX, CY, disc_z0 + 5.6) * zcyl(TURRET_RELIEF_R, 11.6)
def _wrist_flange():
flange = Pos(0, 0, 3.0) * zcyl(23.0, 6.0)
flange = safe_chamfer(flange, flange.edges(), 1.2)
heads = []
for i in range(6):
from math import cos, pi, sin
a = 2.0 * pi * i / 6.0
bx, by = 16.5 * cos(a), 16.5 * sin(a)
flange -= Pos(bx, by, 1.5) * zcyl(3.6, 3.0)
flange -= Pos(bx, by, 3.0) * zcyl(2.3, 8.0)
head = Pos(bx, by, 1.4) * zcyl(3.3, 2.2)
head = safe_chamfer(head, head.edges(), 0.5)
heads.append(styled(head, f"flange_bolt_{i}", GRAPHITE_COLOR))
return styled(flange, "wrist_flange", ALU_COLOR), heads
def _carpal_core():
try:
f1 = Plane.XY.offset(6.0) * RectangleRounded(40.0, 17.5, 7.0)
f2 = Plane.XY.offset(33.0) * RectangleRounded(50.0, 19.0, 8.0)
carpal = loft([f1, f2])
except Exception:
carpal = Pos(0, 0, 19.5) * Box(46.0, 18.2, 27.0)
carpal = safe_fillet(carpal, carpal.edges().filter_by(Axis.Z), 7.0)
return styled(carpal, "carpal_core", GRAPHITE_COLOR)
def _palm_core():
core = _outline_solid(CORE_PTS, -SLAB_HALF_Y, 2 * SLAB_HALF_Y)
core = safe_fillet(core, core.edges().filter_by(Axis.Y), 2.2)
# Knuckle lobe crowns (cut by the clevis slots into cheek arcs).
for finger in FINGERS:
kx, _, kz = MCP_ORIGIN_MM[finger]
s = FINGER_SCALE[finger]
core += Pos(kx, 0, kz) * xcyl(PALM_LOBE_R * s, _slot_gap(finger) + 7.0)
# Thumb turret pedestal (bored for the spindle), merged into the
# radial-palmar corner.
disc_z0 = CZ - TB_TANG_H / 2.0
pedestal = Pos(CX, CY, disc_z0 - 3.4) * (zcyl(TURRET_R, 6.4) - zcyl(SPINDLE_R + 0.7, 9.0))
pedestal = safe_chamfer(pedestal, pedestal.edges(), 1.0)
core += pedestal
core = _knuckle_cuts(core)
core = _thumb_relief(core)
return styled(core, "palm_core", GRAPHITE_COLOR)
def _dorsal_cover():
cover = _outline_solid(COVER_PTS, COVER_Y0, COVER_Y1 - COVER_Y0)
cover = safe_fillet(cover, cover.edges().filter_by(Axis.Y), 2.0)
try:
outer = cover.faces().sort_by(Axis.Y)[0]
cover = safe_chamfer(cover, outer.edges(), 1.0)
except Exception:
pass
# Engraved tendon-channel lines running from the carpal exit to each
# knuckle slot (clean covered cable routing, shown as shadow grooves).
from math import atan2, degrees, hypot
for finger in FINGERS:
kx, _, kz = MCP_ORIGIN_MM[finger]
ax, az = kx * 0.42, 40.0
bx, bz = kx, kz - 12.0
mx, mz = (ax + bx) / 2.0, (az + bz) / 2.0
ang = degrees(atan2(bx - ax, bz - az))
glen = hypot(bx - ax, bz - az)
groove = Pos(mx, COVER_Y0 + 0.25, mz) * Rot(0, ang, 0) * Box(1.6, 1.0, glen)
try:
cover -= groove
except Exception:
pass
cover = _knuckle_cuts(cover)
cover = _thumb_relief(cover)
return styled(cover, "dorsal_cover", PEARL_COLOR)
def _palmar_plate():
plate = _outline_solid(PLATE_PTS, PLATE_Y0, PLATE_Y1 - PLATE_Y0)
plate = safe_fillet(plate, plate.edges().filter_by(Axis.Y), 2.0)
try:
outer = plate.faces().sort_by(Axis.Y)[-1]
plate = safe_chamfer(plate, outer.edges(), 0.8)
except Exception:
pass
# Pocket for the tactile pad (pad sits 0.4 into the plate).
plate -= Pos(0, PLATE_Y1, 65.0) * Box(46.4, 2.0, 30.4)
plate = _knuckle_cuts(plate)
plate = _thumb_relief(plate)
return styled(plate, "palmar_plate", PEARL_COLOR)
def _palm_pad():
pad = Pos(0, PLATE_Y1 - 0.4 + 0.85, 65.0) * Box(46.0, 1.7, 30.0)
pad = safe_fillet(pad, pad.edges().filter_by(Axis.Y), 5.0)
# Tactile zone split grooves (2 x 2 sensor array read as fine lines).
for cut in (
Pos(0, PLATE_Y1 + 1.3, 65.0) * Box(1.2, 1.0, 30.5),
Pos(0, PLATE_Y1 + 1.3, 65.0) * Box(46.5, 1.0, 1.2),
):
try:
pad -= cut
except Exception:
pass
pad = _thumb_relief(pad)
return styled(pad, "palm_pad", PAD_COLOR)
def _palm_sensor():
ring = Pos(0, PLATE_Y1 + 0.3, 87.0) * (ycyl(5.0, 0.6) - ycyl(4.0, 3.0))
lens = Pos(0, PLATE_Y1 + 0.2, 87.0) * ycyl(3.9, 0.8)
return [
styled(ring, "palm_sensor_ring", AMBER_COLOR),
styled(lens, "palm_sensor_lens", SENSOR_COLOR),
]
def _tensioners():
"""Tendon tensioner dial row on the dorsal carpal face."""
out = []
for i, dx in enumerate((-12.0, -6.0, 0.0, 6.0, 12.0)):
face_y = -(8.75 + 0.75 * (20.0 - 6.0) / 27.0)
dial = Pos(dx, face_y - 1.1, 20.0) * ycyl(2.6, 2.4)
dial = safe_chamfer(dial, dial.edges(), 0.5)
out.append(styled(dial, f"tensioner_dial_{i}", ALU_COLOR))
dot = Pos(dx, face_y - 2.5, 20.0) * ycyl(1.0, 0.7)
out.append(styled(dot, f"tensioner_dot_{i}", AMBER_COLOR))
return out
def _knuckle_hardware():
"""Palm-owned MCP pins plus rim washers on the outer index/pinky cheeks."""
out = []
for finger in FINGERS:
kx, _, kz = MCP_ORIGIN_MM[finger]
out.append(
styled(
Pos(kx, 0, kz) * pin("x", MCP_PIN_R, MCP_PIN_LEN),
f"{finger}_mcp_pin",
ALU_COLOR,
)
)
for finger, xr in (("index", 38.2), ("pinky", -37.2)):
kx, _, kz = MCP_ORIGIN_MM[finger]
rim = Pos(xr, 0, kz) * (xcyl(4.6, 1.2) - xcyl(MCP_PIN_R + 0.4, 3.0))
out.append(styled(rim, f"{finger}_mcp_rim", ALU_COLOR))
return out
def _turret_hardware():
"""Bearing collar + spindle/cap for the thumb CMC turret (palm side)."""
disc_z0 = CZ - TB_TANG_H / 2.0
disc_z1 = CZ + TB_TANG_H / 2.0
collar = Pos(CX, CY, disc_z0 - 0.95) * (zcyl(TURRET_R + 0.7, 1.5) - zcyl(TB_TANG_R + 0.8, 3.0))
collar = safe_chamfer(collar, collar.edges(), 0.4)
spindle = Pos(CX, CY, (disc_z0 - 5.0 + disc_z1 + 0.4) / 2.0) * zcyl(
SPINDLE_R, disc_z1 + 0.4 - (disc_z0 - 5.0)
)
cap = Pos(CX, CY, disc_z1 + 0.4 + 0.8) * zcyl(4.8, 1.6)
cap = safe_chamfer(cap, cap.edges(), 0.5)
spindle += cap
return [
styled(collar, "thumb_turret_collar", ALU_COLOR),
styled(spindle, "thumb_turret_spindle", ALU_COLOR),
]
def build_palm():
flange, bolt_heads = _wrist_flange()
solids = [flange]
solids += bolt_heads
solids.append(_carpal_core())
solids.append(_palm_core())
solids.append(_dorsal_cover())
solids.append(_palmar_plate())
solids.append(_palm_pad())
solids += _palm_sensor()
solids += _tensioners()
solids += _knuckle_hardware()
solids += _turret_hardware()
return part_compound("palm", solids)