"""
Example usage of the yapCAD assembly mate system.
This file demonstrates how to define kinematic constraints for mechanical
assemblies using the mate module.
"""
import math
from yapcad.assembly.mate import (
Mate, MateType, MateLimits, MateDynamics,
create_revolute_mate, create_prismatic_mate, create_gear_mate
)
[docs]
def example_robot_arm():
"""
Define a simple 2-DOF robot arm with revolute joints.
The arm consists of:
- Base (fixed)
- Shoulder joint (revolute, ±90°)
- Upper arm
- Elbow joint (revolute, 0-180°)
- Forearm
"""
print("Example 1: Robot Arm Assembly")
print("=" * 60)
# Shoulder joint - allows ±90 degree pitch motion
shoulder = create_revolute_mate(
name="shoulder_pitch",
part_a="robot_base",
datum_a="shoulder_mount_axis",
part_b="upper_arm",
datum_b="arm_root_axis",
axis=[0, 1, 0, 0], # Y-axis rotation
min_angle=-math.pi/2,
max_angle=math.pi/2,
max_velocity=1.5,
max_torque=100.0,
friction=0.05,
damping=0.2
)
print(f"Shoulder joint: {shoulder.name}")
print(f" Type: {shoulder.mate_type.value}")
print(f" DOF: {shoulder.degrees_of_freedom()}")
print(f" Range: {math.degrees(shoulder.limits.min_value):.1f}° to "
f"{math.degrees(shoulder.limits.max_value):.1f}°")
print(f" Max velocity: {shoulder.limits.max_velocity} rad/s")
print(f" Friction: {shoulder.dynamics.friction_static}")
print()
# Elbow joint - allows 0-180 degree flexion
elbow = create_revolute_mate(
name="elbow_flex",
part_a="upper_arm",
datum_a="elbow_axis",
part_b="forearm",
datum_b="forearm_root_axis",
axis=[0, 1, 0, 0], # Y-axis rotation
min_angle=0.0,
max_angle=math.pi,
max_velocity=2.0,
max_torque=50.0,
friction=0.03,
damping=0.15
)
print(f"Elbow joint: {elbow.name}")
print(f" Type: {elbow.mate_type.value}")
print(f" DOF: {elbow.degrees_of_freedom()}")
print(f" Range: {math.degrees(elbow.limits.min_value):.1f}° to "
f"{math.degrees(elbow.limits.max_value):.1f}°")
print()
# Test constraint evaluation
test_position = math.pi / 4 # 45 degrees
result = shoulder.evaluate(current_position=test_position)
print(f"Testing shoulder at {math.degrees(test_position):.1f}°:")
print(f" Satisfied: {result['satisfied']}")
print(f" DOF remaining: {result['dof_remaining']}")
print()
# Test limit violation
test_position = math.pi # 180 degrees - exceeds limit
result = shoulder.evaluate(current_position=test_position)
print(f"Testing shoulder at {math.degrees(test_position):.1f}° (beyond limit):")
print(f" Satisfied: {result['satisfied']}")
print(f" Error: {result['error']:.4f} radians")
print(f" Violations: {result['limit_violations']}")
print()
[docs]
def example_gearbox():
"""
Define a 3-stage planetary gearbox with gear mates.
Each stage has a 3:1 reduction, resulting in 27:1 overall.
"""
print("Example 2: Planetary Gearbox")
print("=" * 60)
# Stage 1: Motor to first sun gear (direct connection, 1:1)
motor_to_sun1 = Mate(
name="motor_to_sun1",
mate_type=MateType.RIGID,
part_a="motor_shaft",
datum_a="output_axis",
part_b="sun_gear_1",
datum_b="gear_axis"
)
print(f"Motor connection: {motor_to_sun1.name}")
print(f" Type: {motor_to_sun1.mate_type.value}")
print(f" DOF: {motor_to_sun1.degrees_of_freedom()}")
print()
# Stage 1: Sun to carrier (3:1 reduction)
stage1 = create_gear_mate(
name="sun1_to_carrier1",
part_a="sun_gear_1",
datum_a="gear_axis",
part_b="carrier_1",
datum_b="carrier_axis",
ratio=1.0/3.0 # Carrier rotates 1/3 speed of sun
)
print(f"Stage 1 reduction: {stage1.name}")
print(f" Ratio: {stage1.coupling_ratio} (3:1 reduction)")
print(f" Is coupled: {stage1.is_coupled()}")
# Test coupling computation
input_angle = math.pi # 180 degrees input
output_angle = stage1.compute_coupled_motion(input_angle)
print(f" Input: {math.degrees(input_angle):.1f}° -> "
f"Output: {math.degrees(output_angle):.1f}°")
print()
# Stage 2: Carrier 1 to sun 2 (rigid connection)
carrier1_to_sun2 = create_gear_mate(
name="carrier1_to_sun2",
part_a="carrier_1",
datum_a="output_axis",
part_b="sun_gear_2",
datum_b="gear_axis",
ratio=1.0
)
# Stage 2: Sun 2 to carrier 2 (3:1 reduction)
stage2 = create_gear_mate(
name="sun2_to_carrier2",
part_a="sun_gear_2",
datum_a="gear_axis",
part_b="carrier_2",
datum_b="carrier_axis",
ratio=1.0/3.0
)
print(f"Stage 2 reduction: {stage2.name}")
print(f" Ratio: {stage2.coupling_ratio} (3:1 reduction)")
print()
# Stage 3: Similar to stages 1 and 2
stage3 = create_gear_mate(
name="sun3_to_carrier3",
part_a="sun_gear_3",
datum_a="gear_axis",
part_b="carrier_3",
datum_b="carrier_axis",
ratio=1.0/3.0
)
print(f"Stage 3 reduction: {stage3.name}")
print(f" Ratio: {stage3.coupling_ratio} (3:1 reduction)")
# Calculate overall reduction
overall_ratio = (1.0/3.0) ** 3
print(f" Overall reduction: {1.0/overall_ratio:.1f}:1")
print()
[docs]
def example_linear_actuator():
"""
Define a linear actuator with prismatic joint and screw coupling.
"""
print("Example 3: Linear Actuator")
print("=" * 60)
# Motor to leadscrew (rigid connection)
motor_coupling = Mate(
name="motor_to_leadscrew",
mate_type=MateType.RIGID,
part_a="motor",
datum_a="output_shaft",
part_b="leadscrew",
datum_b="screw_axis"
)
print(f"Motor coupling: {motor_coupling.name}")
print(f" Type: {motor_coupling.mate_type.value}")
print()
# Leadscrew to carriage (screw coupling, 2mm pitch)
screw_coupling = Mate(
name="leadscrew_to_carriage",
mate_type=MateType.SCREW,
part_a="leadscrew",
datum_a="screw_axis",
part_b="carriage",
datum_b="nut_axis",
coupling_pitch=2.0 # 2mm per revolution
)
print(f"Screw coupling: {screw_coupling.name}")
print(f" Type: {screw_coupling.mate_type.value}")
print(f" Pitch: {screw_coupling.coupling_pitch} mm/rev")
print(f" Is coupled: {screw_coupling.is_coupled()}")
# Test motion calculation
rotations = 10 # 10 revolutions
input_angle = rotations * 2.0 * math.pi
linear_motion = screw_coupling.compute_coupled_motion(input_angle)
print(f" {rotations} revolutions -> {linear_motion:.1f} mm linear travel")
print()
# Carriage to rail (prismatic joint with limits)
rail_guide = create_prismatic_mate(
name="carriage_on_rail",
part_a="base",
datum_a="rail_axis",
part_b="carriage",
datum_b="slider_axis",
axis=[0, 0, 1, 0], # Z-axis travel
min_position=0.0,
max_position=200.0, # 200mm stroke
max_velocity=100.0, # 100 mm/s
max_force=500.0, # 500N force limit
friction=0.02,
damping=10.0
)
print(f"Rail guide: {rail_guide.name}")
print(f" Type: {rail_guide.mate_type.value}")
print(f" DOF: {rail_guide.degrees_of_freedom()}")
print(f" Stroke: {rail_guide.limits.max_value} mm")
print(f" Max speed: {rail_guide.limits.max_velocity} mm/s")
print()
[docs]
def example_spherical_joint():
"""
Define a spherical (ball-and-socket) joint with multi-axis limits.
"""
print("Example 4: Ball-and-Socket Joint")
print("=" * 60)
ball_joint = Mate(
name="shoulder_ball",
mate_type=MateType.SPHERICAL,
part_a="torso",
datum_a="shoulder_socket",
part_b="upper_arm",
datum_b="ball_center",
limits=MateLimits(
min_value=-math.pi/3, # Primary axis: ±60°
max_value=math.pi/3,
min_secondary=-math.pi/4, # Secondary axis: ±45°
max_secondary=math.pi/4,
max_velocity=3.0
),
dynamics=MateDynamics(
friction_static=0.1,
damping=0.5
)
)
print(f"Spherical joint: {ball_joint.name}")
print(f" Type: {ball_joint.mate_type.value}")
print(f" DOF: {ball_joint.degrees_of_freedom()}")
print(f" Primary range: ±{math.degrees(ball_joint.limits.max_value):.1f}°")
print(f" Secondary range: ±{math.degrees(ball_joint.limits.max_secondary):.1f}°")
print()
[docs]
def example_serialization():
"""
Demonstrate mate serialization to/from dictionary for JSON export.
"""
print("Example 5: Mate Serialization")
print("=" * 60)
# Create a mate with full configuration
original = create_revolute_mate(
name="test_joint",
part_a="part_a",
datum_a="axis_a",
part_b="part_b",
datum_b="axis_b",
min_angle=-math.pi/2,
max_angle=math.pi/2,
max_velocity=2.0,
max_torque=50.0,
friction=0.05,
damping=0.2
)
print("Original mate:")
print(f" Name: {original.name}")
print(f" Type: {original.mate_type.value}")
print()
# Serialize to dictionary
mate_dict = original.to_dict()
print("Serialized dictionary keys:")
for key in mate_dict.keys():
print(f" - {key}")
print()
# Deserialize from dictionary
restored = Mate.from_dict(mate_dict)
print("Restored mate:")
print(f" Name: {restored.name}")
print(f" Type: {restored.mate_type.value}")
matches = (restored.name == original.name and
restored.mate_type == original.mate_type)
print(f" Matches original: {matches}")
print()
[docs]
def main():
"""Run all examples."""
example_robot_arm()
print("\n")
example_gearbox()
print("\n")
example_linear_actuator()
print("\n")
example_spherical_joint()
print("\n")
example_serialization()
if __name__ == "__main__":
main()