first commit
This commit is contained in:
@@ -0,0 +1,592 @@
|
||||
"""Bolt-aligned link, crank, pushrod, shank, and wheel parts for Example 18."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
import simplecadapi as scad
|
||||
|
||||
from leg_common import (
|
||||
make_bolt_circle_cutters_rsolidlist,
|
||||
make_part_with_connectors_rpart,
|
||||
make_rounded_bar_rsolid,
|
||||
make_rounded_slot_cutter_rsolid,
|
||||
)
|
||||
from brackets import make_split_case_clamp_rsolid
|
||||
from leg_dimensions import (
|
||||
CASE_CLAMP_PINCH_AXIS_RADIUS,
|
||||
CASE_CLAMP_PINCH_HALF_SPAN,
|
||||
CASE_CLAMP_WIDTH,
|
||||
DISTAL_CRANK_LENGTH,
|
||||
DISTAL_CRANK_THICKNESS,
|
||||
DISTAL_CRANK_WIDTH,
|
||||
DISTAL_CRANK_Z,
|
||||
DISTAL_PUSHROD_PIN,
|
||||
KNEE_AXIS,
|
||||
KNEE_BEARING_BORE_RADIUS,
|
||||
KNEE_BEARING_OUTER_RADIUS,
|
||||
KNEE_DRIVE_AXIS,
|
||||
KNEE_PIVOT_Z,
|
||||
OUTPUT_FLANGE_BOLT_ANGLES_DEGREES,
|
||||
OUTPUT_FLANGE_BOLT_CIRCLE_RADIUS,
|
||||
OUTPUT_FLANGE_BOLT_CLEARANCE_RADIUS,
|
||||
OUTPUT_FLANGE_BOLT_COUNTERBORE_RADIUS,
|
||||
OUTPUT_FLANGE_OUTER_RADIUS,
|
||||
OUTPUT_FLANGE_REGISTER_INNER_RADIUS,
|
||||
PIN_CLEARANCE_RADIUS,
|
||||
PROXIMAL_PUSHROD_PIN,
|
||||
PUSHROD_THICKNESS,
|
||||
PUSHROD_WIDTH,
|
||||
PUSHROD_Z,
|
||||
REMOTE_CRANK_LENGTH,
|
||||
REMOTE_CRANK_THICKNESS,
|
||||
REMOTE_CRANK_WIDTH,
|
||||
REMOTE_CRANK_Z,
|
||||
ROD_PIN_AXIS_Z,
|
||||
ROD_PIN_CLEARANCE_RADIUS,
|
||||
ROOT_AXIS,
|
||||
SHANK_KNEE_RADIUS,
|
||||
SHANK_LENGTH,
|
||||
SHANK_LINK_THICKNESS,
|
||||
SHANK_LINK_Z,
|
||||
SHANK_WEB_WIDTH,
|
||||
SHANK_WHEEL_RADIUS,
|
||||
SHANK_WINDOW_LENGTH,
|
||||
SHANK_WINDOW_WIDTH,
|
||||
UPPER_LINK_KNEE_RADIUS,
|
||||
UPPER_LINK_ROOT_RADIUS,
|
||||
UPPER_LINK_THICKNESS,
|
||||
UPPER_LINK_WEB_WIDTH,
|
||||
UPPER_LINK_WINDOW_LENGTH,
|
||||
UPPER_LINK_WINDOW_WIDTH,
|
||||
UPPER_LINK_Z,
|
||||
WHEEL_AXIS,
|
||||
WHEEL_HUB_PLATE_RADIUS,
|
||||
WHEEL_HUB_PLATE_THICKNESS,
|
||||
WHEEL_SPOKE_COUNT,
|
||||
WHEEL_SPOKE_WIDTH,
|
||||
WHEEL_TIRE_BORE_RADIUS,
|
||||
WHEEL_TIRE_RADIUS,
|
||||
WHEEL_TIRE_WIDTH,
|
||||
WHEEL_CASE_CLAMP_Z,
|
||||
)
|
||||
|
||||
|
||||
def make_upper_link_plate_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Build the output-bolted upper link plate with knee bearing holes."""
|
||||
|
||||
plate = _make_axis_plate_base_rsolid(
|
||||
start=ROOT_AXIS,
|
||||
end=KNEE_AXIS,
|
||||
z_center=UPPER_LINK_Z,
|
||||
thickness=UPPER_LINK_THICKNESS,
|
||||
start_radius=UPPER_LINK_ROOT_RADIUS,
|
||||
end_radius=UPPER_LINK_KNEE_RADIUS,
|
||||
web_width=UPPER_LINK_WEB_WIDTH,
|
||||
tag="role.upper_link_plate_base",
|
||||
)
|
||||
z_min = UPPER_LINK_Z - UPPER_LINK_THICKNESS / 2.0
|
||||
cutters = [
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=OUTPUT_FLANGE_REGISTER_INNER_RADIUS + 0.05,
|
||||
height=UPPER_LINK_THICKNESS + 2.0,
|
||||
bottom_face_center=(ROOT_AXIS[0], ROOT_AXIS[1], z_min - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
),
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=KNEE_BEARING_BORE_RADIUS,
|
||||
height=UPPER_LINK_THICKNESS + 2.0,
|
||||
bottom_face_center=(KNEE_AXIS[0], KNEE_AXIS[1], z_min - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
),
|
||||
]
|
||||
cutters.extend(
|
||||
make_bolt_circle_cutters_rsolidlist(
|
||||
center=ROOT_AXIS,
|
||||
bolt_circle_radius=OUTPUT_FLANGE_BOLT_CIRCLE_RADIUS,
|
||||
angles_degrees=OUTPUT_FLANGE_BOLT_ANGLES_DEGREES,
|
||||
hole_radius=OUTPUT_FLANGE_BOLT_CLEARANCE_RADIUS,
|
||||
z_min=z_min - 1.0,
|
||||
height=UPPER_LINK_THICKNESS + 2.0,
|
||||
counterbore_radius=OUTPUT_FLANGE_BOLT_COUNTERBORE_RADIUS,
|
||||
counterbore_depth=3.0,
|
||||
counterbore_from_top=True,
|
||||
counterbore_face_z=z_min + UPPER_LINK_THICKNESS,
|
||||
)
|
||||
)
|
||||
cutters.extend(
|
||||
_make_link_window_cutters(
|
||||
start=ROOT_AXIS,
|
||||
end=KNEE_AXIS,
|
||||
z_center=UPPER_LINK_Z,
|
||||
thickness=UPPER_LINK_THICKNESS,
|
||||
fractions=(0.40, 0.67),
|
||||
length=UPPER_LINK_WINDOW_LENGTH,
|
||||
width=UPPER_LINK_WINDOW_WIDTH,
|
||||
tag_prefix="upper_link_window",
|
||||
)
|
||||
)
|
||||
plate = scad.cut_rsolid(plate, cutters, skip_non_intersecting=False)
|
||||
plate = scad.apply_tag(shape=plate, tag="role.upper_link_plate")
|
||||
print(
|
||||
f"upper_link_plate: output_holes={len(OUTPUT_FLANGE_BOLT_ANGLES_DEGREES)} "
|
||||
f"output_pcd={OUTPUT_FLANGE_BOLT_CIRCLE_RADIUS * 2.0:.1f} "
|
||||
f"knee_bushing_bore={KNEE_BEARING_BORE_RADIUS * 2.0:.1f} faces={len(plate.get_faces())} "
|
||||
f"volume={plate.get_volume():.3f}"
|
||||
)
|
||||
return make_part_with_connectors_rpart(
|
||||
part_id="upper_link_plate",
|
||||
body=plate,
|
||||
name="Upper link plate bolted to actuator output flange and knee bearing retainer",
|
||||
material=material,
|
||||
connectors=(
|
||||
("output_axis", ROOT_AXIS, "z", "Actuator output flange datum"),
|
||||
("knee_axis", (KNEE_AXIS[0], KNEE_AXIS[1], KNEE_PIVOT_Z), "z", "Knee bearing datum"),
|
||||
*_output_bolt_connectors(center=ROOT_AXIS, face_z=z_min + UPPER_LINK_THICKNESS, axis="z"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def make_proximal_crank_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Create the short crank on the independent knee-drive actuator output."""
|
||||
|
||||
crank = _make_axis_plate_base_rsolid(
|
||||
start=KNEE_DRIVE_AXIS,
|
||||
end=PROXIMAL_PUSHROD_PIN,
|
||||
z_center=REMOTE_CRANK_Z,
|
||||
thickness=REMOTE_CRANK_THICKNESS,
|
||||
start_radius=OUTPUT_FLANGE_OUTER_RADIUS,
|
||||
end_radius=REMOTE_CRANK_WIDTH / 2.0 + 2.0,
|
||||
web_width=REMOTE_CRANK_WIDTH,
|
||||
tag="role.proximal_output_crank_base",
|
||||
)
|
||||
z_min = REMOTE_CRANK_Z - REMOTE_CRANK_THICKNESS / 2.0
|
||||
cutters = [
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=OUTPUT_FLANGE_REGISTER_INNER_RADIUS + 0.05,
|
||||
height=REMOTE_CRANK_THICKNESS + 2.0,
|
||||
bottom_face_center=(KNEE_DRIVE_AXIS[0], KNEE_DRIVE_AXIS[1], z_min - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
),
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=ROD_PIN_CLEARANCE_RADIUS,
|
||||
height=REMOTE_CRANK_THICKNESS + 2.0,
|
||||
bottom_face_center=(PROXIMAL_PUSHROD_PIN[0], PROXIMAL_PUSHROD_PIN[1], z_min - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
),
|
||||
]
|
||||
cutters.extend(
|
||||
make_bolt_circle_cutters_rsolidlist(
|
||||
center=KNEE_DRIVE_AXIS,
|
||||
bolt_circle_radius=OUTPUT_FLANGE_BOLT_CIRCLE_RADIUS,
|
||||
angles_degrees=OUTPUT_FLANGE_BOLT_ANGLES_DEGREES,
|
||||
hole_radius=OUTPUT_FLANGE_BOLT_CLEARANCE_RADIUS,
|
||||
z_min=z_min - 1.0,
|
||||
height=REMOTE_CRANK_THICKNESS + 2.0,
|
||||
counterbore_radius=OUTPUT_FLANGE_BOLT_COUNTERBORE_RADIUS,
|
||||
counterbore_depth=3.0,
|
||||
counterbore_from_top=True,
|
||||
counterbore_face_z=z_min + REMOTE_CRANK_THICKNESS,
|
||||
)
|
||||
)
|
||||
crank = scad.cut_rsolid(crank, cutters, skip_non_intersecting=False)
|
||||
crank = scad.apply_tag(shape=crank, tag="role.proximal_output_crank")
|
||||
print(
|
||||
f"knee_drive_output_crank: length={REMOTE_CRANK_LENGTH:.1f} "
|
||||
f"output_flange_holes={len(OUTPUT_FLANGE_BOLT_ANGLES_DEGREES)} "
|
||||
f"faces={len(crank.get_faces())} volume={crank.get_volume():.3f}"
|
||||
)
|
||||
return make_part_with_connectors_rpart(
|
||||
part_id="proximal_output_crank",
|
||||
body=crank,
|
||||
name="Knee-drive crank plate using the second actuator output flange holes",
|
||||
material=material,
|
||||
connectors=(
|
||||
("output_axis", KNEE_DRIVE_AXIS, "z", "Knee-drive actuator output flange datum"),
|
||||
("rod_pin", (PROXIMAL_PUSHROD_PIN[0], PROXIMAL_PUSHROD_PIN[1], ROD_PIN_AXIS_Z), "z", "Proximal pushrod pin datum"),
|
||||
*_output_bolt_connectors(
|
||||
center=KNEE_DRIVE_AXIS,
|
||||
face_z=z_min + REMOTE_CRANK_THICKNESS,
|
||||
axis="z",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def make_pushrod_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Create the flat pushrod with real pin-clearance holes."""
|
||||
|
||||
pushrod = make_rounded_bar_rsolid(
|
||||
start=(PROXIMAL_PUSHROD_PIN[0], PROXIMAL_PUSHROD_PIN[1], PUSHROD_Z),
|
||||
end=(DISTAL_PUSHROD_PIN[0], DISTAL_PUSHROD_PIN[1], PUSHROD_Z),
|
||||
width=PUSHROD_WIDTH,
|
||||
thickness=PUSHROD_THICKNESS,
|
||||
end_hole_radius=ROD_PIN_CLEARANCE_RADIUS,
|
||||
lightening_hole_radius=None,
|
||||
lightening_count=0,
|
||||
tag="role.knee_pushrod_plate",
|
||||
)
|
||||
print(
|
||||
f"knee_pushrod: pin_distance={_xy_distance(PROXIMAL_PUSHROD_PIN, DISTAL_PUSHROD_PIN):.1f} "
|
||||
f"pin_hole_diameter={ROD_PIN_CLEARANCE_RADIUS * 2.0:.1f}"
|
||||
)
|
||||
return make_part_with_connectors_rpart(
|
||||
part_id="knee_pushrod",
|
||||
body=pushrod,
|
||||
name="Flat pushrod plate with matched clevis pin holes",
|
||||
material=material,
|
||||
connectors=(
|
||||
("proximal_pin", (PROXIMAL_PUSHROD_PIN[0], PROXIMAL_PUSHROD_PIN[1], ROD_PIN_AXIS_Z), "z", "Proximal crank pin datum"),
|
||||
("distal_pin", (DISTAL_PUSHROD_PIN[0], DISTAL_PUSHROD_PIN[1], ROD_PIN_AXIS_Z), "z", "Integral shank ear pin datum"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def make_shank_link_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Build the lower shank plate with an integral pushrod extension ear."""
|
||||
|
||||
shank = _make_axis_plate_base_rsolid(
|
||||
start=KNEE_AXIS,
|
||||
end=WHEEL_AXIS,
|
||||
z_center=SHANK_LINK_Z,
|
||||
thickness=SHANK_LINK_THICKNESS,
|
||||
start_radius=SHANK_KNEE_RADIUS,
|
||||
end_radius=SHANK_WHEEL_RADIUS,
|
||||
web_width=SHANK_WEB_WIDTH,
|
||||
tag="role.shank_link_base",
|
||||
)
|
||||
z_min = SHANK_LINK_Z - SHANK_LINK_THICKNESS / 2.0
|
||||
distal_z_min = DISTAL_CRANK_Z - DISTAL_CRANK_THICKNESS / 2.0
|
||||
distal_z_top = distal_z_min + DISTAL_CRANK_THICKNESS
|
||||
distal_drive_ear = _make_axis_plate_base_rsolid(
|
||||
start=KNEE_AXIS,
|
||||
end=DISTAL_PUSHROD_PIN,
|
||||
z_center=DISTAL_CRANK_Z,
|
||||
thickness=DISTAL_CRANK_THICKNESS,
|
||||
start_radius=KNEE_BEARING_OUTER_RADIUS,
|
||||
end_radius=DISTAL_CRANK_WIDTH / 2.0 + 2.0,
|
||||
web_width=DISTAL_CRANK_WIDTH,
|
||||
tag="role.shank_integral_pushrod_ear_base",
|
||||
)
|
||||
standoff_outer = scad.make_cylinder_rsolid(
|
||||
radius=KNEE_BEARING_OUTER_RADIUS,
|
||||
height=distal_z_top - z_min,
|
||||
bottom_face_center=(KNEE_AXIS[0], KNEE_AXIS[1], z_min),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
standoff_inner = scad.make_cylinder_rsolid(
|
||||
radius=KNEE_BEARING_BORE_RADIUS,
|
||||
height=distal_z_top - z_min + 2.0,
|
||||
bottom_face_center=(KNEE_AXIS[0], KNEE_AXIS[1], z_min - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
knee_standoff = scad.cut_rsolid(standoff_outer, standoff_inner, skip_non_intersecting=False)
|
||||
shank = scad.union_rsolid([shank, distal_drive_ear, knee_standoff], glue=False)
|
||||
wheel_clamp = make_split_case_clamp_rsolid(
|
||||
center=WHEEL_AXIS,
|
||||
z_center=WHEEL_CASE_CLAMP_Z,
|
||||
tag="role.wheel_actuator_split_clamp",
|
||||
)
|
||||
clamp_z_min = WHEEL_CASE_CLAMP_Z - CASE_CLAMP_WIDTH / 2.0
|
||||
clamp_post_height = z_min + SHANK_LINK_THICKNESS - clamp_z_min
|
||||
clamp_posts = [
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=3.8,
|
||||
height=clamp_post_height,
|
||||
bottom_face_center=(WHEEL_AXIS[0], WHEEL_AXIS[1] + sign * 29.5, clamp_z_min),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
for sign in (-1.0, 1.0)
|
||||
]
|
||||
shank = scad.union_rsolid(shank, wheel_clamp, clamp_posts, glue=False)
|
||||
integrated_knee_height = distal_z_top - z_min + 2.0
|
||||
cutters = [
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=PIN_CLEARANCE_RADIUS + 2.8,
|
||||
height=integrated_knee_height,
|
||||
bottom_face_center=(KNEE_AXIS[0], KNEE_AXIS[1], z_min - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
),
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=ROD_PIN_CLEARANCE_RADIUS,
|
||||
height=DISTAL_CRANK_THICKNESS + 2.0,
|
||||
bottom_face_center=(DISTAL_PUSHROD_PIN[0], DISTAL_PUSHROD_PIN[1], distal_z_min - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
),
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=19.5,
|
||||
height=SHANK_LINK_THICKNESS + 2.0,
|
||||
bottom_face_center=(WHEEL_AXIS[0], WHEEL_AXIS[1], z_min - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
),
|
||||
]
|
||||
cutters.extend(
|
||||
_make_link_window_cutters(
|
||||
start=KNEE_AXIS,
|
||||
end=WHEEL_AXIS,
|
||||
z_center=SHANK_LINK_Z,
|
||||
thickness=SHANK_LINK_THICKNESS,
|
||||
fractions=(0.42, 0.66),
|
||||
length=SHANK_WINDOW_LENGTH,
|
||||
width=SHANK_WINDOW_WIDTH,
|
||||
tag_prefix="shank_window",
|
||||
)
|
||||
)
|
||||
shank = scad.cut_rsolid(shank, cutters, skip_non_intersecting=False)
|
||||
shank = scad.apply_tag(shape=shank, tag="role.shank_wheel_plate")
|
||||
print(
|
||||
"shank_link: wheel_case_mount=split_clamp "
|
||||
f"clamp_bore_d={50.3:.1f} "
|
||||
f"integral_pushrod_ear={DISTAL_CRANK_LENGTH:.1f} "
|
||||
f"length={SHANK_LENGTH:.1f} faces={len(shank.get_faces())} volume={shank.get_volume():.3f}"
|
||||
)
|
||||
return make_part_with_connectors_rpart(
|
||||
part_id="shank_link",
|
||||
body=shank,
|
||||
name="Lower shank plate with integral pushrod ear and wheel actuator split clamp",
|
||||
material=material,
|
||||
connectors=(
|
||||
("knee_axis", (KNEE_AXIS[0], KNEE_AXIS[1], KNEE_PIVOT_Z), "z", "Knee revolute datum"),
|
||||
("rod_pin", (DISTAL_PUSHROD_PIN[0], DISTAL_PUSHROD_PIN[1], ROD_PIN_AXIS_Z), "z", "Integral shank pushrod pin datum"),
|
||||
(
|
||||
"wheel_case_axis",
|
||||
(WHEEL_AXIS[0], WHEEL_AXIS[1], WHEEL_CASE_CLAMP_Z),
|
||||
"z",
|
||||
"Wheel hub actuator split-clamp datum",
|
||||
),
|
||||
(
|
||||
"wheel_clamp_bolt_seat",
|
||||
(
|
||||
WHEEL_AXIS[0] + CASE_CLAMP_PINCH_AXIS_RADIUS,
|
||||
WHEEL_AXIS[1] + CASE_CLAMP_PINCH_HALF_SPAN - 0.9,
|
||||
WHEEL_CASE_CLAMP_Z,
|
||||
),
|
||||
"y",
|
||||
"Wheel collar M4 bolt head seat",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def make_wheel_tire_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Create the rubber tire ring as a separate serviceable part."""
|
||||
|
||||
tire_center_z = -1.5
|
||||
tire_outer = scad.make_cylinder_rsolid(
|
||||
radius=WHEEL_TIRE_RADIUS,
|
||||
height=WHEEL_TIRE_WIDTH,
|
||||
bottom_face_center=(
|
||||
WHEEL_AXIS[0],
|
||||
WHEEL_AXIS[1],
|
||||
tire_center_z - WHEEL_TIRE_WIDTH / 2.0,
|
||||
),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
tire_bore = scad.make_cylinder_rsolid(
|
||||
radius=WHEEL_TIRE_BORE_RADIUS,
|
||||
height=WHEEL_TIRE_WIDTH + 2.0,
|
||||
bottom_face_center=(
|
||||
WHEEL_AXIS[0],
|
||||
WHEEL_AXIS[1],
|
||||
tire_center_z - WHEEL_TIRE_WIDTH / 2.0 - 1.0,
|
||||
),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
tire_ring = scad.cut_rsolid(tire_outer, tire_bore, skip_non_intersecting=False)
|
||||
tire_ring = scad.apply_tag(shape=tire_ring, tag="role.replaceable_rubber_tire")
|
||||
print(
|
||||
f"wheel_tire: tire_radius={WHEEL_TIRE_RADIUS:.1f} width={WHEEL_TIRE_WIDTH:.1f} "
|
||||
f"faces={len(tire_ring.get_faces())} volume={tire_ring.get_volume():.3f}"
|
||||
)
|
||||
return make_part_with_connectors_rpart(
|
||||
part_id="wheel_tire",
|
||||
body=tire_ring,
|
||||
name="Replaceable rubber wheel tire ring",
|
||||
material=material,
|
||||
connectors=(("hub_axis", WHEEL_AXIS, "z", "Wheel hub overmold datum"),),
|
||||
)
|
||||
|
||||
|
||||
def make_wheel_hub_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Create the rigid 7075 hub and spokes bolted to the actuator flange."""
|
||||
|
||||
hub_z = WHEEL_HUB_PLATE_THICKNESS / 2.0
|
||||
hub_z_min = hub_z - WHEEL_HUB_PLATE_THICKNESS / 2.0
|
||||
hub = scad.make_cylinder_rsolid(
|
||||
radius=WHEEL_HUB_PLATE_RADIUS,
|
||||
height=WHEEL_HUB_PLATE_THICKNESS,
|
||||
bottom_face_center=(WHEEL_AXIS[0], WHEEL_AXIS[1], hub_z_min),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
spokes = [
|
||||
_make_wheel_spoke_rsolid(
|
||||
angle_degrees=22.5 + 360.0 * index / WHEEL_SPOKE_COUNT
|
||||
)
|
||||
for index in range(WHEEL_SPOKE_COUNT)
|
||||
]
|
||||
wheel = scad.union_rsolid([hub, spokes], glue=False)
|
||||
cutters = [
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=OUTPUT_FLANGE_REGISTER_INNER_RADIUS + 0.05,
|
||||
height=WHEEL_HUB_PLATE_THICKNESS + 2.0,
|
||||
bottom_face_center=(WHEEL_AXIS[0], WHEEL_AXIS[1], hub_z_min - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
]
|
||||
cutters.extend(
|
||||
make_bolt_circle_cutters_rsolidlist(
|
||||
center=WHEEL_AXIS,
|
||||
bolt_circle_radius=OUTPUT_FLANGE_BOLT_CIRCLE_RADIUS,
|
||||
angles_degrees=OUTPUT_FLANGE_BOLT_ANGLES_DEGREES,
|
||||
hole_radius=OUTPUT_FLANGE_BOLT_CLEARANCE_RADIUS,
|
||||
z_min=hub_z_min - 1.0,
|
||||
height=WHEEL_HUB_PLATE_THICKNESS + 2.0,
|
||||
counterbore_radius=OUTPUT_FLANGE_BOLT_COUNTERBORE_RADIUS,
|
||||
counterbore_depth=3.0,
|
||||
counterbore_from_top=True,
|
||||
counterbore_face_z=hub_z_min + WHEEL_HUB_PLATE_THICKNESS,
|
||||
)
|
||||
)
|
||||
wheel = scad.cut_rsolid(wheel, cutters, skip_non_intersecting=False)
|
||||
wheel = scad.apply_tag(shape=wheel, tag="role.rigid_spoked_wheel_hub")
|
||||
print(
|
||||
f"wheel_hub: output_holes={len(OUTPUT_FLANGE_BOLT_ANGLES_DEGREES)} "
|
||||
f"output_pcd={OUTPUT_FLANGE_BOLT_CIRCLE_RADIUS * 2.0:.1f} spokes={WHEEL_SPOKE_COUNT} "
|
||||
f"faces={len(wheel.get_faces())} volume={wheel.get_volume():.3f}"
|
||||
)
|
||||
return make_part_with_connectors_rpart(
|
||||
part_id="wheel_hub",
|
||||
body=wheel,
|
||||
name="7075 spoked wheel hub bolted to the actuator output flange",
|
||||
material=material,
|
||||
connectors=(
|
||||
("wheel_axis", WHEEL_AXIS, "z", "Wheel spin datum"),
|
||||
("tire_axis", WHEEL_AXIS, "z", "Replaceable tire datum"),
|
||||
*_output_bolt_connectors(
|
||||
center=WHEEL_AXIS,
|
||||
face_z=hub_z_min + WHEEL_HUB_PLATE_THICKNESS,
|
||||
axis="z",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _make_axis_plate_base_rsolid(
|
||||
*,
|
||||
start: tuple[float, float, float],
|
||||
end: tuple[float, float, float],
|
||||
z_center: float,
|
||||
thickness: float,
|
||||
start_radius: float,
|
||||
end_radius: float,
|
||||
web_width: float,
|
||||
tag: str,
|
||||
) -> scad.Solid:
|
||||
length = _xy_distance(start, end)
|
||||
if length <= max(start_radius, end_radius):
|
||||
raise ValueError("axis plate endpoints are too close")
|
||||
z_min = z_center - thickness / 2.0
|
||||
web = scad.make_box_rsolid(
|
||||
width=length,
|
||||
height=web_width,
|
||||
depth=thickness,
|
||||
bottom_face_center=(length / 2.0, 0.0, z_min),
|
||||
)
|
||||
start_boss = scad.make_cylinder_rsolid(
|
||||
radius=start_radius,
|
||||
height=thickness,
|
||||
bottom_face_center=(0.0, 0.0, z_min),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
end_boss = scad.make_cylinder_rsolid(
|
||||
radius=end_radius,
|
||||
height=thickness,
|
||||
bottom_face_center=(length, 0.0, z_min),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
plate = scad.union_rsolid([web, start_boss, end_boss], glue=False)
|
||||
angle_degrees = math.degrees(math.atan2(end[1] - start[1], end[0] - start[0]))
|
||||
plate = scad.rotate_shape(
|
||||
shape=plate,
|
||||
angle=angle_degrees,
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
origin=(0.0, 0.0, z_center),
|
||||
)
|
||||
plate = scad.translate_shape(shape=plate, vector=(start[0], start[1], 0.0))
|
||||
return scad.apply_tag(shape=plate, tag=tag)
|
||||
|
||||
|
||||
def _make_link_window_cutters(
|
||||
*,
|
||||
start: tuple[float, float, float],
|
||||
end: tuple[float, float, float],
|
||||
z_center: float,
|
||||
thickness: float,
|
||||
fractions: tuple[float, ...],
|
||||
length: float,
|
||||
width: float,
|
||||
tag_prefix: str,
|
||||
) -> list[scad.Solid]:
|
||||
angle_degrees = math.degrees(math.atan2(end[1] - start[1], end[0] - start[0]))
|
||||
cutters = []
|
||||
for index, fraction in enumerate(fractions, start=1):
|
||||
cutters.append(
|
||||
make_rounded_slot_cutter_rsolid(
|
||||
center=(
|
||||
start[0] + (end[0] - start[0]) * fraction,
|
||||
start[1] + (end[1] - start[1]) * fraction,
|
||||
z_center,
|
||||
),
|
||||
length=length,
|
||||
width=width,
|
||||
height=thickness + 2.0,
|
||||
angle_degrees=angle_degrees,
|
||||
tag=f"role.{tag_prefix}_{index}",
|
||||
)
|
||||
)
|
||||
return cutters
|
||||
|
||||
|
||||
def _make_wheel_spoke_rsolid(*, angle_degrees: float) -> scad.Solid:
|
||||
hub_overlap_radius = WHEEL_HUB_PLATE_RADIUS - 1.5
|
||||
rim_overlap_radius = WHEEL_TIRE_BORE_RADIUS + 1.5
|
||||
length = rim_overlap_radius - hub_overlap_radius
|
||||
radial_center = (hub_overlap_radius + rim_overlap_radius) / 2.0
|
||||
z_center = WHEEL_HUB_PLATE_THICKNESS / 2.0
|
||||
spoke = scad.make_box_rsolid(
|
||||
width=length,
|
||||
height=WHEEL_SPOKE_WIDTH,
|
||||
depth=WHEEL_HUB_PLATE_THICKNESS,
|
||||
bottom_face_center=(WHEEL_AXIS[0] + radial_center, WHEEL_AXIS[1], z_center - WHEEL_HUB_PLATE_THICKNESS / 2.0),
|
||||
)
|
||||
return scad.rotate_shape(
|
||||
shape=spoke,
|
||||
angle=angle_degrees,
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
origin=WHEEL_AXIS,
|
||||
)
|
||||
|
||||
|
||||
def _xy_distance(a: tuple[float, float, float], b: tuple[float, float, float]) -> float:
|
||||
return math.hypot(b[0] - a[0], b[1] - a[1])
|
||||
|
||||
|
||||
def _output_bolt_connectors(
|
||||
*, center: tuple[float, float, float], face_z: float, axis: str
|
||||
) -> tuple[tuple[str, tuple[float, float, float], str, str], ...]:
|
||||
connectors = []
|
||||
for index, angle_degrees in enumerate(OUTPUT_FLANGE_BOLT_ANGLES_DEGREES, start=1):
|
||||
angle = math.radians(angle_degrees)
|
||||
connectors.append(
|
||||
(
|
||||
f"output_bolt_{index}_head_top",
|
||||
(
|
||||
center[0] + OUTPUT_FLANGE_BOLT_CIRCLE_RADIUS * math.cos(angle),
|
||||
center[1] + OUTPUT_FLANGE_BOLT_CIRCLE_RADIUS * math.sin(angle),
|
||||
face_z,
|
||||
),
|
||||
axis,
|
||||
f"M3 output screw {index} head top",
|
||||
)
|
||||
)
|
||||
return tuple(connectors)
|
||||
Reference in New Issue
Block a user