#
# ISC License
#
# Copyright (c) 2025, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import tempfile
import subprocess
import os
import platform
from typing import List, Union
import numpy as np
from Basilisk import __path__
bskPath = __path__[0]
[docs]
def visualize(
time: np.ndarray,
qpos: np.ndarray,
modelFileOrScene: Union[str, "MJScene"],
speedUp: float = 1,
track: str = "",
files: List[str] = [],
):
"""Calls a tool to visualize the movement of multi-body systems
simulated through ``MJScene``.
To be able to use this function, one must have built Basilisk with
the flag: '--mujocoReplay True'.
The arguments ``time`` and ``qpos`` are typically the product of an
``MJScene`` state recorder::
scene: MJScene = ...
stateRecorder = scene.stateOutMsg.recorder()
scSim.AddModelToTask("myTask", stateRecorder)
# Run simulation
mujoco.visualize(
stateRecorder.times(),
np.squeeze(stateRecorder.qpos),
scene,
...
)
Args:
time (np.ndarray): A one-dimensional array with the times
at which the scene positions are recorded (in seconds)
qpos (np.ndarray): A two-dimensional array with the position
of the multi-body in general coordinates. The length of
the first dimension must match the length of the ``time``
argument.
modelFileOrScene (Union[str, MJScene]): Path to a file
describing the MuJoCo model (can be XML or compiled ``.mjb``).
Alternatively, this can be an ``MJScene`` object, from
which a model file will generated.
speedUp (float, optional): Factor with which to speed up
simulation replay. =1 is real time, =2 is double speed,
=0.2 is five times slower, etc.. Defaults to 1.
track (str, optional): Name of the body to track during visualization,
by default, the first free body in the simulation. If 'none',
camera is moved freely by the user. Defaults to "".
files (List[str], optional): Paths to extra files to expose to
MuJoCo, for example to load meshes. Defaults to [].
"""
tqpos = np.column_stack([time, qpos])
with tempfile.NamedTemporaryFile("w", delete=False) as fqpos:
np.savetxt(fqpos, tqpos)
if isinstance(modelFileOrScene, str):
modelFile = modelFileOrScene
fmodel = None
else:
with tempfile.NamedTemporaryFile("w", delete=False) as fmodel:
modelFileOrScene.saveToFile(fmodel.name)
modelFile = fmodel.name
if platform.system() == "Windows":
script_fn = "replay.exe"
else:
script_fn = "replay"
script = os.path.join(bskPath, rf"utilities/mujocoUtils/bin/{script_fn}")
if not os.path.exists(script):
raise RuntimeError(f"Couldn't find the visualization tool at '{script}'."
" Did you build Basilisk with the flag "
"'--mujocoReplay True'? If so, did this tool build correctly?")
args = [script, "--model", modelFile, "--state", fqpos.name]
args.extend(["--speed", str(speedUp)])
if track:
args.extend(["--track", track])
for file in files:
args.extend(["--file", file])
try:
subprocess.check_output(args)
except subprocess.CalledProcessError as e:
print(e)
os.unlink(fqpos.name)
if fmodel is not None:
os.unlink(fmodel.name)