0
# Physics Constraints (Joints)
1
2
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.
3
4
## Constraint Base Class
5
6
All constraints inherit from the base `Constraint` class which provides common functionality for connecting two bodies.
7
8
```python { .api }
9
class Constraint:
10
"""
11
Base class for all constraints/joints.
12
13
Constraints can be copied and pickled. Custom properties are preserved.
14
"""
15
16
# Common Properties
17
constraint.a: Body
18
"""First body connected by this constraint"""
19
20
constraint.b: Body
21
"""Second body connected by this constraint"""
22
23
constraint.max_force: float = float('inf')
24
"""
25
Maximum force the constraint can apply to maintain the connection.
26
Use to make breakable joints - constraint breaks when force exceeds limit.
27
"""
28
29
constraint.error_bias: float # ~0.002 (calculated)
30
"""
31
Rate at which constraint errors are corrected (0-1).
32
Higher values correct errors faster but may cause instability.
33
"""
34
35
constraint.max_bias: float = float('inf')
36
"""Maximum speed at which constraint errors can be corrected"""
37
38
constraint.collide_bodies: bool = True
39
"""
40
Whether the connected bodies can collide with each other.
41
Set to False to prevent connected objects from colliding.
42
"""
43
44
constraint.impulse: float
45
"""Most recent impulse applied by this constraint (readonly)"""
46
47
# Callback Properties
48
constraint.pre_solve: Optional[Callable] = None
49
"""Function called before constraint is solved each step"""
50
51
constraint.post_solve: Optional[Callable] = None
52
"""Function called after constraint is solved each step"""
53
54
# Methods
55
def activate_bodies(self) -> None:
56
"""Wake up both connected bodies"""
57
```
58
59
## Pin Joint
60
61
```python { .api }
62
class PinJoint(Constraint):
63
"""
64
Pin joint keeps anchor points at a fixed distance (like a rigid rod).
65
66
The distance is measured when the joint is created.
67
Bodies can rotate around the connection but maintain fixed distance.
68
"""
69
70
def __init__(
71
self,
72
a: Body,
73
b: Body,
74
anchor_a: tuple[float, float] = (0, 0),
75
anchor_b: tuple[float, float] = (0, 0)
76
) -> None:
77
"""
78
Create a pin joint connecting two bodies with a rigid rod.
79
80
Args:
81
a: First body to connect
82
b: Second body to connect
83
anchor_a: Connection point on body A (local coordinates)
84
anchor_b: Connection point on body B (local coordinates)
85
86
Example:
87
# Connect two bodies with a rigid rod
88
pin = pymunk.PinJoint(body1, body2, (0, 0), (0, 0))
89
90
# Connect at offset points
91
pin = pymunk.PinJoint(body1, body2, (25, 0), (-25, 0))
92
"""
93
94
# Properties
95
anchor_a: Vec2d
96
"""Anchor point on body A (local coordinates)"""
97
98
anchor_b: Vec2d
99
"""Anchor point on body B (local coordinates)"""
100
101
distance: float
102
"""Distance maintained between anchor points (can be modified)"""
103
```
104
105
## Slide Joint
106
107
```python { .api }
108
class SlideJoint(Constraint):
109
"""
110
Slide joint is like a pin joint but with minimum and maximum distance limits.
111
112
Acts like a telescoping rod or piston - maintains connection but allows
113
length variation between limits.
114
"""
115
116
def __init__(
117
self,
118
a: Body,
119
b: Body,
120
anchor_a: tuple[float, float],
121
anchor_b: tuple[float, float],
122
min_distance: float,
123
max_distance: float
124
) -> None:
125
"""
126
Create a slide joint with distance limits.
127
128
Args:
129
a: First body to connect
130
b: Second body to connect
131
anchor_a: Connection point on body A (local coordinates)
132
anchor_b: Connection point on body B (local coordinates)
133
min_distance: Minimum allowed distance
134
max_distance: Maximum allowed distance
135
136
Example:
137
# Telescoping connection (50-150 units)
138
slide = pymunk.SlideJoint(
139
body1, body2, (0, 0), (0, 0),
140
min_distance=50, max_distance=150
141
)
142
"""
143
144
# Properties
145
anchor_a: Vec2d
146
"""Anchor point on body A (local coordinates)"""
147
148
anchor_b: Vec2d
149
"""Anchor point on body B (local coordinates)"""
150
151
min: float
152
"""Minimum distance between anchor points"""
153
154
max: float
155
"""Maximum distance between anchor points"""
156
```
157
158
## Pivot Joint
159
160
```python { .api }
161
class PivotJoint(Constraint):
162
"""
163
Pivot joint allows rotation around a shared pivot point (like a pin or axle).
164
165
Bodies can rotate freely around the pivot but cannot translate away from it.
166
Perfect for hinges, wheels, and rotating mechanisms.
167
"""
168
169
def __init__(
170
self,
171
a: Body,
172
b: Body,
173
*args: Union[tuple[float, float], tuple[tuple[float, float], tuple[float, float]]]
174
) -> None:
175
"""
176
Create a pivot joint around a point.
177
178
Two forms:
179
1. PivotJoint(a, b, pivot_world) - single world coordinate pivot
180
2. PivotJoint(a, b, anchor_a, anchor_b) - separate anchor points
181
182
Args:
183
a: First body to connect
184
b: Second body to connect
185
*args: Either (pivot_point,) or (anchor_a, anchor_b)
186
187
Example:
188
# Pivot around world point (100, 200)
189
pivot = pymunk.PivotJoint(body1, body2, (100, 200))
190
191
# Pivot using anchor points
192
pivot = pymunk.PivotJoint(body1, body2, (25, 0), (-25, 0))
193
"""
194
195
# Properties
196
anchor_a: Vec2d
197
"""Anchor point on body A (local coordinates)"""
198
199
anchor_b: Vec2d
200
"""Anchor point on body B (local coordinates)"""
201
```
202
203
## Groove Joint
204
205
```python { .api }
206
class GrooveJoint(Constraint):
207
"""
208
Groove joint combines pivot and slide - pivot that can slide along a groove.
209
210
One body has a groove (line segment), the other has a pivot point that
211
slides along the groove while allowing rotation.
212
"""
213
214
def __init__(
215
self,
216
a: Body,
217
b: Body,
218
groove_a: tuple[float, float],
219
groove_b: tuple[float, float],
220
anchor_b: tuple[float, float]
221
) -> None:
222
"""
223
Create a groove joint with sliding pivot.
224
225
Args:
226
a: Body with the groove
227
b: Body with the pivot point
228
groove_a: Start of groove on body A (local coordinates)
229
groove_b: End of groove on body A (local coordinates)
230
anchor_b: Pivot point on body B (local coordinates)
231
232
Example:
233
# Sliding door mechanism
234
groove = pymunk.GrooveJoint(
235
track_body, door_body,
236
groove_a=(-50, 0), groove_b=(50, 0), # 100-unit track
237
anchor_b=(0, 0) # Door pivot at center
238
)
239
"""
240
241
# Properties
242
groove_a: Vec2d
243
"""Start of groove on body A (local coordinates)"""
244
245
groove_b: Vec2d
246
"""End of groove on body A (local coordinates)"""
247
248
anchor_b: Vec2d
249
"""Pivot point on body B (local coordinates)"""
250
```
251
252
## Damped Spring
253
254
```python { .api }
255
class DampedSpring(Constraint):
256
"""
257
Damped spring with configurable rest length, stiffness, and damping.
258
259
Provides realistic spring physics with oscillation and energy dissipation.
260
"""
261
262
def __init__(
263
self,
264
a: Body,
265
b: Body,
266
anchor_a: tuple[float, float],
267
anchor_b: tuple[float, float],
268
rest_length: float,
269
stiffness: float,
270
damping: float
271
) -> None:
272
"""
273
Create a damped spring between two bodies.
274
275
Args:
276
a: First body to connect
277
b: Second body to connect
278
anchor_a: Spring attachment on body A (local coordinates)
279
anchor_b: Spring attachment on body B (local coordinates)
280
rest_length: Natural length of spring (no force)
281
stiffness: Spring constant (higher = stiffer spring)
282
damping: Damping factor (higher = less oscillation)
283
284
Example:
285
# Car suspension spring
286
spring = pymunk.DampedSpring(
287
car_body, wheel_body,
288
anchor_a=(0, -20), anchor_b=(0, 10),
289
rest_length=50, stiffness=2000, damping=100
290
)
291
292
# Soft bouncy spring
293
spring = pymunk.DampedSpring(
294
body1, body2, (0, 0), (0, 0),
295
rest_length=100, stiffness=500, damping=20
296
)
297
"""
298
299
# Properties
300
anchor_a: Vec2d
301
"""Spring attachment point on body A (local coordinates)"""
302
303
anchor_b: Vec2d
304
"""Spring attachment point on body B (local coordinates)"""
305
306
rest_length: float
307
"""Natural length of spring when no force is applied"""
308
309
stiffness: float
310
"""Spring constant - higher values make stiffer springs"""
311
312
damping: float
313
"""Damping factor - higher values reduce oscillation"""
314
315
force_func: Optional[Callable] = None
316
"""
317
Custom force calculation function.
318
319
Function signature: force_func(spring, distance) -> float
320
Override to implement non-linear spring behavior.
321
"""
322
323
# Static method for default spring force
324
@staticmethod
325
def spring_force(spring: 'DampedSpring', distance: float) -> float:
326
"""
327
Default spring force calculation: F = -k * (distance - rest_length)
328
329
Args:
330
spring: The spring constraint
331
distance: Current distance between anchor points
332
333
Returns:
334
Force magnitude to apply
335
"""
336
```
337
338
## Damped Rotary Spring
339
340
```python { .api }
341
class DampedRotarySpring(Constraint):
342
"""
343
Rotational spring that applies torque to maintain desired relative angle.
344
345
Like a torsion spring or rotary damper - resists rotation from rest angle.
346
"""
347
348
def __init__(
349
self,
350
a: Body,
351
b: Body,
352
rest_angle: float,
353
stiffness: float,
354
damping: float
355
) -> None:
356
"""
357
Create a rotary spring between two bodies.
358
359
Args:
360
a: First body to connect
361
b: Second body to connect
362
rest_angle: Desired relative angle (radians)
363
stiffness: Rotational spring constant
364
damping: Rotational damping factor
365
366
Example:
367
# Return-to-center steering spring
368
steering = pymunk.DampedRotarySpring(
369
car_body, wheel_body,
370
rest_angle=0, # Wheels straight
371
stiffness=1000, # Strong centering force
372
damping=50 # Smooth return
373
)
374
375
# Door closer spring
376
closer = pymunk.DampedRotarySpring(
377
door_frame, door_body,
378
rest_angle=0, # Door closed
379
stiffness=500, damping=25
380
)
381
"""
382
383
# Properties
384
rest_angle: float
385
"""Desired relative angle between bodies (radians)"""
386
387
stiffness: float
388
"""Rotational spring constant - higher values resist rotation more"""
389
390
damping: float
391
"""Rotational damping factor - higher values reduce oscillation"""
392
393
torque_func: Optional[Callable] = None
394
"""
395
Custom torque calculation function.
396
397
Function signature: torque_func(spring, relative_angle) -> float
398
Override to implement non-linear rotary spring behavior.
399
"""
400
401
# Static method for default torque calculation
402
@staticmethod
403
def spring_torque(spring: 'DampedRotarySpring', relative_angle: float) -> float:
404
"""
405
Default rotary spring torque: T = -k * (angle - rest_angle)
406
407
Args:
408
spring: The rotary spring constraint
409
relative_angle: Current relative angle between bodies
410
411
Returns:
412
Torque to apply
413
"""
414
```
415
416
## Rotary Limit Joint
417
418
```python { .api }
419
class RotaryLimitJoint(Constraint):
420
"""
421
Constrains relative rotation between two bodies within angle limits.
422
423
Like a hinge with stops - allows free rotation within limits but
424
prevents rotation beyond them.
425
"""
426
427
def __init__(
428
self,
429
a: Body,
430
b: Body,
431
min_angle: float,
432
max_angle: float
433
) -> None:
434
"""
435
Create rotary limits between two bodies.
436
437
Args:
438
a: First body
439
b: Second body
440
min_angle: Minimum relative angle (radians)
441
max_angle: Maximum relative angle (radians)
442
443
Example:
444
# Elbow joint (90° range)
445
elbow = pymunk.RotaryLimitJoint(
446
upper_arm, lower_arm,
447
min_angle=-math.pi/2, # -90°
448
max_angle=0 # 0°
449
)
450
451
# Door hinge (180° range)
452
hinge = pymunk.RotaryLimitJoint(
453
door_frame, door,
454
min_angle=0, # Closed
455
max_angle=math.pi # Wide open
456
)
457
"""
458
459
# Properties
460
min: float
461
"""Minimum relative angle (radians)"""
462
463
max: float
464
"""Maximum relative angle (radians)"""
465
```
466
467
## Ratchet Joint
468
469
```python { .api }
470
class RatchetJoint(Constraint):
471
"""
472
Rotary ratchet like a socket wrench - allows rotation in one direction only.
473
474
Makes clicking sounds and prevents reverse rotation, useful for winches,
475
ratcheting mechanisms, and one-way rotation systems.
476
"""
477
478
def __init__(
479
self,
480
a: Body,
481
b: Body,
482
phase: float,
483
ratchet: float
484
) -> None:
485
"""
486
Create a ratchet mechanism.
487
488
Args:
489
a: First body (typically the drive)
490
b: Second body (typically the ratcheted wheel)
491
phase: Initial angular offset
492
ratchet: Distance between ratchet teeth (radians)
493
494
Example:
495
# Socket wrench mechanism
496
ratchet = pymunk.RatchetJoint(
497
handle_body, socket_body,
498
phase=0,
499
ratchet=math.pi/16 # 16 teeth per revolution
500
)
501
"""
502
503
# Properties
504
angle: float
505
"""Current ratchet angle (readonly)"""
506
507
phase: float
508
"""Initial angular offset"""
509
510
ratchet: float
511
"""Distance between ratchet teeth (radians)"""
512
```
513
514
## Gear Joint
515
516
```python { .api }
517
class GearJoint(Constraint):
518
"""
519
Gear joint maintains constant angular velocity ratio between two bodies.
520
521
Simulates gear trains where one body's rotation drives another at a
522
fixed ratio, like mechanical gears or pulleys.
523
"""
524
525
def __init__(
526
self,
527
a: Body,
528
b: Body,
529
phase: float,
530
ratio: float
531
) -> None:
532
"""
533
Create a gear connection between two bodies.
534
535
Args:
536
a: First body (drive gear)
537
b: Second body (driven gear)
538
phase: Initial angular offset between gears
539
ratio: Angular velocity ratio (b_velocity = ratio * a_velocity)
540
541
Example:
542
# 2:1 gear reduction
543
gear = pymunk.GearJoint(
544
motor_body, wheel_body,
545
phase=0, ratio=0.5 # Wheel rotates half as fast
546
)
547
548
# 3:1 gear multiplication
549
gear = pymunk.GearJoint(
550
input_body, output_body,
551
phase=0, ratio=3.0 # Output 3x faster
552
)
553
"""
554
555
# Properties
556
phase: float
557
"""Angular offset between gears"""
558
559
ratio: float
560
"""
561
Angular velocity ratio.
562
Positive values = same direction, negative = opposite direction.
563
"""
564
```
565
566
## Simple Motor
567
568
```python { .api }
569
class SimpleMotor(Constraint):
570
"""
571
Simple motor that maintains constant relative angular velocity.
572
573
Applies torque to maintain desired angular velocity difference between
574
bodies, like an electric motor or servo.
575
"""
576
577
def __init__(
578
self,
579
a: Body,
580
b: Body,
581
rate: float
582
) -> None:
583
"""
584
Create a motor between two bodies.
585
586
Args:
587
a: First body (typically static or heavy)
588
b: Second body (the one being driven)
589
rate: Desired angular velocity difference (rad/s)
590
591
Example:
592
# Rotating platform motor
593
motor = pymunk.SimpleMotor(
594
space.static_body, platform_body,
595
rate=math.pi # 180°/second rotation
596
)
597
598
# Reverse motor
599
motor = pymunk.SimpleMotor(
600
base_body, rotor_body,
601
rate=-2*math.pi # 1 revolution/second backwards
602
)
603
"""
604
605
# Properties
606
rate: float
607
"""Desired relative angular velocity (rad/s)"""
608
```
609
610
## Usage Examples
611
612
### Basic Joints
613
614
```python { .api }
615
import pymunk
616
import math
617
618
space = pymunk.Space()
619
space.gravity = (0, -981)
620
621
# Create bodies
622
body1 = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 25))
623
body1.position = 200, 300
624
625
body2 = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 25))
626
body2.position = 300, 300
627
628
# Add shapes
629
shape1 = pymunk.Circle(body1, 25)
630
shape2 = pymunk.Circle(body2, 25)
631
space.add(body1, shape1, body2, shape2)
632
633
# Pin joint - rigid connection
634
pin = pymunk.PinJoint(body1, body2, (0, 0), (0, 0))
635
space.add(pin)
636
637
# Pivot joint - rotational connection
638
pivot = pymunk.PivotJoint(body1, body2, (250, 300))
639
space.add(pivot)
640
641
# Spring connection
642
spring = pymunk.DampedSpring(
643
body1, body2, (0, 0), (0, 0),
644
rest_length=80, stiffness=1000, damping=50
645
)
646
space.add(spring)
647
```
648
649
### Breakable Joints
650
651
```python { .api }
652
import pymunk
653
654
# Create breakable pin joint
655
breakable_pin = pymunk.PinJoint(body1, body2, (0, 0), (0, 0))
656
breakable_pin.max_force = 5000 # Break at 5000N force
657
658
# Monitor joint stress
659
def check_joint_stress():
660
if abs(breakable_pin.impulse) > 4500: # Close to breaking
661
print("Joint under stress!")
662
663
# Break joint when needed
664
def break_joint_if_needed():
665
if breakable_pin.impulse > breakable_pin.max_force:
666
space.remove(breakable_pin)
667
print("Joint broken!")
668
669
space.add(breakable_pin)
670
```
671
672
### Vehicle Suspension
673
674
```python { .api }
675
import pymunk
676
677
# Car chassis and wheels
678
chassis = pymunk.Body(100, pymunk.moment_for_box(100, (200, 50)))
679
wheel_fl = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 30))
680
wheel_fr = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 30))
681
682
# Position wheels
683
chassis.position = 400, 200
684
wheel_fl.position = 350, 150 # Front left
685
wheel_fr.position = 450, 150 # Front right
686
687
# Suspension springs
688
suspension_fl = pymunk.DampedSpring(
689
chassis, wheel_fl,
690
anchor_a=(-50, -25), anchor_b=(0, 0), # Chassis to wheel center
691
rest_length=40, stiffness=3000, damping=150
692
)
693
694
suspension_fr = pymunk.DampedSpring(
695
chassis, wheel_fr,
696
anchor_a=(50, -25), anchor_b=(0, 0),
697
rest_length=40, stiffness=3000, damping=150
698
)
699
700
# Wheel steering (front wheels only)
701
steering_fl = pymunk.PivotJoint(chassis, wheel_fl, wheel_fl.position)
702
steering_fr = pymunk.PivotJoint(chassis, wheel_fr, wheel_fr.position)
703
704
# Add steering limits
705
steering_limit_fl = pymunk.RotaryLimitJoint(
706
chassis, wheel_fl, -math.pi/6, math.pi/6 # ±30° steering
707
)
708
709
space.add(
710
chassis, wheel_fl, wheel_fr,
711
suspension_fl, suspension_fr,
712
steering_fl, steering_fr, steering_limit_fl
713
)
714
```
715
716
### Mechanical Linkages
717
718
```python { .api }
719
import pymunk
720
import math
721
722
# Four-bar linkage mechanism
723
ground = space.static_body
724
725
# Link bodies
726
crank = pymunk.Body(5, pymunk.moment_for_box(5, (60, 10)))
727
coupler = pymunk.Body(3, pymunk.moment_for_box(3, (100, 8)))
728
rocker = pymunk.Body(4, pymunk.moment_for_box(4, (80, 10)))
729
730
# Position links
731
crank.position = 100, 200
732
coupler.position = 180, 220
733
rocker.position = 280, 200
734
735
# Pivot joints for linkage
736
crank_pivot = pymunk.PivotJoint(ground, crank, (70, 200))
737
crank_coupler = pymunk.PivotJoint(crank, coupler, (130, 200))
738
coupler_rocker = pymunk.PivotJoint(coupler, rocker, (230, 220))
739
rocker_pivot = pymunk.PivotJoint(rocker, ground, (310, 200))
740
741
# Motor to drive crank
742
motor = pymunk.SimpleMotor(ground, crank, rate=math.pi/2) # 90°/s
743
744
space.add(
745
crank, coupler, rocker,
746
crank_pivot, crank_coupler, coupler_rocker, rocker_pivot,
747
motor
748
)
749
```
750
751
### Advanced Constraint Callbacks
752
753
```python { .api }
754
import pymunk
755
756
# Constraint with custom behavior
757
spring = pymunk.DampedSpring(
758
body1, body2, (0, 0), (0, 0),
759
rest_length=100, stiffness=1000, damping=50
760
)
761
762
def pre_solve_callback(constraint, space):
763
"""Called before constraint is solved"""
764
# Modify constraint properties based on conditions
765
current_length = abs(body2.position - body1.position)
766
if current_length > 150:
767
# Stiffen spring when stretched
768
constraint.stiffness = 2000
769
else:
770
constraint.stiffness = 1000
771
772
def post_solve_callback(constraint, space):
773
"""Called after constraint is solved"""
774
# React to constraint forces
775
if abs(constraint.impulse) > 1000:
776
print("High spring force detected!")
777
778
spring.pre_solve = pre_solve_callback
779
spring.post_solve = post_solve_callback
780
781
# Custom spring force function
782
def non_linear_spring_force(spring, distance):
783
"""Non-linear spring with progressive stiffness"""
784
compression = distance - spring.rest_length
785
if abs(compression) < 10:
786
# Soft for small displacements
787
return -spring.stiffness * 0.5 * compression
788
else:
789
# Stiff for large displacements
790
return -spring.stiffness * 2.0 * compression
791
792
spring.force_func = non_linear_spring_force
793
794
space.add(spring)
795
```
796
797
### Constraint Combinations
798
799
```python { .api }
800
import pymunk
801
802
# Door with hinge and closer spring
803
door_frame = space.static_body
804
door = pymunk.Body(20, pymunk.moment_for_box(20, (10, 100)))
805
806
# Hinge pivot
807
hinge = pymunk.PivotJoint(door_frame, door, (100, 200))
808
809
# Rotation limits (door can't swing through wall)
810
limits = pymunk.RotaryLimitJoint(door, door_frame, 0, math.pi/2) # 0-90°
811
812
# Door closer spring
813
closer = pymunk.DampedRotarySpring(
814
door_frame, door,
815
rest_angle=0, # Door wants to close
816
stiffness=200, # Moderate closing force
817
damping=20 # Smooth closing
818
)
819
820
# Prevent door from colliding with frame
821
hinge.collide_bodies = False
822
823
space.add(door, hinge, limits, closer)
824
825
# Elevator with cable and motor
826
elevator_car = pymunk.Body(50, pymunk.moment_for_box(50, (60, 40)))
827
counterweight = pymunk.Body(50, pymunk.moment_for_box(50, (30, 60)))
828
829
# Cable connection (fixed length)
830
cable = pymunk.PinJoint(elevator_car, counterweight, (0, 20), (0, -30))
831
cable.distance = 400 # 400-unit cable
832
833
# Motor for controlled movement
834
elevator_motor = pymunk.SimpleMotor(space.static_body, elevator_car, rate=0)
835
836
def move_elevator_up():
837
elevator_motor.rate = math.pi/2 # Move up
838
839
def move_elevator_down():
840
elevator_motor.rate = -math.pi/2 # Move down
841
842
def stop_elevator():
843
elevator_motor.rate = 0 # Stop
844
845
space.add(elevator_car, counterweight, cable, elevator_motor)
846
```
847
848
Constraints provide powerful tools for creating realistic mechanical systems, vehicles, robots, and interactive objects with proper joint behavior and physical limitations.