637 lines
24 KiB
Python
637 lines
24 KiB
Python
"""Example 12: herringbone planetary reducer carrier assembly.
|
|
|
|
This example builds a planetary reducer layout with a fixed internal ring gear:
|
|
|
|
- one sun-drive carrier plate with a central shaft
|
|
- one herringbone sun gear fixed to that sun-drive plate
|
|
- one fixed herringbone internal ring gear
|
|
- one upper Y-shaped planet-carrier output plate with three pins
|
|
- one reusable herringbone planet gear Part instanced three times
|
|
|
|
The ring gear is the grounded reference in this static CAD assembly. The sun
|
|
gear is fixed to the input shaft, while the planet carrier and each planet gear
|
|
use revolute joints so the product structure reflects the intended power path:
|
|
sun input -> planet gears against fixed ring -> slower planet-carrier output.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import math
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import simplecadapi as scad
|
|
from simplecadapi import ql
|
|
|
|
|
|
# Gear sketches contain many profile entities and produce deep graphs.
|
|
sys.setrecursionlimit(20000)
|
|
|
|
|
|
MODULE = 1.5
|
|
SUN_TEETH = 18
|
|
PLANET_TEETH = 24
|
|
RING_TEETH = SUN_TEETH + 2 * PLANET_TEETH
|
|
PLANET_COUNT = 3
|
|
GEAR_HEIGHT = 8.0
|
|
SUN_HELIX_ANGLE = 25.0
|
|
PLANET_HELIX_ANGLE = -SUN_HELIX_ANGLE
|
|
RING_HELIX_ANGLE = PLANET_HELIX_ANGLE
|
|
RING_RIM_THICKNESS = 5.0
|
|
RING_BACKLASH = 0.08 * MODULE
|
|
SUN_PITCH_RADIUS = MODULE * SUN_TEETH / 2.0
|
|
PLANET_PITCH_RADIUS = MODULE * PLANET_TEETH / 2.0
|
|
RING_PITCH_RADIUS = MODULE * RING_TEETH / 2.0
|
|
SUN_BORE_RADIUS = 4.2
|
|
PLANET_BORE_RADIUS = 3.5
|
|
SUN_SHAFT_RADIUS = SUN_BORE_RADIUS - 0.2
|
|
SUN_AXIS_SHOULDER_RADIUS = SUN_BORE_RADIUS - 0.05
|
|
PLANET_PIN_RADIUS = PLANET_BORE_RADIUS - 0.7
|
|
PLANET_PIN_BEARING_RADIUS = PLANET_BORE_RADIUS - 0.3
|
|
SUN_DRIVE_PLATE_RADIUS = 14.0
|
|
SUN_DRIVE_PLATE_THICKNESS = 4.0
|
|
SUN_DRIVE_PLATE_BOTTOM_Z = -8.0
|
|
PLANET_CARRIER_THICKNESS = 3.0
|
|
PLANET_CARRIER_BOTTOM_Z = GEAR_HEIGHT + 1.0
|
|
CARRIER_AXIS_CONNECTOR_Z = PLANET_CARRIER_BOTTOM_Z + PLANET_CARRIER_THICKNESS
|
|
SUN_AXIS_CONNECTOR_Z = GEAR_HEIGHT
|
|
PLANET_AXIS_CONNECTOR_Z = GEAR_HEIGHT
|
|
PLANET_PIN_BOTTOM_Z = -0.25
|
|
PLANET_PIN_TOP_CLEARANCE = 0.5
|
|
CARRIER_CENTER_CLEARANCE_RADIUS = SUN_BORE_RADIUS + 1.0
|
|
CARRIER_HUB_RADIUS = CARRIER_CENTER_CLEARANCE_RADIUS + 4.5
|
|
CARRIER_ARM_WIDTH = 8.0
|
|
CARRIER_ARM_INNER_CLEARANCE = 0.6
|
|
CARRIER_ARM_END_OVERHANG = 1.0
|
|
PLANET_PAD_RADIUS = PLANET_BORE_RADIUS + 4.5
|
|
SUN_SHAFT_TOP_Z = CARRIER_AXIS_CONNECTOR_Z
|
|
OUTPUT_DIR = Path("examples/out/herringbone_planetary_gears")
|
|
|
|
|
|
def _planet_spin_angle(carrier_angle_deg: float) -> float:
|
|
"""Phase each planet so a tooth gap faces the sun contact line."""
|
|
planet_half_pitch_deg = 180.0 / PLANET_TEETH
|
|
return carrier_angle_deg + 180.0 - planet_half_pitch_deg
|
|
|
|
|
|
def _z_rotation_placement(origin: tuple[float, float, float], angle_degrees: float):
|
|
angle_rad = math.radians(angle_degrees)
|
|
cos_a = math.cos(angle_rad)
|
|
sin_a = math.sin(angle_rad)
|
|
return scad.make_placement_rplacement(
|
|
origin=origin,
|
|
x_axis=(cos_a, sin_a, 0.0),
|
|
y_axis=(-sin_a, cos_a, 0.0),
|
|
)
|
|
|
|
|
|
def _ground_solid(label: str, solid: scad.Solid) -> None:
|
|
faces = ql.select(solid.get_faces()).all()
|
|
tagged_role_faces = ql.select(faces).where(ql.tag("role.*")).all()
|
|
print(
|
|
f"{label}: faces={len(faces)} role_faces={len(tagged_role_faces)} "
|
|
f"volume={solid.get_volume():.1f} tags={','.join(scad.list_tags(solid))}"
|
|
)
|
|
|
|
|
|
def _ground_compound(label: str, compound: scad.Compound) -> None:
|
|
solids = ql.select(compound.get_solids()).all()
|
|
face_count = sum(len(ql.select(solid.get_faces()).all()) for solid in solids)
|
|
volume = sum(solid.get_volume() for solid in solids)
|
|
print(f"{label}: solids={len(solids)} faces={face_count} volume={volume:.1f}")
|
|
|
|
|
|
def _axis_face(
|
|
label: str,
|
|
solid: scad.Solid,
|
|
center_xy: tuple[float, float],
|
|
target_z: float,
|
|
normal_z: float,
|
|
) -> scad.Face:
|
|
candidates = []
|
|
for face in ql.select(solid.get_faces()).all():
|
|
normal = face.get_normal_at()
|
|
if normal_z > 0.0 and normal.z < 0.7:
|
|
continue
|
|
if normal_z < 0.0 and normal.z > -0.7:
|
|
continue
|
|
center = face.get_center()
|
|
xy_error = math.hypot(center.x - center_xy[0], center.y - center_xy[1])
|
|
z_error = abs(center.z - target_z)
|
|
candidates.append((z_error * 100.0 + xy_error, face, center, normal))
|
|
|
|
if not candidates:
|
|
raise ValueError(f"no connector face found for {label}")
|
|
|
|
_score, face, center, normal = min(candidates, key=lambda item: item[0])
|
|
print(
|
|
f"{label}_connector_face: center=({center.x:.3f},{center.y:.3f},{center.z:.3f}) "
|
|
f"normal=({normal.x:.3f},{normal.y:.3f},{normal.z:.3f}) area={face.get_area():.3f}"
|
|
)
|
|
return face
|
|
|
|
|
|
def _cut_axial_bore(label: str, solid: scad.Solid, radius: float) -> scad.Solid:
|
|
cutter = scad.make_cylinder_rsolid(
|
|
radius=radius,
|
|
height=GEAR_HEIGHT + 2.0,
|
|
bottom_face_center=(0.0, 0.0, -1.0),
|
|
axis=(0.0, 0.0, 1.0),
|
|
)
|
|
bored = scad.cut_rsolid(solid, cutter, skip_non_intersecting=False)
|
|
bored = scad.apply_tag(bored, f"solid.cut.{label}")
|
|
faces = ql.select(bored.get_faces()).all()
|
|
print(
|
|
f"{label}: bore_radius={radius:.2f} faces={len(faces)} "
|
|
f"volume={bored.get_volume():.1f} tags={','.join(scad.list_tags(bored))}"
|
|
)
|
|
return bored
|
|
|
|
|
|
def _build_sun_drive_plate() -> scad.Solid:
|
|
plate = scad.make_cylinder_rsolid(
|
|
radius=SUN_DRIVE_PLATE_RADIUS,
|
|
height=SUN_DRIVE_PLATE_THICKNESS,
|
|
bottom_face_center=(0.0, 0.0, SUN_DRIVE_PLATE_BOTTOM_Z),
|
|
axis=(0.0, 0.0, 1.0),
|
|
)
|
|
shaft = scad.make_cylinder_rsolid(
|
|
radius=SUN_SHAFT_RADIUS,
|
|
height=SUN_SHAFT_TOP_Z - SUN_DRIVE_PLATE_BOTTOM_Z,
|
|
bottom_face_center=(0.0, 0.0, SUN_DRIVE_PLATE_BOTTOM_Z),
|
|
axis=(0.0, 0.0, 1.0),
|
|
)
|
|
sun_axis_shoulder = scad.make_cylinder_rsolid(
|
|
radius=SUN_AXIS_SHOULDER_RADIUS,
|
|
height=SUN_AXIS_CONNECTOR_Z - SUN_DRIVE_PLATE_BOTTOM_Z,
|
|
bottom_face_center=(0.0, 0.0, SUN_DRIVE_PLATE_BOTTOM_Z),
|
|
axis=(0.0, 0.0, 1.0),
|
|
)
|
|
drive_plate = scad.union_rsolid(plate, sun_axis_shoulder, shaft, glue=False)
|
|
drive_plate = scad.apply_tag(drive_plate, "role.sun_drive_plate")
|
|
drive_plate = scad.apply_tag(drive_plate, "group.herringbone_planetary")
|
|
_ground_solid("sun_drive_plate", drive_plate)
|
|
return drive_plate
|
|
|
|
|
|
def _build_planet_carrier(planet_center_radius: float) -> scad.Solid:
|
|
arm_inner_x = CARRIER_CENTER_CLEARANCE_RADIUS + CARRIER_ARM_INNER_CLEARANCE
|
|
arm_outer_x = planet_center_radius + PLANET_PAD_RADIUS + CARRIER_ARM_END_OVERHANG
|
|
arm_length = arm_outer_x - arm_inner_x
|
|
arm_center_x = (arm_inner_x + arm_outer_x) / 2.0
|
|
pin_height = (
|
|
PLANET_CARRIER_BOTTOM_Z
|
|
+ PLANET_CARRIER_THICKNESS
|
|
+ PLANET_PIN_TOP_CLEARANCE
|
|
- PLANET_PIN_BOTTOM_Z
|
|
)
|
|
hub = scad.make_cylinder_rsolid(
|
|
radius=CARRIER_HUB_RADIUS,
|
|
height=PLANET_CARRIER_THICKNESS,
|
|
bottom_face_center=(0.0, 0.0, PLANET_CARRIER_BOTTOM_Z),
|
|
axis=(0.0, 0.0, 1.0),
|
|
)
|
|
|
|
solids = [hub]
|
|
for index in range(PLANET_COUNT):
|
|
carrier_angle_deg = 360.0 * index / PLANET_COUNT
|
|
carrier_angle_rad = math.radians(carrier_angle_deg)
|
|
arm = scad.make_box_rsolid(
|
|
CARRIER_ARM_WIDTH,
|
|
arm_length,
|
|
PLANET_CARRIER_THICKNESS,
|
|
bottom_face_center=(arm_center_x, 0.0, PLANET_CARRIER_BOTTOM_Z),
|
|
)
|
|
if carrier_angle_deg != 0.0:
|
|
arm = scad.rotate_shape(
|
|
arm,
|
|
carrier_angle_deg,
|
|
axis=(0.0, 0.0, 1.0),
|
|
origin=(0.0, 0.0, 0.0),
|
|
)
|
|
solids.append(arm)
|
|
|
|
center = (
|
|
planet_center_radius * math.cos(carrier_angle_rad),
|
|
planet_center_radius * math.sin(carrier_angle_rad),
|
|
)
|
|
solids.append(
|
|
scad.make_cylinder_rsolid(
|
|
radius=PLANET_PAD_RADIUS,
|
|
height=PLANET_CARRIER_THICKNESS,
|
|
bottom_face_center=(center[0], center[1], PLANET_CARRIER_BOTTOM_Z),
|
|
axis=(0.0, 0.0, 1.0),
|
|
)
|
|
)
|
|
solids.append(
|
|
scad.make_cylinder_rsolid(
|
|
radius=PLANET_PIN_RADIUS,
|
|
height=pin_height,
|
|
bottom_face_center=(center[0], center[1], PLANET_PIN_BOTTOM_Z),
|
|
axis=(0.0, 0.0, 1.0),
|
|
)
|
|
)
|
|
solids.append(
|
|
scad.make_cylinder_rsolid(
|
|
radius=PLANET_PIN_BEARING_RADIUS,
|
|
height=PLANET_AXIS_CONNECTOR_Z - PLANET_PIN_BOTTOM_Z,
|
|
bottom_face_center=(center[0], center[1], PLANET_PIN_BOTTOM_Z),
|
|
axis=(0.0, 0.0, 1.0),
|
|
)
|
|
)
|
|
|
|
carrier = scad.union_rsolid(solids, glue=False)
|
|
carrier = scad.cut_rsolid(
|
|
carrier,
|
|
scad.make_cylinder_rsolid(
|
|
radius=CARRIER_CENTER_CLEARANCE_RADIUS,
|
|
height=PLANET_CARRIER_THICKNESS + 2.0,
|
|
bottom_face_center=(0.0, 0.0, PLANET_CARRIER_BOTTOM_Z - 1.0),
|
|
axis=(0.0, 0.0, 1.0),
|
|
),
|
|
skip_non_intersecting=False,
|
|
)
|
|
carrier = scad.apply_tag(carrier, "role.planet_carrier")
|
|
carrier = scad.apply_tag(carrier, "group.herringbone_planetary")
|
|
print(
|
|
f"planet_carrier_y_top: arms={PLANET_COUNT} arm_width={CARRIER_ARM_WIDTH:.2f} "
|
|
f"arm_length={arm_length:.2f} top_z={PLANET_CARRIER_BOTTOM_Z + PLANET_CARRIER_THICKNESS:.2f} "
|
|
f"hub_radius={CARRIER_HUB_RADIUS:.2f} pad_radius={PLANET_PAD_RADIUS:.2f}"
|
|
)
|
|
_ground_solid("planet_carrier", carrier)
|
|
return carrier
|
|
|
|
|
|
def build_herringbone_planetary_gearset():
|
|
"""Build the open planetary carrier assembly and return preview/model JSON."""
|
|
planet_center_radius = MODULE * (SUN_TEETH + PLANET_TEETH) / 2.0
|
|
|
|
with scad.GraphSession() as session:
|
|
sun_drive_plate = _build_sun_drive_plate()
|
|
planet_carrier = _build_planet_carrier(planet_center_radius)
|
|
|
|
ring = scad.std.gear.make_herringbone_ring_gear_rsolid(
|
|
n_teeth=RING_TEETH,
|
|
module=MODULE,
|
|
helix_angle=RING_HELIX_ANGLE,
|
|
gear_height=GEAR_HEIGHT,
|
|
rim_thickness=RING_RIM_THICKNESS,
|
|
backlash=RING_BACKLASH,
|
|
)
|
|
ring = scad.apply_tag(ring, "role.fixed_ring_gear")
|
|
ring = scad.apply_tag(ring, "group.herringbone_planetary")
|
|
_ground_solid("fixed_ring", ring)
|
|
|
|
sun = scad.std.gear.make_herringbone_gear_rsolid(
|
|
n_teeth=SUN_TEETH,
|
|
module=MODULE,
|
|
helix_angle=SUN_HELIX_ANGLE,
|
|
gear_height=GEAR_HEIGHT,
|
|
)
|
|
sun = _cut_axial_bore("sun_bore", sun, SUN_BORE_RADIUS)
|
|
sun = scad.apply_tag(sun, "role.sun_gear")
|
|
sun = scad.apply_tag(sun, "group.herringbone_planetary")
|
|
_ground_solid("sun", sun)
|
|
|
|
planet_base = scad.std.gear.make_herringbone_gear_rsolid(
|
|
n_teeth=PLANET_TEETH,
|
|
module=MODULE,
|
|
helix_angle=PLANET_HELIX_ANGLE,
|
|
gear_height=GEAR_HEIGHT,
|
|
)
|
|
planet_base = _cut_axial_bore("planet_bore", planet_base, PLANET_BORE_RADIUS)
|
|
planet_base = scad.apply_tag(planet_base, "role.planet_gear")
|
|
planet_base = scad.apply_tag(planet_base, "group.herringbone_planetary")
|
|
_ground_solid("planet_part", planet_base)
|
|
|
|
carrier_material = scad.make_material_rmaterial(
|
|
"matte_anodized_aluminum",
|
|
name="Matte anodized aluminum",
|
|
density=2.7e-6,
|
|
density_unit="kg/mm^3",
|
|
color=(0.28, 0.30, 0.32),
|
|
)
|
|
gear_material = scad.make_material_rmaterial(
|
|
"case_hardened_gear_steel",
|
|
name="Case hardened gear steel",
|
|
density=7.85e-6,
|
|
density_unit="kg/mm^3",
|
|
color=(0.68, 0.70, 0.72),
|
|
)
|
|
print(f"materials: {carrier_material.material_id},{gear_material.material_id}")
|
|
|
|
ring_part = scad.make_part_rpart(
|
|
"fixed_herringbone_ring",
|
|
ring,
|
|
name="Fixed herringbone internal ring gear",
|
|
)
|
|
ring_part = scad.assign_material_rpart(ring_part, gear_material)
|
|
ring_part = scad.add_connector_rpart(
|
|
ring_part,
|
|
scad.make_face_connector_rconnector(
|
|
"axis",
|
|
_axis_face("ring_axis", ring, (0.0, 0.0), GEAR_HEIGHT, 1.0),
|
|
),
|
|
)
|
|
|
|
sun_drive_part = scad.make_part_rpart(
|
|
"sun_drive_plate",
|
|
sun_drive_plate,
|
|
name="Grounded sun-drive plate with central shaft",
|
|
)
|
|
sun_drive_part = scad.assign_material_rpart(sun_drive_part, carrier_material)
|
|
sun_drive_part = scad.add_connector_rpart(
|
|
sun_drive_part,
|
|
scad.make_face_connector_rconnector(
|
|
"carrier_axis",
|
|
_axis_face(
|
|
"sun_drive_carrier_axis",
|
|
sun_drive_plate,
|
|
(0.0, 0.0),
|
|
CARRIER_AXIS_CONNECTOR_Z,
|
|
1.0,
|
|
),
|
|
),
|
|
)
|
|
sun_drive_part = scad.add_connector_rpart(
|
|
sun_drive_part,
|
|
scad.make_face_connector_rconnector(
|
|
"sun_axis",
|
|
_axis_face(
|
|
"sun_drive_sun_axis",
|
|
sun_drive_plate,
|
|
(0.0, 0.0),
|
|
SUN_AXIS_CONNECTOR_Z,
|
|
1.0,
|
|
),
|
|
),
|
|
)
|
|
|
|
carrier_part = scad.make_part_rpart(
|
|
"planet_carrier",
|
|
planet_carrier,
|
|
name="Planet carrier output plate with three pins",
|
|
)
|
|
carrier_part = scad.assign_material_rpart(carrier_part, carrier_material)
|
|
carrier_part = scad.add_connector_rpart(
|
|
carrier_part,
|
|
scad.make_face_connector_rconnector(
|
|
"carrier_axis",
|
|
_axis_face(
|
|
"planet_carrier_axis",
|
|
planet_carrier,
|
|
(0.0, 0.0),
|
|
CARRIER_AXIS_CONNECTOR_Z,
|
|
1.0,
|
|
),
|
|
),
|
|
)
|
|
for index in range(PLANET_COUNT):
|
|
carrier_angle_deg = 360.0 * index / PLANET_COUNT
|
|
carrier_angle_rad = math.radians(carrier_angle_deg)
|
|
center_xy = (
|
|
planet_center_radius * math.cos(carrier_angle_rad),
|
|
planet_center_radius * math.sin(carrier_angle_rad),
|
|
)
|
|
carrier_part = scad.add_connector_rpart(
|
|
carrier_part,
|
|
scad.make_face_connector_rconnector(
|
|
f"planet_{index + 1}_axis",
|
|
_axis_face(
|
|
f"carrier_planet_{index + 1}_axis",
|
|
planet_carrier,
|
|
center_xy,
|
|
PLANET_AXIS_CONNECTOR_Z,
|
|
1.0,
|
|
),
|
|
),
|
|
)
|
|
|
|
sun_part = scad.make_part_rpart(
|
|
"herringbone_sun", sun, name="Herringbone sun gear"
|
|
)
|
|
sun_part = scad.assign_material_rpart(sun_part, gear_material)
|
|
sun_part = scad.add_connector_rpart(
|
|
sun_part,
|
|
scad.make_face_connector_rconnector(
|
|
"axis",
|
|
_axis_face("sun_axis", sun, (0.0, 0.0), GEAR_HEIGHT, 1.0),
|
|
),
|
|
)
|
|
|
|
planet_part = scad.make_part_rpart(
|
|
"herringbone_planet", planet_base, name="Reusable herringbone planet gear"
|
|
)
|
|
planet_part = scad.assign_material_rpart(planet_part, gear_material)
|
|
planet_part = scad.add_connector_rpart(
|
|
planet_part,
|
|
scad.make_face_connector_rconnector(
|
|
"axis",
|
|
_axis_face("planet_axis", planet_base, (0.0, 0.0), GEAR_HEIGHT, 1.0),
|
|
),
|
|
)
|
|
print(
|
|
"parts: "
|
|
f"{ring_part.part_id},{sun_drive_part.part_id},{carrier_part.part_id},"
|
|
f"{sun_part.part_id},{planet_part.part_id}"
|
|
)
|
|
|
|
gearset = scad.make_assembly_rassembly(
|
|
"herringbone_planetary_gearset",
|
|
name="Fixed-ring herringbone planetary reducer assembly",
|
|
)
|
|
gearset = scad.add_component_rassembly(
|
|
gearset,
|
|
ring_part,
|
|
component_id="fixed_ring",
|
|
placement=_z_rotation_placement((0.0, 0.0, 0.0), 0.0),
|
|
name="Grounded fixed internal ring gear",
|
|
)
|
|
gearset = scad.add_component_rassembly(
|
|
gearset,
|
|
sun_drive_part,
|
|
component_id="sun_drive_plate",
|
|
placement=_z_rotation_placement((0.0, 0.0, 0.0), 0.0),
|
|
name="Grounded sun-drive input plate",
|
|
)
|
|
gearset = scad.add_component_rassembly(
|
|
gearset,
|
|
carrier_part,
|
|
component_id="planet_carrier",
|
|
placement=_z_rotation_placement((0.0, 0.0, 0.0), 0.0),
|
|
name="Planet-carrier output plate",
|
|
)
|
|
gearset = scad.add_component_rassembly(
|
|
gearset,
|
|
sun_part,
|
|
component_id="sun",
|
|
placement=_z_rotation_placement((0.0, 0.0, 0.0), 0.0),
|
|
name="Sun gear fixed to input plate",
|
|
)
|
|
|
|
for index in range(PLANET_COUNT):
|
|
carrier_angle_deg = 360.0 * index / PLANET_COUNT
|
|
carrier_angle_rad = math.radians(carrier_angle_deg)
|
|
center = (
|
|
planet_center_radius * math.cos(carrier_angle_rad),
|
|
planet_center_radius * math.sin(carrier_angle_rad),
|
|
0.0,
|
|
)
|
|
spin_angle = _planet_spin_angle(carrier_angle_deg)
|
|
gearset = scad.add_component_rassembly(
|
|
gearset,
|
|
planet_part,
|
|
component_id=f"planet_{index + 1}",
|
|
placement=_z_rotation_placement(center, spin_angle),
|
|
name=f"Planet gear {index + 1}",
|
|
)
|
|
print(
|
|
f"planet_{index + 1}: carrier={carrier_angle_deg:.1f}deg "
|
|
f"center=({center[0]:.3f},{center[1]:.3f},{center[2]:.3f}) "
|
|
f"spin={spin_angle:.1f}deg"
|
|
)
|
|
|
|
gearset = scad.ground_component_rassembly(gearset, "fixed_ring")
|
|
gearset = scad.add_revolute_constraint_rassembly(
|
|
gearset,
|
|
"sun_input_revolute",
|
|
scad.make_connector_ref_rconnectorref("fixed_ring", "axis"),
|
|
scad.make_connector_ref_rconnectorref("sun_drive_plate", "sun_axis"),
|
|
name="Sun input shaft rotates inside the fixed ring gear",
|
|
)
|
|
gearset = scad.add_revolute_constraint_rassembly(
|
|
gearset,
|
|
"carrier_output_revolute",
|
|
scad.make_connector_ref_rconnectorref("sun_drive_plate", "carrier_axis"),
|
|
scad.make_connector_ref_rconnectorref("planet_carrier", "carrier_axis"),
|
|
name="Planet carrier rotates around the sun-drive plate axis",
|
|
)
|
|
gearset = scad.add_fixed_constraint_rassembly(
|
|
gearset,
|
|
"sun_fixed_to_drive_plate",
|
|
scad.make_connector_ref_rconnectorref("sun_drive_plate", "sun_axis"),
|
|
scad.make_connector_ref_rconnectorref("sun", "axis"),
|
|
name="Sun gear fixed to the input shaft",
|
|
)
|
|
for index in range(PLANET_COUNT):
|
|
gearset = scad.add_revolute_constraint_rassembly(
|
|
gearset,
|
|
f"planet_{index + 1}_revolute",
|
|
scad.make_connector_ref_rconnectorref("planet_carrier", f"planet_{index + 1}_axis"),
|
|
scad.make_connector_ref_rconnectorref(f"planet_{index + 1}", "axis"),
|
|
name=f"Planet gear {index + 1} rotates on its carrier pin",
|
|
)
|
|
for index in range(PLANET_COUNT):
|
|
planet_ref = scad.make_connector_ref_rconnectorref(
|
|
component_id=f"planet_{index + 1}",
|
|
connector_id="axis",
|
|
)
|
|
gearset = scad.add_gear_constraint_rassembly(
|
|
assembly=gearset,
|
|
constraint_id=f"sun_planet_{index + 1}_external_mesh",
|
|
connector_a=scad.make_connector_ref_rconnectorref(
|
|
component_id="sun_drive_plate",
|
|
connector_id="sun_axis",
|
|
),
|
|
connector_b=planet_ref,
|
|
pitch_radius_a=SUN_PITCH_RADIUS,
|
|
pitch_radius_b=PLANET_PITCH_RADIUS,
|
|
name=f"External sun to planet {index + 1} gear mesh",
|
|
)
|
|
gearset = scad.add_belt_constraint_rassembly(
|
|
assembly=gearset,
|
|
constraint_id=f"ring_planet_{index + 1}_internal_mesh",
|
|
connector_a=scad.make_connector_ref_rconnectorref(
|
|
component_id="fixed_ring",
|
|
connector_id="axis",
|
|
),
|
|
connector_b=planet_ref,
|
|
pulley_radius_a=RING_PITCH_RADIUS,
|
|
pulley_radius_b=PLANET_PITCH_RADIUS,
|
|
name=f"Internal fixed-ring to planet {index + 1} gear mesh",
|
|
)
|
|
print(
|
|
"gear_constraints: "
|
|
f"sun_planet_external={PLANET_COUNT} ring_planet_internal={PLANET_COUNT} "
|
|
f"radii=({SUN_PITCH_RADIUS:.3f},{PLANET_PITCH_RADIUS:.3f},{RING_PITCH_RADIUS:.3f})"
|
|
)
|
|
gearset = scad.solve_assembly_constraints_rassembly(gearset)
|
|
report = scad.inspect_assembly_constraints_rconstraintreport(gearset)
|
|
print(
|
|
"assembly: "
|
|
f"components={','.join(gearset.component_ids())} "
|
|
f"grounded={','.join(gearset.grounded_component_ids)} "
|
|
f"solved={report.solved} constraints={len(gearset.constraints)}"
|
|
)
|
|
for residual in report.residuals:
|
|
print(
|
|
f"constraint_{residual.constraint_id}: "
|
|
f"translation={residual.translation_error:.6g} "
|
|
f"angle={residual.angular_error_degrees:.6g} "
|
|
f"ok={residual.within_tolerance}"
|
|
)
|
|
|
|
preview = scad.make_compound_from_assembly_rcompound(gearset)
|
|
_ground_compound("assembly_preview", preview)
|
|
model_json = scad.export_model_json(session)
|
|
|
|
return gearset, preview, model_json
|
|
|
|
|
|
def main() -> None:
|
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
model_path = OUTPUT_DIR / "herringbone_planetary_gearset.model.json"
|
|
step_path = OUTPUT_DIR / "herringbone_planetary_gearset.step"
|
|
fcstd_path = OUTPUT_DIR / "herringbone_planetary_gearset.FCStd"
|
|
if fcstd_path.exists():
|
|
fcstd_path.unlink()
|
|
|
|
assembly, preview, model_json = build_herringbone_planetary_gearset()
|
|
model_path.write_text(model_json, encoding="utf-8")
|
|
scad.export_step(preview, str(step_path))
|
|
|
|
fcstd_status = "not attempted"
|
|
try:
|
|
scad.translator.freecad_translator.translate_model_json_to_fcstd(model_json, str(fcstd_path.resolve()))
|
|
fcstd_status = f"{fcstd_path} ({fcstd_path.stat().st_size} bytes)"
|
|
except Exception as exc: # pragma: no cover - depends on local FreeCAD install
|
|
fcstd_status = f"failed ({exc.__class__.__name__}: {exc})"
|
|
|
|
payload = json.loads(model_json)
|
|
replayed = scad.replay_model_json(model_json)
|
|
solids = ql.select(preview.get_solids()).all()
|
|
face_count = sum(len(ql.select(solid.get_faces()).all()) for solid in solids)
|
|
volumes = [solid.get_volume() for solid in solids]
|
|
|
|
print(
|
|
"sun_z={sun} planet_z={planet} ring_z={ring} planets={count} "
|
|
"module={module} height={height} sun_helix={sun_helix} planet_helix={planet_helix}".format(
|
|
sun=SUN_TEETH,
|
|
planet=PLANET_TEETH,
|
|
ring=RING_TEETH,
|
|
count=PLANET_COUNT,
|
|
module=MODULE,
|
|
height=GEAR_HEIGHT,
|
|
sun_helix=SUN_HELIX_ANGLE,
|
|
planet_helix=PLANET_HELIX_ANGLE,
|
|
)
|
|
)
|
|
print(f"planet_center_radius={MODULE * (SUN_TEETH + PLANET_TEETH) / 2.0:.3f}")
|
|
print(f"assembly={assembly.assembly_id}")
|
|
print("components=" + ",".join(assembly.component_ids()))
|
|
print(f"preview_solids={len(solids)}")
|
|
print(f"preview_faces={face_count}")
|
|
print("volumes=" + ",".join(f"{volume:.1f}" for volume in volumes))
|
|
print(f"replay_outputs={len(replayed)}")
|
|
print("replay_types=" + ",".join(type(item).__name__ for item in replayed))
|
|
print(f"graph_nodes={len(payload['graph']['nodes'])}")
|
|
print(f"model={model_path}")
|
|
print(f"step={step_path}")
|
|
print(f"fcstd={fcstd_status}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|