first commit
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
# Case 20: Integrated 50 mm BLDC Joint Actuator
|
||||
|
||||
## Classification
|
||||
|
||||
Product-level kinematic assembly. The actuator contains separately manufactured
|
||||
electrical, magnetic, bearing, gear, housing, and output parts. The fixed housing
|
||||
is the ground; the rotor, two carriers, and six planets are rotating components.
|
||||
|
||||
## Design Intent
|
||||
|
||||
- Fit a real inner-rotor brushless motor, a 20:1 reducer, a circular controller
|
||||
PCB, and serviceable wiring terminals into one coaxial 50 mm package.
|
||||
- Eliminate the separate motor-to-reducer coupler. The rotor shaft and stage-1
|
||||
sun are one steel solid.
|
||||
- Preserve a short load path from the output flange through two adjacent output
|
||||
bearings into a separate front bearing cap.
|
||||
- Keep the stator and both ring gears as replaceable press-fit inserts instead of
|
||||
hiding them inside an impossible one-piece enclosure.
|
||||
- Reserve rear-facing connector apertures and PCB mounting holes so electronics
|
||||
are not represented by an empty cosmetic volume.
|
||||
|
||||
## Envelope And Performance
|
||||
|
||||
| Item | Value |
|
||||
|---|---:|
|
||||
| Motor / housing outside diameter | 50.0 mm |
|
||||
| Structural axial envelope | 75.8 mm |
|
||||
| Terminal protrusion included | 77.3 mm |
|
||||
| Motor topology | 12-slot / 14-pole inner rotor |
|
||||
| Stator active length | 16.0 mm |
|
||||
| Rotor magnetic length | 17.5 mm |
|
||||
| Radial air gap | 0.30 mm |
|
||||
| Stage 1 | 15/15/45 teeth, 4:1 |
|
||||
| Stage 2 | 18/27/72 teeth, 5:1 |
|
||||
| Total reduction | 20:1 |
|
||||
| Reducer housing minimum cylindrical wall | 2.20 mm |
|
||||
|
||||
The two stages intentionally use different modules. Stage 1 uses module 0.80 to
|
||||
leave enough root section around the 8 mm direct-drive shaft. Stage 2 uses module
|
||||
0.55 to fit the 72-tooth, 5:1 ring inside the 50 mm housing. Both tooth sets obey
|
||||
the three-planet equal-spacing condition `(sun teeth + ring teeth) mod 3 = 0`.
|
||||
|
||||
## Bill Of Materials
|
||||
|
||||
| Part / subassembly | Manufacturing intent | Material / connection |
|
||||
|---|---|---|
|
||||
| Main reducer housing | One machined fixed part | 6061-T6 aluminum |
|
||||
| Motor shell | One machined fixed part | 6061-T6, six M3 front screws |
|
||||
| Rear bearing spider | Separate machined part | 7075-T6, four M2.5 screws |
|
||||
| Rear electronics cover | Separate machined part with PCB bosses | 6061-T6, four M2.5 screws |
|
||||
| Output bearing cap | Separate machined part | 7075-T6, six M3 screws |
|
||||
| Stator core | 12-slot laminated stack | Electrical steel, thermal press fit |
|
||||
| Windings | Twelve separately represented coil packs | Copper, varnish/potting retained |
|
||||
| Rotor shaft + stage-1 sun | One integrated machined solid | Hardened alloy steel |
|
||||
| Rotor magnets | Fourteen bonded inserts | NdFeB |
|
||||
| PCB | Circular controller board with real holes/notches | FR-4/copper |
|
||||
| Phase terminal | Three-position rear-access terminal | High-temperature polymer |
|
||||
| Power/CAN terminal | Four-position rear-access terminal | High-temperature polymer |
|
||||
| Fixed ring gears | Two replaceable press-fit inserts | Case-hardened steel |
|
||||
| Planet gears | Six gears with standard bearing seats | Case-hardened steel |
|
||||
| Stage-1 carrier + stage-2 sun | One integrated interstage part | 7075 carrier / modeled as steel-duty part |
|
||||
| Output carrier + flange | One integrated output part | 7075-T6 aluminum |
|
||||
| Motor bearings | 8x16x5 and 8x19x6 | Standard ball bearings |
|
||||
| Interstage bearing | 5x10x3 | Thin radial ball bearing in fixed divider |
|
||||
| Planet bearings | Six 3x6x3 bearings | Standard ball bearings |
|
||||
| Output bearings | Two 16x24x5 bearings | Standard ball bearings |
|
||||
|
||||
Fasteners are represented by matching holes and documented interfaces rather
|
||||
than individual screw solids. This keeps the graph focused while retaining
|
||||
manufacturable attachment geometry.
|
||||
|
||||
## Interface Table
|
||||
|
||||
| Constraint / interface | Motion meaning | Real connection | Geometry and clearance |
|
||||
|---|---|---|---|
|
||||
| `motor_shell_to_reducer_housing` | Fixed | Six M3 screws | 43.0 mm PCD, 3.2 mm holes |
|
||||
| `rear_spider_to_motor_shell` | Fixed | Four M2.5 screws | 40.6 mm PCD, 2.7 mm holes; face-to-face column/spider joint |
|
||||
| `rear_cover_to_motor_shell` | Fixed | Four M2.5 screws | Shared rear columns and holes |
|
||||
| `stator_to_motor_shell` | Fixed | Thermal press fit + potting | Nominal line-to-line CAD fit; tolerance sets interference |
|
||||
| `electronics_to_rear_cover` | Fixed | Four M2 PCB screws | 33.0 mm PCD and integrated standoffs |
|
||||
| `rotor_revolute` | Motor input rotation | Front/rear radial bearings | 8 mm shaft, 0.30 mm magnetic air gap |
|
||||
| `stage1_ring_fixed` | Fixed ring | Interference fit and axial clamp | 0.04 mm diametral modeled interference |
|
||||
| `stage1_carrier_revolute` | First reduction output | 5x10x3 radial bearing | Integrated 5 mm stage-2 sun shaft |
|
||||
| `stage2_ring_fixed` | Fixed ring | Interference fit and axial clamp | 0.04 mm diametral modeled interference |
|
||||
| `output_carrier_revolute` | Joint output rotation | Paired output bearings | 16 mm shaft in two 16x24x5 bearings |
|
||||
| Planet revolutes | Planet spin | 3x6x3 bearings on 3 mm pins | 0.05 mm radial gear-seat clearance |
|
||||
| Output cap to housing | Fixed | Six M3 screws | 43.0 mm PCD, 3.2 mm holes |
|
||||
| Output link | External fixed attachment | Six M3 screws | 34.0 mm PCD tapped holes, Ø15.96 locating pilot |
|
||||
|
||||
## Electronics Packaging
|
||||
|
||||
The controller PCB is a 44.4 mm circular board behind the rear motor bearing. It
|
||||
has a 10 mm center service bore, four M2 mounting holes, four large edge notches
|
||||
for the rear structural columns, three phase-terminal pin holes, and four
|
||||
power/CAN pin holes. Two rear-cover apertures expose the terminal bodies without
|
||||
removing the controller. The board remains removable after the rear cover and
|
||||
terminal screws are released.
|
||||
|
||||
## Assembly Order
|
||||
|
||||
1. Press the stator stack into the motor shell and pot the twelve winding packs.
|
||||
2. Install the rear bearing into the four-arm spider and bolt the spider to the
|
||||
shell's rear columns.
|
||||
3. Insert the rotor/shaft from the reducer side and support it with the front
|
||||
motor bearing in the reducer bulkhead.
|
||||
4. Bolt the motor shell to the reducer housing with the six-hole front interface.
|
||||
5. Insert the first fixed ring and planetary stage, the 5x10x3 interstage
|
||||
bearing, then the second fixed ring and planetary stage from the open front.
|
||||
6. Install the paired output bearings in the removable output cap, then bolt the
|
||||
cap to the reducer housing.
|
||||
7. Install the PCB and terminals on the rear-cover standoffs, connect phases and
|
||||
sensors, and attach the rear cover.
|
||||
|
||||
This sequence avoids the trapped 43 mm ring-gear problem in Case 16: both ring
|
||||
inserts and carriers enter through the open reducer front before the bearing cap
|
||||
is installed.
|
||||
|
||||
## Strength And Thermal Notes
|
||||
|
||||
- The motor shell retains 1.80 mm radial wall around the stator and the reducer
|
||||
shell retains 2.20 mm around the steel ring inserts.
|
||||
- The 43 mm PCD case holes pass through 18.5 mm-radius end lands, retaining
|
||||
1.4 mm of continuous aluminum ligament on the bore side of each M3 clearance
|
||||
hole instead of clipping only the thin cylindrical shell.
|
||||
- The front reducer bulkhead is 8 mm long around the 19 mm motor bearing.
|
||||
- Two adjacent output bearings form a 10 mm stack with 5 mm center spacing to
|
||||
distribute overturning load.
|
||||
- The output flange leaves 4.15 mm radial ligament beyond the M3 tapped-hole edges.
|
||||
- The output face carries a 1.5 mm-high, Ø15.96 locating pilot. Mating links use
|
||||
the pilot for concentric location and six Ø3.3 clearance holes with Ø6 socket-
|
||||
head counterbores; the screws provide clamp load rather than radial location.
|
||||
- External robot structure clamps the continuous Ø50 reducer sleeve at the
|
||||
`case_clamp_axis` datum (`Z = 20.0`) instead of sharing the internal output-cap
|
||||
retention screws.
|
||||
- The stator yoke contacts the aluminum shell over its full active length for a
|
||||
direct thermal path; controller heat can flow through the rear standoffs and
|
||||
cover.
|
||||
- Detailed tooth stress, bearing life, winding thermal limits, rotor retention,
|
||||
and fastener preload still require engineering calculation and prototype test.
|
||||
@@ -0,0 +1 @@
|
||||
"""Reusable implementation package for Example 20."""
|
||||
@@ -0,0 +1,441 @@
|
||||
"""Top-level integrated BLDC motor, controller, reducer, and housing assembly."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import simplecadapi as scad
|
||||
|
||||
try:
|
||||
from .bearings import (
|
||||
make_coaxial_bearing_rplacement,
|
||||
make_main_bearing_rassembly,
|
||||
make_planet_bearing_rplacement,
|
||||
make_standard_planet_bearing_rassembly,
|
||||
)
|
||||
from .common import connector_ref, ground_constraint_report
|
||||
from .dimensions import (
|
||||
FRONT_MOTOR_BEARING,
|
||||
FRONT_MOTOR_BEARING_CENTER_Z,
|
||||
INTERSTAGE_BEARING,
|
||||
INTERSTAGE_BEARING_CENTER_Z,
|
||||
OUTPUT_BEARING,
|
||||
OUTPUT_BEARING_1_CENTER_Z,
|
||||
OUTPUT_BEARING_2_CENTER_Z,
|
||||
PLANET_BEARING,
|
||||
PLANET_COUNT,
|
||||
REAR_BEARING_CENTER_Z,
|
||||
REAR_MOTOR_BEARING,
|
||||
STAGE_1,
|
||||
STAGE_2,
|
||||
TOTAL_REDUCTION,
|
||||
StageSpec,
|
||||
)
|
||||
from .electronics import make_integrated_controller_rassembly
|
||||
from .gears import (
|
||||
make_output_carrier_flange_rpart,
|
||||
make_planet_rplacement,
|
||||
make_stage1_carrier_sun_rpart,
|
||||
make_stage_planet_gear_rpart,
|
||||
make_stage_ring_gear_rpart,
|
||||
)
|
||||
from .housing import (
|
||||
make_motor_shell_rpart,
|
||||
make_output_bearing_cap_rpart,
|
||||
make_rear_bearing_spider_rpart,
|
||||
make_rear_electronics_cover_rpart,
|
||||
make_reducer_housing_rpart,
|
||||
)
|
||||
from .motor import make_bldc_rotor_rassembly, make_bldc_stator_rassembly
|
||||
except ImportError: # Support direct execution from this example directory.
|
||||
from bearings import (
|
||||
make_coaxial_bearing_rplacement,
|
||||
make_main_bearing_rassembly,
|
||||
make_planet_bearing_rplacement,
|
||||
make_standard_planet_bearing_rassembly,
|
||||
)
|
||||
from common import connector_ref, ground_constraint_report
|
||||
from dimensions import (
|
||||
FRONT_MOTOR_BEARING,
|
||||
FRONT_MOTOR_BEARING_CENTER_Z,
|
||||
INTERSTAGE_BEARING,
|
||||
INTERSTAGE_BEARING_CENTER_Z,
|
||||
OUTPUT_BEARING,
|
||||
OUTPUT_BEARING_1_CENTER_Z,
|
||||
OUTPUT_BEARING_2_CENTER_Z,
|
||||
PLANET_BEARING,
|
||||
PLANET_COUNT,
|
||||
REAR_BEARING_CENTER_Z,
|
||||
REAR_MOTOR_BEARING,
|
||||
STAGE_1,
|
||||
STAGE_2,
|
||||
TOTAL_REDUCTION,
|
||||
StageSpec,
|
||||
)
|
||||
from electronics import make_integrated_controller_rassembly
|
||||
from gears import (
|
||||
make_output_carrier_flange_rpart,
|
||||
make_planet_rplacement,
|
||||
make_stage1_carrier_sun_rpart,
|
||||
make_stage_planet_gear_rpart,
|
||||
make_stage_ring_gear_rpart,
|
||||
)
|
||||
from housing import (
|
||||
make_motor_shell_rpart,
|
||||
make_output_bearing_cap_rpart,
|
||||
make_rear_bearing_spider_rpart,
|
||||
make_rear_electronics_cover_rpart,
|
||||
make_reducer_housing_rpart,
|
||||
)
|
||||
from motor import make_bldc_rotor_rassembly, make_bldc_stator_rassembly
|
||||
|
||||
|
||||
def make_integrated_bldc_joint_actuator_rassembly(
|
||||
*, materials: dict[str, scad.Material]
|
||||
) -> scad.Assembly:
|
||||
"""Build and solve the complete compact 50 mm joint actuator."""
|
||||
|
||||
component_specs = make_integrated_bldc_joint_actuator_components_rtuple(
|
||||
materials=materials
|
||||
)
|
||||
actuator = scad.make_assembly_rassembly(
|
||||
assembly_id="integrated_50mm_bldc_joint_actuator",
|
||||
name="50 mm 12-slot/14-pole BLDC joint actuator with 20:1 reducer and circular ESC",
|
||||
)
|
||||
for component_id, item, placement, name in component_specs:
|
||||
actuator = scad.add_component_rassembly(
|
||||
assembly=actuator,
|
||||
item=item,
|
||||
component_id=component_id,
|
||||
placement=placement,
|
||||
name=name,
|
||||
)
|
||||
|
||||
actuator = _add_public_connectors_rassembly(assembly=actuator)
|
||||
actuator = _add_constraints_rassembly(assembly=actuator)
|
||||
actuator = scad.solve_assembly_constraints_rassembly(assembly=actuator, strict=True)
|
||||
ground_constraint_report(label="actuator", assembly=actuator)
|
||||
return actuator
|
||||
|
||||
|
||||
def make_integrated_bldc_joint_actuator_components_rtuple(
|
||||
*, materials: dict[str, scad.Material]
|
||||
) -> tuple[tuple[str, scad.Part | scad.Assembly, scad.Placement, str], ...]:
|
||||
"""Build the actuator component inventory without creating a parent assembly."""
|
||||
|
||||
print(
|
||||
f"ratio_plan: stage1={STAGE_1.fixed_ring_ratio:.1f}:1 "
|
||||
f"stage2={STAGE_2.fixed_ring_ratio:.1f}:1 total={TOTAL_REDUCTION:.1f}:1"
|
||||
)
|
||||
reducer_housing = make_reducer_housing_rpart(material=materials["housing"])
|
||||
motor_shell = make_motor_shell_rpart(material=materials["housing"])
|
||||
rear_spider = make_rear_bearing_spider_rpart(material=materials["carrier"])
|
||||
rear_cover = make_rear_electronics_cover_rpart(material=materials["housing"])
|
||||
output_cap = make_output_bearing_cap_rpart(material=materials["carrier"])
|
||||
stator = make_bldc_stator_rassembly(
|
||||
steel_material=materials["electrical_steel"],
|
||||
copper_material=materials["copper"],
|
||||
)
|
||||
rotor = make_bldc_rotor_rassembly(
|
||||
steel_material=materials["gear"],
|
||||
magnet_material=materials["magnet"],
|
||||
)
|
||||
controller = make_integrated_controller_rassembly(
|
||||
pcb_material=materials["pcb"],
|
||||
terminal_material=materials["terminal"],
|
||||
)
|
||||
|
||||
stage1_ring = make_stage_ring_gear_rpart(stage=STAGE_1, material=materials["gear"])
|
||||
stage1_planet = make_stage_planet_gear_rpart(stage=STAGE_1, material=materials["gear"])
|
||||
stage1_carrier = make_stage1_carrier_sun_rpart(material=materials["gear"])
|
||||
stage2_ring = make_stage_ring_gear_rpart(stage=STAGE_2, material=materials["gear"])
|
||||
stage2_planet = make_stage_planet_gear_rpart(stage=STAGE_2, material=materials["gear"])
|
||||
output_carrier = make_output_carrier_flange_rpart(stage=STAGE_2, material=materials["carrier"])
|
||||
|
||||
rear_motor_bearing = make_main_bearing_rassembly(
|
||||
bearing_id="rear_motor_8x16x5",
|
||||
spec=REAR_MOTOR_BEARING,
|
||||
material=materials["gear"],
|
||||
)
|
||||
front_motor_bearing = make_main_bearing_rassembly(
|
||||
bearing_id="front_motor_8x19x6",
|
||||
spec=FRONT_MOTOR_BEARING,
|
||||
material=materials["gear"],
|
||||
)
|
||||
interstage_bearing = make_main_bearing_rassembly(
|
||||
bearing_id="interstage_5x10x3",
|
||||
spec=INTERSTAGE_BEARING,
|
||||
material=materials["gear"],
|
||||
)
|
||||
planet_bearing = make_standard_planet_bearing_rassembly(
|
||||
bearing_id="planet_3x6x3",
|
||||
spec=PLANET_BEARING,
|
||||
material=materials["gear"],
|
||||
)
|
||||
output_bearing = make_main_bearing_rassembly(
|
||||
bearing_id="output_16x24x5",
|
||||
spec=OUTPUT_BEARING,
|
||||
material=materials["gear"],
|
||||
)
|
||||
|
||||
fixed_components = (
|
||||
("reducer_housing", reducer_housing, scad.identity_placement_rplacement(), "Fixed reducer housing"),
|
||||
("motor_shell", motor_shell, scad.identity_placement_rplacement(), "Fixed BLDC shell"),
|
||||
("rear_bearing_spider", rear_spider, scad.identity_placement_rplacement(), "Rear motor-bearing spider"),
|
||||
("rear_electronics_cover", rear_cover, scad.identity_placement_rplacement(), "Rear controller cover"),
|
||||
("output_bearing_cap", output_cap, scad.identity_placement_rplacement(), "Output bearing cap"),
|
||||
("stator", stator, scad.identity_placement_rplacement(), "12-slot fixed stator"),
|
||||
("rotor", rotor, scad.identity_placement_rplacement(), "14-pole rotor and direct sun shaft"),
|
||||
("controller", controller, scad.identity_placement_rplacement(), "Circular integrated controller"),
|
||||
("stage1_carrier", stage1_carrier, scad.identity_placement_rplacement(), "Stage 1 carrier and stage 2 sun"),
|
||||
("output_carrier", output_carrier, scad.identity_placement_rplacement(), "Stage 2 carrier and output flange"),
|
||||
("stage1_ring", stage1_ring, _stage_rplacement(stage=STAGE_1), "Stage 1 fixed ring insert"),
|
||||
("stage2_ring", stage2_ring, _stage_rplacement(stage=STAGE_2), "Stage 2 fixed ring insert"),
|
||||
)
|
||||
print(f"actuator_base_components: count={len(fixed_components)}")
|
||||
|
||||
planet_components = []
|
||||
for stage, planet in ((STAGE_1, stage1_planet), (STAGE_2, stage2_planet)):
|
||||
for index in range(PLANET_COUNT):
|
||||
planet_components.append(
|
||||
(
|
||||
f"{stage.stage_id}_planet_{index + 1}",
|
||||
planet,
|
||||
make_planet_rplacement(stage=stage, index=index),
|
||||
f"{stage.label} planet {index + 1}",
|
||||
)
|
||||
)
|
||||
|
||||
bearing_components = (
|
||||
(
|
||||
"rear_motor_bearing",
|
||||
rear_motor_bearing,
|
||||
make_coaxial_bearing_rplacement(center_z=REAR_BEARING_CENTER_Z),
|
||||
"Rear rotor bearing",
|
||||
),
|
||||
(
|
||||
"front_motor_bearing",
|
||||
front_motor_bearing,
|
||||
make_coaxial_bearing_rplacement(center_z=FRONT_MOTOR_BEARING_CENTER_Z),
|
||||
"Front rotor bearing",
|
||||
),
|
||||
(
|
||||
"interstage_bearing",
|
||||
interstage_bearing,
|
||||
make_coaxial_bearing_rplacement(center_z=INTERSTAGE_BEARING_CENTER_Z),
|
||||
"Stage 1 carrier support bearing",
|
||||
),
|
||||
(
|
||||
"output_bearing_1",
|
||||
output_bearing,
|
||||
make_coaxial_bearing_rplacement(center_z=OUTPUT_BEARING_1_CENTER_Z),
|
||||
"Rear output bearing",
|
||||
),
|
||||
(
|
||||
"output_bearing_2",
|
||||
output_bearing,
|
||||
make_coaxial_bearing_rplacement(center_z=OUTPUT_BEARING_2_CENTER_Z),
|
||||
"Front output bearing",
|
||||
),
|
||||
)
|
||||
planet_bearing_components = []
|
||||
for stage in (STAGE_1, STAGE_2):
|
||||
for index in range(PLANET_COUNT):
|
||||
planet_bearing_components.append(
|
||||
(
|
||||
f"{stage.stage_id}_planet_bearing_{index + 1}",
|
||||
planet_bearing,
|
||||
make_planet_bearing_rplacement(stage=stage, index=index),
|
||||
f"{stage.label} planet bearing {index + 1}",
|
||||
)
|
||||
)
|
||||
print(f"bearing_components: motor=2 interstage=1 output=2 planet={PLANET_COUNT * 2}")
|
||||
return tuple(
|
||||
[
|
||||
*fixed_components,
|
||||
*planet_components,
|
||||
*bearing_components,
|
||||
*planet_bearing_components,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _add_public_connectors_rassembly(*, assembly: scad.Assembly) -> scad.Assembly:
|
||||
forwarded = (
|
||||
("case_clamp_axis", "reducer_housing", "case_clamp_axis", "External split-clamp datum"),
|
||||
("case_mount_axis", "output_bearing_cap", "case_mount_axis", "Fixed actuator case datum"),
|
||||
("output_link_axis", "output_carrier", "output_link_axis", "Rotating six-hole output flange"),
|
||||
("phase_terminal_access", "controller", "phase_access", "Rear phase-terminal service datum"),
|
||||
("power_can_terminal_access", "controller", "power_can_access", "Rear power/CAN service datum"),
|
||||
)
|
||||
for connector_id, source_component_id, source_connector_id, name in forwarded:
|
||||
assembly = scad.forward_connector_rassembly(
|
||||
assembly=assembly,
|
||||
connector_id=connector_id,
|
||||
source_component_id=source_component_id,
|
||||
source_connector_id=source_connector_id,
|
||||
name=name,
|
||||
offset=None,
|
||||
)
|
||||
print("actuator_public_connectors: " + ",".join(item[0] for item in forwarded))
|
||||
return assembly
|
||||
|
||||
|
||||
def _add_constraints_rassembly(*, assembly: scad.Assembly) -> scad.Assembly:
|
||||
assembly = scad.ground_component_rassembly(assembly=assembly, component_id="reducer_housing")
|
||||
assembly = scad.ground_component_rassembly(assembly=assembly, component_id="stage1_ring")
|
||||
assembly = scad.ground_component_rassembly(assembly=assembly, component_id="stage2_ring")
|
||||
fixed_pairs = (
|
||||
("motor_shell_to_reducer_housing", "reducer_housing", "motor_mount_axis", "motor_shell", "reducer_mount_axis"),
|
||||
("rear_spider_to_motor_shell", "motor_shell", "rear_spider_axis", "rear_bearing_spider", "shell_axis"),
|
||||
("rear_cover_to_motor_shell", "motor_shell", "rear_cover_axis", "rear_electronics_cover", "shell_axis"),
|
||||
("stator_to_motor_shell", "motor_shell", "stator_axis", "stator", "shell_axis"),
|
||||
("controller_to_rear_cover", "rear_electronics_cover", "pcb_axis", "controller", "cover_axis"),
|
||||
("stage1_ring_fixed", "reducer_housing", "stage1_ring_axis", "stage1_ring", "axis"),
|
||||
("stage2_ring_fixed", "reducer_housing", "stage2_ring_axis", "stage2_ring", "axis"),
|
||||
("output_cap_to_reducer_housing", "reducer_housing", "output_cap_axis", "output_bearing_cap", "housing_axis"),
|
||||
)
|
||||
for constraint_id, a_component, a_connector, b_component, b_connector in fixed_pairs:
|
||||
assembly = scad.add_fixed_constraint_rassembly(
|
||||
assembly=assembly,
|
||||
constraint_id=constraint_id,
|
||||
connector_a=connector_ref(component_id=a_component, connector_id=a_connector),
|
||||
connector_b=connector_ref(component_id=b_component, connector_id=b_connector),
|
||||
name=constraint_id.replace("_", " "),
|
||||
)
|
||||
|
||||
primary_revolutes = (
|
||||
("rotor_revolute", "reducer_housing", "front_motor_bearing_axis", "rotor", "front_bearing_axis"),
|
||||
("stage1_carrier_revolute", "reducer_housing", "stage1_carrier_axis", "stage1_carrier", "carrier_axis"),
|
||||
("output_carrier_revolute", "reducer_housing", "stage2_carrier_axis", "output_carrier", "carrier_axis"),
|
||||
)
|
||||
for constraint_id, a_component, a_connector, b_component, b_connector in primary_revolutes:
|
||||
assembly = scad.add_revolute_constraint_rassembly(
|
||||
assembly=assembly,
|
||||
constraint_id=constraint_id,
|
||||
connector_a=connector_ref(component_id=a_component, connector_id=a_connector),
|
||||
connector_b=connector_ref(component_id=b_component, connector_id=b_connector),
|
||||
drive_angle_degrees=0.0,
|
||||
angle_limit=None,
|
||||
name=constraint_id.replace("_", " "),
|
||||
)
|
||||
|
||||
assembly = _add_stage_constraints_rassembly(
|
||||
assembly=assembly,
|
||||
stage=STAGE_1,
|
||||
sun_component="rotor",
|
||||
sun_connector="front_bearing_axis",
|
||||
ring_component="stage1_ring",
|
||||
carrier_component="stage1_carrier",
|
||||
)
|
||||
assembly = _add_stage_constraints_rassembly(
|
||||
assembly=assembly,
|
||||
stage=STAGE_2,
|
||||
sun_component="stage1_carrier",
|
||||
sun_connector="carrier_axis",
|
||||
ring_component="stage2_ring",
|
||||
carrier_component="output_carrier",
|
||||
)
|
||||
assembly = _add_bearing_constraints_rassembly(assembly=assembly)
|
||||
print("actuator_constraints: fixed=8 primary_revolute=3 planet_revolute=6 gear=6 internal=6 bearing_interfaces=22")
|
||||
return assembly
|
||||
|
||||
|
||||
def _add_stage_constraints_rassembly(
|
||||
*,
|
||||
assembly: scad.Assembly,
|
||||
stage: StageSpec,
|
||||
sun_component: str,
|
||||
sun_connector: str,
|
||||
ring_component: str,
|
||||
carrier_component: str,
|
||||
) -> scad.Assembly:
|
||||
for index in range(PLANET_COUNT):
|
||||
planet_component = f"{stage.stage_id}_planet_{index + 1}"
|
||||
assembly = scad.add_revolute_constraint_rassembly(
|
||||
assembly=assembly,
|
||||
constraint_id=f"{planet_component}_revolute",
|
||||
connector_a=connector_ref(component_id=carrier_component, connector_id=f"planet_{index + 1}_axis"),
|
||||
connector_b=connector_ref(component_id=planet_component, connector_id="axis"),
|
||||
drive_angle_degrees=None,
|
||||
angle_limit=None,
|
||||
name=f"{stage.label} planet {index + 1} bearing axis",
|
||||
)
|
||||
assembly = scad.add_gear_constraint_rassembly(
|
||||
assembly=assembly,
|
||||
constraint_id=f"{stage.stage_id}_sun_planet_{index + 1}_mesh",
|
||||
connector_a=connector_ref(component_id=sun_component, connector_id=sun_connector),
|
||||
connector_b=connector_ref(component_id=planet_component, connector_id="axis"),
|
||||
pitch_radius_a=stage.sun_pitch_radius,
|
||||
pitch_radius_b=stage.planet_pitch_radius,
|
||||
phase_offset=None,
|
||||
name=f"{stage.label} sun to planet {index + 1} external mesh",
|
||||
)
|
||||
assembly = scad.add_belt_constraint_rassembly(
|
||||
assembly=assembly,
|
||||
constraint_id=f"{stage.stage_id}_ring_planet_{index + 1}_internal_mesh",
|
||||
connector_a=connector_ref(component_id=ring_component, connector_id="axis"),
|
||||
connector_b=connector_ref(component_id=planet_component, connector_id="axis"),
|
||||
pulley_radius_a=stage.ring_pitch_radius,
|
||||
pulley_radius_b=stage.planet_pitch_radius,
|
||||
phase_offset=None,
|
||||
name=f"{stage.label} fixed-ring to planet {index + 1} internal mesh",
|
||||
)
|
||||
print(
|
||||
f"{stage.stage_id}_mesh: sun_r={stage.sun_pitch_radius:.3f} "
|
||||
f"planet_r={stage.planet_pitch_radius:.3f} center={stage.planet_center_radius:.3f}"
|
||||
)
|
||||
return assembly
|
||||
|
||||
|
||||
def _add_bearing_constraints_rassembly(*, assembly: scad.Assembly) -> scad.Assembly:
|
||||
interfaces = (
|
||||
("rear_bearing_outer_to_spider", "rear_bearing_spider", "bearing_axis", "rear_motor_bearing", "outer_axis"),
|
||||
("rear_bearing_inner_to_rotor", "rotor", "rear_bearing_axis", "rear_motor_bearing", "inner_axis"),
|
||||
("front_bearing_outer_to_housing", "reducer_housing", "front_motor_bearing_axis", "front_motor_bearing", "outer_axis"),
|
||||
("front_bearing_inner_to_rotor", "rotor", "front_bearing_axis", "front_motor_bearing", "inner_axis"),
|
||||
("interstage_bearing_outer_to_housing", "reducer_housing", "interstage_bearing_axis", "interstage_bearing", "outer_axis"),
|
||||
("interstage_bearing_inner_to_carrier", "stage1_carrier", "interstage_bearing_axis", "interstage_bearing", "inner_axis"),
|
||||
("output_bearing_1_outer_to_cap", "output_bearing_cap", "bearing_1_axis", "output_bearing_1", "outer_axis"),
|
||||
("output_bearing_1_inner_to_carrier", "output_carrier", "bearing_1_axis", "output_bearing_1", "inner_axis"),
|
||||
("output_bearing_2_outer_to_cap", "output_bearing_cap", "bearing_2_axis", "output_bearing_2", "outer_axis"),
|
||||
("output_bearing_2_inner_to_carrier", "output_carrier", "bearing_2_axis", "output_bearing_2", "inner_axis"),
|
||||
)
|
||||
for constraint_id, a_component, a_connector, b_component, b_connector in interfaces:
|
||||
assembly = scad.add_revolute_constraint_rassembly(
|
||||
assembly=assembly,
|
||||
constraint_id=constraint_id,
|
||||
connector_a=connector_ref(component_id=a_component, connector_id=a_connector),
|
||||
connector_b=connector_ref(component_id=b_component, connector_id=b_connector),
|
||||
drive_angle_degrees=None,
|
||||
angle_limit=None,
|
||||
name=constraint_id.replace("_", " "),
|
||||
)
|
||||
for stage, carrier_component in ((STAGE_1, "stage1_carrier"), (STAGE_2, "output_carrier")):
|
||||
for index in range(PLANET_COUNT):
|
||||
planet = f"{stage.stage_id}_planet_{index + 1}"
|
||||
bearing = f"{stage.stage_id}_planet_bearing_{index + 1}"
|
||||
assembly = scad.add_revolute_constraint_rassembly(
|
||||
assembly=assembly,
|
||||
constraint_id=f"{bearing}_outer_to_planet",
|
||||
connector_a=connector_ref(component_id=planet, connector_id="bearing_axis"),
|
||||
connector_b=connector_ref(component_id=bearing, connector_id="outer_axis"),
|
||||
drive_angle_degrees=None,
|
||||
angle_limit=None,
|
||||
name=f"{stage.label} planet {index + 1} bearing outer-ring fit",
|
||||
)
|
||||
assembly = scad.add_revolute_constraint_rassembly(
|
||||
assembly=assembly,
|
||||
constraint_id=f"{bearing}_inner_to_pin",
|
||||
connector_a=connector_ref(
|
||||
component_id=carrier_component,
|
||||
connector_id=f"planet_{index + 1}_bearing_axis",
|
||||
),
|
||||
connector_b=connector_ref(component_id=bearing, connector_id="inner_axis"),
|
||||
drive_angle_degrees=None,
|
||||
angle_limit=None,
|
||||
name=f"{stage.label} planet {index + 1} bearing inner-ring pin fit",
|
||||
)
|
||||
return assembly
|
||||
|
||||
|
||||
def _stage_rplacement(*, stage: StageSpec) -> scad.Placement:
|
||||
return scad.make_placement_rplacement(origin=(0.0, 0.0, stage.bottom_z))
|
||||
@@ -0,0 +1,174 @@
|
||||
"""Standard bearing factories and coaxial/planet placements."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import simplecadapi as scad
|
||||
|
||||
if __package__:
|
||||
from .common import make_annulus_rsolid, make_axis_part_rpart, make_z_rotation_rplacement
|
||||
from .dimensions import BearingSpec, PLANET_COUNT, StageSpec
|
||||
from .gears import planet_center_xy
|
||||
else:
|
||||
from common import make_annulus_rsolid, make_axis_part_rpart, make_z_rotation_rplacement
|
||||
from dimensions import BearingSpec, PLANET_COUNT, StageSpec
|
||||
from gears import planet_center_xy
|
||||
|
||||
|
||||
def make_standard_planet_bearing_rassembly(
|
||||
*,
|
||||
bearing_id: str,
|
||||
spec: BearingSpec,
|
||||
material: scad.Material,
|
||||
) -> scad.Assembly:
|
||||
"""Create the reused catalog-style planet ball bearing assembly."""
|
||||
|
||||
bearing = make_main_bearing_rassembly(
|
||||
bearing_id=bearing_id,
|
||||
spec=spec,
|
||||
material=material,
|
||||
)
|
||||
print(
|
||||
f"bearing_{bearing_id}: bore={spec.bore_diameter:.1f} od={spec.outer_diameter:.1f} "
|
||||
f"width={spec.width:.1f} balls={spec.ball_count} material={material.material_id}"
|
||||
)
|
||||
return bearing
|
||||
|
||||
|
||||
def make_main_bearing_rassembly(
|
||||
*,
|
||||
bearing_id: str,
|
||||
spec: BearingSpec,
|
||||
material: scad.Material,
|
||||
) -> scad.Assembly:
|
||||
"""Create a uniquely identified bearing when several catalog sizes share one graph."""
|
||||
|
||||
bore_radius = spec.bore_diameter / 2.0
|
||||
outer_radius = spec.outer_diameter / 2.0
|
||||
ball_radius = spec.ball_diameter / 2.0
|
||||
pitch_radius = (bore_radius + outer_radius) / 2.0
|
||||
inner_outer_radius = pitch_radius - ball_radius * 0.55
|
||||
outer_inner_radius = pitch_radius + ball_radius * 0.55
|
||||
inner_ring = make_annulus_rsolid(
|
||||
outer_radius=inner_outer_radius,
|
||||
inner_radius=bore_radius,
|
||||
bottom_z=-spec.width / 2.0,
|
||||
height=spec.width,
|
||||
tags=("role.bearing_inner_ring",),
|
||||
)
|
||||
outer_ring = make_annulus_rsolid(
|
||||
outer_radius=outer_radius,
|
||||
inner_radius=outer_inner_radius,
|
||||
bottom_z=-spec.width / 2.0,
|
||||
height=spec.width,
|
||||
tags=("role.bearing_outer_ring",),
|
||||
)
|
||||
ball = scad.make_sphere_rsolid(
|
||||
radius=ball_radius,
|
||||
center=(pitch_radius, 0.0, 0.0),
|
||||
)
|
||||
inner_part = make_axis_part_rpart(
|
||||
part_id=f"{bearing_id}_inner_ring",
|
||||
body=inner_ring,
|
||||
name=f"{bearing_id} inner race",
|
||||
material=material,
|
||||
connectors=(("axis", (0.0, 0.0, 0.0), "Inner-ring axis"),),
|
||||
)
|
||||
outer_part = make_axis_part_rpart(
|
||||
part_id=f"{bearing_id}_outer_ring",
|
||||
body=outer_ring,
|
||||
name=f"{bearing_id} outer race",
|
||||
material=material,
|
||||
connectors=(("axis", (0.0, 0.0, 0.0), "Outer-ring axis"),),
|
||||
)
|
||||
ball_part = make_axis_part_rpart(
|
||||
part_id=f"{bearing_id}_reusable_ball",
|
||||
body=ball,
|
||||
name=f"{bearing_id} reusable rolling element",
|
||||
material=material,
|
||||
connectors=(),
|
||||
)
|
||||
bearing = scad.make_assembly_rassembly(
|
||||
assembly_id=bearing_id,
|
||||
name=f"Ball bearing {spec.bore_diameter:g}x{spec.outer_diameter:g}x{spec.width:g}",
|
||||
)
|
||||
bearing = scad.add_component_rassembly(
|
||||
assembly=bearing,
|
||||
item=outer_part,
|
||||
component_id="outer_ring",
|
||||
placement=scad.identity_placement_rplacement(),
|
||||
name="Outer ring",
|
||||
)
|
||||
bearing = scad.add_component_rassembly(
|
||||
assembly=bearing,
|
||||
item=inner_part,
|
||||
component_id="inner_ring",
|
||||
placement=scad.identity_placement_rplacement(),
|
||||
name="Inner ring",
|
||||
)
|
||||
for index in range(spec.ball_count):
|
||||
angle = 360.0 * index / spec.ball_count
|
||||
bearing = scad.add_component_rassembly(
|
||||
assembly=bearing,
|
||||
item=ball_part,
|
||||
component_id=f"ball_{index + 1:02d}",
|
||||
placement=make_z_rotation_rplacement(origin=(0.0, 0.0, 0.0), angle_degrees=angle),
|
||||
name=f"Rolling element {index + 1}",
|
||||
)
|
||||
bearing = scad.add_revolute_constraint_rassembly(
|
||||
assembly=bearing,
|
||||
constraint_id="inner_outer_revolute",
|
||||
connector_a=scad.make_connector_ref_rconnectorref(
|
||||
component_id="outer_ring",
|
||||
connector_id="axis",
|
||||
),
|
||||
connector_b=scad.make_connector_ref_rconnectorref(
|
||||
component_id="inner_ring",
|
||||
connector_id="axis",
|
||||
),
|
||||
drive_angle_degrees=None,
|
||||
angle_limit=None,
|
||||
name="Inner ring rotation in outer ring",
|
||||
)
|
||||
for connector_id, component_id in (("outer_axis", "outer_ring"), ("inner_axis", "inner_ring")):
|
||||
bearing = scad.forward_connector_rassembly(
|
||||
assembly=bearing,
|
||||
connector_id=connector_id,
|
||||
source_component_id=component_id,
|
||||
source_connector_id="axis",
|
||||
name=connector_id.replace("_", " "),
|
||||
offset=None,
|
||||
)
|
||||
radial_wall = (spec.outer_diameter - spec.bore_diameter) / 2.0 - spec.ball_diameter
|
||||
axial_margin = spec.width - spec.ball_diameter
|
||||
print(
|
||||
f"bearing_{bearing_id}: bore={spec.bore_diameter:.1f} od={spec.outer_diameter:.1f} "
|
||||
f"width={spec.width:.1f} balls={spec.ball_count} radial_wall={radial_wall:.2f} "
|
||||
f"axial_margin={axial_margin:.2f}"
|
||||
)
|
||||
return bearing
|
||||
|
||||
|
||||
def make_coaxial_bearing_rplacement(*, center_z: float) -> scad.Placement:
|
||||
"""Place a standard bearing center plane on the actuator Z axis."""
|
||||
|
||||
return make_z_rotation_rplacement(origin=(0.0, 0.0, center_z), angle_degrees=0.0)
|
||||
|
||||
|
||||
def make_planet_bearing_rplacement(
|
||||
*,
|
||||
stage: StageSpec,
|
||||
index: int,
|
||||
) -> scad.Placement:
|
||||
"""Place a standard planet bearing at the gear midplane."""
|
||||
|
||||
if index < 0 or index >= PLANET_COUNT:
|
||||
raise ValueError(f"planet bearing index out of range: {index}")
|
||||
center = planet_center_xy(stage=stage, index=index)
|
||||
print(
|
||||
f"{stage.stage_id}_planet_bearing_{index + 1}: "
|
||||
f"center=({center[0]:.3f},{center[1]:.3f},{stage.mid_z:.3f})"
|
||||
)
|
||||
return make_z_rotation_rplacement(
|
||||
origin=(center[0], center[1], stage.mid_z),
|
||||
angle_degrees=0.0,
|
||||
)
|
||||
@@ -0,0 +1,164 @@
|
||||
"""Shared construction, tagging, connector, and grounding helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from collections.abc import Iterable
|
||||
|
||||
import simplecadapi as scad
|
||||
from simplecadapi import ql
|
||||
|
||||
|
||||
def apply_tags(*, shape: scad.Solid, tags: Iterable[str]) -> scad.Solid:
|
||||
"""Apply semantic tags through the public functional API."""
|
||||
|
||||
tagged = shape
|
||||
for tag in tags:
|
||||
tagged = scad.apply_tag(shape=tagged, tag=tag)
|
||||
return tagged
|
||||
|
||||
|
||||
def make_annulus_rsolid(
|
||||
*,
|
||||
outer_radius: float,
|
||||
inner_radius: float,
|
||||
bottom_z: float,
|
||||
height: float,
|
||||
tags: Iterable[str],
|
||||
) -> scad.Solid:
|
||||
"""Create a strict single-solid annular cylinder."""
|
||||
|
||||
outer = scad.make_cylinder_rsolid(
|
||||
radius=outer_radius,
|
||||
height=height,
|
||||
bottom_face_center=(0.0, 0.0, bottom_z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
bore = scad.make_cylinder_rsolid(
|
||||
radius=inner_radius,
|
||||
height=height + 2.0,
|
||||
bottom_face_center=(0.0, 0.0, bottom_z - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
annulus = scad.cut_rsolid(outer, bore, skip_non_intersecting=False)
|
||||
return apply_tags(shape=annulus, tags=tags)
|
||||
|
||||
|
||||
def make_axis_part_rpart(
|
||||
*,
|
||||
part_id: str,
|
||||
body: scad.Solid,
|
||||
name: str,
|
||||
material: scad.Material,
|
||||
connectors: Iterable[tuple[str, tuple[float, float, float], str]],
|
||||
) -> scad.Part:
|
||||
"""Create a single-body part with stable placement-based axis datums."""
|
||||
|
||||
part = scad.make_part_rpart(part_id=part_id, body=body, name=name)
|
||||
part = scad.assign_material_rpart(part=part, material=material)
|
||||
connector_count = 0
|
||||
for connector_id, origin, connector_name in connectors:
|
||||
connector = scad.make_placement_connector_rconnector(
|
||||
connector_id=connector_id,
|
||||
placement=scad.make_placement_rplacement(origin=origin),
|
||||
name=connector_name,
|
||||
)
|
||||
part = scad.add_connector_rpart(part=part, connector=connector)
|
||||
connector_count += 1
|
||||
ground_solid(label=part_id, solid=body)
|
||||
print(f"part_{part_id}: connectors={connector_count} material={material.material_id}")
|
||||
return part
|
||||
|
||||
|
||||
def make_z_rotation_rplacement(
|
||||
*,
|
||||
origin: tuple[float, float, float],
|
||||
angle_degrees: float,
|
||||
) -> scad.Placement:
|
||||
"""Return a right-handed placement rotated about Z."""
|
||||
|
||||
angle = math.radians(angle_degrees)
|
||||
return scad.make_placement_rplacement(
|
||||
origin=origin,
|
||||
x_axis=(math.cos(angle), math.sin(angle), 0.0),
|
||||
y_axis=(-math.sin(angle), math.cos(angle), 0.0),
|
||||
)
|
||||
|
||||
|
||||
def radial_centers(*, count: int, radius: float, angle_offset: float = 0.0):
|
||||
"""Yield index, angle in degrees, and XY center on a bolt/pole circle."""
|
||||
|
||||
for index in range(count):
|
||||
angle_degrees = angle_offset + 360.0 * index / count
|
||||
angle = math.radians(angle_degrees)
|
||||
yield index, angle_degrees, (radius * math.cos(angle), radius * math.sin(angle))
|
||||
|
||||
|
||||
def make_axial_hole_cutters_rsolids(
|
||||
*,
|
||||
count: int,
|
||||
pcd: float,
|
||||
hole_radius: float,
|
||||
bottom_z: float,
|
||||
height: float,
|
||||
angle_offset: float = 0.0,
|
||||
) -> list[scad.Solid]:
|
||||
"""Create equally spaced axial hole cutters."""
|
||||
|
||||
cutters = []
|
||||
for _index, _angle, center in radial_centers(
|
||||
count=count,
|
||||
radius=pcd / 2.0,
|
||||
angle_offset=angle_offset,
|
||||
):
|
||||
cutters.append(
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=hole_radius,
|
||||
height=height,
|
||||
bottom_face_center=(center[0], center[1], bottom_z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
)
|
||||
return cutters
|
||||
|
||||
|
||||
def ground_solid(*, label: str, solid: scad.Solid) -> None:
|
||||
"""Print a concise QL-backed solid summary."""
|
||||
|
||||
faces = ql.select(items=solid.get_faces()).all()
|
||||
role_faces = ql.select(items=faces).where(ql.tag(pattern="role.*")).all()
|
||||
print(
|
||||
f"{label}: faces={len(faces)} role_faces={len(role_faces)} "
|
||||
f"volume={solid.get_volume():.3f} tags={','.join(scad.list_tags(shape=solid))}"
|
||||
)
|
||||
|
||||
|
||||
def ground_compound(*, label: str, compound: scad.Compound) -> None:
|
||||
"""Print a concise QL-backed assembly projection summary."""
|
||||
|
||||
solids = ql.select(items=compound.get_solids()).all()
|
||||
faces = sum(len(ql.select(items=solid.get_faces()).all()) for solid in solids)
|
||||
volume = sum(solid.get_volume() for solid in solids)
|
||||
print(f"{label}: solids={len(solids)} faces={faces} volume={volume:.3f}")
|
||||
|
||||
|
||||
def connector_ref(*, component_id: str, connector_id: str) -> scad.ConnectorRef:
|
||||
"""Create a component-scoped connector reference."""
|
||||
|
||||
return scad.make_connector_ref_rconnectorref(
|
||||
component_id=component_id,
|
||||
connector_id=connector_id,
|
||||
)
|
||||
|
||||
|
||||
def ground_constraint_report(*, label: str, assembly: scad.Assembly) -> None:
|
||||
"""Print solved state and only non-zero residual facts."""
|
||||
|
||||
report = scad.inspect_assembly_constraints_rconstraintreport(assembly=assembly)
|
||||
worst_translation = max((item.translation_error for item in report.residuals), default=0.0)
|
||||
worst_angle = max((item.angular_error_degrees for item in report.residuals), default=0.0)
|
||||
print(
|
||||
f"{label}_constraints: solved={report.solved} components={len(assembly.component_ids())} "
|
||||
f"constraints={len(assembly.constraint_ids())} unsolved={len(report.unsolved_component_ids)} "
|
||||
f"max_translation={worst_translation:.6g} max_angle={worst_angle:.6g}"
|
||||
)
|
||||
@@ -0,0 +1,235 @@
|
||||
"""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}"
|
||||
)
|
||||
@@ -0,0 +1,270 @@
|
||||
"""Circular integrated controller PCB, power stages, and rear terminals."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
import simplecadapi as scad
|
||||
|
||||
try:
|
||||
from .common import (
|
||||
apply_tags,
|
||||
connector_ref,
|
||||
ground_constraint_report,
|
||||
make_axial_hole_cutters_rsolids,
|
||||
make_axis_part_rpart,
|
||||
radial_centers,
|
||||
)
|
||||
from .dimensions import (
|
||||
PCB_BOTTOM_Z,
|
||||
PCB_CENTER_BORE_RADIUS,
|
||||
PCB_MOUNT_HOLE_RADIUS,
|
||||
PCB_RADIUS,
|
||||
PCB_STANDOFF_PCD,
|
||||
PCB_THICKNESS,
|
||||
REAR_COLUMN_PCD,
|
||||
)
|
||||
except ImportError: # Support direct execution from this example directory.
|
||||
from common import (
|
||||
apply_tags,
|
||||
connector_ref,
|
||||
ground_constraint_report,
|
||||
make_axial_hole_cutters_rsolids,
|
||||
make_axis_part_rpart,
|
||||
radial_centers,
|
||||
)
|
||||
from dimensions import (
|
||||
PCB_BOTTOM_Z,
|
||||
PCB_CENTER_BORE_RADIUS,
|
||||
PCB_MOUNT_HOLE_RADIUS,
|
||||
PCB_RADIUS,
|
||||
PCB_STANDOFF_PCD,
|
||||
PCB_THICKNESS,
|
||||
REAR_COLUMN_PCD,
|
||||
)
|
||||
|
||||
|
||||
PHASE_TERMINAL_CENTER = (-11.0, 0.0)
|
||||
POWER_CAN_TERMINAL_CENTER = (11.0, 0.0)
|
||||
MOSFET_ANGLES = (22.5, 67.5, 112.5, 202.5, 247.5, 292.5)
|
||||
|
||||
|
||||
def make_integrated_controller_rassembly(
|
||||
*,
|
||||
pcb_material: scad.Material,
|
||||
terminal_material: scad.Material,
|
||||
) -> scad.Assembly:
|
||||
"""Build the circular ESC with six power devices and two service terminals."""
|
||||
|
||||
pcb = _make_controller_pcb_rpart(material=pcb_material)
|
||||
mosfet = _make_mosfet_package_rpart(material=terminal_material)
|
||||
phase_terminal = _make_terminal_block_rpart(
|
||||
part_id="three_phase_terminal",
|
||||
name="Three-position motor phase terminal",
|
||||
width=8.0,
|
||||
pin_count=3,
|
||||
material=terminal_material,
|
||||
)
|
||||
power_terminal = _make_terminal_block_rpart(
|
||||
part_id="power_can_terminal",
|
||||
name="Four-position DC power and CAN terminal",
|
||||
width=8.0,
|
||||
pin_count=4,
|
||||
material=terminal_material,
|
||||
)
|
||||
controller = scad.make_assembly_rassembly(
|
||||
assembly_id="integrated_circular_motor_controller",
|
||||
name="44.4 mm circular integrated BLDC controller",
|
||||
)
|
||||
controller = scad.add_component_rassembly(
|
||||
assembly=controller,
|
||||
item=pcb,
|
||||
component_id="pcb",
|
||||
placement=scad.identity_placement_rplacement(),
|
||||
name="Circular controller PCB",
|
||||
)
|
||||
controller = scad.ground_component_rassembly(assembly=controller, component_id="pcb")
|
||||
|
||||
for index, angle in enumerate(MOSFET_ANGLES):
|
||||
radians = math.radians(angle)
|
||||
center = (13.2 * math.cos(radians), 13.2 * math.sin(radians))
|
||||
component_id = f"mosfet_{index + 1}"
|
||||
z = PCB_BOTTOM_Z + PCB_THICKNESS
|
||||
controller = scad.add_component_rassembly(
|
||||
assembly=controller,
|
||||
item=mosfet,
|
||||
component_id=component_id,
|
||||
placement=scad.make_placement_rplacement(origin=(center[0], center[1], z)),
|
||||
name=f"Power MOSFET package {index + 1}",
|
||||
)
|
||||
controller = scad.add_fixed_constraint_rassembly(
|
||||
assembly=controller,
|
||||
constraint_id=f"{component_id}_soldered",
|
||||
connector_a=connector_ref(component_id="pcb", connector_id=component_id),
|
||||
connector_b=connector_ref(component_id=component_id, connector_id="solder_axis"),
|
||||
name=f"MOSFET {index + 1} solder attachment",
|
||||
)
|
||||
|
||||
for component_id, item, center, connector_id in (
|
||||
("phase_terminal", phase_terminal, PHASE_TERMINAL_CENTER, "phase_terminal"),
|
||||
("power_can_terminal", power_terminal, POWER_CAN_TERMINAL_CENTER, "power_can_terminal"),
|
||||
):
|
||||
controller = scad.add_component_rassembly(
|
||||
assembly=controller,
|
||||
item=item,
|
||||
component_id=component_id,
|
||||
placement=scad.make_placement_rplacement(origin=(center[0], center[1], -38.5)),
|
||||
name=item.name,
|
||||
)
|
||||
controller = scad.add_fixed_constraint_rassembly(
|
||||
assembly=controller,
|
||||
constraint_id=f"{component_id}_soldered",
|
||||
connector_a=connector_ref(component_id="pcb", connector_id=connector_id),
|
||||
connector_b=connector_ref(component_id=component_id, connector_id="solder_axis"),
|
||||
name=f"{component_id.replace('_', ' ')} solder and screw retention",
|
||||
)
|
||||
|
||||
for connector_id in ("cover_axis", "phase_access", "power_can_access"):
|
||||
controller = scad.forward_connector_rassembly(
|
||||
assembly=controller,
|
||||
connector_id=connector_id,
|
||||
source_component_id="pcb",
|
||||
source_connector_id=connector_id,
|
||||
name=connector_id.replace("_", " "),
|
||||
offset=None,
|
||||
)
|
||||
controller = scad.solve_assembly_constraints_rassembly(assembly=controller, strict=True)
|
||||
ground_constraint_report(label="controller", assembly=controller)
|
||||
print("controller_packaging: pcb_d=44.4 mosfets=6 phase_pins=3 power_can_pins=4")
|
||||
return controller
|
||||
|
||||
|
||||
def _make_controller_pcb_rpart(*, material: scad.Material) -> scad.Part:
|
||||
board = scad.make_cylinder_rsolid(
|
||||
radius=PCB_RADIUS,
|
||||
height=PCB_THICKNESS,
|
||||
bottom_face_center=(0.0, 0.0, PCB_BOTTOM_Z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
cutters: list[scad.Solid] = [
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=PCB_CENTER_BORE_RADIUS,
|
||||
height=PCB_THICKNESS + 2.0,
|
||||
bottom_face_center=(0.0, 0.0, PCB_BOTTOM_Z - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
]
|
||||
cutters.extend(
|
||||
make_axial_hole_cutters_rsolids(
|
||||
count=4,
|
||||
pcd=PCB_STANDOFF_PCD,
|
||||
hole_radius=PCB_MOUNT_HOLE_RADIUS,
|
||||
bottom_z=PCB_BOTTOM_Z - 1.0,
|
||||
height=PCB_THICKNESS + 2.0,
|
||||
angle_offset=45.0,
|
||||
)
|
||||
)
|
||||
cutters.extend(
|
||||
make_axial_hole_cutters_rsolids(
|
||||
count=4,
|
||||
pcd=REAR_COLUMN_PCD,
|
||||
hole_radius=3.45,
|
||||
bottom_z=PCB_BOTTOM_Z - 1.0,
|
||||
height=PCB_THICKNESS + 2.0,
|
||||
)
|
||||
)
|
||||
for x, count in ((PHASE_TERMINAL_CENTER[0], 3), (POWER_CAN_TERMINAL_CENTER[0], 4)):
|
||||
for pin in range(count):
|
||||
y = (pin - (count - 1) / 2.0) * 1.8
|
||||
cutters.append(
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=0.65,
|
||||
height=PCB_THICKNESS + 2.0,
|
||||
bottom_face_center=(x, y, PCB_BOTTOM_Z - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
)
|
||||
board = scad.cut_rsolid(board, cutters, skip_non_intersecting=False)
|
||||
board = apply_tags(
|
||||
shape=board,
|
||||
tags=("role.circular_esc_pcb", "role.controller_mounting_holes", "group.integrated_electronics"),
|
||||
)
|
||||
connectors = [
|
||||
("cover_axis", (0.0, 0.0, PCB_BOTTOM_Z + PCB_THICKNESS / 2.0), "Rear-cover PCB plane"),
|
||||
("phase_terminal", (*PHASE_TERMINAL_CENTER, PCB_BOTTOM_Z), "Phase terminal solder datum"),
|
||||
("power_can_terminal", (*POWER_CAN_TERMINAL_CENTER, PCB_BOTTOM_Z), "Power/CAN terminal solder datum"),
|
||||
("phase_access", (*PHASE_TERMINAL_CENTER, -37.0), "Phase terminal service axis"),
|
||||
("power_can_access", (*POWER_CAN_TERMINAL_CENTER, -37.0), "Power/CAN service axis"),
|
||||
]
|
||||
for index, angle in enumerate(MOSFET_ANGLES):
|
||||
radians = math.radians(angle)
|
||||
center = (13.2 * math.cos(radians), 13.2 * math.sin(radians))
|
||||
connectors.append(
|
||||
(
|
||||
f"mosfet_{index + 1}",
|
||||
(center[0], center[1], PCB_BOTTOM_Z + PCB_THICKNESS),
|
||||
f"MOSFET {index + 1} solder datum",
|
||||
)
|
||||
)
|
||||
print("pcb_holes: center=10.0 mount=4 column_notches=4 terminal_pins=7")
|
||||
return make_axis_part_rpart(
|
||||
part_id="circular_controller_pcb",
|
||||
body=board,
|
||||
name="44.4 mm circular ESC PCB with service cutouts",
|
||||
material=material,
|
||||
connectors=connectors,
|
||||
)
|
||||
|
||||
|
||||
def _make_mosfet_package_rpart(*, material: scad.Material) -> scad.Part:
|
||||
package = scad.make_box_rsolid(
|
||||
width=4.0,
|
||||
height=3.0,
|
||||
depth=1.4,
|
||||
bottom_face_center=(0.0, 0.0, 0.0),
|
||||
)
|
||||
package = apply_tags(shape=package, tags=("role.power_mosfet", "group.three_phase_bridge"))
|
||||
return make_axis_part_rpart(
|
||||
part_id="reusable_power_mosfet",
|
||||
body=package,
|
||||
name="Reusable power MOSFET package",
|
||||
material=material,
|
||||
connectors=(("solder_axis", (0.0, 0.0, 0.0), "PCB solder plane"),),
|
||||
)
|
||||
|
||||
|
||||
def _make_terminal_block_rpart(
|
||||
*,
|
||||
part_id: str,
|
||||
name: str,
|
||||
width: float,
|
||||
pin_count: int,
|
||||
material: scad.Material,
|
||||
) -> scad.Part:
|
||||
body = scad.make_box_rsolid(
|
||||
width=width,
|
||||
height=6.0,
|
||||
depth=4.9,
|
||||
bottom_face_center=(0.0, 0.0, 0.0),
|
||||
)
|
||||
access_cutters = []
|
||||
for pin in range(pin_count):
|
||||
y = (pin - (pin_count - 1) / 2.0) * 1.8
|
||||
access_cutters.append(
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=0.75,
|
||||
height=width + 2.0,
|
||||
bottom_face_center=(-width / 2.0 - 1.0, y, 2.45),
|
||||
axis=(1.0, 0.0, 0.0),
|
||||
)
|
||||
)
|
||||
body = scad.cut_rsolid(body, access_cutters, skip_non_intersecting=False)
|
||||
body = apply_tags(shape=body, tags=("role.rear_wiring_terminal", "role.service_access"))
|
||||
print(f"terminal_{part_id}: pins={pin_count} access_holes={pin_count} width={width:.1f}")
|
||||
return make_axis_part_rpart(
|
||||
part_id=part_id,
|
||||
body=body,
|
||||
name=name,
|
||||
material=material,
|
||||
connectors=(("solder_axis", (0.0, 0.0, 4.9), "PCB solder and screw datum"),),
|
||||
)
|
||||
@@ -0,0 +1,441 @@
|
||||
"""Herringbone ring, planet, carrier, and output parts for the 20:1 reducer."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
import simplecadapi as scad
|
||||
|
||||
try:
|
||||
from .common import (
|
||||
apply_tags,
|
||||
make_axis_part_rpart,
|
||||
make_axial_hole_cutters_rsolids,
|
||||
make_z_rotation_rplacement,
|
||||
)
|
||||
from .dimensions import (
|
||||
ADDENDUM_FACTOR,
|
||||
BACKLASH,
|
||||
CLEARANCE_FACTOR,
|
||||
GEAR_HEIGHT,
|
||||
HELIX_ANGLE,
|
||||
INTERSTAGE_BEARING_CENTER_Z,
|
||||
INTERSTAGE_SHAFT_RADIUS,
|
||||
OUTPUT_BEARING_1_CENTER_Z,
|
||||
OUTPUT_BEARING_2_CENTER_Z,
|
||||
OUTPUT_FLANGE_BOTTOM_Z,
|
||||
OUTPUT_FLANGE_RADIUS,
|
||||
OUTPUT_FLANGE_TOP_Z,
|
||||
OUTPUT_LINK_BOLT_ANGLES_DEGREES,
|
||||
OUTPUT_LINK_BOLT_COUNT,
|
||||
OUTPUT_LINK_HOLE_PCD,
|
||||
OUTPUT_LINK_TAP_RADIUS,
|
||||
OUTPUT_LINK_THREAD_DEPTH,
|
||||
OUTPUT_REGISTER_HEIGHT,
|
||||
OUTPUT_SHAFT_RADIUS,
|
||||
PLANET_BEARING,
|
||||
PLANET_COUNT,
|
||||
PRESSURE_ANGLE,
|
||||
RING_INSERT_OUTER_RADIUS,
|
||||
RING_RIM_THICKNESS,
|
||||
RING_SUPPORT_OVERLAP,
|
||||
STAGE1_ARM_WIDTH,
|
||||
STAGE1_CARRIER_BOTTOM_Z,
|
||||
STAGE1_CARRIER_THICKNESS,
|
||||
STAGE1_HUB_RADIUS,
|
||||
STAGE1_PAD_RADIUS,
|
||||
STAGE1_PIN_BOTTOM_Z,
|
||||
STAGE1_PIN_RADIUS,
|
||||
STAGE2_ARM_WIDTH,
|
||||
STAGE2_CARRIER_BOTTOM_Z,
|
||||
STAGE2_CARRIER_THICKNESS,
|
||||
STAGE2_HUB_RADIUS,
|
||||
STAGE2_PAD_RADIUS,
|
||||
STAGE2_PIN_BOTTOM_Z,
|
||||
STAGE2_PIN_RADIUS,
|
||||
STAGE_1,
|
||||
STAGE_2,
|
||||
StageSpec,
|
||||
)
|
||||
except ImportError: # Support direct execution from this example directory.
|
||||
from common import (
|
||||
apply_tags,
|
||||
make_axis_part_rpart,
|
||||
make_axial_hole_cutters_rsolids,
|
||||
make_z_rotation_rplacement,
|
||||
)
|
||||
from dimensions import (
|
||||
ADDENDUM_FACTOR,
|
||||
BACKLASH,
|
||||
CLEARANCE_FACTOR,
|
||||
GEAR_HEIGHT,
|
||||
HELIX_ANGLE,
|
||||
INTERSTAGE_BEARING_CENTER_Z,
|
||||
INTERSTAGE_SHAFT_RADIUS,
|
||||
OUTPUT_BEARING_1_CENTER_Z,
|
||||
OUTPUT_BEARING_2_CENTER_Z,
|
||||
OUTPUT_FLANGE_BOTTOM_Z,
|
||||
OUTPUT_FLANGE_RADIUS,
|
||||
OUTPUT_FLANGE_TOP_Z,
|
||||
OUTPUT_LINK_BOLT_ANGLES_DEGREES,
|
||||
OUTPUT_LINK_BOLT_COUNT,
|
||||
OUTPUT_LINK_HOLE_PCD,
|
||||
OUTPUT_LINK_TAP_RADIUS,
|
||||
OUTPUT_LINK_THREAD_DEPTH,
|
||||
OUTPUT_REGISTER_HEIGHT,
|
||||
OUTPUT_SHAFT_RADIUS,
|
||||
PLANET_BEARING,
|
||||
PLANET_COUNT,
|
||||
PRESSURE_ANGLE,
|
||||
RING_INSERT_OUTER_RADIUS,
|
||||
RING_RIM_THICKNESS,
|
||||
RING_SUPPORT_OVERLAP,
|
||||
STAGE1_ARM_WIDTH,
|
||||
STAGE1_CARRIER_BOTTOM_Z,
|
||||
STAGE1_CARRIER_THICKNESS,
|
||||
STAGE1_HUB_RADIUS,
|
||||
STAGE1_PAD_RADIUS,
|
||||
STAGE1_PIN_BOTTOM_Z,
|
||||
STAGE1_PIN_RADIUS,
|
||||
STAGE2_ARM_WIDTH,
|
||||
STAGE2_CARRIER_BOTTOM_Z,
|
||||
STAGE2_CARRIER_THICKNESS,
|
||||
STAGE2_HUB_RADIUS,
|
||||
STAGE2_PAD_RADIUS,
|
||||
STAGE2_PIN_BOTTOM_Z,
|
||||
STAGE2_PIN_RADIUS,
|
||||
STAGE_1,
|
||||
STAGE_2,
|
||||
StageSpec,
|
||||
)
|
||||
|
||||
|
||||
def make_stage_ring_gear_rpart(
|
||||
*,
|
||||
stage: StageSpec,
|
||||
material: scad.Material,
|
||||
) -> scad.Part:
|
||||
"""Create one herringbone ring insert with a full housing support rim."""
|
||||
|
||||
ring = scad.std.gear.make_herringbone_ring_gear_rsolid(
|
||||
n_teeth=stage.ring_teeth,
|
||||
module=stage.module,
|
||||
pressure_angle=PRESSURE_ANGLE,
|
||||
helix_angle=-HELIX_ANGLE,
|
||||
gear_height=GEAR_HEIGHT,
|
||||
rim_thickness=RING_RIM_THICKNESS,
|
||||
backlash=BACKLASH,
|
||||
addendum_factor=ADDENDUM_FACTOR,
|
||||
clearance_factor=CLEARANCE_FACTOR,
|
||||
)
|
||||
support = scad.make_cylinder_rsolid(
|
||||
radius=RING_INSERT_OUTER_RADIUS,
|
||||
height=GEAR_HEIGHT,
|
||||
bottom_face_center=(0.0, 0.0, 0.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
support_bore = scad.make_cylinder_rsolid(
|
||||
radius=stage.ring_outer_radius - RING_SUPPORT_OVERLAP,
|
||||
height=GEAR_HEIGHT + 2.0,
|
||||
bottom_face_center=(0.0, 0.0, -1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
support = scad.cut_rsolid(support, support_bore, skip_non_intersecting=False)
|
||||
ring = scad.union_rsolid(ring, support, glue=False)
|
||||
ring = apply_tags(
|
||||
shape=ring,
|
||||
tags=(f"role.{stage.stage_id}.fixed_ring_gear", "role.ring_gear_press_fit", "group.two_stage_reducer"),
|
||||
)
|
||||
print(
|
||||
f"{stage.stage_id}_ring: teeth={stage.ring_teeth} pitch_r={stage.ring_pitch_radius:.3f} "
|
||||
f"toothed_outer_r={stage.ring_outer_radius:.3f} insert_d={RING_INSERT_OUTER_RADIUS * 2.0:.2f}"
|
||||
)
|
||||
return make_axis_part_rpart(
|
||||
part_id=f"{stage.stage_id}_fixed_ring",
|
||||
body=ring,
|
||||
name=f"{stage.label} replaceable fixed herringbone ring insert",
|
||||
material=material,
|
||||
connectors=(("axis", (0.0, 0.0, GEAR_HEIGHT / 2.0), "Fixed ring axis"),),
|
||||
)
|
||||
|
||||
|
||||
def make_stage_planet_gear_rpart(
|
||||
*,
|
||||
stage: StageSpec,
|
||||
material: scad.Material,
|
||||
) -> scad.Part:
|
||||
"""Create one reusable herringbone planet with a standard-bearing seat."""
|
||||
|
||||
planet = scad.std.gear.make_herringbone_gear_rsolid(
|
||||
n_teeth=stage.planet_teeth,
|
||||
module=stage.module,
|
||||
pressure_angle=PRESSURE_ANGLE,
|
||||
helix_angle=-HELIX_ANGLE,
|
||||
gear_height=GEAR_HEIGHT,
|
||||
addendum_factor=ADDENDUM_FACTOR,
|
||||
clearance_factor=CLEARANCE_FACTOR,
|
||||
backlash=BACKLASH,
|
||||
)
|
||||
bearing_seat_radius = PLANET_BEARING.outer_diameter / 2.0 + 0.05
|
||||
bearing_seat = scad.make_cylinder_rsolid(
|
||||
radius=bearing_seat_radius,
|
||||
height=GEAR_HEIGHT + 2.0,
|
||||
bottom_face_center=(0.0, 0.0, -1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
planet = scad.cut_rsolid(planet, bearing_seat, skip_non_intersecting=False)
|
||||
planet = apply_tags(
|
||||
shape=planet,
|
||||
tags=(f"role.{stage.stage_id}.planet_gear", "role.planet_bearing_seat", "group.two_stage_reducer"),
|
||||
)
|
||||
root_radius = stage.planet_pitch_radius - stage.module * (ADDENDUM_FACTOR + CLEARANCE_FACTOR)
|
||||
print(
|
||||
f"{stage.stage_id}_planet: teeth={stage.planet_teeth} pitch_r={stage.planet_pitch_radius:.3f} "
|
||||
f"bearing_seat_r={bearing_seat_radius:.3f} root_ligament={root_radius - bearing_seat_radius:.3f}"
|
||||
)
|
||||
return make_axis_part_rpart(
|
||||
part_id=f"{stage.stage_id}_reusable_planet",
|
||||
body=planet,
|
||||
name=f"{stage.label} reusable bearing-supported planet",
|
||||
material=material,
|
||||
connectors=(
|
||||
("axis", (0.0, 0.0, GEAR_HEIGHT / 2.0), "Planet spin axis"),
|
||||
("bearing_axis", (0.0, 0.0, GEAR_HEIGHT / 2.0), "Planet bearing outer-ring axis"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def make_stage1_carrier_sun_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Create the first carrier and integral second-stage sun/shaft."""
|
||||
|
||||
carrier = _make_carrier_body_rsolid(
|
||||
stage=STAGE_1,
|
||||
plate_bottom_z=STAGE1_CARRIER_BOTTOM_Z,
|
||||
plate_thickness=STAGE1_CARRIER_THICKNESS,
|
||||
pin_bottom_z=STAGE1_PIN_BOTTOM_Z,
|
||||
pin_radius=STAGE1_PIN_RADIUS,
|
||||
hub_radius=STAGE1_HUB_RADIUS,
|
||||
arm_width=STAGE1_ARM_WIDTH,
|
||||
pad_radius=STAGE1_PAD_RADIUS,
|
||||
)
|
||||
shaft = scad.make_cylinder_rsolid(
|
||||
radius=INTERSTAGE_SHAFT_RADIUS,
|
||||
height=STAGE_2.top_z - STAGE1_CARRIER_BOTTOM_Z + 0.1,
|
||||
bottom_face_center=(0.0, 0.0, STAGE1_CARRIER_BOTTOM_Z - 0.05),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
stage2_sun = scad.std.gear.make_herringbone_gear_rsolid(
|
||||
n_teeth=STAGE_2.sun_teeth,
|
||||
module=STAGE_2.module,
|
||||
pressure_angle=PRESSURE_ANGLE,
|
||||
helix_angle=HELIX_ANGLE,
|
||||
gear_height=GEAR_HEIGHT,
|
||||
addendum_factor=ADDENDUM_FACTOR,
|
||||
clearance_factor=CLEARANCE_FACTOR,
|
||||
backlash=BACKLASH,
|
||||
)
|
||||
stage2_sun = scad.translate_shape(shape=stage2_sun, vector=(0.0, 0.0, STAGE_2.bottom_z))
|
||||
carrier = scad.union_rsolid(carrier, shaft, stage2_sun, glue=False)
|
||||
carrier = apply_tags(
|
||||
shape=carrier,
|
||||
tags=("role.stage1.planet_carrier", "role.stage2.sun_gear", "role.integral_interstage_drive", "group.two_stage_reducer"),
|
||||
)
|
||||
connectors = [
|
||||
("carrier_axis", (0.0, 0.0, INTERSTAGE_BEARING_CENTER_Z), "Stage 1 carrier bearing axis"),
|
||||
(
|
||||
"interstage_bearing_axis",
|
||||
(0.0, 0.0, INTERSTAGE_BEARING_CENTER_Z),
|
||||
"Interstage bearing inner-ring seat",
|
||||
),
|
||||
("stage2_sun_axis", (0.0, 0.0, STAGE_2.mid_z), "Integral stage 2 sun axis"),
|
||||
]
|
||||
for index in range(PLANET_COUNT):
|
||||
center = planet_center_xy(stage=STAGE_1, index=index)
|
||||
connectors.extend(
|
||||
(
|
||||
(f"planet_{index + 1}_axis", (*center, STAGE_1.mid_z), f"Stage 1 planet {index + 1} axis"),
|
||||
(f"planet_{index + 1}_bearing_axis", (*center, STAGE_1.mid_z), f"Stage 1 planet {index + 1} bearing pin"),
|
||||
)
|
||||
)
|
||||
print(
|
||||
f"stage1_carrier_sun: pins={PLANET_COUNT} shaft_d={INTERSTAGE_SHAFT_RADIUS * 2.0:.2f} "
|
||||
f"stage2_sun_teeth={STAGE_2.sun_teeth}"
|
||||
)
|
||||
return make_axis_part_rpart(
|
||||
part_id="stage1_carrier_integral_stage2_sun",
|
||||
body=carrier,
|
||||
name="Stage 1 carrier with integral stage-2 sun shaft",
|
||||
material=material,
|
||||
connectors=connectors,
|
||||
)
|
||||
|
||||
|
||||
def make_output_carrier_flange_rpart(
|
||||
*,
|
||||
stage: StageSpec,
|
||||
material: scad.Material,
|
||||
) -> scad.Part:
|
||||
"""Create the second carrier, 16 mm bearing land, and output flange."""
|
||||
|
||||
carrier = _make_carrier_body_rsolid(
|
||||
stage=stage,
|
||||
plate_bottom_z=STAGE2_CARRIER_BOTTOM_Z,
|
||||
plate_thickness=STAGE2_CARRIER_THICKNESS,
|
||||
pin_bottom_z=STAGE2_PIN_BOTTOM_Z,
|
||||
pin_radius=STAGE2_PIN_RADIUS,
|
||||
hub_radius=STAGE2_HUB_RADIUS,
|
||||
arm_width=STAGE2_ARM_WIDTH,
|
||||
pad_radius=STAGE2_PAD_RADIUS,
|
||||
)
|
||||
shaft = scad.make_cylinder_rsolid(
|
||||
radius=OUTPUT_SHAFT_RADIUS,
|
||||
height=(
|
||||
OUTPUT_FLANGE_TOP_Z
|
||||
+ OUTPUT_REGISTER_HEIGHT
|
||||
- STAGE2_CARRIER_BOTTOM_Z
|
||||
+ 0.05
|
||||
),
|
||||
bottom_face_center=(0.0, 0.0, STAGE2_CARRIER_BOTTOM_Z - 0.05),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
flange = scad.make_cylinder_rsolid(
|
||||
radius=OUTPUT_FLANGE_RADIUS,
|
||||
height=OUTPUT_FLANGE_TOP_Z - OUTPUT_FLANGE_BOTTOM_Z,
|
||||
bottom_face_center=(0.0, 0.0, OUTPUT_FLANGE_BOTTOM_Z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
output = scad.union_rsolid(carrier, shaft, flange, glue=False)
|
||||
output = scad.cut_rsolid(
|
||||
output,
|
||||
make_axial_hole_cutters_rsolids(
|
||||
count=OUTPUT_LINK_BOLT_COUNT,
|
||||
pcd=OUTPUT_LINK_HOLE_PCD,
|
||||
hole_radius=OUTPUT_LINK_TAP_RADIUS,
|
||||
bottom_z=OUTPUT_FLANGE_TOP_Z - OUTPUT_LINK_THREAD_DEPTH,
|
||||
height=OUTPUT_LINK_THREAD_DEPTH + 1.0,
|
||||
angle_offset=OUTPUT_LINK_BOLT_ANGLES_DEGREES[0],
|
||||
),
|
||||
skip_non_intersecting=False,
|
||||
)
|
||||
output = apply_tags(
|
||||
shape=output,
|
||||
tags=("role.stage2.output_carrier", "role.output_bearing_land", "role.output_link_flange", "group.two_stage_reducer"),
|
||||
)
|
||||
connectors = [
|
||||
("carrier_axis", (0.0, 0.0, STAGE2_CARRIER_BOTTOM_Z + STAGE2_CARRIER_THICKNESS / 2.0), "Stage 2 output carrier axis"),
|
||||
("bearing_1_axis", (0.0, 0.0, OUTPUT_BEARING_1_CENTER_Z), "Rear output bearing inner-ring seat"),
|
||||
("bearing_2_axis", (0.0, 0.0, OUTPUT_BEARING_2_CENTER_Z), "Front output bearing inner-ring seat"),
|
||||
("output_link_axis", (0.0, 0.0, OUTPUT_FLANGE_TOP_Z), "Six-hole driven-link flange"),
|
||||
]
|
||||
for index in range(PLANET_COUNT):
|
||||
center = planet_center_xy(stage=stage, index=index)
|
||||
connectors.extend(
|
||||
(
|
||||
(f"planet_{index + 1}_axis", (*center, stage.mid_z), f"Stage 2 planet {index + 1} axis"),
|
||||
(f"planet_{index + 1}_bearing_axis", (*center, stage.mid_z), f"Stage 2 planet {index + 1} bearing pin"),
|
||||
)
|
||||
)
|
||||
radial_ligament = OUTPUT_FLANGE_RADIUS - (OUTPUT_LINK_HOLE_PCD / 2.0 + OUTPUT_LINK_TAP_RADIUS)
|
||||
print(
|
||||
f"output_carrier_flange: shaft_d={OUTPUT_SHAFT_RADIUS * 2.0:.2f} "
|
||||
f"pilot_h={OUTPUT_REGISTER_HEIGHT:.1f} tapped_holes={OUTPUT_LINK_BOLT_COUNT} "
|
||||
f"pcd={OUTPUT_LINK_HOLE_PCD:.1f} thread_depth={OUTPUT_LINK_THREAD_DEPTH:.1f} "
|
||||
f"radial_ligament={radial_ligament:.2f}"
|
||||
)
|
||||
return make_axis_part_rpart(
|
||||
part_id="stage2_output_carrier_flange",
|
||||
body=output,
|
||||
name="Stage 2 carrier with paired-bearing shaft and output flange",
|
||||
material=material,
|
||||
connectors=connectors,
|
||||
)
|
||||
|
||||
|
||||
def make_planet_rplacement(*, stage: StageSpec, index: int) -> scad.Placement:
|
||||
"""Place and visually phase one planet at its pitch center."""
|
||||
|
||||
center = planet_center_xy(stage=stage, index=index)
|
||||
carrier_angle = 360.0 * index / PLANET_COUNT
|
||||
spin = carrier_angle + 180.0 - 180.0 / stage.planet_teeth
|
||||
print(
|
||||
f"{stage.stage_id}_planet_{index + 1}_placement: center="
|
||||
f"({center[0]:.3f},{center[1]:.3f},{stage.bottom_z:.3f}) spin={spin:.2f}"
|
||||
)
|
||||
return make_z_rotation_rplacement(
|
||||
origin=(center[0], center[1], stage.bottom_z),
|
||||
angle_degrees=spin,
|
||||
)
|
||||
|
||||
|
||||
def planet_center_xy(*, stage: StageSpec, index: int) -> tuple[float, float]:
|
||||
"""Return one equally spaced planet pitch center."""
|
||||
|
||||
angle = math.radians(360.0 * index / PLANET_COUNT)
|
||||
return (
|
||||
stage.planet_center_radius * math.cos(angle),
|
||||
stage.planet_center_radius * math.sin(angle),
|
||||
)
|
||||
|
||||
|
||||
def _make_carrier_body_rsolid(
|
||||
*,
|
||||
stage: StageSpec,
|
||||
plate_bottom_z: float,
|
||||
plate_thickness: float,
|
||||
pin_bottom_z: float,
|
||||
pin_radius: float,
|
||||
hub_radius: float,
|
||||
arm_width: float,
|
||||
pad_radius: float,
|
||||
) -> scad.Solid:
|
||||
hub = scad.make_cylinder_rsolid(
|
||||
radius=hub_radius,
|
||||
height=plate_thickness,
|
||||
bottom_face_center=(0.0, 0.0, plate_bottom_z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
solids = [hub]
|
||||
pin_height = plate_bottom_z + plate_thickness - pin_bottom_z
|
||||
arm_inner_radius = hub_radius - 1.25
|
||||
arm_outer_radius = stage.planet_center_radius + pad_radius - 0.25
|
||||
arm_length = arm_outer_radius - arm_inner_radius
|
||||
arm_center_radius = (arm_inner_radius + arm_outer_radius) / 2.0
|
||||
for index in range(PLANET_COUNT):
|
||||
angle = 360.0 * index / PLANET_COUNT
|
||||
center = planet_center_xy(stage=stage, index=index)
|
||||
arm = scad.make_box_rsolid(
|
||||
width=arm_length,
|
||||
height=arm_width,
|
||||
depth=plate_thickness,
|
||||
bottom_face_center=(arm_center_radius, 0.0, plate_bottom_z),
|
||||
)
|
||||
solids.append(
|
||||
scad.rotate_shape(
|
||||
shape=arm,
|
||||
angle=angle,
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
origin=(0.0, 0.0, 0.0),
|
||||
)
|
||||
)
|
||||
solids.append(
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=pad_radius,
|
||||
height=plate_thickness,
|
||||
bottom_face_center=(center[0], center[1], plate_bottom_z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
)
|
||||
solids.append(
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=pin_radius,
|
||||
height=pin_height,
|
||||
bottom_face_center=(center[0], center[1], pin_bottom_z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
)
|
||||
carrier = scad.union_rsolid(solids, glue=False)
|
||||
print(
|
||||
f"{stage.stage_id}_carrier_body: arm_length={arm_length:.3f} "
|
||||
f"hub_embed={hub_radius - arm_inner_radius:.3f} pin_height={pin_height:.3f}"
|
||||
)
|
||||
return carrier
|
||||
@@ -0,0 +1,474 @@
|
||||
"""Serviceable motor shell, reducer case, bearing caps, and electronics cover."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import simplecadapi as scad
|
||||
|
||||
try:
|
||||
from .common import (
|
||||
apply_tags,
|
||||
make_annulus_rsolid,
|
||||
make_axis_part_rpart,
|
||||
make_axial_hole_cutters_rsolids,
|
||||
radial_centers,
|
||||
)
|
||||
from .dimensions import (
|
||||
FRONT_MOTOR_BEARING,
|
||||
FRONT_MOTOR_BEARING_CENTER_Z,
|
||||
HOUSING_INTERFACE_LAND_INNER_RADIUS,
|
||||
INTERSTAGE_BEARING,
|
||||
INTERSTAGE_BEARING_CENTER_Z,
|
||||
MOTOR_INTERFACE_PCD,
|
||||
MOTOR_SHELL_BOTTOM_Z,
|
||||
MOTOR_SHELL_INNER_RADIUS,
|
||||
MOTOR_SHELL_TOP_Z,
|
||||
MOTOR_STATOR_BOTTOM_Z,
|
||||
MOTOR_STATOR_TOP_Z,
|
||||
M3_CLEARANCE_RADIUS,
|
||||
OUTPUT_BEARING_1_CENTER_Z,
|
||||
OUTPUT_BEARING_2_CENTER_Z,
|
||||
OUTPUT_CAP_BOTTOM_Z,
|
||||
OUTPUT_CAP_CARTRIDGE_TOP_Z,
|
||||
OUTPUT_CAP_INTERFACE_PCD,
|
||||
OUTPUT_CAP_TOP_Z,
|
||||
OUTPUT_CASE_CLAMP_CENTER_Z,
|
||||
OUTPUT_FLANGE_RADIUS,
|
||||
PACKAGE_RADIUS,
|
||||
PCB_BOTTOM_Z,
|
||||
PCB_STANDOFF_PCD,
|
||||
REAR_BEARING_CENTER_Z,
|
||||
REAR_COLUMN_PCD,
|
||||
REAR_COLUMN_RADIUS,
|
||||
REAR_COVER_BOTTOM_Z,
|
||||
REAR_COVER_THICKNESS,
|
||||
REAR_FASTENER_HOLE_RADIUS,
|
||||
REAR_SPIDER_BOSS_RADIUS,
|
||||
REAR_SPIDER_BOTTOM_Z,
|
||||
REDUCER_HOUSING_BOTTOM_Z,
|
||||
REDUCER_HOUSING_FRONT_Z,
|
||||
REDUCER_HOUSING_INNER_RADIUS,
|
||||
STAGE1_CARRIER_BOTTOM_Z,
|
||||
STAGE2_CARRIER_BOTTOM_Z,
|
||||
STAGE_1,
|
||||
STAGE_2,
|
||||
)
|
||||
except ImportError: # Support direct execution from this example directory.
|
||||
from common import (
|
||||
apply_tags,
|
||||
make_annulus_rsolid,
|
||||
make_axial_hole_cutters_rsolids,
|
||||
make_axis_part_rpart,
|
||||
radial_centers,
|
||||
)
|
||||
from dimensions import (
|
||||
FRONT_MOTOR_BEARING,
|
||||
FRONT_MOTOR_BEARING_CENTER_Z,
|
||||
HOUSING_INTERFACE_LAND_INNER_RADIUS,
|
||||
INTERSTAGE_BEARING,
|
||||
INTERSTAGE_BEARING_CENTER_Z,
|
||||
M3_CLEARANCE_RADIUS,
|
||||
MOTOR_INTERFACE_PCD,
|
||||
MOTOR_SHELL_BOTTOM_Z,
|
||||
MOTOR_SHELL_INNER_RADIUS,
|
||||
MOTOR_SHELL_TOP_Z,
|
||||
MOTOR_STATOR_BOTTOM_Z,
|
||||
MOTOR_STATOR_TOP_Z,
|
||||
OUTPUT_BEARING_1_CENTER_Z,
|
||||
OUTPUT_BEARING_2_CENTER_Z,
|
||||
OUTPUT_CAP_BOTTOM_Z,
|
||||
OUTPUT_CAP_CARTRIDGE_TOP_Z,
|
||||
OUTPUT_CAP_INTERFACE_PCD,
|
||||
OUTPUT_CAP_TOP_Z,
|
||||
OUTPUT_CASE_CLAMP_CENTER_Z,
|
||||
OUTPUT_FLANGE_RADIUS,
|
||||
PACKAGE_RADIUS,
|
||||
PCB_BOTTOM_Z,
|
||||
PCB_STANDOFF_PCD,
|
||||
REAR_BEARING_CENTER_Z,
|
||||
REAR_COLUMN_PCD,
|
||||
REAR_COLUMN_RADIUS,
|
||||
REAR_COVER_BOTTOM_Z,
|
||||
REAR_COVER_THICKNESS,
|
||||
REAR_FASTENER_HOLE_RADIUS,
|
||||
REAR_SPIDER_BOSS_RADIUS,
|
||||
REAR_SPIDER_BOTTOM_Z,
|
||||
REDUCER_HOUSING_BOTTOM_Z,
|
||||
REDUCER_HOUSING_FRONT_Z,
|
||||
REDUCER_HOUSING_INNER_RADIUS,
|
||||
STAGE1_CARRIER_BOTTOM_Z,
|
||||
STAGE2_CARRIER_BOTTOM_Z,
|
||||
STAGE_1,
|
||||
STAGE_2,
|
||||
)
|
||||
|
||||
|
||||
def make_motor_shell_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Create the stator sleeve, front attachment land, and rear columns."""
|
||||
|
||||
sleeve = make_annulus_rsolid(
|
||||
outer_radius=PACKAGE_RADIUS,
|
||||
inner_radius=MOTOR_SHELL_INNER_RADIUS,
|
||||
bottom_z=MOTOR_SHELL_BOTTOM_Z,
|
||||
height=MOTOR_SHELL_TOP_Z - MOTOR_SHELL_BOTTOM_Z,
|
||||
tags=("role.motor_shell", "role.stator_thermal_path"),
|
||||
)
|
||||
front_land = make_annulus_rsolid(
|
||||
outer_radius=PACKAGE_RADIUS,
|
||||
inner_radius=HOUSING_INTERFACE_LAND_INNER_RADIUS,
|
||||
bottom_z=MOTOR_SHELL_TOP_Z - 1.4,
|
||||
height=1.4,
|
||||
tags=("role.motor_reducer_mount",),
|
||||
)
|
||||
columns = []
|
||||
for _index, _angle, center in radial_centers(count=4, radius=REAR_COLUMN_PCD / 2.0):
|
||||
columns.append(
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=REAR_COLUMN_RADIUS,
|
||||
height=REAR_SPIDER_BOTTOM_Z - MOTOR_SHELL_BOTTOM_Z,
|
||||
bottom_face_center=(center[0], center[1], MOTOR_SHELL_BOTTOM_Z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
)
|
||||
shell = scad.union_rsolid(sleeve, front_land, columns, glue=False)
|
||||
shell = scad.cut_rsolid(
|
||||
shell,
|
||||
make_axial_hole_cutters_rsolids(
|
||||
count=6,
|
||||
pcd=MOTOR_INTERFACE_PCD,
|
||||
hole_radius=M3_CLEARANCE_RADIUS,
|
||||
bottom_z=MOTOR_SHELL_TOP_Z - 2.8,
|
||||
height=3.6,
|
||||
angle_offset=30.0,
|
||||
),
|
||||
make_axial_hole_cutters_rsolids(
|
||||
count=4,
|
||||
pcd=REAR_COLUMN_PCD,
|
||||
hole_radius=REAR_FASTENER_HOLE_RADIUS,
|
||||
bottom_z=MOTOR_SHELL_BOTTOM_Z - 1.0,
|
||||
height=10.4,
|
||||
),
|
||||
skip_non_intersecting=False,
|
||||
)
|
||||
shell = apply_tags(
|
||||
shape=shell,
|
||||
tags=("role.fixed_motor_housing", "group.integrated_bldc_actuator"),
|
||||
)
|
||||
print(
|
||||
f"motor_shell_interface: stator_d={MOTOR_SHELL_INNER_RADIUS * 2.0:.2f} "
|
||||
f"front_holes=6 rear_columns=4 wall={PACKAGE_RADIUS - MOTOR_SHELL_INNER_RADIUS:.2f}"
|
||||
)
|
||||
return make_axis_part_rpart(
|
||||
part_id="motor_shell",
|
||||
body=shell,
|
||||
name="50 mm BLDC motor shell with rear structural columns",
|
||||
material=material,
|
||||
connectors=(
|
||||
("reducer_mount_axis", (0.0, 0.0, MOTOR_SHELL_TOP_Z), "Six-screw reducer mount"),
|
||||
(
|
||||
"stator_axis",
|
||||
(0.0, 0.0, (MOTOR_STATOR_BOTTOM_Z + MOTOR_STATOR_TOP_Z) / 2.0),
|
||||
"Stator thermal press-fit axis",
|
||||
),
|
||||
("rear_spider_axis", (0.0, 0.0, REAR_SPIDER_BOTTOM_Z), "Rear bearing spider mount"),
|
||||
("rear_cover_axis", (0.0, 0.0, MOTOR_SHELL_BOTTOM_Z), "Rear electronics cover mount"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def make_reducer_housing_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Create the reducer sleeve and front motor-bearing bulkhead."""
|
||||
|
||||
sleeve = make_annulus_rsolid(
|
||||
outer_radius=PACKAGE_RADIUS,
|
||||
inner_radius=REDUCER_HOUSING_INNER_RADIUS,
|
||||
bottom_z=REDUCER_HOUSING_BOTTOM_Z,
|
||||
height=REDUCER_HOUSING_FRONT_Z - REDUCER_HOUSING_BOTTOM_Z,
|
||||
tags=("role.reducer_housing_sleeve",),
|
||||
)
|
||||
bulkhead = make_annulus_rsolid(
|
||||
outer_radius=23.10,
|
||||
inner_radius=FRONT_MOTOR_BEARING.outer_diameter / 2.0 + 0.05,
|
||||
bottom_z=REDUCER_HOUSING_BOTTOM_Z,
|
||||
height=STAGE_1.bottom_z - REDUCER_HOUSING_BOTTOM_Z,
|
||||
tags=("role.motor_front_bearing_bulkhead",),
|
||||
)
|
||||
interstage_divider = make_annulus_rsolid(
|
||||
outer_radius=23.10,
|
||||
inner_radius=INTERSTAGE_BEARING.outer_diameter / 2.0 + 0.05,
|
||||
bottom_z=INTERSTAGE_BEARING_CENTER_Z - INTERSTAGE_BEARING.width / 2.0,
|
||||
height=INTERSTAGE_BEARING.width,
|
||||
tags=("role.interstage_bearing_divider",),
|
||||
)
|
||||
output_mount_land = make_annulus_rsolid(
|
||||
outer_radius=PACKAGE_RADIUS,
|
||||
inner_radius=HOUSING_INTERFACE_LAND_INNER_RADIUS,
|
||||
bottom_z=REDUCER_HOUSING_FRONT_Z - 2.2,
|
||||
height=2.2,
|
||||
tags=("role.output_cap_mount_land",),
|
||||
)
|
||||
housing = scad.union_rsolid(
|
||||
sleeve,
|
||||
bulkhead,
|
||||
interstage_divider,
|
||||
output_mount_land,
|
||||
glue=False,
|
||||
)
|
||||
housing = scad.cut_rsolid(
|
||||
housing,
|
||||
make_axial_hole_cutters_rsolids(
|
||||
count=6,
|
||||
pcd=MOTOR_INTERFACE_PCD,
|
||||
hole_radius=M3_CLEARANCE_RADIUS,
|
||||
bottom_z=REDUCER_HOUSING_BOTTOM_Z - 1.0,
|
||||
height=STAGE_1.bottom_z - REDUCER_HOUSING_BOTTOM_Z + 2.0,
|
||||
angle_offset=30.0,
|
||||
),
|
||||
make_axial_hole_cutters_rsolids(
|
||||
count=6,
|
||||
pcd=OUTPUT_CAP_INTERFACE_PCD,
|
||||
hole_radius=M3_CLEARANCE_RADIUS,
|
||||
bottom_z=REDUCER_HOUSING_FRONT_Z - 2.2,
|
||||
height=3.2,
|
||||
),
|
||||
skip_non_intersecting=False,
|
||||
)
|
||||
housing = apply_tags(
|
||||
shape=housing,
|
||||
tags=("role.fixed_reducer_housing", "role.ring_gear_press_fit", "group.integrated_bldc_actuator"),
|
||||
)
|
||||
print(
|
||||
f"reducer_housing: bore_d={REDUCER_HOUSING_INNER_RADIUS * 2.0:.2f} "
|
||||
f"wall={PACKAGE_RADIUS - REDUCER_HOUSING_INNER_RADIUS:.2f} bulkhead=8.00 "
|
||||
f"interstage_bearing_z={INTERSTAGE_BEARING_CENTER_Z:.2f} "
|
||||
f"m3_inner_ligament={MOTOR_INTERFACE_PCD / 2.0 - M3_CLEARANCE_RADIUS - HOUSING_INTERFACE_LAND_INNER_RADIUS:.2f}"
|
||||
)
|
||||
return make_axis_part_rpart(
|
||||
part_id="reducer_housing",
|
||||
body=housing,
|
||||
name="50 mm reducer housing with motor bearing bulkhead",
|
||||
material=material,
|
||||
connectors=(
|
||||
("motor_mount_axis", (0.0, 0.0, MOTOR_SHELL_TOP_Z), "Motor shell six-screw interface"),
|
||||
("front_motor_bearing_axis", (0.0, 0.0, FRONT_MOTOR_BEARING_CENTER_Z), "Front motor bearing seat"),
|
||||
("stage1_ring_axis", (0.0, 0.0, STAGE_1.mid_z), "Stage 1 fixed ring seat"),
|
||||
("stage1_carrier_axis", (0.0, 0.0, INTERSTAGE_BEARING_CENTER_Z), "Stage 1 carrier axis"),
|
||||
("interstage_bearing_axis", (0.0, 0.0, INTERSTAGE_BEARING_CENTER_Z), "Interstage bearing outer seat"),
|
||||
("stage2_ring_axis", (0.0, 0.0, STAGE_2.mid_z), "Stage 2 fixed ring seat"),
|
||||
("stage2_carrier_axis", (0.0, 0.0, STAGE2_CARRIER_BOTTOM_Z + 1.50), "Output carrier axis"),
|
||||
(
|
||||
"case_clamp_axis",
|
||||
(0.0, 0.0, OUTPUT_CASE_CLAMP_CENTER_Z),
|
||||
"External split-clamp datum on reducer sleeve",
|
||||
),
|
||||
("output_cap_axis", (0.0, 0.0, REDUCER_HOUSING_FRONT_Z), "Output bearing cap interface"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def make_rear_bearing_spider_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Create a four-arm removable rear motor-bearing support."""
|
||||
|
||||
bottom_z = REAR_SPIDER_BOTTOM_Z
|
||||
hub = make_annulus_rsolid(
|
||||
outer_radius=10.5,
|
||||
inner_radius=8.05,
|
||||
bottom_z=bottom_z,
|
||||
height=5.0,
|
||||
tags=("role.rear_motor_bearing_seat",),
|
||||
)
|
||||
solids = [hub]
|
||||
for _index, angle, center in radial_centers(count=4, radius=REAR_COLUMN_PCD / 2.0):
|
||||
arm = scad.make_box_rsolid(
|
||||
width=12.0,
|
||||
height=3.0,
|
||||
depth=5.0,
|
||||
bottom_face_center=(14.5, 0.0, bottom_z),
|
||||
)
|
||||
solids.append(
|
||||
scad.rotate_shape(
|
||||
shape=arm,
|
||||
angle=angle,
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
origin=(0.0, 0.0, 0.0),
|
||||
)
|
||||
)
|
||||
solids.append(
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=REAR_SPIDER_BOSS_RADIUS,
|
||||
height=5.0,
|
||||
bottom_face_center=(center[0], center[1], bottom_z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
)
|
||||
spider = scad.union_rsolid(solids, glue=False)
|
||||
spider = scad.cut_rsolid(
|
||||
spider,
|
||||
make_axial_hole_cutters_rsolids(
|
||||
count=4,
|
||||
pcd=REAR_COLUMN_PCD,
|
||||
hole_radius=REAR_FASTENER_HOLE_RADIUS,
|
||||
bottom_z=bottom_z - 1.0,
|
||||
height=7.0,
|
||||
),
|
||||
skip_non_intersecting=False,
|
||||
)
|
||||
spider = apply_tags(
|
||||
shape=spider,
|
||||
tags=("role.removable_rear_bearing_spider", "group.integrated_bldc_actuator"),
|
||||
)
|
||||
print("rear_bearing_spider: arms=4 bearing_seat_d=16.10 fasteners=4")
|
||||
return make_axis_part_rpart(
|
||||
part_id="rear_bearing_spider",
|
||||
body=spider,
|
||||
name="Four-arm removable rear motor-bearing spider",
|
||||
material=material,
|
||||
connectors=(
|
||||
("shell_axis", (0.0, 0.0, REAR_SPIDER_BOTTOM_Z), "Motor shell column interface"),
|
||||
("bearing_axis", (0.0, 0.0, REAR_BEARING_CENTER_Z), "Rear motor bearing outer seat"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def make_rear_electronics_cover_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Create the rear cover with PCB standoffs and terminal apertures."""
|
||||
|
||||
cover = scad.make_cylinder_rsolid(
|
||||
radius=PACKAGE_RADIUS,
|
||||
height=REAR_COVER_THICKNESS,
|
||||
bottom_face_center=(0.0, 0.0, REAR_COVER_BOTTOM_Z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
standoffs = []
|
||||
for _index, _angle, center in radial_centers(count=4, radius=PCB_STANDOFF_PCD / 2.0, angle_offset=45.0):
|
||||
standoffs.append(
|
||||
scad.make_cylinder_rsolid(
|
||||
radius=2.4,
|
||||
height=PCB_BOTTOM_Z - REAR_COVER_BOTTOM_Z - REAR_COVER_THICKNESS + 0.1,
|
||||
bottom_face_center=(center[0], center[1], REAR_COVER_BOTTOM_Z + REAR_COVER_THICKNESS - 0.1),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
)
|
||||
cover = scad.union_rsolid(cover, standoffs, glue=False)
|
||||
phase_aperture = scad.make_box_rsolid(
|
||||
width=9.2,
|
||||
height=7.2,
|
||||
depth=REAR_COVER_THICKNESS + 2.0,
|
||||
bottom_face_center=(-11.0, 0.0, REAR_COVER_BOTTOM_Z - 1.0),
|
||||
)
|
||||
power_aperture = scad.make_box_rsolid(
|
||||
width=9.2,
|
||||
height=7.2,
|
||||
depth=REAR_COVER_THICKNESS + 2.0,
|
||||
bottom_face_center=(11.0, 0.0, REAR_COVER_BOTTOM_Z - 1.0),
|
||||
)
|
||||
center_service = scad.make_cylinder_rsolid(
|
||||
radius=3.2,
|
||||
height=REAR_COVER_THICKNESS + 2.0,
|
||||
bottom_face_center=(0.0, 0.0, REAR_COVER_BOTTOM_Z - 1.0),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
cover = scad.cut_rsolid(
|
||||
cover,
|
||||
phase_aperture,
|
||||
power_aperture,
|
||||
center_service,
|
||||
make_axial_hole_cutters_rsolids(
|
||||
count=4,
|
||||
pcd=REAR_COLUMN_PCD,
|
||||
hole_radius=REAR_FASTENER_HOLE_RADIUS,
|
||||
bottom_z=REAR_COVER_BOTTOM_Z - 1.0,
|
||||
height=REAR_COVER_THICKNESS + 2.0,
|
||||
),
|
||||
make_axial_hole_cutters_rsolids(
|
||||
count=4,
|
||||
pcd=PCB_STANDOFF_PCD,
|
||||
hole_radius=1.1,
|
||||
bottom_z=REAR_COVER_BOTTOM_Z - 1.0,
|
||||
height=PCB_BOTTOM_Z - REAR_COVER_BOTTOM_Z + 2.0,
|
||||
angle_offset=45.0,
|
||||
),
|
||||
skip_non_intersecting=False,
|
||||
)
|
||||
cover = apply_tags(
|
||||
shape=cover,
|
||||
tags=("role.rear_electronics_cover", "role.terminal_access", "group.integrated_bldc_actuator"),
|
||||
)
|
||||
print("rear_cover_access: phase_opening=9.2x7.2 power_can_opening=9.2x7.2 pcb_holes=4")
|
||||
return make_axis_part_rpart(
|
||||
part_id="rear_electronics_cover",
|
||||
body=cover,
|
||||
name="Rear electronics cover with terminal access",
|
||||
material=material,
|
||||
connectors=(
|
||||
("shell_axis", (0.0, 0.0, MOTOR_SHELL_BOTTOM_Z), "Four-screw motor shell interface"),
|
||||
("pcb_axis", (0.0, 0.0, PCB_BOTTOM_Z + 0.8), "Controller PCB mounting plane"),
|
||||
("phase_access", (-11.0, 0.0, REAR_COVER_BOTTOM_Z), "Three-phase terminal access"),
|
||||
("power_can_access", (11.0, 0.0, REAR_COVER_BOTTOM_Z), "Power and CAN terminal access"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def make_output_bearing_cap_rpart(*, material: scad.Material) -> scad.Part:
|
||||
"""Create the removable paired-bearing cartridge and front cap."""
|
||||
|
||||
bearing_clearance_radius = 12.05
|
||||
rear_flange = make_annulus_rsolid(
|
||||
outer_radius=PACKAGE_RADIUS,
|
||||
inner_radius=bearing_clearance_radius,
|
||||
bottom_z=OUTPUT_CAP_BOTTOM_Z,
|
||||
height=3.0,
|
||||
tags=("role.output_cap_mount_flange",),
|
||||
)
|
||||
cartridge = make_annulus_rsolid(
|
||||
outer_radius=15.0,
|
||||
inner_radius=bearing_clearance_radius,
|
||||
bottom_z=OUTPUT_CAP_BOTTOM_Z,
|
||||
height=OUTPUT_CAP_CARTRIDGE_TOP_Z - OUTPUT_CAP_BOTTOM_Z + 0.1,
|
||||
tags=("role.paired_output_bearing_seat",),
|
||||
)
|
||||
bearing_retainer = make_annulus_rsolid(
|
||||
outer_radius=PACKAGE_RADIUS,
|
||||
inner_radius=8.10,
|
||||
bottom_z=OUTPUT_CAP_CARTRIDGE_TOP_Z - 0.1,
|
||||
height=0.5,
|
||||
tags=("role.output_axial_retainer",),
|
||||
)
|
||||
outer_lip = make_annulus_rsolid(
|
||||
outer_radius=PACKAGE_RADIUS,
|
||||
inner_radius=OUTPUT_FLANGE_RADIUS + 0.30,
|
||||
bottom_z=OUTPUT_CAP_CARTRIDGE_TOP_Z - 0.1,
|
||||
height=OUTPUT_CAP_TOP_Z - OUTPUT_CAP_CARTRIDGE_TOP_Z + 0.1,
|
||||
tags=("role.output_labyrinth_lip",),
|
||||
)
|
||||
cap = scad.union_rsolid(rear_flange, cartridge, bearing_retainer, outer_lip, glue=False)
|
||||
cap = scad.cut_rsolid(
|
||||
cap,
|
||||
make_axial_hole_cutters_rsolids(
|
||||
count=6,
|
||||
pcd=OUTPUT_CAP_INTERFACE_PCD,
|
||||
hole_radius=M3_CLEARANCE_RADIUS,
|
||||
bottom_z=OUTPUT_CAP_BOTTOM_Z - 1.0,
|
||||
height=OUTPUT_CAP_TOP_Z - OUTPUT_CAP_BOTTOM_Z + 2.0,
|
||||
),
|
||||
skip_non_intersecting=False,
|
||||
)
|
||||
cap = apply_tags(
|
||||
shape=cap,
|
||||
tags=("role.removable_output_bearing_cap", "group.integrated_bldc_actuator"),
|
||||
)
|
||||
print(
|
||||
f"output_bearing_cap: bearing_seat_d={bearing_clearance_radius * 2.0:.2f} "
|
||||
f"bearing_span={OUTPUT_BEARING_2_CENTER_Z - OUTPUT_BEARING_1_CENTER_Z:.1f} fasteners=6"
|
||||
)
|
||||
return make_axis_part_rpart(
|
||||
part_id="output_bearing_cap",
|
||||
body=cap,
|
||||
name="Paired output-bearing cartridge and removable cap",
|
||||
material=material,
|
||||
connectors=(
|
||||
("housing_axis", (0.0, 0.0, OUTPUT_CAP_BOTTOM_Z), "Six-screw housing interface"),
|
||||
("bearing_1_axis", (0.0, 0.0, OUTPUT_BEARING_1_CENTER_Z), "Rear output bearing seat"),
|
||||
("bearing_2_axis", (0.0, 0.0, OUTPUT_BEARING_2_CENTER_Z), "Front output bearing seat"),
|
||||
("case_mount_axis", (0.0, 0.0, OUTPUT_CAP_TOP_Z), "Fixed actuator case datum"),
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,98 @@
|
||||
"""Build, validate, replay, and export the integrated BLDC joint actuator."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import simplecadapi as scad
|
||||
|
||||
from assembly import make_integrated_bldc_joint_actuator_rassembly
|
||||
from common import ground_compound
|
||||
from dimensions import (
|
||||
MOTOR_AIR_GAP,
|
||||
MOTOR_POLE_COUNT,
|
||||
MOTOR_SLOT_COUNT,
|
||||
PACKAGE_RADIUS,
|
||||
PACKAGE_STRUCTURAL_BOTTOM_Z,
|
||||
PACKAGE_TOP_Z,
|
||||
TOTAL_REDUCTION,
|
||||
validate_design_dimensions,
|
||||
)
|
||||
from materials import make_actuator_materials_rdict
|
||||
|
||||
|
||||
sys.setrecursionlimit(30000)
|
||||
|
||||
OUT_DIR = Path("examples/out/integrated_bldc_joint_actuator")
|
||||
|
||||
|
||||
def build_integrated_bldc_joint_actuator():
|
||||
"""Build the replayable actuator and return product and interchange outputs."""
|
||||
|
||||
validate_design_dimensions()
|
||||
materials = make_actuator_materials_rdict()
|
||||
with scad.GraphSession(graph_id="integrated_50mm_bldc_joint_actuator") as session:
|
||||
assembly = make_integrated_bldc_joint_actuator_rassembly(materials=materials)
|
||||
preview = scad.make_compound_from_assembly_rcompound(assembly=assembly)
|
||||
ground_compound(label="integrated_actuator_preview", compound=preview)
|
||||
session_json = scad.export_session_json(session=session, indent=2)
|
||||
model_json = scad.export_model_json(session=session, indent=2)
|
||||
return assembly, preview, model_json, session_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Generate canonical model JSON, STEP, and optional FreeCAD output."""
|
||||
|
||||
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
model_path = OUT_DIR / "integrated_bldc_joint_actuator.model.json"
|
||||
session_path = OUT_DIR / "integrated_bldc_joint_actuator.session.json"
|
||||
step_path = OUT_DIR / "integrated_bldc_joint_actuator.step"
|
||||
fcstd_path = OUT_DIR / "integrated_bldc_joint_actuator.FCStd"
|
||||
if fcstd_path.exists():
|
||||
fcstd_path.unlink()
|
||||
|
||||
assembly, preview, model_json, session_json = build_integrated_bldc_joint_actuator()
|
||||
model_path.write_text(model_json, encoding="utf-8")
|
||||
session_path.write_text(session_json, encoding="utf-8")
|
||||
scad.export_step(shapes=preview, filename=str(step_path))
|
||||
|
||||
imported = scad.import_model_json(json_str=model_json)
|
||||
replayed = scad.replay_model_json(json_str=model_json, strict=True)
|
||||
payload = json.loads(model_json)
|
||||
|
||||
fcstd_status = "not attempted"
|
||||
try:
|
||||
scad.translator.freecad_translator.translate_model_json_to_fcstd(
|
||||
json_str=model_json,
|
||||
output_path=str(fcstd_path.resolve()),
|
||||
document_name="Integrated50mmBLDCJointActuator",
|
||||
freecad_cmd=None,
|
||||
)
|
||||
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"skipped ({exc.__class__.__name__}: {exc})"
|
||||
|
||||
print(f"envelope_diameter={PACKAGE_RADIUS * 2.0:.1f}")
|
||||
print(f"structural_length={PACKAGE_TOP_Z - PACKAGE_STRUCTURAL_BOTTOM_Z:.1f}")
|
||||
print(f"motor_topology={MOTOR_SLOT_COUNT}_slot_{MOTOR_POLE_COUNT}_pole")
|
||||
print(f"motor_air_gap={MOTOR_AIR_GAP:.2f}")
|
||||
print(f"total_reduction={TOTAL_REDUCTION:.1f}")
|
||||
print(f"assembly={assembly.assembly_id}")
|
||||
print(f"components={len(assembly.component_ids())}")
|
||||
print(f"constraints={len(assembly.constraint_ids())}")
|
||||
print(f"preview_solids={len(preview.get_solids())}")
|
||||
print(f"preview_volume={preview.get_volume():.3f}")
|
||||
print(f"imported_keys={','.join(sorted(imported.keys()))}")
|
||||
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"session={session_path}")
|
||||
print(f"step={step_path}")
|
||||
print(f"fcstd={fcstd_status}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Material records for structural, magnetic, and electronic parts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import simplecadapi as scad
|
||||
|
||||
|
||||
def make_actuator_materials_rdict() -> dict[str, scad.Material]:
|
||||
"""Create the materials used by Case 20."""
|
||||
|
||||
materials = {
|
||||
"housing": scad.make_material_rmaterial(
|
||||
material_id="aluminum_6061_t6",
|
||||
name="Hard-anodized 6061-T6 aluminum",
|
||||
density=2.70e-6,
|
||||
density_unit="kg/mm^3",
|
||||
color=(0.12, 0.15, 0.18),
|
||||
),
|
||||
"carrier": scad.make_material_rmaterial(
|
||||
material_id="aluminum_7075_t6",
|
||||
name="7075-T6 aluminum",
|
||||
density=2.81e-6,
|
||||
density_unit="kg/mm^3",
|
||||
color=(0.72, 0.74, 0.78),
|
||||
),
|
||||
"gear": scad.make_material_rmaterial(
|
||||
material_id="case_hardened_gear_steel",
|
||||
name="Case-hardened alloy gear steel",
|
||||
density=7.85e-6,
|
||||
density_unit="kg/mm^3",
|
||||
color=(0.46, 0.50, 0.56),
|
||||
),
|
||||
"electrical_steel": scad.make_material_rmaterial(
|
||||
material_id="laminated_electrical_steel",
|
||||
name="Laminated electrical steel",
|
||||
density=7.65e-6,
|
||||
density_unit="kg/mm^3",
|
||||
color=(0.20, 0.27, 0.35),
|
||||
),
|
||||
"copper": scad.make_material_rmaterial(
|
||||
material_id="enameled_copper",
|
||||
name="Enameled copper winding",
|
||||
density=8.96e-6,
|
||||
density_unit="kg/mm^3",
|
||||
color=(0.78, 0.27, 0.06),
|
||||
),
|
||||
"magnet": scad.make_material_rmaterial(
|
||||
material_id="ndfeb_n42sh",
|
||||
name="NdFeB N42SH magnet",
|
||||
density=7.50e-6,
|
||||
density_unit="kg/mm^3",
|
||||
color=(0.14, 0.34, 0.75),
|
||||
),
|
||||
"pcb": scad.make_material_rmaterial(
|
||||
material_id="fr4_copper_laminate",
|
||||
name="FR-4 copper laminate",
|
||||
density=1.85e-6,
|
||||
density_unit="kg/mm^3",
|
||||
color=(0.03, 0.42, 0.16),
|
||||
),
|
||||
"terminal": scad.make_material_rmaterial(
|
||||
material_id="high_temperature_terminal_polymer",
|
||||
name="High-temperature connector polymer",
|
||||
density=1.45e-6,
|
||||
density_unit="kg/mm^3",
|
||||
color=(0.88, 0.70, 0.16),
|
||||
),
|
||||
}
|
||||
print("materials: " + ",".join(sorted(materials)))
|
||||
return materials
|
||||
@@ -0,0 +1,378 @@
|
||||
"""True 12-slot/14-pole BLDC stator and direct-drive rotor assemblies."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import simplecadapi as scad
|
||||
|
||||
try:
|
||||
from .common import (
|
||||
apply_tags,
|
||||
connector_ref,
|
||||
ground_constraint_report,
|
||||
make_annulus_rsolid,
|
||||
make_axis_part_rpart,
|
||||
make_z_rotation_rplacement,
|
||||
radial_centers,
|
||||
)
|
||||
from .dimensions import (
|
||||
ADDENDUM_FACTOR,
|
||||
BACKLASH,
|
||||
CLEARANCE_FACTOR,
|
||||
GEAR_HEIGHT,
|
||||
HELIX_ANGLE,
|
||||
MOTOR_MAGNET_OUTER_RADIUS,
|
||||
MOTOR_MAGNET_TANGENTIAL_WIDTH,
|
||||
MOTOR_POLE_COUNT,
|
||||
MOTOR_ROTOR_BACKIRON_RADIUS,
|
||||
MOTOR_ROTOR_BOTTOM_Z,
|
||||
MOTOR_ROTOR_TOP_Z,
|
||||
MOTOR_SHAFT_RADIUS,
|
||||
MOTOR_SHELL_INNER_RADIUS,
|
||||
MOTOR_SLOT_COUNT,
|
||||
MOTOR_STATOR_BOTTOM_Z,
|
||||
MOTOR_STATOR_OUTER_RADIUS,
|
||||
MOTOR_STATOR_TOOTH_INNER_RADIUS,
|
||||
MOTOR_STATOR_TOOTH_WIDTH,
|
||||
MOTOR_STATOR_TOP_Z,
|
||||
MOTOR_STATOR_YOKE_INNER_RADIUS,
|
||||
PRESSURE_ANGLE,
|
||||
REAR_BEARING_CENTER_Z,
|
||||
STAGE_1,
|
||||
)
|
||||
except ImportError: # Support direct execution from this example directory.
|
||||
from common import (
|
||||
apply_tags,
|
||||
connector_ref,
|
||||
ground_constraint_report,
|
||||
make_annulus_rsolid,
|
||||
make_axis_part_rpart,
|
||||
make_z_rotation_rplacement,
|
||||
radial_centers,
|
||||
)
|
||||
from dimensions import (
|
||||
ADDENDUM_FACTOR,
|
||||
BACKLASH,
|
||||
CLEARANCE_FACTOR,
|
||||
GEAR_HEIGHT,
|
||||
HELIX_ANGLE,
|
||||
MOTOR_MAGNET_OUTER_RADIUS,
|
||||
MOTOR_MAGNET_TANGENTIAL_WIDTH,
|
||||
MOTOR_POLE_COUNT,
|
||||
MOTOR_ROTOR_BACKIRON_RADIUS,
|
||||
MOTOR_ROTOR_BOTTOM_Z,
|
||||
MOTOR_ROTOR_TOP_Z,
|
||||
MOTOR_SHAFT_RADIUS,
|
||||
MOTOR_SHELL_INNER_RADIUS,
|
||||
MOTOR_SLOT_COUNT,
|
||||
MOTOR_STATOR_BOTTOM_Z,
|
||||
MOTOR_STATOR_OUTER_RADIUS,
|
||||
MOTOR_STATOR_TOOTH_INNER_RADIUS,
|
||||
MOTOR_STATOR_TOOTH_WIDTH,
|
||||
MOTOR_STATOR_TOP_Z,
|
||||
MOTOR_STATOR_YOKE_INNER_RADIUS,
|
||||
PRESSURE_ANGLE,
|
||||
REAR_BEARING_CENTER_Z,
|
||||
STAGE_1,
|
||||
)
|
||||
|
||||
|
||||
def make_bldc_stator_rassembly(
|
||||
*,
|
||||
steel_material: scad.Material,
|
||||
copper_material: scad.Material,
|
||||
) -> scad.Assembly:
|
||||
"""Build a laminated 12-slot stator and twelve fixed winding packs."""
|
||||
|
||||
core = _make_stator_core_rpart(material=steel_material)
|
||||
winding = _make_winding_pack_rpart(material=copper_material)
|
||||
stator = scad.make_assembly_rassembly(
|
||||
assembly_id="bldc_12_slot_stator",
|
||||
name="12-slot laminated stator with discrete copper slot packs",
|
||||
)
|
||||
stator = scad.add_component_rassembly(
|
||||
assembly=stator,
|
||||
item=core,
|
||||
component_id="stator_core",
|
||||
placement=scad.identity_placement_rplacement(),
|
||||
name="Laminated stator core",
|
||||
)
|
||||
stator = scad.ground_component_rassembly(assembly=stator, component_id="stator_core")
|
||||
for index, angle, _center in radial_centers(
|
||||
count=MOTOR_SLOT_COUNT,
|
||||
radius=0.0,
|
||||
angle_offset=0.0,
|
||||
):
|
||||
component_id = f"winding_{index + 1:02d}"
|
||||
stator = scad.add_component_rassembly(
|
||||
assembly=stator,
|
||||
item=winding,
|
||||
component_id=component_id,
|
||||
placement=make_z_rotation_rplacement(origin=(0.0, 0.0, 0.0), angle_degrees=angle),
|
||||
name=f"Slot winding pack {index + 1}",
|
||||
)
|
||||
stator = scad.add_fixed_constraint_rassembly(
|
||||
assembly=stator,
|
||||
constraint_id=f"{component_id}_potted_to_core",
|
||||
connector_a=connector_ref(component_id="stator_core", connector_id=component_id),
|
||||
connector_b=connector_ref(component_id=component_id, connector_id="mount_axis"),
|
||||
name=f"Winding {index + 1} varnish and potting retention",
|
||||
)
|
||||
stator = scad.forward_connector_rassembly(
|
||||
assembly=stator,
|
||||
connector_id="shell_axis",
|
||||
source_component_id="stator_core",
|
||||
source_connector_id="shell_axis",
|
||||
name="Stator press-fit axis",
|
||||
offset=None,
|
||||
)
|
||||
stator = scad.solve_assembly_constraints_rassembly(assembly=stator, strict=True)
|
||||
ground_constraint_report(label="stator", assembly=stator)
|
||||
return stator
|
||||
|
||||
|
||||
def make_bldc_rotor_rassembly(
|
||||
*,
|
||||
steel_material: scad.Material,
|
||||
magnet_material: scad.Material,
|
||||
) -> scad.Assembly:
|
||||
"""Build the rotor, bonded magnets, shaft, and integrated stage-1 sun."""
|
||||
|
||||
core = _make_rotor_shaft_sun_rpart(material=steel_material)
|
||||
magnet = _make_rotor_magnet_rpart(material=magnet_material)
|
||||
rotor = scad.make_assembly_rassembly(
|
||||
assembly_id="direct_coupled_bldc_rotor",
|
||||
name="14-pole BLDC rotor with integrated stage-1 sun shaft",
|
||||
)
|
||||
rotor = scad.add_component_rassembly(
|
||||
assembly=rotor,
|
||||
item=core,
|
||||
component_id="rotor_core_shaft_sun",
|
||||
placement=scad.identity_placement_rplacement(),
|
||||
name="Rotor back iron, shaft, and stage-1 sun",
|
||||
)
|
||||
rotor = scad.ground_component_rassembly(assembly=rotor, component_id="rotor_core_shaft_sun")
|
||||
for index, angle, _center in radial_centers(count=MOTOR_POLE_COUNT, radius=0.0):
|
||||
component_id = f"magnet_{index + 1:02d}"
|
||||
rotor = scad.add_component_rassembly(
|
||||
assembly=rotor,
|
||||
item=magnet,
|
||||
component_id=component_id,
|
||||
placement=make_z_rotation_rplacement(origin=(0.0, 0.0, 0.0), angle_degrees=angle),
|
||||
name=f"Bonded rotor magnet {index + 1}",
|
||||
)
|
||||
rotor = scad.add_fixed_constraint_rassembly(
|
||||
assembly=rotor,
|
||||
constraint_id=f"{component_id}_bonded_to_rotor",
|
||||
connector_a=connector_ref(component_id="rotor_core_shaft_sun", connector_id=component_id),
|
||||
connector_b=connector_ref(component_id=component_id, connector_id="bond_axis"),
|
||||
name=f"Magnet {index + 1} adhesive and sleeve retention",
|
||||
)
|
||||
for connector_id in (
|
||||
"rotor_axis",
|
||||
"rear_bearing_axis",
|
||||
"front_bearing_axis",
|
||||
"stage1_sun_axis",
|
||||
):
|
||||
rotor = scad.forward_connector_rassembly(
|
||||
assembly=rotor,
|
||||
connector_id=connector_id,
|
||||
source_component_id="rotor_core_shaft_sun",
|
||||
source_connector_id=connector_id,
|
||||
name=connector_id.replace("_", " "),
|
||||
offset=None,
|
||||
)
|
||||
rotor = scad.solve_assembly_constraints_rassembly(assembly=rotor, strict=True)
|
||||
ground_constraint_report(label="rotor", assembly=rotor)
|
||||
print(
|
||||
f"rotor_direct_coupling: shaft_d={MOTOR_SHAFT_RADIUS * 2.0:.1f} "
|
||||
f"magnets={MOTOR_POLE_COUNT} stage1_sun_teeth={STAGE_1.sun_teeth}"
|
||||
)
|
||||
return rotor
|
||||
|
||||
|
||||
def _make_stator_core_rpart(*, material: scad.Material) -> scad.Part:
|
||||
yoke = make_annulus_rsolid(
|
||||
outer_radius=MOTOR_STATOR_OUTER_RADIUS,
|
||||
inner_radius=MOTOR_STATOR_YOKE_INNER_RADIUS,
|
||||
bottom_z=MOTOR_STATOR_BOTTOM_Z,
|
||||
height=MOTOR_STATOR_TOP_Z - MOTOR_STATOR_BOTTOM_Z,
|
||||
tags=("role.stator_back_iron",),
|
||||
)
|
||||
tooth_length = MOTOR_STATOR_YOKE_INNER_RADIUS - MOTOR_STATOR_TOOTH_INNER_RADIUS + 0.40
|
||||
tooth_center_radius = MOTOR_STATOR_TOOTH_INNER_RADIUS + tooth_length / 2.0
|
||||
teeth = []
|
||||
for _index, angle, _center in radial_centers(count=MOTOR_SLOT_COUNT, radius=0.0):
|
||||
tooth = scad.make_box_rsolid(
|
||||
width=tooth_length,
|
||||
height=MOTOR_STATOR_TOOTH_WIDTH,
|
||||
depth=MOTOR_STATOR_TOP_Z - MOTOR_STATOR_BOTTOM_Z,
|
||||
bottom_face_center=(tooth_center_radius, 0.0, MOTOR_STATOR_BOTTOM_Z),
|
||||
)
|
||||
teeth.append(
|
||||
scad.rotate_shape(
|
||||
shape=tooth,
|
||||
angle=angle,
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
origin=(0.0, 0.0, 0.0),
|
||||
)
|
||||
)
|
||||
core = scad.union_rsolid(yoke, teeth, glue=False)
|
||||
core = apply_tags(
|
||||
shape=core,
|
||||
tags=("role.stator_core", "role.stator_thermal_path", "group.bldc_motor"),
|
||||
)
|
||||
connectors = [
|
||||
(
|
||||
"shell_axis",
|
||||
(0.0, 0.0, (MOTOR_STATOR_BOTTOM_Z + MOTOR_STATOR_TOP_Z) / 2.0),
|
||||
"Stator press-fit axis",
|
||||
)
|
||||
]
|
||||
part = make_axis_part_rpart(
|
||||
part_id="stator_core",
|
||||
body=core,
|
||||
name="12-slot laminated electrical-steel stator stack",
|
||||
material=material,
|
||||
connectors=connectors,
|
||||
)
|
||||
for index, angle, _center in radial_centers(
|
||||
count=MOTOR_SLOT_COUNT,
|
||||
radius=0.0,
|
||||
angle_offset=0.0,
|
||||
):
|
||||
rotation = make_z_rotation_rplacement(origin=(0.0, 0.0, 0.0), angle_degrees=angle)
|
||||
connector = scad.make_placement_connector_rconnector(
|
||||
connector_id=f"winding_{index + 1:02d}",
|
||||
placement=rotation,
|
||||
name=f"Slot winding {index + 1} retention datum",
|
||||
)
|
||||
part = scad.add_connector_rpart(part=part, connector=connector)
|
||||
print(
|
||||
f"stator_core_geometry: slots={MOTOR_SLOT_COUNT} active_length="
|
||||
f"{MOTOR_STATOR_TOP_Z - MOTOR_STATOR_BOTTOM_Z:.1f} radial_fit_clearance="
|
||||
f"{MOTOR_SHELL_INNER_RADIUS - MOTOR_STATOR_OUTER_RADIUS:.2f}"
|
||||
)
|
||||
return part
|
||||
|
||||
|
||||
def _make_winding_pack_rpart(*, material: scad.Material) -> scad.Part:
|
||||
side_depth = MOTOR_STATOR_TOP_Z - MOTOR_STATOR_BOTTOM_Z + 0.4
|
||||
side_bottom_z = MOTOR_STATOR_BOTTOM_Z - 0.2
|
||||
side_positive = scad.make_box_rsolid(
|
||||
width=4.2,
|
||||
height=1.2,
|
||||
depth=side_depth,
|
||||
bottom_face_center=(17.55, 2.1, side_bottom_z),
|
||||
)
|
||||
side_negative = scad.make_box_rsolid(
|
||||
width=4.2,
|
||||
height=1.2,
|
||||
depth=side_depth,
|
||||
bottom_face_center=(17.55, -2.1, side_bottom_z),
|
||||
)
|
||||
rear_end_turn = scad.make_box_rsolid(
|
||||
width=4.2,
|
||||
height=5.4,
|
||||
depth=0.9,
|
||||
bottom_face_center=(17.55, 0.0, MOTOR_STATOR_BOTTOM_Z - 1.0),
|
||||
)
|
||||
front_end_turn = scad.make_box_rsolid(
|
||||
width=4.2,
|
||||
height=5.4,
|
||||
depth=0.9,
|
||||
bottom_face_center=(17.55, 0.0, MOTOR_STATOR_TOP_Z + 0.1),
|
||||
)
|
||||
winding = scad.union_rsolid(
|
||||
side_positive,
|
||||
side_negative,
|
||||
rear_end_turn,
|
||||
front_end_turn,
|
||||
glue=False,
|
||||
)
|
||||
winding = apply_tags(
|
||||
shape=winding,
|
||||
tags=("role.copper_slot_winding", "group.three_phase_windings"),
|
||||
)
|
||||
return make_axis_part_rpart(
|
||||
part_id="reusable_slot_winding",
|
||||
body=winding,
|
||||
name="Reusable four-segment copper tooth winding pack",
|
||||
material=material,
|
||||
connectors=(("mount_axis", (0.0, 0.0, 0.0), "Core potting datum"),),
|
||||
)
|
||||
|
||||
|
||||
def _make_rotor_shaft_sun_rpart(*, material: scad.Material) -> scad.Part:
|
||||
shaft_bottom_z = REAR_BEARING_CENTER_Z - 3.0
|
||||
shaft = scad.make_cylinder_rsolid(
|
||||
radius=MOTOR_SHAFT_RADIUS,
|
||||
height=STAGE_1.top_z - shaft_bottom_z,
|
||||
bottom_face_center=(0.0, 0.0, shaft_bottom_z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
back_iron = scad.make_cylinder_rsolid(
|
||||
radius=MOTOR_ROTOR_BACKIRON_RADIUS,
|
||||
height=MOTOR_ROTOR_TOP_Z - MOTOR_ROTOR_BOTTOM_Z,
|
||||
bottom_face_center=(0.0, 0.0, MOTOR_ROTOR_BOTTOM_Z),
|
||||
axis=(0.0, 0.0, 1.0),
|
||||
)
|
||||
sun = scad.std.gear.make_herringbone_gear_rsolid(
|
||||
n_teeth=STAGE_1.sun_teeth,
|
||||
module=STAGE_1.module,
|
||||
pressure_angle=PRESSURE_ANGLE,
|
||||
helix_angle=HELIX_ANGLE,
|
||||
gear_height=GEAR_HEIGHT,
|
||||
addendum_factor=ADDENDUM_FACTOR,
|
||||
clearance_factor=CLEARANCE_FACTOR,
|
||||
backlash=BACKLASH,
|
||||
)
|
||||
sun = scad.translate_shape(shape=sun, vector=(0.0, 0.0, STAGE_1.bottom_z))
|
||||
rotor = scad.union_rsolid(shaft, back_iron, sun, glue=False)
|
||||
rotor = apply_tags(
|
||||
shape=rotor,
|
||||
tags=("role.rotor_back_iron", "role.direct_drive_shaft", "role.stage1.sun_gear", "group.bldc_motor"),
|
||||
)
|
||||
part = make_axis_part_rpart(
|
||||
part_id="rotor_core_shaft_sun",
|
||||
body=rotor,
|
||||
name="Integrated rotor back iron, 8 mm shaft, and stage-1 sun",
|
||||
material=material,
|
||||
connectors=(
|
||||
("rotor_axis", (0.0, 0.0, -2.5), "Motor rotation axis"),
|
||||
("rear_bearing_axis", (0.0, 0.0, REAR_BEARING_CENTER_Z), "Rear motor bearing shaft seat"),
|
||||
("front_bearing_axis", (0.0, 0.0, -2.5), "Front motor bearing shaft seat"),
|
||||
("stage1_sun_axis", (0.0, 0.0, STAGE_1.mid_z), "Integrated stage-1 sun axis"),
|
||||
),
|
||||
)
|
||||
for index, angle, _center in radial_centers(count=MOTOR_POLE_COUNT, radius=0.0):
|
||||
connector = scad.make_placement_connector_rconnector(
|
||||
connector_id=f"magnet_{index + 1:02d}",
|
||||
placement=make_z_rotation_rplacement(origin=(0.0, 0.0, 0.0), angle_degrees=angle),
|
||||
name=f"Magnet {index + 1} bond datum",
|
||||
)
|
||||
part = scad.add_connector_rpart(part=part, connector=connector)
|
||||
return part
|
||||
|
||||
|
||||
def _make_rotor_magnet_rpart(*, material: scad.Material) -> scad.Part:
|
||||
half_width = MOTOR_MAGNET_TANGENTIAL_WIDTH / 2.0
|
||||
outer_x = (MOTOR_MAGNET_OUTER_RADIUS**2 - half_width**2) ** 0.5
|
||||
radial_depth = outer_x - MOTOR_ROTOR_BACKIRON_RADIUS + 0.05
|
||||
magnet = scad.make_box_rsolid(
|
||||
width=radial_depth,
|
||||
height=MOTOR_MAGNET_TANGENTIAL_WIDTH,
|
||||
depth=MOTOR_ROTOR_TOP_Z - MOTOR_ROTOR_BOTTOM_Z,
|
||||
bottom_face_center=(outer_x - radial_depth / 2.0, 0.0, MOTOR_ROTOR_BOTTOM_Z),
|
||||
)
|
||||
magnet = apply_tags(shape=magnet, tags=("role.rotor_magnet", "group.rotor_magnets"))
|
||||
print(
|
||||
f"rotor_magnet_envelope: corner_radius={MOTOR_MAGNET_OUTER_RADIUS:.2f} "
|
||||
f"air_gap={MOTOR_STATOR_TOOTH_INNER_RADIUS - MOTOR_MAGNET_OUTER_RADIUS:.2f}"
|
||||
)
|
||||
return make_axis_part_rpart(
|
||||
part_id="reusable_rotor_magnet",
|
||||
body=magnet,
|
||||
name="Reusable bonded NdFeB rotor magnet",
|
||||
material=material,
|
||||
connectors=(("bond_axis", (0.0, 0.0, 0.0), "Rotor bond datum"),),
|
||||
)
|
||||
Reference in New Issue
Block a user