CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pymunk

Pymunk is a easy-to-use pythonic 2D physics library built on Munk2D

Pending
Overview
Eval results
Files

constraints.mddocs/

Physics Constraints (Joints)

Constraints define relationships and connections between bodies, enabling realistic joint behavior like hinges, springs, motors, and limits. They constrain how bodies can move relative to each other.

Constraint Base Class

All constraints inherit from the base Constraint class which provides common functionality for connecting two bodies.

class Constraint:
    """
    Base class for all constraints/joints.
    
    Constraints can be copied and pickled. Custom properties are preserved.
    """

# Common Properties
constraint.a: Body
"""First body connected by this constraint"""

constraint.b: Body  
"""Second body connected by this constraint"""

constraint.max_force: float = float('inf')
"""
Maximum force the constraint can apply to maintain the connection.
Use to make breakable joints - constraint breaks when force exceeds limit.
"""

constraint.error_bias: float  # ~0.002 (calculated)
"""
Rate at which constraint errors are corrected (0-1).
Higher values correct errors faster but may cause instability.
"""

constraint.max_bias: float = float('inf')
"""Maximum speed at which constraint errors can be corrected"""

constraint.collide_bodies: bool = True
"""
Whether the connected bodies can collide with each other.
Set to False to prevent connected objects from colliding.
"""

constraint.impulse: float
"""Most recent impulse applied by this constraint (readonly)"""

# Callback Properties
constraint.pre_solve: Optional[Callable] = None
"""Function called before constraint is solved each step"""

constraint.post_solve: Optional[Callable] = None  
"""Function called after constraint is solved each step"""

# Methods
def activate_bodies(self) -> None:
    """Wake up both connected bodies"""

Pin Joint

class PinJoint(Constraint):
    """
    Pin joint keeps anchor points at a fixed distance (like a rigid rod).
    
    The distance is measured when the joint is created.
    Bodies can rotate around the connection but maintain fixed distance.
    """
    
    def __init__(
        self,
        a: Body, 
        b: Body,
        anchor_a: tuple[float, float] = (0, 0),
        anchor_b: tuple[float, float] = (0, 0)
    ) -> None:
        """
        Create a pin joint connecting two bodies with a rigid rod.
        
        Args:
            a: First body to connect
            b: Second body to connect  
            anchor_a: Connection point on body A (local coordinates)
            anchor_b: Connection point on body B (local coordinates)
            
        Example:
            # Connect two bodies with a rigid rod
            pin = pymunk.PinJoint(body1, body2, (0, 0), (0, 0))
            
            # Connect at offset points
            pin = pymunk.PinJoint(body1, body2, (25, 0), (-25, 0))
        """
    
    # Properties
    anchor_a: Vec2d
    """Anchor point on body A (local coordinates)"""
    
    anchor_b: Vec2d
    """Anchor point on body B (local coordinates)"""
    
    distance: float
    """Distance maintained between anchor points (can be modified)"""

Slide Joint

class SlideJoint(Constraint):
    """
    Slide joint is like a pin joint but with minimum and maximum distance limits.
    
    Acts like a telescoping rod or piston - maintains connection but allows
    length variation between limits.
    """
    
    def __init__(
        self,
        a: Body,
        b: Body, 
        anchor_a: tuple[float, float],
        anchor_b: tuple[float, float],
        min_distance: float,
        max_distance: float
    ) -> None:
        """
        Create a slide joint with distance limits.
        
        Args:
            a: First body to connect
            b: Second body to connect
            anchor_a: Connection point on body A (local coordinates)  
            anchor_b: Connection point on body B (local coordinates)
            min_distance: Minimum allowed distance
            max_distance: Maximum allowed distance
            
        Example:
            # Telescoping connection (50-150 units)
            slide = pymunk.SlideJoint(
                body1, body2, (0, 0), (0, 0), 
                min_distance=50, max_distance=150
            )
        """
    
    # Properties
    anchor_a: Vec2d
    """Anchor point on body A (local coordinates)"""
    
    anchor_b: Vec2d  
    """Anchor point on body B (local coordinates)"""
    
    min: float
    """Minimum distance between anchor points"""
    
    max: float
    """Maximum distance between anchor points"""

Pivot Joint

class PivotJoint(Constraint):
    """
    Pivot joint allows rotation around a shared pivot point (like a pin or axle).
    
    Bodies can rotate freely around the pivot but cannot translate away from it.
    Perfect for hinges, wheels, and rotating mechanisms.
    """
    
    def __init__(
        self,
        a: Body,
        b: Body, 
        *args: Union[tuple[float, float], tuple[tuple[float, float], tuple[float, float]]]
    ) -> None:
        """
        Create a pivot joint around a point.
        
        Two forms:
        1. PivotJoint(a, b, pivot_world) - single world coordinate pivot
        2. PivotJoint(a, b, anchor_a, anchor_b) - separate anchor points
        
        Args:
            a: First body to connect
            b: Second body to connect
            *args: Either (pivot_point,) or (anchor_a, anchor_b)
            
        Example:
            # Pivot around world point (100, 200)
            pivot = pymunk.PivotJoint(body1, body2, (100, 200))
            
            # Pivot using anchor points  
            pivot = pymunk.PivotJoint(body1, body2, (25, 0), (-25, 0))
        """
    
    # Properties
    anchor_a: Vec2d
    """Anchor point on body A (local coordinates)"""
    
    anchor_b: Vec2d
    """Anchor point on body B (local coordinates)"""

Groove Joint

class GrooveJoint(Constraint):
    """
    Groove joint combines pivot and slide - pivot that can slide along a groove.
    
    One body has a groove (line segment), the other has a pivot point that
    slides along the groove while allowing rotation.
    """
    
    def __init__(
        self,
        a: Body,
        b: Body,
        groove_a: tuple[float, float], 
        groove_b: tuple[float, float],
        anchor_b: tuple[float, float]
    ) -> None:
        """
        Create a groove joint with sliding pivot.
        
        Args:
            a: Body with the groove
            b: Body with the pivot point
            groove_a: Start of groove on body A (local coordinates)
            groove_b: End of groove on body A (local coordinates)  
            anchor_b: Pivot point on body B (local coordinates)
            
        Example:
            # Sliding door mechanism
            groove = pymunk.GrooveJoint(
                track_body, door_body,
                groove_a=(-50, 0), groove_b=(50, 0),  # 100-unit track
                anchor_b=(0, 0)  # Door pivot at center
            )
        """
    
    # Properties  
    groove_a: Vec2d
    """Start of groove on body A (local coordinates)"""
    
    groove_b: Vec2d
    """End of groove on body A (local coordinates)"""
    
    anchor_b: Vec2d
    """Pivot point on body B (local coordinates)"""

Damped Spring

class DampedSpring(Constraint):
    """
    Damped spring with configurable rest length, stiffness, and damping.
    
    Provides realistic spring physics with oscillation and energy dissipation.
    """
    
    def __init__(
        self,
        a: Body,
        b: Body,
        anchor_a: tuple[float, float],
        anchor_b: tuple[float, float], 
        rest_length: float,
        stiffness: float,
        damping: float
    ) -> None:
        """
        Create a damped spring between two bodies.
        
        Args:
            a: First body to connect
            b: Second body to connect
            anchor_a: Spring attachment on body A (local coordinates)
            anchor_b: Spring attachment on body B (local coordinates)
            rest_length: Natural length of spring (no force)
            stiffness: Spring constant (higher = stiffer spring)
            damping: Damping factor (higher = less oscillation)
            
        Example:
            # Car suspension spring
            spring = pymunk.DampedSpring(
                car_body, wheel_body,
                anchor_a=(0, -20), anchor_b=(0, 10),
                rest_length=50, stiffness=2000, damping=100
            )
            
            # Soft bouncy spring
            spring = pymunk.DampedSpring(
                body1, body2, (0, 0), (0, 0),
                rest_length=100, stiffness=500, damping=20
            )
        """
    
    # Properties
    anchor_a: Vec2d
    """Spring attachment point on body A (local coordinates)"""
    
    anchor_b: Vec2d
    """Spring attachment point on body B (local coordinates)"""
    
    rest_length: float
    """Natural length of spring when no force is applied"""
    
    stiffness: float
    """Spring constant - higher values make stiffer springs"""
    
    damping: float
    """Damping factor - higher values reduce oscillation"""
    
    force_func: Optional[Callable] = None
    """
    Custom force calculation function.
    
    Function signature: force_func(spring, distance) -> float
    Override to implement non-linear spring behavior.
    """
    
    # Static method for default spring force
    @staticmethod
    def spring_force(spring: 'DampedSpring', distance: float) -> float:
        """
        Default spring force calculation: F = -k * (distance - rest_length)
        
        Args:
            spring: The spring constraint
            distance: Current distance between anchor points
            
        Returns:
            Force magnitude to apply
        """

Damped Rotary Spring

class DampedRotarySpring(Constraint):
    """
    Rotational spring that applies torque to maintain desired relative angle.
    
    Like a torsion spring or rotary damper - resists rotation from rest angle.
    """
    
    def __init__(
        self,
        a: Body,
        b: Body,
        rest_angle: float,
        stiffness: float, 
        damping: float
    ) -> None:
        """
        Create a rotary spring between two bodies.
        
        Args:
            a: First body to connect
            b: Second body to connect  
            rest_angle: Desired relative angle (radians)
            stiffness: Rotational spring constant
            damping: Rotational damping factor
            
        Example:
            # Return-to-center steering spring
            steering = pymunk.DampedRotarySpring(
                car_body, wheel_body,
                rest_angle=0,       # Wheels straight
                stiffness=1000,     # Strong centering force
                damping=50          # Smooth return
            )
            
            # Door closer spring
            closer = pymunk.DampedRotarySpring(
                door_frame, door_body,
                rest_angle=0,       # Door closed
                stiffness=500, damping=25
            )
        """
    
    # Properties
    rest_angle: float
    """Desired relative angle between bodies (radians)"""
    
    stiffness: float  
    """Rotational spring constant - higher values resist rotation more"""
    
    damping: float
    """Rotational damping factor - higher values reduce oscillation"""
    
    torque_func: Optional[Callable] = None
    """
    Custom torque calculation function.
    
    Function signature: torque_func(spring, relative_angle) -> float  
    Override to implement non-linear rotary spring behavior.
    """
    
    # Static method for default torque calculation
    @staticmethod  
    def spring_torque(spring: 'DampedRotarySpring', relative_angle: float) -> float:
        """
        Default rotary spring torque: T = -k * (angle - rest_angle)
        
        Args:
            spring: The rotary spring constraint
            relative_angle: Current relative angle between bodies
            
        Returns:
            Torque to apply
        """

Rotary Limit Joint

class RotaryLimitJoint(Constraint):
    """
    Constrains relative rotation between two bodies within angle limits.
    
    Like a hinge with stops - allows free rotation within limits but 
    prevents rotation beyond them.
    """
    
    def __init__(
        self,
        a: Body,
        b: Body,
        min_angle: float,
        max_angle: float
    ) -> None:
        """
        Create rotary limits between two bodies.
        
        Args:
            a: First body
            b: Second body
            min_angle: Minimum relative angle (radians)
            max_angle: Maximum relative angle (radians) 
            
        Example:
            # Elbow joint (90° range)
            elbow = pymunk.RotaryLimitJoint(
                upper_arm, lower_arm,
                min_angle=-math.pi/2,  # -90°
                max_angle=0            # 0°  
            )
            
            # Door hinge (180° range)
            hinge = pymunk.RotaryLimitJoint(
                door_frame, door,
                min_angle=0,           # Closed
                max_angle=math.pi      # Wide open
            )
        """
    
    # Properties
    min: float
    """Minimum relative angle (radians)"""
    
    max: float  
    """Maximum relative angle (radians)"""

Ratchet Joint

class RatchetJoint(Constraint):
    """
    Rotary ratchet like a socket wrench - allows rotation in one direction only.
    
    Makes clicking sounds and prevents reverse rotation, useful for winches,
    ratcheting mechanisms, and one-way rotation systems.
    """
    
    def __init__(
        self,
        a: Body,
        b: Body, 
        phase: float,
        ratchet: float
    ) -> None:
        """
        Create a ratchet mechanism.
        
        Args:
            a: First body (typically the drive)
            b: Second body (typically the ratcheted wheel) 
            phase: Initial angular offset
            ratchet: Distance between ratchet teeth (radians)
            
        Example:
            # Socket wrench mechanism  
            ratchet = pymunk.RatchetJoint(
                handle_body, socket_body,
                phase=0,
                ratchet=math.pi/16  # 16 teeth per revolution
            )
        """
    
    # Properties
    angle: float
    """Current ratchet angle (readonly)"""
    
    phase: float
    """Initial angular offset"""
    
    ratchet: float
    """Distance between ratchet teeth (radians)"""

Gear Joint

class GearJoint(Constraint):
    """
    Gear joint maintains constant angular velocity ratio between two bodies.
    
    Simulates gear trains where one body's rotation drives another at a 
    fixed ratio, like mechanical gears or pulleys.
    """
    
    def __init__(
        self, 
        a: Body,
        b: Body,
        phase: float, 
        ratio: float
    ) -> None:
        """
        Create a gear connection between two bodies.
        
        Args:
            a: First body (drive gear)
            b: Second body (driven gear)
            phase: Initial angular offset between gears
            ratio: Angular velocity ratio (b_velocity = ratio * a_velocity)
            
        Example:
            # 2:1 gear reduction
            gear = pymunk.GearJoint(
                motor_body, wheel_body, 
                phase=0, ratio=0.5  # Wheel rotates half as fast
            )
            
            # 3:1 gear multiplication
            gear = pymunk.GearJoint(
                input_body, output_body,
                phase=0, ratio=3.0  # Output 3x faster
            )
        """
    
    # Properties
    phase: float
    """Angular offset between gears"""
    
    ratio: float  
    """
    Angular velocity ratio.
    Positive values = same direction, negative = opposite direction.
    """

Simple Motor

class SimpleMotor(Constraint):
    """
    Simple motor that maintains constant relative angular velocity.
    
    Applies torque to maintain desired angular velocity difference between
    bodies, like an electric motor or servo.
    """
    
    def __init__(
        self,
        a: Body,
        b: Body,
        rate: float
    ) -> None:
        """
        Create a motor between two bodies.
        
        Args:
            a: First body (typically static or heavy)
            b: Second body (the one being driven)
            rate: Desired angular velocity difference (rad/s)
            
        Example:
            # Rotating platform motor
            motor = pymunk.SimpleMotor(
                space.static_body, platform_body,
                rate=math.pi  # 180°/second rotation
            )
            
            # Reverse motor
            motor = pymunk.SimpleMotor(
                base_body, rotor_body,
                rate=-2*math.pi  # 1 revolution/second backwards
            )
        """
    
    # Properties
    rate: float
    """Desired relative angular velocity (rad/s)"""

Usage Examples

Basic Joints

import pymunk
import math

space = pymunk.Space()
space.gravity = (0, -981)

# Create bodies
body1 = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 25))
body1.position = 200, 300

body2 = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 25)) 
body2.position = 300, 300

# Add shapes
shape1 = pymunk.Circle(body1, 25)
shape2 = pymunk.Circle(body2, 25)
space.add(body1, shape1, body2, shape2)

# Pin joint - rigid connection
pin = pymunk.PinJoint(body1, body2, (0, 0), (0, 0))
space.add(pin)

# Pivot joint - rotational connection  
pivot = pymunk.PivotJoint(body1, body2, (250, 300))
space.add(pivot)

# Spring connection
spring = pymunk.DampedSpring(
    body1, body2, (0, 0), (0, 0),
    rest_length=80, stiffness=1000, damping=50
)
space.add(spring)

Breakable Joints

import pymunk

# Create breakable pin joint
breakable_pin = pymunk.PinJoint(body1, body2, (0, 0), (0, 0))
breakable_pin.max_force = 5000  # Break at 5000N force

# Monitor joint stress
def check_joint_stress():
    if abs(breakable_pin.impulse) > 4500:  # Close to breaking
        print("Joint under stress!")
    
# Break joint when needed
def break_joint_if_needed():
    if breakable_pin.impulse > breakable_pin.max_force:
        space.remove(breakable_pin)
        print("Joint broken!")

space.add(breakable_pin)

Vehicle Suspension

import pymunk

# Car chassis and wheels
chassis = pymunk.Body(100, pymunk.moment_for_box(100, (200, 50)))
wheel_fl = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 30))
wheel_fr = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 30))

# Position wheels
chassis.position = 400, 200
wheel_fl.position = 350, 150  # Front left
wheel_fr.position = 450, 150  # Front right

# Suspension springs  
suspension_fl = pymunk.DampedSpring(
    chassis, wheel_fl,
    anchor_a=(-50, -25), anchor_b=(0, 0),  # Chassis to wheel center
    rest_length=40, stiffness=3000, damping=150
)

suspension_fr = pymunk.DampedSpring(
    chassis, wheel_fr, 
    anchor_a=(50, -25), anchor_b=(0, 0),
    rest_length=40, stiffness=3000, damping=150
)

# Wheel steering (front wheels only)
steering_fl = pymunk.PivotJoint(chassis, wheel_fl, wheel_fl.position)
steering_fr = pymunk.PivotJoint(chassis, wheel_fr, wheel_fr.position)

# Add steering limits
steering_limit_fl = pymunk.RotaryLimitJoint(
    chassis, wheel_fl, -math.pi/6, math.pi/6  # ±30° steering
)

space.add(
    chassis, wheel_fl, wheel_fr,
    suspension_fl, suspension_fr,
    steering_fl, steering_fr, steering_limit_fl
)

Mechanical Linkages

import pymunk
import math

# Four-bar linkage mechanism
ground = space.static_body

# Link bodies  
crank = pymunk.Body(5, pymunk.moment_for_box(5, (60, 10)))
coupler = pymunk.Body(3, pymunk.moment_for_box(3, (100, 8)))  
rocker = pymunk.Body(4, pymunk.moment_for_box(4, (80, 10)))

# Position links
crank.position = 100, 200
coupler.position = 180, 220
rocker.position = 280, 200

# Pivot joints for linkage
crank_pivot = pymunk.PivotJoint(ground, crank, (70, 200))
crank_coupler = pymunk.PivotJoint(crank, coupler, (130, 200))  
coupler_rocker = pymunk.PivotJoint(coupler, rocker, (230, 220))
rocker_pivot = pymunk.PivotJoint(rocker, ground, (310, 200))

# Motor to drive crank
motor = pymunk.SimpleMotor(ground, crank, rate=math.pi/2)  # 90°/s

space.add(
    crank, coupler, rocker,
    crank_pivot, crank_coupler, coupler_rocker, rocker_pivot,
    motor
)

Advanced Constraint Callbacks

import pymunk

# Constraint with custom behavior
spring = pymunk.DampedSpring(
    body1, body2, (0, 0), (0, 0),
    rest_length=100, stiffness=1000, damping=50
)

def pre_solve_callback(constraint, space):
    """Called before constraint is solved"""
    # Modify constraint properties based on conditions
    current_length = abs(body2.position - body1.position)
    if current_length > 150:
        # Stiffen spring when stretched
        constraint.stiffness = 2000
    else:
        constraint.stiffness = 1000

def post_solve_callback(constraint, space):
    """Called after constraint is solved"""  
    # React to constraint forces
    if abs(constraint.impulse) > 1000:
        print("High spring force detected!")

spring.pre_solve = pre_solve_callback  
spring.post_solve = post_solve_callback

# Custom spring force function
def non_linear_spring_force(spring, distance):
    """Non-linear spring with progressive stiffness"""
    compression = distance - spring.rest_length
    if abs(compression) < 10:
        # Soft for small displacements
        return -spring.stiffness * 0.5 * compression
    else:
        # Stiff for large displacements
        return -spring.stiffness * 2.0 * compression

spring.force_func = non_linear_spring_force

space.add(spring)

Constraint Combinations

import pymunk

# Door with hinge and closer spring
door_frame = space.static_body
door = pymunk.Body(20, pymunk.moment_for_box(20, (10, 100)))

# Hinge pivot
hinge = pymunk.PivotJoint(door_frame, door, (100, 200))

# Rotation limits (door can't swing through wall)
limits = pymunk.RotaryLimitJoint(door, door_frame, 0, math.pi/2)  # 0-90°

# Door closer spring  
closer = pymunk.DampedRotarySpring(
    door_frame, door,
    rest_angle=0,      # Door wants to close
    stiffness=200,     # Moderate closing force
    damping=20         # Smooth closing
)

# Prevent door from colliding with frame
hinge.collide_bodies = False

space.add(door, hinge, limits, closer)

# Elevator with cable and motor
elevator_car = pymunk.Body(50, pymunk.moment_for_box(50, (60, 40)))
counterweight = pymunk.Body(50, pymunk.moment_for_box(50, (30, 60)))

# Cable connection (fixed length)
cable = pymunk.PinJoint(elevator_car, counterweight, (0, 20), (0, -30))
cable.distance = 400  # 400-unit cable

# Motor for controlled movement
elevator_motor = pymunk.SimpleMotor(space.static_body, elevator_car, rate=0)

def move_elevator_up():
    elevator_motor.rate = math.pi/2  # Move up
    
def move_elevator_down(): 
    elevator_motor.rate = -math.pi/2  # Move down
    
def stop_elevator():
    elevator_motor.rate = 0  # Stop

space.add(elevator_car, counterweight, cable, elevator_motor)

Constraints provide powerful tools for creating realistic mechanical systems, vehicles, robots, and interactive objects with proper joint behavior and physical limitations.

Install with Tessl CLI

npx tessl i tessl/pypi-pymunk

docs

bodies-shapes.md

constraints.md

geometry.md

index.md

physics-world.md

utilities.md

visualization.md

tile.json