261 lines
7.8 KiB
Python
261 lines
7.8 KiB
Python
"""juno pelvis: waist-yaw ring, graphite chassis, hip pods, shell fairings.
|
|
|
|
Local frame: origin = waist-yaw joint center at the TOP of the pelvis.
|
|
Geometry spans z -120..+2. Robot faces +X, +Y robot-left, +Z up.
|
|
|
|
Locked interfaces (do not move):
|
|
- Waist ring: aluminum cylinder dia 96, z -2..+2, centered on Z.
|
|
- Hip pods: flat round mounting face dia 80 at z=-120 centered (0, +-90, -120);
|
|
everything below z=-120 stays clear for the leg hip-yaw actuator (dia 76).
|
|
- Envelope: x -65..+65, |y| <= 115 (pod faces at y=+-90 r40 reach 122 by the
|
|
locked interface itself).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
import math
|
|
|
|
from build123d import (
|
|
Axis,
|
|
Box,
|
|
Cylinder,
|
|
Plane,
|
|
Pos,
|
|
RectangleRounded,
|
|
Rot,
|
|
chamfer,
|
|
extrude,
|
|
fillet,
|
|
)
|
|
|
|
from .juno_lib import (
|
|
ALU_COLOR,
|
|
SHELL_COLOR,
|
|
STRUCT_COLOR,
|
|
part_compound,
|
|
styled,
|
|
)
|
|
|
|
|
|
def _safe(op, solid):
|
|
"""Run a fillet/chamfer op; fall back to the input solid on any failure."""
|
|
try:
|
|
out = op(solid)
|
|
if out is not None and out.volume > 1.0:
|
|
return out
|
|
except Exception:
|
|
pass
|
|
return solid
|
|
|
|
|
|
def _zband(solid, lo, hi):
|
|
return solid.edges().filter_by_position(Axis.Z, lo, hi)
|
|
|
|
|
|
# ---------------------------------------------------------------- chassis ---
|
|
|
|
def _waist_deck():
|
|
deck = Pos(0, 0, -8) * Box(104, 118, 12)
|
|
deck = _safe(lambda s: fillet(s.edges().filter_by(Axis.Z), 14), deck)
|
|
deck = _safe(lambda s: chamfer(_zband(s, -2.01, -1.99), 2.0), deck)
|
|
deck = _safe(lambda s: chamfer(_zband(s, -14.01, -13.99), 2.0), deck)
|
|
return deck
|
|
|
|
|
|
def _abdomen_column():
|
|
try:
|
|
sk = Plane(
|
|
origin=(0, 0, -14), x_dir=(1, 0, 0), z_dir=(0, 0, -1)
|
|
) * RectangleRounded(86, 96, 13)
|
|
col = extrude(sk, amount=58, taper=6)
|
|
if col.volume > 1.0:
|
|
return col
|
|
except Exception:
|
|
pass
|
|
return Pos(0, 0, -43) * Box(80, 90, 58)
|
|
|
|
|
|
def _hip_yoke():
|
|
yoke = Pos(0, 0, -86) * Box(76, 164, 36)
|
|
yoke = _safe(lambda s: chamfer(s.edges().filter_by(Axis.Y), 5.0), yoke)
|
|
return yoke
|
|
|
|
|
|
def _pod(side):
|
|
return Pos(0, side * 90, -86) * Cylinder(radius=40, height=56)
|
|
|
|
|
|
def _chassis():
|
|
chassis = _waist_deck() + _abdomen_column() + _hip_yoke()
|
|
chassis = chassis + _pod(+1) + _pod(-1)
|
|
|
|
# Recessed service pockets on yoke front/back faces.
|
|
for sx in (+1, -1):
|
|
for sy in (+1, -1):
|
|
chassis -= Pos(sx * 38, sy * 48, -86) * Box(4, 36, 22)
|
|
|
|
# Panel seam groove around each pod above the aluminum rim.
|
|
for sy in (+1, -1):
|
|
ring = Pos(0, sy * 90, -100) * (
|
|
Cylinder(radius=45, height=1.8) - Cylinder(radius=37.5, height=1.8)
|
|
)
|
|
chassis -= ring
|
|
return styled(chassis, "pelvis_chassis", STRUCT_COLOR)
|
|
|
|
|
|
# ----------------------------------------------------------------- accents ---
|
|
|
|
def _waist_ring():
|
|
ring = Cylinder(radius=48, height=4)
|
|
ring = _safe(lambda s: chamfer(s.edges(), 0.8), ring)
|
|
return styled(ring, "pelvis_waist_ring", ALU_COLOR)
|
|
|
|
|
|
def _pod_rim(side, tag):
|
|
rim = Pos(0, side * 90, -117) * Cylinder(radius=40, height=6)
|
|
rim = _safe(lambda s: chamfer(_zband(s, -120.01, -119.9), 1.0), rim)
|
|
rim = _safe(lambda s: chamfer(_zband(s, -114.1, -113.9), 0.8), rim)
|
|
# machined witness groove
|
|
rim -= Pos(0, side * 90, -116) * (
|
|
Cylinder(radius=41, height=1.0) - Cylinder(radius=39, height=1.0)
|
|
)
|
|
return styled(rim, f"pelvis_pod_rim_{tag}", ALU_COLOR)
|
|
|
|
|
|
def _pod_collar(side, tag):
|
|
collar = Pos(0, side * 90, -83) * (
|
|
Cylinder(radius=42.5, height=4) - Cylinder(radius=40, height=4)
|
|
)
|
|
collar = _safe(lambda s: chamfer(_zband(s, -85.01, -84.9), 0.7), collar)
|
|
return styled(collar, f"pelvis_pod_collar_{tag}", ALU_COLOR)
|
|
|
|
|
|
# ---------------------------------------------------------------- fairings ---
|
|
|
|
def _fairing(side, tag):
|
|
s = side
|
|
# Volumetric upper hip shell: y 56..112, z -76..-12, x -58..58.
|
|
blank = Pos(0, s * 84, -44) * Box(116, 56, 64)
|
|
blank = _safe(lambda b: fillet(b.edges().filter_by(Axis.Z), 14), blank)
|
|
|
|
# Plan-view 45 deg facets at the outer-front / outer-rear corners.
|
|
blank -= (
|
|
Pos(50, s * 100, -44) * Rot(0, 0, -s * 45.0) * Pos(16, 0, 0) * Box(32, 64, 84)
|
|
)
|
|
blank -= (
|
|
Pos(-50, s * 100, -44) * Rot(0, 0, s * 45.0) * Pos(-16, 0, 0) * Box(32, 64, 84)
|
|
)
|
|
blank = _safe(lambda b: fillet(b.edges().filter_by(Axis.Z), 5.0), blank)
|
|
|
|
# Gentle top-outer slope (hip shoulder).
|
|
blank -= (
|
|
Pos(0, s * 72, -12)
|
|
* Rot(-18.0 * s, 0, 0)
|
|
* Pos(0, 0, 36)
|
|
* Box(150, 170, 72)
|
|
)
|
|
# Gentle front-top and rear-top slopes.
|
|
blank -= Pos(38, s * 84, -13) * Rot(0, 20, 0) * Pos(0, 0, 36) * Box(110, 150, 72)
|
|
blank -= Pos(-38, s * 84, -13) * Rot(0, -20, 0) * Pos(0, 0, 36) * Box(110, 150, 72)
|
|
|
|
# Mild bottom tucks: lower edge rises toward front and rear.
|
|
blank -= (
|
|
Pos(28, s * 84, -76) * Rot(0, -14, 0) * Pos(0, 0, -36) * Box(130, 150, 72)
|
|
)
|
|
blank -= (
|
|
Pos(-28, s * 84, -76) * Rot(0, 14, 0) * Pos(0, 0, -36) * Box(130, 150, 72)
|
|
)
|
|
# Angled under-edge: bottom rises inboard so the shell lower line meets
|
|
# the yoke clearance (-64 at y=56) flush, no stepped notch.
|
|
blank -= (
|
|
Pos(0, s * 56, -64)
|
|
* Rot(-12.0 * s, 0, 0)
|
|
* Pos(0, 0, -36)
|
|
* Box(150, 170, 72)
|
|
)
|
|
# NOTE: no post-cut fillets here. OCCT's fillet on the sloped-cut edge
|
|
# network can throw inside ChFi3d and the unwind wedges the process under
|
|
# Rosetta, so the sculpted creases stay crisp by design.
|
|
|
|
# Clearance cuts: ride over the chassis with a visible seam gap.
|
|
blank -= Pos(0, s * 90, -88) * Cylinder(radius=43.5, height=70) # pod dome
|
|
blank -= Pos(0, s * 60, -86) * Box(84, 120, 44) # yoke
|
|
blank -= Pos(0, 0, -7) * Box(110, 126, 14) # waist deck
|
|
|
|
# Angled vent grooves on the front 45 deg facet.
|
|
ux, uy = math.cos(math.radians(45)), math.sin(math.radians(45))
|
|
for d in (-8.0, 0.0, 8.0):
|
|
cx = 50.0 + d * ux + 2.5 * ux
|
|
cy = s * (100.0 - d * uy + 2.5 * uy)
|
|
blank -= Pos(cx, cy, -48) * Rot(0, 0, -s * 45.0) * Box(3.0, 8.0, 20.0)
|
|
|
|
# Horizontal panel-line groove across the outer face.
|
|
blank -= Pos(0, s * 112.8, -42) * Box(60.0, 4.0, 2.2)
|
|
|
|
return styled(blank, f"pelvis_fairing_{tag}", SHELL_COLOR)
|
|
|
|
|
|
# ------------------------------------------------------------- crotch guard --
|
|
|
|
def _crotch_guard():
|
|
# Faceted plate built in a local frame, then tilted and pushed to the
|
|
# front of the yoke.
|
|
p = Box(8, 46, 40)
|
|
front_vert = (
|
|
lambda g: chamfer(
|
|
g.edges().filter_by(Axis.Z).filter_by_position(Axis.X, 3.9, 4.1),
|
|
6.0,
|
|
)
|
|
)
|
|
p = _safe(front_vert, p)
|
|
p = _safe(
|
|
lambda g: chamfer(
|
|
g.edges()
|
|
.filter_by(Axis.Y)
|
|
.filter_by_position(Axis.Z, -20.1, -19.9)
|
|
.filter_by_position(Axis.X, 0.0, 4.2),
|
|
5.0,
|
|
),
|
|
p,
|
|
)
|
|
p = _safe(
|
|
lambda g: chamfer(
|
|
g.edges()
|
|
.filter_by(Axis.Y)
|
|
.filter_by_position(Axis.Z, 19.9, 20.1)
|
|
.filter_by_position(Axis.X, 0.0, 4.2),
|
|
3.0,
|
|
),
|
|
p,
|
|
)
|
|
guard = Pos(50, 0, -86) * Rot(0, 4, 0) * p
|
|
# Base wedge filling the gap back to the yoke front face (x=38).
|
|
guard += Pos(42.75, 0, -88) * Box(9.5, 40, 32)
|
|
return styled(guard, "pelvis_crotch_guard", STRUCT_COLOR)
|
|
|
|
|
|
# ------------------------------------------------------------------- part ---
|
|
|
|
def build_pelvis():
|
|
children = [
|
|
_chassis(),
|
|
_waist_ring(),
|
|
_pod_rim(+1, "left"),
|
|
_pod_rim(-1, "right"),
|
|
_pod_collar(+1, "left"),
|
|
_pod_collar(-1, "right"),
|
|
_fairing(+1, "left"),
|
|
_fairing(-1, "right"),
|
|
_crotch_guard(),
|
|
]
|
|
return part_compound("pelvis", children)
|
|
|
|
|
|
def gen_step():
|
|
return build_pelvis()
|