Skip to content

Changelog

Version 2.1.1

Additions

  • Added NeuroMechFlyWithSeparateTrochanterFemur, a NeuroMechFly variant in which each leg's trochanter and femur are separate bodies (with separate meshes) rather than a single fused trochanterfemur body. This enables valid segmentation maps for these segments. We tentatively plan to make this the only supported NeuroMechFly body in FlyGym v3 (#313).
  • Added GPUSimulation.set_state(), get_base_linear_velocity(), and get_base_angular_velocity() (#305).
  • Added forward() to both Simulation and GPUSimulation to recompute derived quantities (e.g. body positions) without stepping physics.
  • Lazy getters in GPUSimulation now accept an optional dst array to write into an existing buffer instead of allocating a new one (#301).
  • Added World.fly and Simulation.fly shorthands for single-fly setups, Simulation.fly_lookup, and BaseFly.n_bodysegs, n_jointdofs, and get_n_actuated_jointdofs() (#301).
  • Added Retina.fisheye_image_to_hex_pxls(), which fuses fisheye correction and ommatidia pooling into a single pass. CPU vision now uses it, making this step about 2× faster (#308, #315; thanks @Cr0me1ve).
  • Added scripts/export_model_for_flexik.py to export the fly body plan for FlexIK.
  • Added trajectory recording, which decouples the number of simulated worlds from the number of rendered ones. Simulation.set_trajectory_recorder() and GPUSimulation.set_trajectory_recorder() attach a recorder that stores only the kinematic state (qpos and mocap poses) at the video frame rate. The resulting RecordedTrajectory objects can be saved, sub-selected, and rendered to video afterwards with flygym.recording.render_trajectories() (CPU) or flygym.warp.recording.render_trajectories_gpu() (GPU batch rendering). A recorder can be used together with a renderer, and rendering a recorded trajectory gives exactly the same frames as rendering live. See the new tutorial 8. Recording and rendering trajectories (#296, #319).

Changes

  • Renderers now draw a private copy of the simulation state in which body, camera, light, and tendon positions are recomputed from the current qpos. Previously, they drew the positions computed at the start of the last physics step, so frames lagged by one timestep. The simulation state itself is not modified (#319).
  • WarpGPUBatchRenderer now renders only the selected worlds instead of all simulated worlds, so rendering a few out of many worlds is much faster and uses less GPU memory (#319).
  • GPUSimulation.reset() now resets mjw_data in place using MuJoCo Warp's reset_data_keyframe(), instead of reallocating the GPU-side model and data on every reset (#302). Note that, as with Simulation.reset(), derived kinematic quantities (xpos, xquat, …) are not recomputed on reset; call forward() if you need them.
  • Remote assets are now stored as tarballs with SHA-256 checksums. FlyGym verifies the checksum after download and moves the extracted files into the cache atomically, which also makes concurrent downloads safe (#303).
  • GPUSimulation now uses a constraint solver tolerance of 1e-5 by default, configurable via the new solver_tolerance argument. Since MuJoCo Warp 3.10, which fixed a float32 precision issue in its convergence check, MuJoCo Warp's default tolerance (1e-6) causes a small fraction of worlds to stall until they hit the solver iteration limit, printing "solver iterations limit reached" warnings. Because all worlds wait for the slowest one, this slowed down GPU simulation by ~25% with 1000 worlds. With 1e-5, these stalls are essentially gone, while joint-angle tracking is unchanged within GPU run-to-run variability.
  • GPUSimulation now disables MuJoCo Warp's solver and line search iteration limit warnings, which are printed once per world per step and could flood the output. This follows MuJoCo Playground. Warnings about contact and constraint buffer overflows are still shown.
  • The WASM NeuroMechFly Live game is easier to play: stronger leg adhesion, faster turning, gamepad leg buttons matching the physical left/right layout, and correct colors for split trochanter/femur geoms (#314).

Bug fixes

  • Fixed body-segment position/orientation getters returning wrong values when MuJoCo's fusestatic compiler option merges bodies and invalidates body IDs (#312).
  • Fixed the ground texture not showing in GPU batch rendering after the MuJoCo Warp 3.16 upgrade. MuJoCo Warp now maps textures on finite planes like the CPU renderer does, so FlyGym's workaround of scaling texrepeat down by 1000× was removed.
  • Fixed GPUSimulation dropping the timestep override when GPU batch rendering is enabled, which recompiles the model.
  • Fixed GPU benchmark and tutorial timings, which stopped the timer before queued GPU work finished and therefore overstated throughput by up to ~50% (#311; thanks @AdeliyaLeleytner).

Housekeeping

  • Bumped MuJoCo and MuJoCo Warp versions to 3.16.
  • FlyGym now supports Python 3.12 – 3.15. The rl extra is not yet installable on Python 3.15 because PyTorch does not ship wheels for it.
  • Updated Warp kernel type hints to the PEP 484-compliant wp.array[dtype] syntax and replaced string array dimensions with TypeVars defined in flygym.utils.typing (#304).
  • Unit tests can now run in parallel via pytest-xdist (pytest -n auto).

Contributors

@sibocw, @stimpfli, @AdeliyaLeleytner, @Cr0me1ve

Version 2.1.0

API-breaking changes

FlyGym 2.1.0 drops the dm-control PyMJCF backend in favour of MuJoCo's native MjSpec API (#282, #285). Model elements (cameras, materials, bodies, joints, …) are now mujoco._specs.Mjs* objects rather than dm_control.mjcf wrappers. The following changes may require updates:

  • element.full_identifier → element.name: PyMJCF exposed full_identifier to return the fully-scoped compiled name (e.g. "fly/trackcam"). MjSpec mutates .name in place when a child spec is attached with a prefix, so .name already returns the prefixed name after world.add_fly(). Replace every occurrence of .full_identifier with .name.

    Migration: If you stored references to spec elements (e.g. the MjsCamera returned by fly.add_tracking_camera()) and later used element.full_identifier as a dict key or to look up the compiled ID, replace it with element.name:

    # Before (PyMJCF)
    cam = fly.add_tracking_camera(name="body_cam")
    world.add_fly(fly, ...)
    frames = renderer.frames[cam.full_identifier]  # AttributeError under MjSpec
    
    # After (MjSpec)
    cam = fly.add_tracking_camera(name="body_cam")
    world.add_fly(fly, ...)
    frames = renderer.frames[cam.name]  # "fly_name/body_cam" after attachment
    

    No other change is needed: cam.name already returns the same prefixed string that cam.full_identifier used to return, because MjSpec mutates held element references in place when the child spec is attached.

  • spec.asset.find_all("material") → spec.materials: The PyMJCF tree-traversal helper is gone. MjSpec exposes typed collection properties (spec.materials, spec.bodies, spec.joints, etc.) directly on the MjSpec object.

  • add_tracking_camera placement is now relative to the fly's root segment: Fly.add_tracking_camera() now adds the camera inside the fly's root segment (thorax) body so that MuJoCo's track mode actually follows the fly as it moves. As a result, pos_offset (and rotation) are interpreted in the root segment's body frame rather than in world coordinates as before, and the default pos_offset changed from (0, -7.5, 6) to (-0.5, -7.5, 5). The same pos_offset now yields the same camera position relative to the fly in every context (FlatGroundWorld, TetheredWorld, or a fly compiled on its own for preview_model).

    Migration: Any hard-coded pos_offset values must be re-tuned. A value that previously positioned the camera in the world frame now positions it relative to the root segment, which in the neutral pose sits roughly (0.5, 0, 1.3) mm from the fly's attachment point plus the spawn height. The camera will therefore appear higher and shifted toward the head unless you adjust the offset (to reproduce the old framing, subtract the root segment's rest position from your previous world-frame offset).

  • World.world_dof_neutral_states is now a set, not a dict: Under MjSpec, the neutral pose of world-level DoFs (e.g. the free joints by which flies are attached) is read from the compiled model's qpos0 rest configuration. The explicit per-DoF state values that World.world_dof_neutral_states used to map to are therefore no longer needed, so the attribute is now a set[str] of DoF (joint) names rather than a dict[str, list[float]]. Relatedly, World._attach_fly_mjcf now returns a set[str] of the DoF names it created instead of a name → state mapping.

    Migration: If you read this attribute, iterate it as a set of names (iterating a dict already yielded its keys, so plain for name in world.world_dof_neutral_states is unaffected); subscripting it (world.world_dof_neutral_states[name]) no longer works. If you implement a custom World subclass, return a set[str] from _attach_fly_mjcf.

Additions

  • [Experimental feature] Integrated FlyMimic (Özdil et al., 2026): an imitation-learning task and a muscle-actuated musculoskeletal fly model (#270). A tutorial is also added.
  • [Experimental feature] Integrated the FlyBody model (#272). Two tutorials are also added.
  • Added an in-browser interactive viewer using WebAssembly (#273).
  • Moved the NeuroMechFly Live game into FlyGym and reimplemented it to run in-browser using WebAssembly (#277, #278).
  • Added standalone kinematic-replay scripts for end-to-end testing and profiling (#283). A tutorial on profiling is also added.
  • Added Docker installation instructions.
  • Tutorial are now runnable in Google Colab (#284).
  • Bumped MuJoCo and MuJoCo Warp versions to 3.9 (#281, #291).
  • FlyGym now supports Python 3.12 – 3.14.
  • Moved large asset files to an S3 bucket and made FlyGym download them lazily (#280, #292).
  • Added unit tests that execute the tutorial notebooks and check for errors (#286).

Bug fixes

  • Fixed geom fitting that forced claw (tarsus5) geoms to capsules even when UNMODIFIED (#274).
  • Fixed packaging so the flygym package's bundled assets (simplified meshes, poses, configs, and the musculoskeletal MJCF) are reliably shipped in the built wheel/sdist. They are now declared explicitly under [tool.setuptools.package-data] rather than relying on include-package-data, which silently included nothing on a clean build (no MANIFEST.in / setuptools-scm).

Housekeeping

  • Switch from MkDocs to ProperDocs, a fork of MkDocs 1.x.x that will continue to support for plugins like Material for MkDocs (see this note).
  • Added packaging tests (tests/core/test_packaging.py) that build a wheel and assert every bundled asset is shipped (large lazily-downloaded meshes excluded), with flygym_demo assets checked separately.

Version 2.0.2

API-breaking changes

  • Adhesion control range: Fly.add_leg_adhesion() and Simulation.set_leg_adhesion_states() now use a normalised [0, 1] control range (0 = fully released, 1 = full gain) instead of the previous [1, 100] range. Boolean 0/1 adhesion commands that previously fell outside the valid range will now work as expected.
  • get_ground_contact_info() first return value: renamed from contact_active to contact_found and now returns the raw MuJoCo contact-sensor found channel (a floating-point value) rather than a booleanized flag. Code that compared contact_active == 1 should now check contact_found > 0.
  • ContactParams.get_solimp_tuple(): now returns a 5-element tuple (was 4). The previously missing solver_impedance_min2max_width field is now included as the third element ([dmin, dmax, width, midpoint, power]), matching MuJoCo's solimp specification. Any code that destructured or indexed the old 4-tuple needs to be updated.

Bug fixes

  • ContactParams.get_solimp_tuple() previously returned a 4-element tuple, causing MuJoCo to silently use a default value for the transition-width parameter. It now returns the correct 5-element tuple.

Additions

  • Added vision to CPU-based simulation. Addition of vision to GPU-based simulation will be deferred for now.
  • Added GappedTerrainWorld, BlocksTerrainWorld, and MixedTerrainWorld to flygym.compose.
  • Added Simulation.get_bodysegment_contact_forces() for querying ground-contact forces on arbitrary body segments.
  • Added locomotion controller examples (CPGController, RuleBasedController, HybridController, HybridTurningController) in flygym_demo.complex_terrain. Added tutorials accordingly.

Housekeeping

  • Switched formatter from Black to Ruff

Version 2.0.1 (2026–04–16)

Bug fixes

  • Include sample Spotlight data in shipped flygym_demo build (#259).
  • Fix dimension check for 3D rotations specified in axisangle format.
  • Fix bugs with creating AnatomicalJoint and JointDOF objects from strings.

Additions

  • New API to add sites to fly model and read out their positions
  • Additional tutorial: 1bis. Advanced model composition

Housekeeping

  • Add changelog and correct info in LICENSE

Contributors

@sibocw, @djsamseng

Version 2.0.0 (2026–04–02)

Full rewrite — not backward-compatible

This version is a full rewrite of the codebase and it is not backward-compatible. See this page for more info.

Version 1.2.1

  • Improve vision readout computing time by avoiding redundant memory copies
  • Bump NumPy version to 2.* and end official support for macOS 13

Version 1.2.0

API-breaking changes

  • Enhance camera logic. See the Camera API reference for details.
  • Use the FlyVis package as published on PyPI. Users should now use flyvis instead of flyvision as module name.

Other changes

  • Add NeuroMechFly game for outreach (used at EPFL Scientastic! Days, etc.).
  • Transition from setup.py to pyproject.toml, specifically using Poetry as the build backend. Users can now install FlyGym with the exact versions of dependencies used by the developers by running poetry install from the root directory. This will create a virtual environment with the correct dependencies as specified in the included poetry.lock file, which is version-tracked as a part of the FlyGym Github repository.
  • Add VS Code devcontainer support.
  • Remove support for Python 3.9.

Version 1.1.0

  • Added cardinal direction sensing (vectors describing +x, +y, +z of the fly) to the observation space.
  • Removed legacy spawn orientation preprocessing: Previously, pi/2 was subtracted from the user-specified spawn orientation on the x-y plane. This was to make the behavior consistent with a legacy version of NeuroMechFly. This behavior is no longer desired; from this version onwards, the spawn orientation is used as is.
  • Strictly fixed the required MuJoCo version to 3.2.3, and dm_control version to 1.0.23. This is to prevent API-breaking changes in future versions of these libraries from affecting FlyGym. FlyGym maintainers will periodically check for compatibility with newer versions of these libraries.
  • Changed flip detection method: Previously, flips are reported when all legs reliably lose contact with the ground. Now, we simply check if the z component of the "up" cardinal vector is negative. Additionally, the detect_flip parameter of Fly is now deprecated; flips are always detect and reported.
  • Allowed different sets of DoFs to be monitored vs. actuated. Previously, the two sets are always the same.
  • From this version onwards, we will use EffVer as the versioning policy. The version number will communicate how much effort we expect a user will need to spend to adopt the new version. While we previously tried to adhere to the stricter SemVer, we found that it was not effective because many core dependencies of FlyGym (e.g., MuJoCo, NumPy, and Python itself) do not use SemVer.

Version 1.0.1

Fixed minor bugs related to the set of DoFs in the predefined poses, and to rendering at extremely high frequencies. Fixed outdated class names and links in the docs. In addition, contact sensor placements used by the hybrid turning controller are now added to the preprogrammed module.

Version 1.0.0

In spring 2024, NeuroMechFly was used, for the second time, in a course titled Controlling behavior in animals and robots at EPFL. At the same time, we revised the NeuroMechFly v2 manuscript. In these processes, we significantly improved the FlyGym package, added new functionalities, and incorporated changes as we received feedback from the students. These enhancements are released as FlyGym version 1.0.0. This release is not backward compatible; please refer to the tutorials and API references for more information. The main changes are:

Major API changes

  • The NeuroMechFly class is split into Fly, a class that represents the fly, and Simulation, a class that represents the simulation, which can potentially contain multiple flies.
  • The Parameters class is deprecated. Parameters related to the fly (such as joint parameters, actuated DoFs, etc.) should be set directly on the Fly object. Parameters related to the simulation (such as the time step, the render cameras, etc.) should be set directly on the Simulation object.
  • A new Camera class is introduced. A simulation can contain multiple cameras.

New examples

See examples:

  • Path integration based on ascending mechanosensory feedback.
  • Head stabilization based on ascending mechanosensory feedback.
  • Navigating a complex plume, simulated separately in a fluid mechanics simulator.
  • Following another fly using a realistic, connectome-constrained neural network that processes visual inputs.

Version 0.2.5

Modify model file to make it compatible with MuJoCo 3.1.1. Disable Python 3.7 support accordingly.

Version 0.2.4

Set MuJoCo version to 2.3.7. Documentation updates.

Version 0.2.3

Various bug fixes. Improved placement of the spherical treadmill in the tethered environment.

Version 0.2.2

Changed default joint kp and adhesion forces to those used in the controller comparison task. Various minor bug fixes. Documentation updates.

Version 0.2.1

Simplified class names: NeuroMechFlyMuJoCo → NeuroMechFly, MuJoCoParameters → Parameters. Minor documentation updates.

Version 0.2.0

The current base version — major API change from 0.1.x.

Version 0.1.x

Versions used during the initial development of NeuroMechFly v2.

Unversioned

Versions used for the Spring 2023 offering of BIOENG-456 Controlling Behavior in Animals and Robots course at EPFL.