LeggedSnake is a Python toolkit for designing, simulating, and optimizing planar walking linkages. It layers a pymunk physics engine and multi-objective optimizers on top of pylinkage's kinematic model, so you can go from a mechanism sketch to an evolved walker in a few lines of code.
One Strider mechanism optimized over 10 generations of a genetic algorithm, rendered walking on flat ground with 4 phase-offset legs (2 per side, mirrored).
From PyPI:
pip install leggedsnakeFrom source with uv:
git clone https://github.com/hugofara/leggedsnake
cd leggedsnake
uv syncA walker in five lines — the canonical Theo Jansen "Strandbeest" built from the Holy Numbers, mirrored left/right and cloned into four phase-offset pairs for stance stability, rendered in a live pyglet window:
import leggedsnake as ls
walker = ls.Walker.from_jansen(scale=0.1)
walker.add_opposite_leg() # mirror for left/right pair
walker.add_legs(3) # 4 legs per side → 8-leg Strandbeest
ls.video(walker, duration=10) # live simulationFewer legs pitch 10–20° and can tip over; four legs per side keeps at least two feet in stance at every crank angle, which is why real Strandbeests have many legs.
Other classical mechanisms ship as one-line factories too:
Walker.from_strider, Walker.from_klann, Walker.from_chebyshev,
Walker.from_watt, and Walker.from_catalog (pylinkage's topology
catalog).
To build a custom mechanism, declare its topology (nodes + edges) and dimensions separately:
from math import tau
import leggedsnake as ls
from leggedsnake import (
HypergraphLinkage, Node, Edge, NodeRole,
Dimensions, DriverAngle, Walker,
)
hg = HypergraphLinkage(name="MyWalker")
for node_id, role in [
("frame", NodeRole.GROUND), ("frame2", NodeRole.GROUND),
("crank", NodeRole.DRIVER),
("upper", NodeRole.DRIVEN), ("foot", NodeRole.DRIVEN),
]:
hg.add_node(Node(node_id, role=role))
for edge in [
("frame_crank", "frame", "crank"), ("frame2_upper", "frame2", "upper"),
("crank_upper", "crank", "upper"),
("crank_foot", "crank", "foot"), ("upper_foot", "upper", "foot"),
]:
hg.add_edge(Edge(*edge))
dims = Dimensions(
node_positions={
"frame": (0, 0), "frame2": (2, 0),
"crank": (1, 0), "upper": (1, 2), "foot": (1, 3),
},
edge_distances={
"frame_crank": 1.0, "frame2_upper": 2.24,
"crank_upper": 2.0, "crank_foot": 3.16, "upper_foot": 1.0,
},
driver_angles={"crank": DriverAngle(angular_velocity=-tau / 12)},
)
walker = Walker(hg, dims, name="My Walker")
walker.add_opposite_leg(axis_x=1.0) # mirror for left/right pair
walker.add_legs(1) # add a phase-offset copy
ls.video(walker)| Capability | Entry points |
|---|---|
| Build mechanisms | Walker, HypergraphLinkage, Walker.from_strider/jansen/klann/chebyshev/watt/catalog |
| Kinematic fitness | leggedsnake.utility.stride, leggedsnake.utility.step |
| Physics simulation | World, video, all_linkages_video, video_debug |
| Dynamic fitness | DistanceFitness, EfficiencyFitness, StrideFitness, StabilityFitness, CompositeFitness |
| Single-objective GA | GeneticOptimization, genetic_algorithm_optimization |
| Multi-objective (NSGA) | nsga_walking_optimization, NsgaWalkingConfig |
| Topology co-design | topology_walking_optimization, optimize_walking_mechanism |
| Gait & stability | analyze_gait, StabilityTimeSeries, compute_tip_over_margin |
| Export | to_urdf (ROS), save_walker (JSON), save_walker_svg |
| Plotting | plot_pareto_front, plot_gait_diagram, plot_foot_trajectories, plot_optimization_dashboard |
Start with the numbered notebooks — they walk through the full pipeline end to end:
examples/01_walkers_gallery.ipynb— build and inspect the classical linkages.examples/02_physics_and_fitness.ipynb— physics simulation and fitness evaluation.examples/03_genetic_optimization.ipynb— evolve a walker with the genetic algorithm.examples/04_multi_objective_and_gait.ipynb— NSGA Pareto fronts plus gait / stability analysis.
The scripted examples cover specific mechanisms and full pipelines:
strider.py (PSO + GA on the Strider),
theo_jansen.py,
klann_linkage.py,
chebyshev_linkage.py,
simple_fourbar.py,
simple_walker.py,
optimization_pipeline.py,
compare_linkages.py.
The two GIFs above are regenerated deterministically by
examples/generate_readme_gifs.py
(matplotlib PillowWriter, headless).
- Visualize early and often. Every optimizer will hand you a linkage with a better score; only the animation tells you whether it walks the way you wanted.
- Don't start from a hand-tuned optimum. A random starting population is more robust against collapsing into a nearby suboptimum.
- Exploit symmetry. A Strider half-leg has the same kinematic stride as
the full mechanism and evaluates an order of magnitude faster. Use
kinematic PSO on the reduced problem, then hand off the winner to a
dynamic GA on the full mechanism.

- Checkpoint long runs.
GeneticOptimization(..., startnstop="run.json")resumes automatically on the next launch. - Wrap optimization scripts in
if __name__ == "__main__":— the GA and NSGA optimizers spawn worker processes.
Contributions, feature requests, and "look at this weird walker" submissions are all welcome. See CONTRIBUTING.md for the developer workflow, or drop by the GitHub discussions. A star or a link to your favourite walker also helps.
- Documentation: hugofara.github.io/leggedsnake
- Source: HugoFara/leggedsnake
- PyPI: leggedsnake
- Changelog: CHANGELOG.md

