Files
2026-07-22 19:38:36 +08:00

236 lines
6.9 KiB
Python

"""Design constants for the integrated 50 mm BLDC joint actuator."""
from __future__ import annotations
from dataclasses import dataclass
PACKAGE_RADIUS = 25.0
PACKAGE_STRUCTURAL_BOTTOM_Z = -37.0
PACKAGE_TOP_Z = 40.3
MOTOR_SLOT_COUNT = 12
MOTOR_POLE_COUNT = 14
MOTOR_SHELL_INNER_RADIUS = 23.20
MOTOR_SHELL_BOTTOM_Z = -34.0
MOTOR_SHELL_TOP_Z = -6.0
MOTOR_STATOR_BOTTOM_Z = -24.5
MOTOR_STATOR_TOP_Z = -8.5
MOTOR_STATOR_OUTER_RADIUS = 23.20
MOTOR_STATOR_YOKE_INNER_RADIUS = 20.20
MOTOR_STATOR_TOOTH_INNER_RADIUS = 15.00
MOTOR_STATOR_TOOTH_WIDTH = 2.60
MOTOR_ROTOR_BOTTOM_Z = -25.0
MOTOR_ROTOR_TOP_Z = -7.5
MOTOR_ROTOR_BACKIRON_RADIUS = 13.50
MOTOR_MAGNET_OUTER_RADIUS = 14.70
MOTOR_MAGNET_TANGENTIAL_WIDTH = 5.0
MOTOR_SHAFT_RADIUS = 4.0
MOTOR_AIR_GAP = MOTOR_STATOR_TOOTH_INNER_RADIUS - MOTOR_MAGNET_OUTER_RADIUS
REAR_COVER_BOTTOM_Z = -37.0
REAR_COVER_THICKNESS = 3.0
PCB_BOTTOM_Z = -33.6
PCB_THICKNESS = 1.6
PCB_RADIUS = 22.2
PCB_CENTER_BORE_RADIUS = 5.0
PCB_STANDOFF_PCD = 33.0
PCB_MOUNT_HOLE_RADIUS = 1.10
REAR_COLUMN_PCD = 40.6
REAR_COLUMN_RADIUS = 3.2
REAR_SPIDER_BOSS_RADIUS = 2.9
REAR_FASTENER_HOLE_RADIUS = 1.35
REAR_BEARING_CENTER_Z = -29.0
FRONT_MOTOR_BEARING_CENTER_Z = -2.5
REDUCER_HOUSING_BOTTOM_Z = -6.0
REDUCER_HOUSING_FRONT_Z = 24.3
REDUCER_HOUSING_INNER_RADIUS = 22.80
RING_INSERT_OUTER_RADIUS = 22.82
MOTOR_INTERFACE_PCD = 43.0
OUTPUT_CAP_INTERFACE_PCD = 43.0
M3_CLEARANCE_RADIUS = 1.60
HOUSING_INTERFACE_LAND_INNER_RADIUS = 18.50
PLANET_COUNT = 3
PRESSURE_ANGLE = 20.0
HELIX_ANGLE = 24.0
BACKLASH = 0.03
ADDENDUM_FACTOR = 1.0
CLEARANCE_FACTOR = 0.25
RING_RIM_THICKNESS = 1.80
RING_SUPPORT_OVERLAP = 0.30
GEAR_HEIGHT = 5.50
STAGE1_CARRIER_BOTTOM_Z = 8.20
STAGE1_CARRIER_THICKNESS = 2.50
STAGE1_PIN_RADIUS = 1.48
STAGE1_PIN_BOTTOM_Z = 2.25
STAGE1_HUB_RADIUS = 4.2
STAGE1_PAD_RADIUS = 4.0
STAGE1_ARM_WIDTH = 3.2
INTERSTAGE_SHAFT_RADIUS = 2.50
STAGE2_CARRIER_BOTTOM_Z = 20.20
STAGE2_CARRIER_THICKNESS = 3.0
STAGE2_PIN_RADIUS = 1.48
STAGE2_PIN_BOTTOM_Z = 14.25
STAGE2_HUB_RADIUS = 8.8
STAGE2_PAD_RADIUS = 4.1
STAGE2_ARM_WIDTH = 3.5
OUTPUT_SHAFT_RADIUS = 7.98
OUTPUT_CAP_BOTTOM_Z = 24.3
OUTPUT_CAP_CARTRIDGE_TOP_Z = 34.8
OUTPUT_CAP_TOP_Z = 36.3
OUTPUT_BEARING_1_CENTER_Z = 26.8
OUTPUT_BEARING_2_CENTER_Z = 31.8
OUTPUT_FLANGE_BOTTOM_Z = 35.3
OUTPUT_FLANGE_TOP_Z = 38.8
OUTPUT_FLANGE_RADIUS = 22.4
OUTPUT_LINK_HOLE_PCD = 34.0
OUTPUT_LINK_BOLT_COUNT = 6
OUTPUT_LINK_BOLT_ANGLES_DEGREES = (30.0, 90.0, 150.0, 210.0, 270.0, 330.0)
OUTPUT_LINK_TAP_RADIUS = 1.25
OUTPUT_LINK_THREAD_DEPTH = 3.0
OUTPUT_REGISTER_RADIUS = OUTPUT_SHAFT_RADIUS
OUTPUT_REGISTER_HEIGHT = PACKAGE_TOP_Z - OUTPUT_FLANGE_TOP_Z
OUTPUT_CASE_CLAMP_CENTER_Z = 20.0
@dataclass(frozen=True)
class StageSpec:
"""One fixed-ring planetary stage."""
stage_id: str
label: str
module: float
sun_teeth: int
planet_teeth: int
bottom_z: float
@property
def ring_teeth(self) -> int:
return self.sun_teeth + 2 * self.planet_teeth
@property
def top_z(self) -> float:
return self.bottom_z + GEAR_HEIGHT
@property
def mid_z(self) -> float:
return self.bottom_z + GEAR_HEIGHT / 2.0
@property
def sun_pitch_radius(self) -> float:
return self.module * self.sun_teeth / 2.0
@property
def planet_pitch_radius(self) -> float:
return self.module * self.planet_teeth / 2.0
@property
def ring_pitch_radius(self) -> float:
return self.module * self.ring_teeth / 2.0
@property
def planet_center_radius(self) -> float:
return self.sun_pitch_radius + self.planet_pitch_radius
@property
def fixed_ring_ratio(self) -> float:
return 1.0 + self.ring_teeth / self.sun_teeth
@property
def ring_outer_radius(self) -> float:
return (
self.ring_pitch_radius
+ self.module * (ADDENDUM_FACTOR + CLEARANCE_FACTOR)
+ RING_RIM_THICKNESS
)
@dataclass(frozen=True)
class BearingSpec:
"""Catalog-style radial ball bearing dimensions."""
bore_diameter: float
outer_diameter: float
width: float
ball_diameter: float
ball_count: int
STAGE_1 = StageSpec(
stage_id="stage1",
label="Stage 1",
module=0.80,
sun_teeth=15,
planet_teeth=15,
bottom_z=2.0,
)
STAGE_2 = StageSpec(
stage_id="stage2",
label="Stage 2",
module=0.55,
sun_teeth=18,
planet_teeth=27,
bottom_z=14.0,
)
REAR_MOTOR_BEARING = BearingSpec(8.0, 16.0, 5.0, 2.0, 8)
FRONT_MOTOR_BEARING = BearingSpec(8.0, 19.0, 6.0, 2.4, 9)
INTERSTAGE_BEARING = BearingSpec(5.0, 10.0, 3.0, 1.0, 8)
PLANET_BEARING = BearingSpec(3.0, 6.0, 3.0, 0.7, 8)
OUTPUT_BEARING = BearingSpec(16.0, 24.0, 5.0, 2.5, 12)
REAR_SPIDER_BOTTOM_Z = REAR_BEARING_CENTER_Z - REAR_MOTOR_BEARING.width / 2.0
INTERSTAGE_BEARING_CENTER_Z = (
STAGE1_CARRIER_BOTTOM_Z + STAGE1_CARRIER_THICKNESS + STAGE_2.bottom_z
) / 2.0
TOTAL_REDUCTION = STAGE_1.fixed_ring_ratio * STAGE_2.fixed_ring_ratio
def validate_design_dimensions() -> None:
"""Fail early if a packaging or minimum-ligament invariant is broken."""
assert MOTOR_AIR_GAP >= 0.30
assert MOTOR_STATOR_OUTER_RADIUS == MOTOR_SHELL_INNER_RADIUS
assert abs(PACKAGE_RADIUS - MOTOR_SHELL_INNER_RADIUS - 1.80) < 1.0e-9
assert abs(PACKAGE_RADIUS - REDUCER_HOUSING_INNER_RADIUS - 2.20) < 1.0e-9
assert max(STAGE_1.ring_outer_radius, STAGE_2.ring_outer_radius) < RING_INSERT_OUTER_RADIUS
assert RING_INSERT_OUTER_RADIUS > REDUCER_HOUSING_INNER_RADIUS
assert (
MOTOR_INTERFACE_PCD / 2.0
- M3_CLEARANCE_RADIUS
- HOUSING_INTERFACE_LAND_INNER_RADIUS
>= 1.40 - 1.0e-9
)
assert REAR_SPIDER_BOSS_RADIUS - REAR_FASTENER_HOLE_RADIUS >= 1.50
assert REAR_COLUMN_RADIUS > REAR_SPIDER_BOSS_RADIUS
assert REAR_SPIDER_BOTTOM_Z > MOTOR_SHELL_BOTTOM_Z
assert OUTPUT_FLANGE_RADIUS <= PACKAGE_RADIUS
assert OUTPUT_LINK_HOLE_PCD / 2.0 + OUTPUT_LINK_TAP_RADIUS < OUTPUT_FLANGE_RADIUS
assert OUTPUT_LINK_THREAD_DEPTH < OUTPUT_FLANGE_TOP_Z - OUTPUT_FLANGE_BOTTOM_Z
assert OUTPUT_REGISTER_HEIGHT >= 1.5
assert OUTPUT_REGISTER_RADIUS < OUTPUT_LINK_HOLE_PCD / 2.0 - OUTPUT_LINK_TAP_RADIUS
assert REDUCER_HOUSING_BOTTOM_Z < OUTPUT_CASE_CLAMP_CENTER_Z < REDUCER_HOUSING_FRONT_Z
assert OUTPUT_BEARING_2_CENTER_Z - OUTPUT_BEARING_1_CENTER_Z == OUTPUT_BEARING.width
assert abs(TOTAL_REDUCTION - 20.0) < 1.0e-9
assert (STAGE_1.sun_teeth + STAGE_1.ring_teeth) % PLANET_COUNT == 0
assert (STAGE_2.sun_teeth + STAGE_2.ring_teeth) % PLANET_COUNT == 0
assert (
INTERSTAGE_BEARING_CENTER_Z - INTERSTAGE_BEARING.width / 2.0
> STAGE1_CARRIER_BOTTOM_Z + STAGE1_CARRIER_THICKNESS
)
assert (
INTERSTAGE_BEARING_CENTER_Z + INTERSTAGE_BEARING.width / 2.0
< STAGE_2.bottom_z
)
print(
"design_dimensions: "
f"diameter={PACKAGE_RADIUS * 2.0:.1f} structural_length="
f"{PACKAGE_TOP_Z - PACKAGE_STRUCTURAL_BOTTOM_Z:.1f} air_gap={MOTOR_AIR_GAP:.2f} "
f"ratio={TOTAL_REDUCTION:.1f}"
)