Cam-Follower Mechanisms
This tutorial covers the cam-follower module in pylinkage, which allows you to create mechanisms with disk/plate cams driving translating or oscillating followers.
Introduction
A cam-follower mechanism converts rotary motion into reciprocating or oscillating motion. The cam is a rotating disk with a specially shaped profile, and the follower is a component that traces this profile, producing the desired output motion.
Pylinkage supports:
Translating followers: Move along a fixed linear guide
Oscillating followers: Pivot about a fixed point (rocker arm)
Knife-edge followers: Direct contact with cam surface (roller_radius=0)
Roller followers: Rolling contact via a roller (roller_radius>0)
Motion Laws
Motion laws define the dimensionless displacement function that shapes the cam profile. All motion laws produce normalized displacement s(u) where u is in [0,1] and s is in [0,1].
Available Motion Laws
from pylinkage.cam import (
HarmonicMotionLaw,
CycloidalMotionLaw,
ModifiedTrapezoidalMotionLaw,
PolynomialMotionLaw,
polynomial_345,
polynomial_4567,
)
Harmonic Motion Law
Simple cosine-based motion. Has non-zero acceleration at boundaries.
law = HarmonicMotionLaw()
print(law.displacement(0.0)) # 0.0 (start)
print(law.displacement(0.5)) # 0.5 (midpoint)
print(law.displacement(1.0)) # 1.0 (end)
Cycloidal Motion Law
Zero velocity and acceleration at boundaries (smooth start/stop). Higher peak acceleration than harmonic.
law = CycloidalMotionLaw()
print(law.velocity(0.0)) # 0.0 (starts smoothly)
print(law.velocity(0.5)) # 2.0 (maximum velocity at midpoint)
Modified Trapezoidal Motion Law
Lower peak acceleration, good for high-load applications.
law = ModifiedTrapezoidalMotionLaw()
Polynomial Motion Laws
Polynomial motion with custom coefficients. The 3-4-5 and 4-5-6-7 polynomials are common choices with zero velocity/acceleration at boundaries.
# 3-4-5 polynomial (zero velocity and acceleration at ends)
law = polynomial_345()
# 4-5-6-7 polynomial (also zero jerk at ends)
law = polynomial_4567()
# Custom polynomial coefficients
law = PolynomialMotionLaw([0, 0, 0, 10, -15, 6])
Cam Profiles
Cam profiles define how the cam radius varies with rotation angle.
FunctionProfile
Creates a profile from a motion law with rise-dwell-fall-dwell timing.
import math
from pylinkage.cam import FunctionProfile, CycloidalMotionLaw
profile = FunctionProfile(
motion_law=CycloidalMotionLaw(),
base_radius=1.0, # Minimum radius
total_lift=0.5, # Maximum displacement
rise_start=0.0, # Angle where rise begins
rise_end=math.pi/2, # Angle where rise ends (dwell-high starts)
dwell_high_end=math.pi, # Angle where dwell-high ends (fall starts)
fall_end=3*math.pi/2, # Angle where fall ends (dwell-low starts)
)
# Evaluate the profile
print(profile.evaluate(0.0)) # 1.0 (base radius)
print(profile.evaluate(math.pi/4)) # Rising...
print(profile.evaluate(math.pi)) # 1.5 (base + lift, at peak)
PointArrayProfile
Creates a profile from discrete (angle, radius) points using spline interpolation.
import math
from pylinkage.cam import PointArrayProfile
angles = [0, math.pi/4, math.pi/2, math.pi, 3*math.pi/2, 2*math.pi]
radii = [1.0, 1.2, 1.5, 1.5, 1.2, 1.0]
profile = PointArrayProfile(angles=angles, radii=radii, periodic=True)
# Evaluate at any angle (spline interpolation)
print(profile.evaluate(math.pi/4)) # 1.2
Translating Cam Follower
A translating follower moves linearly along a fixed guide axis based on the cam rotation.
Basic Example
import math
from pylinkage.components import Ground
from pylinkage.actuators import Crank
from pylinkage.dyads import TranslatingCamFollower
from pylinkage.simulation import Linkage
from pylinkage.cam import FunctionProfile, HarmonicMotionLaw
# Fixed cam center
cam_center = Ground(0.0, 0.0, name="cam_center")
# Cam driver (rotating crank)
cam_crank = Crank(
anchor=cam_center,
radius=0.1, # Small radius (just for visualization)
angular_velocity=0.1, # Rotation speed
name="cam"
)
# Cam profile
profile = FunctionProfile(
motion_law=HarmonicMotionLaw(),
base_radius=1.0,
total_lift=0.5,
rise_start=0.0,
rise_end=math.pi,
dwell_high_end=math.pi,
fall_end=2*math.pi,
)
# Guide point (defines direction of follower motion)
guide = Ground(0.0, 1.5, name="guide")
# Translating follower
follower = TranslatingCamFollower(
cam_driver=cam_crank,
profile=profile,
guide=guide,
guide_angle=math.pi/2, # Vertical motion
roller_radius=0.0, # Knife-edge follower
name="follower"
)
# Create linkage
linkage = Linkage(
components=[cam_center, guide, cam_crank, follower],
name="Cam mechanism"
)
# Simulate
for positions in linkage.step():
follower_pos = follower.output.position
print(f"Follower at: {follower_pos}")
Roller Follower
For a roller follower, set roller_radius to a positive value:
follower = TranslatingCamFollower(
cam_driver=cam_crank,
profile=profile,
guide=guide,
guide_angle=math.pi/2,
roller_radius=0.1, # Roller with 0.1 unit radius
)
Oscillating Cam Follower
An oscillating follower pivots about a fixed point, with an arm that traces the cam profile.
Basic Example
import math
from pylinkage.components import Ground
from pylinkage.actuators import Crank
from pylinkage.dyads import OscillatingCamFollower
from pylinkage.simulation import Linkage
from pylinkage.cam import FunctionProfile, CycloidalMotionLaw
# Fixed points
cam_center = Ground(0.0, 0.0, name="cam_center")
pivot = Ground(2.0, 0.0, name="pivot")
# Cam driver
cam_crank = Crank(
anchor=cam_center,
radius=0.1,
angular_velocity=0.1,
)
# Profile that maps cam angle to angular displacement of the arm
profile = FunctionProfile(
motion_law=CycloidalMotionLaw(),
base_radius=0.0, # Base angle offset (radians)
total_lift=math.pi/4, # Maximum angular swing (45 degrees)
rise_start=0.0,
rise_end=math.pi,
dwell_high_end=math.pi,
fall_end=2*math.pi,
)
# Oscillating follower
follower = OscillatingCamFollower(
cam_driver=cam_crank,
profile=profile,
pivot_anchor=pivot,
arm_length=1.5,
initial_angle=math.pi/2, # Arm points upward at rest
roller_radius=0.0,
)
linkage = Linkage(
components=[cam_center, pivot, cam_crank, follower],
)
for positions in linkage.step():
output_pos = follower.output.position
print(f"Arm end at: {output_pos}")
Connecting to Downstream Linkages
Cam followers can drive downstream linkages. The output property provides
an anchor point that other dyads can connect to.
Example: Cam-Driven Four-Bar
import math
from pylinkage.components import Ground
from pylinkage.actuators import Crank
from pylinkage.dyads import TranslatingCamFollower, RRRDyad
from pylinkage.simulation import Linkage
from pylinkage.cam import FunctionProfile, HarmonicMotionLaw
# Ground points
cam_center = Ground(0.0, 0.0, name="cam_center")
guide = Ground(0.0, 2.0, name="guide")
rocker_pivot = Ground(3.0, 0.0, name="rocker_pivot")
# Cam mechanism
cam_crank = Crank(anchor=cam_center, radius=0.1, angular_velocity=0.1)
profile = FunctionProfile(
motion_law=HarmonicMotionLaw(),
base_radius=1.0,
total_lift=1.0,
rise_start=0.0,
rise_end=math.pi,
dwell_high_end=math.pi,
fall_end=2*math.pi,
)
follower = TranslatingCamFollower(
cam_driver=cam_crank,
profile=profile,
guide=guide,
guide_angle=math.pi/2,
roller_radius=0.0,
)
# Connect downstream rocker via RRRDyad
rocker = RRRDyad(
anchor1=follower.output, # Connects to follower output
anchor2=rocker_pivot,
distance1=2.5,
distance2=2.0,
)
linkage = Linkage(
components=[cam_center, guide, rocker_pivot, cam_crank, follower, rocker],
)
# Simulate the complete mechanism
for positions in linkage.step():
rocker_pos = rocker.output.position
print(f"Rocker output at: {rocker_pos}")
Profile Analysis
Cam profiles provide methods for mechanical analysis.
Pressure Angle
The pressure angle indicates the efficiency of force transmission. High pressure angles (>30 degrees) can cause binding.
import math
# Check pressure angle at various cam positions
for angle in [0, math.pi/4, math.pi/2, math.pi]:
pressure = profile.pressure_angle(angle)
print(f"At {math.degrees(angle):.0f} deg: pressure angle = {math.degrees(pressure):.1f} deg")
Pitch Radius
For roller followers, the pitch curve is the path traced by the roller center.
roller_radius = 0.1
for angle in [0, math.pi/4, math.pi/2]:
pitch = profile.pitch_radius(angle, roller_radius)
print(f"Pitch radius at {math.degrees(angle):.0f} deg: {pitch:.3f}")
Optimization
Profile parameters can be optimized using pylinkage’s constraint system.
# Get optimizable constraints
constraints = profile.get_constraints()
print(constraints) # (base_radius, total_lift)
# Modify constraints
profile.set_constraints(base_radius=1.2, total_lift=0.6)
For cam followers in a linkage:
# Get all constraints from the follower
constraints = list(follower.get_constraints())
# Returns: roller_radius + profile constraints
# These can be used with PSO optimization
from pylinkage.optimization import particle_swarm_optimization
Next Steps
Advanced Optimization Techniques - Optimize cam profile parameters with PSO
Kinematics-Based Optimization - Analyze velocities and accelerations
Custom Joint Creation - Create custom dyad types