Add support for custom physics step functions in StudioApp.

This change allows users to provide an optional `step_fn` callable to `StudioApp.update` and `StudioApp.update_from_viewer`. When provided, this function is called to advance the physics simulation instead of the default `step_control.advance`.

PiperOrigin-RevId: 925439890
Change-Id: Ia833503f6dd0c22fb8d75af6c5f06757496853e5
This commit is contained in:
Matija Kecman
2026-06-02 10:27:01 -07:00
committed by Copybara-Service
parent 0e4749501c
commit 49211a05c5
@@ -34,6 +34,7 @@ See the sample/ folder for usage examples.
import os
import sys
import typing
import mujoco
from mujoco.experimental.studio import parser
@@ -45,6 +46,9 @@ import numpy as np
from mujoco.experimental.dear_imgui import dear_imgui as imgui
# Type alias for a custom physics step function.
StepFn = typing.Callable[[mujoco.MjModel, mujoco.MjData], None]
def load_model_from_file(
model_path: str,
@@ -246,13 +250,28 @@ class StudioApp:
else:
mujoco.mjv_applyPerturbPose(self.model, self.data, perturb, 1)
def update_physics(self, perturb: mujoco.MjvPerturb) -> None:
"""Applies the purturbations and advances the physics."""
def update_physics(
self,
perturb: mujoco.MjvPerturb,
*,
step_fn: StepFn | None = None,
) -> None:
"""Applies the perturbations and advances the physics.
Args:
perturb: The MuJoCo perturbation object.
step_fn: Optional custom physics step function. When provided, it is
called instead of ``step_control.advance``. The function receives
``(model, data)`` and should step the simulation in-place.
"""
self.apply_perturb(perturb)
advance_status = self.step_control.advance(self.model, self.data)
if advance_status == sim.StepStatus.AUTO_RESET:
self.reset_physics()
if step_fn is not None:
step_fn(self.model, self.data)
else:
advance_status = self.step_control.advance(self.model, self.data)
if advance_status == sim.StepStatus.AUTO_RESET:
self.reset_physics()
def reset_physics_gui(self) -> None:
"""GUI to Reset the physics i.e., the reset button."""
@@ -272,13 +291,15 @@ class StudioApp:
camera: mujoco.MjvCamera,
vis_options: mujoco.MjvOption,
perturb: mujoco.MjvPerturb,
*,
drop_file: str = '',
step_fn: StepFn | None = None,
) -> bool:
"""Update the simulation and handle user input.
Handles mouse input to compute perturbations or camera motion.
Handles keyboard input e.g., for keybindings or camera motion.
Applies the purturbations and advances the physics.
Applies the perturbations and advances the physics.
The argument objects are provided by the viewer.
Args:
@@ -287,6 +308,8 @@ class StudioApp:
perturb: The MuJoCo perturbation object.
drop_file: Path of a file dropped into the viewer window. If non-empty the
current model is replaced with the dropped file.
step_fn: Optional custom physics step function. When provided, it is
called instead of ``step_control.advance``.
Returns:
Whether the application should continue running, this is a
@@ -297,16 +320,31 @@ class StudioApp:
self.handle_mouse_events(camera, vis_options, perturb)
self.handle_keyboard_events(camera, vis_options)
self.update_physics(perturb)
self.update_physics(perturb, step_fn=step_fn)
return self.is_running()
def update_from_viewer(self, viewer: viewer_protocol.Viewer) -> bool:
"""Convenience wrapper around update() that unpacks viewer attributes."""
def update_from_viewer(
self,
viewer: viewer_protocol.Viewer,
*,
step_fn: StepFn | None = None,
) -> bool:
"""Convenience wrapper around update() that unpacks viewer attributes.
Args:
viewer: A viewer conforming to the Viewer protocol.
step_fn: Optional custom physics step function. When provided, it is
called instead of ``step_control.advance``.
Returns:
Whether the application should continue running.
"""
return self.update(
viewer.camera,
viewer.vis_options,
viewer.perturb,
drop_file=viewer.get_drop_file(),
step_fn=step_fn,
)
def build_gui(