or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

bodies-shapes.mdconstraints.mdgeometry.mdindex.mdphysics-world.mdutilities.mdvisualization.md

constraints.mddocs/

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.