feat: integrate SimpleCADAPI 2.0.2 CAD workflows

This commit is contained in:
Jerry
2026-08-03 11:17:05 +08:00
parent b5738e9109
commit c3a0f269b7
481 changed files with 110229 additions and 12826 deletions
@@ -88,6 +88,7 @@ except ImportError: # Support direct execution from this example directory.
from motor import make_bldc_rotor_rassembly, make_bldc_stator_rassembly
@scad.requires_session
def make_integrated_bldc_joint_actuator_rassembly(
*, materials: dict[str, scad.Material]
) -> scad.Assembly:
@@ -116,6 +117,7 @@ def make_integrated_bldc_joint_actuator_rassembly(
return actuator
@scad.requires_session
def make_integrated_bldc_joint_actuator_components_rtuple(
*, materials: dict[str, scad.Material]
) -> tuple[tuple[str, scad.Part | scad.Assembly, scad.Placement, str], ...]:
@@ -258,6 +260,7 @@ def make_integrated_bldc_joint_actuator_components_rtuple(
)
@scad.requires_session
def _add_public_connectors_rassembly(*, assembly: scad.Assembly) -> scad.Assembly:
forwarded = (
("case_clamp_axis", "reducer_housing", "case_clamp_axis", "External split-clamp datum"),
@@ -279,6 +282,7 @@ def _add_public_connectors_rassembly(*, assembly: scad.Assembly) -> scad.Assembl
return assembly
@scad.requires_session
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")
@@ -339,6 +343,7 @@ def _add_constraints_rassembly(*, assembly: scad.Assembly) -> scad.Assembly:
return assembly
@scad.requires_session
def _add_stage_constraints_rassembly(
*,
assembly: scad.Assembly,
@@ -386,6 +391,7 @@ def _add_stage_constraints_rassembly(
return assembly
@scad.requires_session
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"),
@@ -437,5 +443,6 @@ def _add_bearing_constraints_rassembly(*, assembly: scad.Assembly) -> scad.Assem
return assembly
@scad.requires_session
def _stage_rplacement(*, stage: StageSpec) -> scad.Placement:
return scad.make_placement_rplacement(origin=(0.0, 0.0, stage.bottom_z))
@@ -14,6 +14,7 @@ else:
from gears import planet_center_xy
@scad.requires_session
def make_standard_planet_bearing_rassembly(
*,
bearing_id: str,
@@ -34,6 +35,7 @@ def make_standard_planet_bearing_rassembly(
return bearing
@scad.requires_session
def make_main_bearing_rassembly(
*,
bearing_id: str,
@@ -48,11 +50,16 @@ def make_main_bearing_rassembly(
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
bearing_tag_prefix = ".".join(
f"size{token}" if token[0].isdigit() else token
for token in bearing_id.split("_")
)
inner_ring = make_annulus_rsolid(
outer_radius=inner_outer_radius,
inner_radius=bore_radius,
bottom_z=-spec.width / 2.0,
height=spec.width,
tag_prefix=f"bearing.{bearing_tag_prefix}.inner.ring",
tags=("role.bearing_inner_ring",),
)
outer_ring = make_annulus_rsolid(
@@ -60,12 +67,17 @@ def make_main_bearing_rassembly(
inner_radius=outer_inner_radius,
bottom_z=-spec.width / 2.0,
height=spec.width,
tag_prefix=f"bearing.{bearing_tag_prefix}.outer.ring",
tags=("role.bearing_outer_ring",),
)
ball = scad.make_sphere_rsolid(
radius=ball_radius,
center=(pitch_radius, 0.0, 0.0),
)
ball = scad.apply_tag(
shape=ball,
tag=f"solid.bearing.{bearing_tag_prefix}.rolling.element",
)
inner_part = make_axis_part_rpart(
part_id=f"{bearing_id}_inner_ring",
body=inner_ring,
@@ -148,12 +160,14 @@ def make_main_bearing_rassembly(
return bearing
@scad.requires_session
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)
@scad.requires_session
def make_planet_bearing_rplacement(
*,
stage: StageSpec,
@@ -9,6 +9,7 @@ import simplecadapi as scad
from simplecadapi import ql
@scad.requires_session
def apply_tags(*, shape: scad.Solid, tags: Iterable[str]) -> scad.Solid:
"""Apply semantic tags through the public functional API."""
@@ -18,12 +19,14 @@ def apply_tags(*, shape: scad.Solid, tags: Iterable[str]) -> scad.Solid:
return tagged
@scad.requires_session
def make_annulus_rsolid(
*,
outer_radius: float,
inner_radius: float,
bottom_z: float,
height: float,
tag_prefix: str,
tags: Iterable[str],
) -> scad.Solid:
"""Create a strict single-solid annular cylinder."""
@@ -33,17 +36,22 @@ def make_annulus_rsolid(
height=height,
bottom_face_center=(0.0, 0.0, bottom_z),
axis=(0.0, 0.0, 1.0),
tag_prefix=f"{tag_prefix}.outer",
result_tag=f"feature.{tag_prefix}.outer",
)
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),
tag_prefix=f"{tag_prefix}.bore",
result_tag=f"tool.{tag_prefix}.bore",
)
annulus = scad.cut_rsolid(outer, bore, skip_non_intersecting=False)
return apply_tags(shape=annulus, tags=tags)
@scad.requires_session
def make_axis_part_rpart(
*,
part_id: str,
@@ -70,6 +78,7 @@ def make_axis_part_rpart(
return part
@scad.requires_session
def make_z_rotation_rplacement(
*,
origin: tuple[float, float, float],
@@ -94,6 +103,7 @@ def radial_centers(*, count: int, radius: float, angle_offset: float = 0.0):
yield index, angle_degrees, (radius * math.cos(angle), radius * math.sin(angle))
@scad.requires_session
def make_axial_hole_cutters_rsolids(
*,
count: int,
@@ -101,12 +111,13 @@ def make_axial_hole_cutters_rsolids(
hole_radius: float,
bottom_z: float,
height: float,
tag_prefix: str,
angle_offset: float = 0.0,
) -> list[scad.Solid]:
"""Create equally spaced axial hole cutters."""
cutters = []
for _index, _angle, center in radial_centers(
for index, _angle, center in radial_centers(
count=count,
radius=pcd / 2.0,
angle_offset=angle_offset,
@@ -117,6 +128,8 @@ def make_axial_hole_cutters_rsolids(
height=height,
bottom_face_center=(center[0], center[1], bottom_z),
axis=(0.0, 0.0, 1.0),
tag_prefix=f"{tag_prefix}.hole{index + 1}",
result_tag=f"tool.{tag_prefix}.hole{index + 1}",
)
)
return cutters
@@ -126,9 +139,13 @@ 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()
local_roles = [
tag
for tag in scad.list_tags(shape=solid, scope="local")
if tag.startswith("role.")
]
print(
f"{label}: faces={len(faces)} role_faces={len(role_faces)} "
f"{label}: faces={len(faces)} local_roles={len(local_roles)} "
f"volume={solid.get_volume():.3f} tags={','.join(scad.list_tags(shape=solid))}"
)
@@ -142,6 +159,7 @@ def ground_compound(*, label: str, compound: scad.Compound) -> None:
print(f"{label}: solids={len(solids)} faces={faces} volume={volume:.3f}")
@scad.requires_session
def connector_ref(*, component_id: str, connector_id: str) -> scad.ConnectorRef:
"""Create a component-scoped connector reference."""
@@ -49,6 +49,7 @@ POWER_CAN_TERMINAL_CENTER = (11.0, 0.0)
MOSFET_ANGLES = (22.5, 67.5, 112.5, 202.5, 247.5, 292.5)
@scad.requires_session
def make_integrated_controller_rassembly(
*,
pcb_material: scad.Material,
@@ -139,12 +140,15 @@ def make_integrated_controller_rassembly(
return controller
@scad.requires_session
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),
tag_prefix="controller.pcb.board",
result_tag="feature.controller.pcb.board",
)
cutters: list[scad.Solid] = [
scad.make_cylinder_rsolid(
@@ -152,6 +156,8 @@ def _make_controller_pcb_rpart(*, material: scad.Material) -> scad.Part:
height=PCB_THICKNESS + 2.0,
bottom_face_center=(0.0, 0.0, PCB_BOTTOM_Z - 1.0),
axis=(0.0, 0.0, 1.0),
tag_prefix="controller.pcb.center.bore",
result_tag="tool.controller.pcb.center.bore",
)
]
cutters.extend(
@@ -161,6 +167,7 @@ def _make_controller_pcb_rpart(*, material: scad.Material) -> scad.Part:
hole_radius=PCB_MOUNT_HOLE_RADIUS,
bottom_z=PCB_BOTTOM_Z - 1.0,
height=PCB_THICKNESS + 2.0,
tag_prefix="controller.pcb.mount.hole",
angle_offset=45.0,
)
)
@@ -171,9 +178,13 @@ def _make_controller_pcb_rpart(*, material: scad.Material) -> scad.Part:
hole_radius=3.45,
bottom_z=PCB_BOTTOM_Z - 1.0,
height=PCB_THICKNESS + 2.0,
tag_prefix="controller.pcb.rear.column.clearance",
)
)
for x, count in ((PHASE_TERMINAL_CENTER[0], 3), (POWER_CAN_TERMINAL_CENTER[0], 4)):
for terminal_id, x, count in (
("phase", PHASE_TERMINAL_CENTER[0], 3),
("power.can", POWER_CAN_TERMINAL_CENTER[0], 4),
):
for pin in range(count):
y = (pin - (count - 1) / 2.0) * 1.8
cutters.append(
@@ -182,6 +193,8 @@ def _make_controller_pcb_rpart(*, material: scad.Material) -> scad.Part:
height=PCB_THICKNESS + 2.0,
bottom_face_center=(x, y, PCB_BOTTOM_Z - 1.0),
axis=(0.0, 0.0, 1.0),
tag_prefix=f"controller.pcb.terminal.{terminal_id}.pin{pin + 1}",
result_tag=f"tool.controller.pcb.terminal.{terminal_id}.pin{pin + 1}",
)
)
board = scad.cut_rsolid(board, cutters, skip_non_intersecting=False)
@@ -216,12 +229,15 @@ def _make_controller_pcb_rpart(*, material: scad.Material) -> scad.Part:
)
@scad.requires_session
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),
tag_prefix="controller.mosfet.package",
result_tag="feature.controller.mosfet.package",
)
package = apply_tags(shape=package, tags=("role.power_mosfet", "group.three_phase_bridge"))
return make_axis_part_rpart(
@@ -233,6 +249,7 @@ def _make_mosfet_package_rpart(*, material: scad.Material) -> scad.Part:
)
@scad.requires_session
def _make_terminal_block_rpart(
*,
part_id: str,
@@ -241,11 +258,14 @@ def _make_terminal_block_rpart(
pin_count: int,
material: scad.Material,
) -> scad.Part:
terminal_tag_prefix = part_id.replace("_", ".")
body = scad.make_box_rsolid(
width=width,
height=6.0,
depth=4.9,
bottom_face_center=(0.0, 0.0, 0.0),
tag_prefix=f"controller.terminal.{terminal_tag_prefix}.body",
result_tag=f"feature.controller.terminal.{terminal_tag_prefix}.body",
)
access_cutters = []
for pin in range(pin_count):
@@ -256,6 +276,8 @@ def _make_terminal_block_rpart(
height=width + 2.0,
bottom_face_center=(-width / 2.0 - 1.0, y, 2.45),
axis=(1.0, 0.0, 0.0),
tag_prefix=f"controller.terminal.{terminal_tag_prefix}.access{pin + 1}",
result_tag=f"tool.controller.terminal.{terminal_tag_prefix}.access{pin + 1}",
)
)
body = scad.cut_rsolid(body, access_cutters, skip_non_intersecting=False)
@@ -110,6 +110,7 @@ except ImportError: # Support direct execution from this example directory.
)
@scad.requires_session
def make_stage_ring_gear_rpart(
*,
stage: StageSpec,
@@ -128,17 +129,25 @@ def make_stage_ring_gear_rpart(
addendum_factor=ADDENDUM_FACTOR,
clearance_factor=CLEARANCE_FACTOR,
)
ring = scad.apply_tag(
shape=ring,
tag=f"solid.stdlib.{stage.stage_id}.fixed.herringbone.ring.gear",
)
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),
tag_prefix=f"reducer.{stage.stage_id}.ring.support",
result_tag=f"feature.reducer.{stage.stage_id}.ring.support",
)
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),
tag_prefix=f"reducer.{stage.stage_id}.ring.support.bore",
result_tag=f"tool.reducer.{stage.stage_id}.ring.support.bore",
)
support = scad.cut_rsolid(support, support_bore, skip_non_intersecting=False)
ring = scad.union_rsolid(ring, support, glue=False)
@@ -159,6 +168,7 @@ def make_stage_ring_gear_rpart(
)
@scad.requires_session
def make_stage_planet_gear_rpart(
*,
stage: StageSpec,
@@ -176,12 +186,18 @@ def make_stage_planet_gear_rpart(
clearance_factor=CLEARANCE_FACTOR,
backlash=BACKLASH,
)
planet = scad.apply_tag(
shape=planet,
tag=f"solid.stdlib.{stage.stage_id}.reusable.herringbone.planet.gear",
)
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),
tag_prefix=f"reducer.{stage.stage_id}.planet.bearing.seat",
result_tag=f"tool.reducer.{stage.stage_id}.planet.bearing.seat",
)
planet = scad.cut_rsolid(planet, bearing_seat, skip_non_intersecting=False)
planet = apply_tags(
@@ -205,6 +221,7 @@ def make_stage_planet_gear_rpart(
)
@scad.requires_session
def make_stage1_carrier_sun_rpart(*, material: scad.Material) -> scad.Part:
"""Create the first carrier and integral second-stage sun/shaft."""
@@ -223,6 +240,8 @@ def make_stage1_carrier_sun_rpart(*, material: scad.Material) -> scad.Part:
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),
tag_prefix="reducer.stage1.carrier.interstage.shaft",
result_tag="feature.reducer.stage1.carrier.interstage.shaft",
)
stage2_sun = scad.std.gear.make_herringbone_gear_rsolid(
n_teeth=STAGE_2.sun_teeth,
@@ -234,6 +253,10 @@ def make_stage1_carrier_sun_rpart(*, material: scad.Material) -> scad.Part:
clearance_factor=CLEARANCE_FACTOR,
backlash=BACKLASH,
)
stage2_sun = scad.apply_tag(
shape=stage2_sun,
tag="solid.stdlib.stage2.integral.herringbone.sun.gear",
)
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(
@@ -270,6 +293,7 @@ def make_stage1_carrier_sun_rpart(*, material: scad.Material) -> scad.Part:
)
@scad.requires_session
def make_output_carrier_flange_rpart(
*,
stage: StageSpec,
@@ -297,12 +321,16 @@ def make_output_carrier_flange_rpart(
),
bottom_face_center=(0.0, 0.0, STAGE2_CARRIER_BOTTOM_Z - 0.05),
axis=(0.0, 0.0, 1.0),
tag_prefix="reducer.stage2.output.shaft",
result_tag="feature.reducer.stage2.output.shaft",
)
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),
tag_prefix="reducer.stage2.output.flange",
result_tag="feature.reducer.stage2.output.flange",
)
output = scad.union_rsolid(carrier, shaft, flange, glue=False)
output = scad.cut_rsolid(
@@ -313,6 +341,7 @@ def make_output_carrier_flange_rpart(
hole_radius=OUTPUT_LINK_TAP_RADIUS,
bottom_z=OUTPUT_FLANGE_TOP_Z - OUTPUT_LINK_THREAD_DEPTH,
height=OUTPUT_LINK_THREAD_DEPTH + 1.0,
tag_prefix="reducer.stage2.output.flange.thread",
angle_offset=OUTPUT_LINK_BOLT_ANGLES_DEGREES[0],
),
skip_non_intersecting=False,
@@ -351,6 +380,7 @@ def make_output_carrier_flange_rpart(
)
@scad.requires_session
def make_planet_rplacement(*, stage: StageSpec, index: int) -> scad.Placement:
"""Place and visually phase one planet at its pitch center."""
@@ -377,6 +407,7 @@ def planet_center_xy(*, stage: StageSpec, index: int) -> tuple[float, float]:
)
@scad.requires_session
def _make_carrier_body_rsolid(
*,
stage: StageSpec,
@@ -393,6 +424,8 @@ def _make_carrier_body_rsolid(
height=plate_thickness,
bottom_face_center=(0.0, 0.0, plate_bottom_z),
axis=(0.0, 0.0, 1.0),
tag_prefix=f"reducer.{stage.stage_id}.carrier.hub",
result_tag=f"feature.reducer.{stage.stage_id}.carrier.hub",
)
solids = [hub]
pin_height = plate_bottom_z + plate_thickness - pin_bottom_z
@@ -408,6 +441,8 @@ def _make_carrier_body_rsolid(
height=arm_width,
depth=plate_thickness,
bottom_face_center=(arm_center_radius, 0.0, plate_bottom_z),
tag_prefix=f"reducer.{stage.stage_id}.carrier.arm{index + 1}",
result_tag=f"feature.reducer.{stage.stage_id}.carrier.arm{index + 1}",
)
solids.append(
scad.rotate_shape(
@@ -423,6 +458,8 @@ def _make_carrier_body_rsolid(
height=plate_thickness,
bottom_face_center=(center[0], center[1], plate_bottom_z),
axis=(0.0, 0.0, 1.0),
tag_prefix=f"reducer.{stage.stage_id}.carrier.pad{index + 1}",
result_tag=f"feature.reducer.{stage.stage_id}.carrier.pad{index + 1}",
)
)
solids.append(
@@ -431,6 +468,8 @@ def _make_carrier_body_rsolid(
height=pin_height,
bottom_face_center=(center[0], center[1], pin_bottom_z),
axis=(0.0, 0.0, 1.0),
tag_prefix=f"reducer.{stage.stage_id}.carrier.pin{index + 1}",
result_tag=f"feature.reducer.{stage.stage_id}.carrier.pin{index + 1}",
)
)
carrier = scad.union_rsolid(solids, glue=False)
@@ -102,6 +102,7 @@ except ImportError: # Support direct execution from this example directory.
)
@scad.requires_session
def make_motor_shell_rpart(*, material: scad.Material) -> scad.Part:
"""Create the stator sleeve, front attachment land, and rear columns."""
@@ -110,6 +111,7 @@ def make_motor_shell_rpart(*, material: scad.Material) -> scad.Part:
inner_radius=MOTOR_SHELL_INNER_RADIUS,
bottom_z=MOTOR_SHELL_BOTTOM_Z,
height=MOTOR_SHELL_TOP_Z - MOTOR_SHELL_BOTTOM_Z,
tag_prefix="housing.motor.shell.sleeve",
tags=("role.motor_shell", "role.stator_thermal_path"),
)
front_land = make_annulus_rsolid(
@@ -117,16 +119,19 @@ def make_motor_shell_rpart(*, material: scad.Material) -> scad.Part:
inner_radius=HOUSING_INTERFACE_LAND_INNER_RADIUS,
bottom_z=MOTOR_SHELL_TOP_Z - 1.4,
height=1.4,
tag_prefix="housing.motor.shell.front.land",
tags=("role.motor_reducer_mount",),
)
columns = []
for _index, _angle, center in radial_centers(count=4, radius=REAR_COLUMN_PCD / 2.0):
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),
tag_prefix=f"housing.motor.shell.rear.column{index + 1}",
result_tag=f"feature.housing.motor.shell.rear.column{index + 1}",
)
)
shell = scad.union_rsolid(sleeve, front_land, columns, glue=False)
@@ -138,6 +143,7 @@ def make_motor_shell_rpart(*, material: scad.Material) -> scad.Part:
hole_radius=M3_CLEARANCE_RADIUS,
bottom_z=MOTOR_SHELL_TOP_Z - 2.8,
height=3.6,
tag_prefix="housing.motor.shell.interface.clearance",
angle_offset=30.0,
),
make_axial_hole_cutters_rsolids(
@@ -146,6 +152,7 @@ def make_motor_shell_rpart(*, material: scad.Material) -> scad.Part:
hole_radius=REAR_FASTENER_HOLE_RADIUS,
bottom_z=MOTOR_SHELL_BOTTOM_Z - 1.0,
height=10.4,
tag_prefix="housing.motor.shell.rear.fastener.clearance",
),
skip_non_intersecting=False,
)
@@ -175,6 +182,7 @@ def make_motor_shell_rpart(*, material: scad.Material) -> scad.Part:
)
@scad.requires_session
def make_reducer_housing_rpart(*, material: scad.Material) -> scad.Part:
"""Create the reducer sleeve and front motor-bearing bulkhead."""
@@ -183,6 +191,7 @@ def make_reducer_housing_rpart(*, material: scad.Material) -> scad.Part:
inner_radius=REDUCER_HOUSING_INNER_RADIUS,
bottom_z=REDUCER_HOUSING_BOTTOM_Z,
height=REDUCER_HOUSING_FRONT_Z - REDUCER_HOUSING_BOTTOM_Z,
tag_prefix="housing.reducer.sleeve",
tags=("role.reducer_housing_sleeve",),
)
bulkhead = make_annulus_rsolid(
@@ -190,6 +199,7 @@ def make_reducer_housing_rpart(*, material: scad.Material) -> scad.Part:
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,
tag_prefix="housing.reducer.front.bulkhead",
tags=("role.motor_front_bearing_bulkhead",),
)
interstage_divider = make_annulus_rsolid(
@@ -197,6 +207,7 @@ def make_reducer_housing_rpart(*, material: scad.Material) -> scad.Part:
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,
tag_prefix="housing.reducer.interstage.divider",
tags=("role.interstage_bearing_divider",),
)
output_mount_land = make_annulus_rsolid(
@@ -204,6 +215,7 @@ def make_reducer_housing_rpart(*, material: scad.Material) -> scad.Part:
inner_radius=HOUSING_INTERFACE_LAND_INNER_RADIUS,
bottom_z=REDUCER_HOUSING_FRONT_Z - 2.2,
height=2.2,
tag_prefix="housing.reducer.output.mount.land",
tags=("role.output_cap_mount_land",),
)
housing = scad.union_rsolid(
@@ -221,6 +233,7 @@ def make_reducer_housing_rpart(*, material: scad.Material) -> scad.Part:
hole_radius=M3_CLEARANCE_RADIUS,
bottom_z=REDUCER_HOUSING_BOTTOM_Z - 1.0,
height=STAGE_1.bottom_z - REDUCER_HOUSING_BOTTOM_Z + 2.0,
tag_prefix="housing.reducer.motor.interface.clearance",
angle_offset=30.0,
),
make_axial_hole_cutters_rsolids(
@@ -229,6 +242,7 @@ def make_reducer_housing_rpart(*, material: scad.Material) -> scad.Part:
hole_radius=M3_CLEARANCE_RADIUS,
bottom_z=REDUCER_HOUSING_FRONT_Z - 2.2,
height=3.2,
tag_prefix="housing.reducer.output.interface.clearance",
),
skip_non_intersecting=False,
)
@@ -265,6 +279,7 @@ def make_reducer_housing_rpart(*, material: scad.Material) -> scad.Part:
)
@scad.requires_session
def make_rear_bearing_spider_rpart(*, material: scad.Material) -> scad.Part:
"""Create a four-arm removable rear motor-bearing support."""
@@ -274,15 +289,18 @@ def make_rear_bearing_spider_rpart(*, material: scad.Material) -> scad.Part:
inner_radius=8.05,
bottom_z=bottom_z,
height=5.0,
tag_prefix="housing.rear.spider.bearing.hub",
tags=("role.rear_motor_bearing_seat",),
)
solids = [hub]
for _index, angle, center in radial_centers(count=4, radius=REAR_COLUMN_PCD / 2.0):
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),
tag_prefix=f"housing.rear.spider.arm{index + 1}",
result_tag=f"feature.housing.rear.spider.arm{index + 1}",
)
solids.append(
scad.rotate_shape(
@@ -298,6 +316,8 @@ def make_rear_bearing_spider_rpart(*, material: scad.Material) -> scad.Part:
height=5.0,
bottom_face_center=(center[0], center[1], bottom_z),
axis=(0.0, 0.0, 1.0),
tag_prefix=f"housing.rear.spider.boss{index + 1}",
result_tag=f"feature.housing.rear.spider.boss{index + 1}",
)
)
spider = scad.union_rsolid(solids, glue=False)
@@ -309,6 +329,7 @@ def make_rear_bearing_spider_rpart(*, material: scad.Material) -> scad.Part:
hole_radius=REAR_FASTENER_HOLE_RADIUS,
bottom_z=bottom_z - 1.0,
height=7.0,
tag_prefix="housing.rear.spider.fastener.clearance",
),
skip_non_intersecting=False,
)
@@ -329,6 +350,7 @@ def make_rear_bearing_spider_rpart(*, material: scad.Material) -> scad.Part:
)
@scad.requires_session
def make_rear_electronics_cover_rpart(*, material: scad.Material) -> scad.Part:
"""Create the rear cover with PCB standoffs and terminal apertures."""
@@ -337,15 +359,23 @@ def make_rear_electronics_cover_rpart(*, material: scad.Material) -> scad.Part:
height=REAR_COVER_THICKNESS,
bottom_face_center=(0.0, 0.0, REAR_COVER_BOTTOM_Z),
axis=(0.0, 0.0, 1.0),
tag_prefix="housing.rear.cover.plate",
result_tag="feature.housing.rear.cover.plate",
)
standoffs = []
for _index, _angle, center in radial_centers(count=4, radius=PCB_STANDOFF_PCD / 2.0, angle_offset=45.0):
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),
tag_prefix=f"housing.rear.cover.pcb.standoff{index + 1}",
result_tag=f"feature.housing.rear.cover.pcb.standoff{index + 1}",
)
)
cover = scad.union_rsolid(cover, standoffs, glue=False)
@@ -354,18 +384,24 @@ def make_rear_electronics_cover_rpart(*, material: scad.Material) -> scad.Part:
height=7.2,
depth=REAR_COVER_THICKNESS + 2.0,
bottom_face_center=(-11.0, 0.0, REAR_COVER_BOTTOM_Z - 1.0),
tag_prefix="housing.rear.cover.phase.aperture",
result_tag="tool.housing.rear.cover.phase.aperture",
)
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),
tag_prefix="housing.rear.cover.power.can.aperture",
result_tag="tool.housing.rear.cover.power.can.aperture",
)
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),
tag_prefix="housing.rear.cover.center.service",
result_tag="tool.housing.rear.cover.center.service",
)
cover = scad.cut_rsolid(
cover,
@@ -378,6 +414,7 @@ def make_rear_electronics_cover_rpart(*, material: scad.Material) -> scad.Part:
hole_radius=REAR_FASTENER_HOLE_RADIUS,
bottom_z=REAR_COVER_BOTTOM_Z - 1.0,
height=REAR_COVER_THICKNESS + 2.0,
tag_prefix="housing.rear.cover.column.fastener.clearance",
),
make_axial_hole_cutters_rsolids(
count=4,
@@ -385,6 +422,7 @@ def make_rear_electronics_cover_rpart(*, material: scad.Material) -> scad.Part:
hole_radius=1.1,
bottom_z=REAR_COVER_BOTTOM_Z - 1.0,
height=PCB_BOTTOM_Z - REAR_COVER_BOTTOM_Z + 2.0,
tag_prefix="housing.rear.cover.pcb.fastener.clearance",
angle_offset=45.0,
),
skip_non_intersecting=False,
@@ -408,6 +446,7 @@ def make_rear_electronics_cover_rpart(*, material: scad.Material) -> scad.Part:
)
@scad.requires_session
def make_output_bearing_cap_rpart(*, material: scad.Material) -> scad.Part:
"""Create the removable paired-bearing cartridge and front cap."""
@@ -417,6 +456,7 @@ def make_output_bearing_cap_rpart(*, material: scad.Material) -> scad.Part:
inner_radius=bearing_clearance_radius,
bottom_z=OUTPUT_CAP_BOTTOM_Z,
height=3.0,
tag_prefix="housing.output.cap.rear.flange",
tags=("role.output_cap_mount_flange",),
)
cartridge = make_annulus_rsolid(
@@ -424,6 +464,7 @@ def make_output_bearing_cap_rpart(*, material: scad.Material) -> scad.Part:
inner_radius=bearing_clearance_radius,
bottom_z=OUTPUT_CAP_BOTTOM_Z,
height=OUTPUT_CAP_CARTRIDGE_TOP_Z - OUTPUT_CAP_BOTTOM_Z + 0.1,
tag_prefix="housing.output.cap.bearing.cartridge",
tags=("role.paired_output_bearing_seat",),
)
bearing_retainer = make_annulus_rsolid(
@@ -431,6 +472,7 @@ def make_output_bearing_cap_rpart(*, material: scad.Material) -> scad.Part:
inner_radius=8.10,
bottom_z=OUTPUT_CAP_CARTRIDGE_TOP_Z - 0.1,
height=0.5,
tag_prefix="housing.output.cap.bearing.retainer",
tags=("role.output_axial_retainer",),
)
outer_lip = make_annulus_rsolid(
@@ -438,6 +480,7 @@ def make_output_bearing_cap_rpart(*, material: scad.Material) -> scad.Part:
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,
tag_prefix="housing.output.cap.labyrinth.lip",
tags=("role.output_labyrinth_lip",),
)
cap = scad.union_rsolid(rear_flange, cartridge, bearing_retainer, outer_lip, glue=False)
@@ -449,6 +492,7 @@ def make_output_bearing_cap_rpart(*, material: scad.Material) -> scad.Part:
hole_radius=M3_CLEARANCE_RADIUS,
bottom_z=OUTPUT_CAP_BOTTOM_Z - 1.0,
height=OUTPUT_CAP_TOP_Z - OUTPUT_CAP_BOTTOM_Z + 2.0,
tag_prefix="housing.output.cap.interface.clearance",
),
skip_non_intersecting=False,
)
@@ -1,4 +1,4 @@
"""Build, validate, replay, and export the integrated BLDC joint actuator."""
"""Build and export the integrated BLDC joint actuator."""
from __future__ import annotations
@@ -8,19 +8,34 @@ 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
try:
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
except ImportError: # Support direct execution from this example directory.
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)
@@ -28,18 +43,21 @@ sys.setrecursionlimit(30000)
OUT_DIR = Path("examples/out/integrated_bldc_joint_actuator")
@scad.model(graph_id="integrated_50mm_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
assembly = make_integrated_bldc_joint_actuator_rassembly(materials=materials)
preview = scad.make_compound_from_assembly_rcompound(assembly=assembly)
preview = scad.apply_tag(
shape=preview,
tag="scene.integrated.bldc.joint.actuator.preview",
)
ground_compound(label="integrated_actuator_preview", compound=preview)
scad.capture_result(value=(assembly, preview))
return assembly, preview
def main() -> None:
@@ -53,19 +71,18 @@ def main() -> None:
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")
result = build_integrated_bldc_joint_actuator()
assembly, preview = result.value
model_path.write_text(result.model_json, encoding="utf-8")
session_path.write_text(result.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)
payload = json.loads(result.model_json)
fcstd_status = "not attempted"
try:
scad.translator.freecad_translator.translate_model_json_to_fcstd(
json_str=model_json,
json_str=result.model_json,
output_path=str(fcstd_path.resolve()),
document_name="Integrated50mmBLDCJointActuator",
freecad_cmd=None,
@@ -84,9 +101,6 @@ def main() -> None:
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}")
@@ -5,6 +5,7 @@ from __future__ import annotations
import simplecadapi as scad
@scad.requires_session
def make_actuator_materials_rdict() -> dict[str, scad.Material]:
"""Create the materials used by Case 20."""
@@ -76,6 +76,7 @@ except ImportError: # Support direct execution from this example directory.
)
@scad.requires_session
def make_bldc_stator_rassembly(
*,
steel_material: scad.Material,
@@ -130,6 +131,7 @@ def make_bldc_stator_rassembly(
return stator
@scad.requires_session
def make_bldc_rotor_rassembly(
*,
steel_material: scad.Material,
@@ -190,23 +192,27 @@ def make_bldc_rotor_rassembly(
return rotor
@scad.requires_session
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,
tag_prefix="motor.stator.back.iron",
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):
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),
tag_prefix=f"motor.stator.tooth{index + 1}",
result_tag=f"feature.motor.stator.tooth{index + 1}",
)
teeth.append(
scad.rotate_shape(
@@ -255,6 +261,7 @@ def _make_stator_core_rpart(*, material: scad.Material) -> scad.Part:
return part
@scad.requires_session
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
@@ -263,24 +270,32 @@ def _make_winding_pack_rpart(*, material: scad.Material) -> scad.Part:
height=1.2,
depth=side_depth,
bottom_face_center=(17.55, 2.1, side_bottom_z),
tag_prefix="motor.winding.side.positive",
result_tag="feature.motor.winding.side.positive",
)
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),
tag_prefix="motor.winding.side.negative",
result_tag="feature.motor.winding.side.negative",
)
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),
tag_prefix="motor.winding.rear.end.turn",
result_tag="feature.motor.winding.rear.end.turn",
)
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),
tag_prefix="motor.winding.front.end.turn",
result_tag="feature.motor.winding.front.end.turn",
)
winding = scad.union_rsolid(
side_positive,
@@ -302,6 +317,7 @@ def _make_winding_pack_rpart(*, material: scad.Material) -> scad.Part:
)
@scad.requires_session
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(
@@ -309,12 +325,16 @@ def _make_rotor_shaft_sun_rpart(*, material: scad.Material) -> scad.Part:
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),
tag_prefix="motor.rotor.drive.shaft",
result_tag="feature.motor.rotor.drive.shaft",
)
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),
tag_prefix="motor.rotor.back.iron",
result_tag="feature.motor.rotor.back.iron",
)
sun = scad.std.gear.make_herringbone_gear_rsolid(
n_teeth=STAGE_1.sun_teeth,
@@ -326,6 +346,10 @@ def _make_rotor_shaft_sun_rpart(*, material: scad.Material) -> scad.Part:
clearance_factor=CLEARANCE_FACTOR,
backlash=BACKLASH,
)
sun = scad.apply_tag(
shape=sun,
tag="solid.stdlib.stage1.integral.herringbone.sun.gear",
)
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(
@@ -354,6 +378,7 @@ def _make_rotor_shaft_sun_rpart(*, material: scad.Material) -> scad.Part:
return part
@scad.requires_session
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
@@ -363,6 +388,8 @@ def _make_rotor_magnet_rpart(*, material: scad.Material) -> scad.Part:
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),
tag_prefix="motor.rotor.reusable.magnet",
result_tag="feature.motor.rotor.reusable.magnet",
)
magnet = apply_tags(shape=magnet, tags=("role.rotor_magnet", "group.rotor_magnets"))
print(