Merge pull request #419 from aftersomemath:simulate-python
PiperOrigin-RevId: 487496285 Change-Id: I17d15bd2a3886e5ce1631f4eb78afa94830d08cc
This commit is contained in:
@@ -6,6 +6,12 @@ Changelog
|
||||
Upcoming version (not yet released)
|
||||
-----------------------------------
|
||||
|
||||
Python bindings
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- The ``simulate`` GUI is now available through the ``mujoco`` Python package. See :ref:`documentation<PyGUI>` for
|
||||
details. (Contribution by `Levi Burner <https://github.com/aftersomemath>`_.)
|
||||
|
||||
General
|
||||
^^^^^^^
|
||||
|
||||
|
||||
@@ -152,6 +152,38 @@ Minimal example
|
||||
mujoco.mj_step(model, data)
|
||||
print(data.geom_xpos)
|
||||
|
||||
.. _PyGUI:
|
||||
|
||||
Interactive visualizer
|
||||
----------------------
|
||||
|
||||
MuJoCo's interactive GUI (also known as the ``simulate`` application) is available as part of the Python package.
|
||||
Three distinct use cases are supported:
|
||||
|
||||
- Launching as a standalone application:
|
||||
|
||||
* ``python -m mujoco.simulate`` launches an empty visualization session, where a model can be loaded by drag-and-drop.
|
||||
* ``python -m mujoco.simulate --mjcf=/path/to/some/mjcf.xml`` launches a visualization session for the specified
|
||||
model file.
|
||||
|
||||
- Launching from a Python program/script -- import the module via ``from mujoco import simulate`` and launch the GUI
|
||||
using one of the following invocations:
|
||||
|
||||
* ``simulate.launch()`` launches an empty visualization session, where a model can be loaded by drag-and-drop.
|
||||
* ``simulate.launch(model)`` launches a visualzation session for the given ``mjModel`` where the visualizer
|
||||
internally creates its own instance of ``mjData``
|
||||
* ``simulate.launch(model, data)`` is the same as above, except that the visualizer operates directly on the given
|
||||
``mjData`` instance -- upon exit the ``data`` object will have been modified.
|
||||
|
||||
- Launching from an interactive Python session (aka REPL): when working interactively either in a ``python`` or
|
||||
``ipython`` shell, the visualizer can be launched in a "passive" mode via ``simulate.launch_repl(model, data)``, where
|
||||
the user remains in full control of modifying or stepping the physics. In this mode, the user can interact with the
|
||||
visualizer using the mouse and keyboard as usual, however the physics will be frozen unless the user explicitly calls
|
||||
``mj_step`` (or perform any other modification of the ``mjData`` or ``mjModel``) in the REPL terminal. Note that since
|
||||
the visualizer does not modify ``mjData`` in this mode, mouse-drag perturbations will not work unless the user
|
||||
explicitly handles incoming GUI perturbation events in the REPL session.
|
||||
|
||||
|
||||
.. _PyNamed:
|
||||
|
||||
Named access
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
include LICENSE *.md
|
||||
recursive-include mujoco *.h *.cc CMakeLists.txt
|
||||
recursive-include mujoco *.h *.cc *.mm CMakeLists.txt Simulate*.cmake
|
||||
recursive-include cmake *.cmake
|
||||
|
||||
@@ -53,6 +53,9 @@ cp "${package_dir}"/../LICENSE .
|
||||
mkdir cmake
|
||||
cp "${package_dir}"/../cmake/*.cmake cmake
|
||||
|
||||
# Copy over Simulate source code.
|
||||
cp -r "${package_dir}"/../simulate mujoco
|
||||
|
||||
python setup.py sdist --formats=gztar
|
||||
tar -tf dist/mujoco-*.tar.gz
|
||||
popd
|
||||
|
||||
@@ -119,6 +119,7 @@ if(NOT TARGET mujoco)
|
||||
)
|
||||
set_target_properties(mujoco PROPERTIES IMPORTED_SONAME "${MUJOCO_SONAME}")
|
||||
endif()
|
||||
add_library(mujoco::mujoco ALIAS mujoco)
|
||||
endif()
|
||||
|
||||
# ==================== ABSEIL ==================================================
|
||||
@@ -190,6 +191,9 @@ findorfetch(
|
||||
)
|
||||
|
||||
# ==================== MUJOCO PYTHON BINDINGS ==================================
|
||||
set(SIMULATE_BUILD_EXECUTABLE OFF)
|
||||
set(SIMULATE_GLFW_DYNAMIC_SYMBOLS ON)
|
||||
add_subdirectory(simulate)
|
||||
|
||||
add_subdirectory(util)
|
||||
|
||||
@@ -376,6 +380,14 @@ target_link_libraries(
|
||||
structs_header
|
||||
)
|
||||
|
||||
mujoco_pybind11_module(_simulate simulate.cc)
|
||||
target_link_libraries(
|
||||
_simulate
|
||||
PRIVATE mujoco
|
||||
mujoco::libsimulate
|
||||
raw
|
||||
structs_header)
|
||||
|
||||
set(LIBRARIES_FOR_WHEEL
|
||||
"$<TARGET_FILE:_callbacks>"
|
||||
"$<TARGET_FILE:_constants>"
|
||||
@@ -384,6 +396,7 @@ set(LIBRARIES_FOR_WHEEL
|
||||
"$<TARGET_FILE:_functions>"
|
||||
"$<TARGET_FILE:_render>"
|
||||
"$<TARGET_FILE:_rollout>"
|
||||
"$<TARGET_FILE:_simulate>"
|
||||
"$<TARGET_FILE:_structs>"
|
||||
"$<TARGET_FILE:mujoco>"
|
||||
)
|
||||
@@ -411,6 +424,7 @@ if(MUJOCO_PYTHON_MAKE_WHEEL)
|
||||
_functions
|
||||
_render
|
||||
_rollout
|
||||
_simulate
|
||||
_structs
|
||||
mujoco
|
||||
)
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright 2022 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <simulate.h>
|
||||
#include "raw.h"
|
||||
#include "structs.h"
|
||||
#include <pybind11/detail/common.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
namespace mujoco::python {
|
||||
namespace {
|
||||
PYBIND11_MODULE(_simulate, pymodule) {
|
||||
namespace py = ::pybind11;
|
||||
using SimulateMutex = decltype(mujoco::Simulate::mtx);
|
||||
|
||||
py::class_<SimulateMutex>(pymodule, "SimulateMutex")
|
||||
.def(
|
||||
"__enter__", [](SimulateMutex& mtx) { mtx.lock(); },
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def(
|
||||
"__exit__",
|
||||
[](SimulateMutex& mtx, py::handle, py::handle, py::handle) {
|
||||
mtx.unlock();
|
||||
},
|
||||
py::call_guard<py::gil_scoped_release>());
|
||||
|
||||
py::class_<mujoco::Simulate>(pymodule, "Simulate")
|
||||
.def(py::init<>())
|
||||
.def(
|
||||
"renderloop",
|
||||
[](mujoco::Simulate& simulate) { simulate.renderloop(); },
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def(
|
||||
"load",
|
||||
[](mujoco::Simulate& simulate, MjModelWrapper& m, MjDataWrapper& d) {
|
||||
simulate.load("", m.get(), d.get());
|
||||
},
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def("applyposepertubations", &mujoco::Simulate::applyposepertubations,
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def("applyforceperturbations",
|
||||
&mujoco::Simulate::applyforceperturbations,
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
|
||||
.def(
|
||||
"lock",
|
||||
[](mujoco::Simulate& simulate) -> SimulateMutex& {
|
||||
return simulate.mtx;
|
||||
},
|
||||
py::call_guard<py::gil_scoped_release>(),
|
||||
py::return_value_policy::reference)
|
||||
.def_readonly("ctrlnoisestd", &mujoco::Simulate::ctrlnoisestd,
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def_readonly("ctrlnoiserate", &mujoco::Simulate::ctrlnoiserate,
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
|
||||
.def_readonly("real_time_index", &mujoco::Simulate::realTimeIndex,
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def_readwrite("speed_changed", &mujoco::Simulate::speedChanged,
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def_readwrite("measured_slowdown", &mujoco::Simulate::measuredSlowdown,
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def_readonly("refresh_rate", &mujoco::Simulate::refreshRate,
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
|
||||
.def_readonly("busywait", &mujoco::Simulate::busywait,
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def_readonly("run", &mujoco::Simulate::run,
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
|
||||
.def_property_readonly(
|
||||
"exitrequest",
|
||||
[](mujoco::Simulate& simulate) {
|
||||
return simulate.exitrequest.load();
|
||||
},
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
|
||||
.def_property_readonly(
|
||||
"uiloadrequest",
|
||||
[](mujoco::Simulate& simulate) {
|
||||
return simulate.uiloadrequest.load();
|
||||
},
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def(
|
||||
"uiloadrequest_decrement",
|
||||
[](mujoco::Simulate& simulate) {
|
||||
simulate.uiloadrequest.fetch_sub(1);
|
||||
},
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
|
||||
.def_property(
|
||||
"droploadrequest",
|
||||
[](mujoco::Simulate& simulate) {
|
||||
return simulate.droploadrequest.load();
|
||||
},
|
||||
[](mujoco::Simulate& simulate, bool droploadrequest) {
|
||||
simulate.droploadrequest.store(droploadrequest);
|
||||
},
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def_property_readonly(
|
||||
"dropfilename",
|
||||
[](mujoco::Simulate& simulate) -> std::string {
|
||||
return simulate.dropfilename;
|
||||
},
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def_property_readonly(
|
||||
"filename",
|
||||
[](mujoco::Simulate& simulate) -> std::string {
|
||||
return simulate.filename;
|
||||
},
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.def_property(
|
||||
"load_error",
|
||||
[](mujoco::Simulate& simulate) -> std::string {
|
||||
return simulate.loadError;
|
||||
},
|
||||
[](mujoco::Simulate& simulate, const std::string& error) {
|
||||
std::strncpy(simulate.loadError, error.c_str(),
|
||||
simulate.kMaxFilenameLength);
|
||||
});
|
||||
|
||||
pymodule.def("setglfwdlhandle", [](std::uintptr_t dlhandle) {
|
||||
mujoco::setglfwdlhandle(reinterpret_cast<void*>(dlhandle));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco::python
|
||||
@@ -0,0 +1,266 @@
|
||||
# Copyright 2022 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""Python bindings for the Simulate GUI."""
|
||||
|
||||
import atexit
|
||||
import code
|
||||
import inspect
|
||||
import math
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import typing
|
||||
from typing import Callable, Optional
|
||||
|
||||
import glfw
|
||||
import mujoco
|
||||
from mujoco import _simulate
|
||||
import numpy as np
|
||||
|
||||
if not glfw._glfw: # pylint: disable=protected-access
|
||||
raise RuntimeError('GLFW dynamic library handle is not available')
|
||||
else:
|
||||
_simulate.setglfwdlhandle(glfw._glfw._handle) # pylint: disable=protected-access
|
||||
|
||||
# Logarithmically spaced realtime slow-down coefficients (percent).
|
||||
PERCENT_REALTIME = (
|
||||
100, 80, 66, 50, 40, 33, 25, 20, 16, 13,
|
||||
10, 8, 6.6, 5, 4, 3.3, 2.5, 2, 1.6, 1.3,
|
||||
1, 0.8, 0.66, 0.5, 0.4, 0.33, 0.25, 0.2, 0.16, 0.13,
|
||||
0.1
|
||||
)
|
||||
|
||||
# Maximum time mis-alignment before re-sync.
|
||||
MAX_SYNC_MISALIGN = 0.1
|
||||
|
||||
# Fraction of refresh available for simulation.
|
||||
SIM_REFRESH_FRACTION = 0.7
|
||||
|
||||
CallbackType = Callable[[mujoco.MjModel, mujoco.MjData], None]
|
||||
|
||||
Simulate = _simulate.Simulate
|
||||
|
||||
|
||||
def _reload_from_file(simulate: Simulate, filename: str):
|
||||
"""Loads an MJCF model into the Simulate GUI."""
|
||||
try:
|
||||
m = mujoco.MjModel.from_xml_path(filename)
|
||||
except mujoco.FatalError as e:
|
||||
m = None
|
||||
simulate.load_error = str(e)
|
||||
|
||||
if m is not None:
|
||||
d = mujoco.MjData(m)
|
||||
simulate.load(m, d)
|
||||
mujoco.mj_forward(m, d)
|
||||
else:
|
||||
d = None
|
||||
|
||||
return m, d
|
||||
|
||||
|
||||
def _physics_loop(simulate: Simulate,
|
||||
m: Optional[mujoco.MjModel],
|
||||
d: Optional[mujoco.MjData]):
|
||||
"""Physics loop for the Simulate GUI, to be run in a separate thread."""
|
||||
ctrlnoise = None
|
||||
if m is not None:
|
||||
ctrlnoise = np.zeros((m.nu,))
|
||||
|
||||
# CPU-sim synchronization point.
|
||||
synccpu = 0.0
|
||||
syncsim = 0.0
|
||||
|
||||
# Run until asked to exit.
|
||||
while not simulate.exitrequest:
|
||||
if simulate.droploadrequest:
|
||||
simulate.droploadrequest = 0
|
||||
new_m, new_d = _reload_from_file(simulate, simulate.dropfilename)
|
||||
if new_m is not None:
|
||||
m = new_m
|
||||
d = new_d
|
||||
ctrlnoise = np.zeros((m.nu,))
|
||||
|
||||
if simulate.uiloadrequest:
|
||||
simulate.uiloadrequest_decrement()
|
||||
new_m, new_d = _reload_from_file(simulate, simulate.dropfilename)
|
||||
if new_m is not None:
|
||||
m = new_m
|
||||
d = new_d
|
||||
ctrlnoise = np.zeros((m.nu,))
|
||||
|
||||
# Sleep for 1 ms or yield, to let main thread run.
|
||||
if simulate.run != 0 and simulate.busywait != 0:
|
||||
time.sleep(0)
|
||||
else:
|
||||
time.sleep(0.001)
|
||||
|
||||
with simulate.lock():
|
||||
if m is not None:
|
||||
assert d is not None
|
||||
if simulate.run:
|
||||
# Record CPU time at start of iteration.
|
||||
startcpu = glfw.get_time()
|
||||
|
||||
elapsedcpu = startcpu - synccpu
|
||||
elapsedsim = d.time - syncsim
|
||||
|
||||
# Inject noise.
|
||||
if simulate.ctrlnoisestd != 0.0:
|
||||
# Convert rate and scale to discrete time (Ornstein–Uhlenbeck).
|
||||
rate = math.exp(-m.opt.timestep / simulate.ctrlnoiserate)
|
||||
scale = simulate.ctrlnoisestd * math.sqrt(1 - rate * rate)
|
||||
|
||||
for i in range(m.nu):
|
||||
# Update noise.
|
||||
ctrlnoise[i] = (
|
||||
rate * ctrlnoise[i] + scale * mujoco.mju_standardNormal(None))
|
||||
|
||||
# Apply noise.
|
||||
d.ctrl[i] = ctrlnoise[i]
|
||||
|
||||
# Requested slow-down factor.
|
||||
slowdown = 100 / PERCENT_REALTIME[simulate.real_time_index]
|
||||
|
||||
# Misalignment: distance from target sim time > MAX_SYNC_MISALIGN.
|
||||
misaligned = abs(elapsedcpu / slowdown -
|
||||
elapsedsim) > MAX_SYNC_MISALIGN
|
||||
|
||||
# Out-of-sync (for any reason): reset sync times, step.
|
||||
if (elapsedsim < 0 or elapsedcpu < 0 or synccpu == 0 or misaligned or
|
||||
simulate.speed_changed):
|
||||
# Re-sync.
|
||||
synccpu = startcpu
|
||||
syncsim = d.time
|
||||
simulate.speed_changed = False
|
||||
|
||||
# Clear old perturbations, apply new.
|
||||
d.xfrc_applied[:, :] = 0
|
||||
simulate.applyposepertubations(0) # Move mocap bodies only.
|
||||
simulate.applyforceperturbations()
|
||||
|
||||
# Run single step, let next iteration deal with timing.
|
||||
mujoco.mj_step(m, d)
|
||||
|
||||
# In-sync: step until ahead of cpu.
|
||||
else:
|
||||
measured = False
|
||||
prevsim = d.time
|
||||
refreshtime = SIM_REFRESH_FRACTION / simulate.refresh_rate
|
||||
# Step while sim lags behind CPU and within refreshtime.
|
||||
while (((d.time - syncsim) * slowdown <
|
||||
(glfw.get_time() - synccpu)) and
|
||||
((glfw.get_time() - startcpu) < refreshtime)):
|
||||
# Measure slowdown before first step.
|
||||
if not measured and elapsedsim:
|
||||
simulate.measured_slowdown = elapsedcpu / elapsedsim
|
||||
measured = True
|
||||
|
||||
# Clear old perturbations, apply new.
|
||||
d.xfrc_applied[:, :] = 0
|
||||
simulate.applyposepertubations(0) # Move mocap bodies only.
|
||||
simulate.applyforceperturbations()
|
||||
|
||||
# Call mj_step.
|
||||
mujoco.mj_step(m, d)
|
||||
|
||||
# Break if reset.
|
||||
if d.time < prevsim:
|
||||
break
|
||||
else: # simulate.run is False: GUI is paused.
|
||||
# Apply pose perturbation.
|
||||
simulate.applyposepertubations(1) # Move mocap and dynamic bodies.
|
||||
|
||||
# Run mj_forward, to update rendering and joint sliders.
|
||||
mujoco.mj_forward(m, d)
|
||||
|
||||
|
||||
def launch(model: Optional[mujoco.MjModel] = None,
|
||||
data: Optional[mujoco.MjData] = None,
|
||||
*,
|
||||
run_physics_thread: bool = True) -> None:
|
||||
"""Launches the Simulate GUI."""
|
||||
if model is None and data is not None:
|
||||
raise ValueError('mjData is specified but mjModel is not')
|
||||
elif model is not None and data is None:
|
||||
data = mujoco.MjData(model)
|
||||
|
||||
# The simulate object encapsulates the UI.
|
||||
simulate = Simulate()
|
||||
|
||||
# Initialize GLFW.
|
||||
if not glfw.init():
|
||||
raise mujoco.FatalError('could not initialize GLFW')
|
||||
|
||||
atexit.register(glfw.terminate)
|
||||
|
||||
if run_physics_thread:
|
||||
physics_thread = threading.Thread(
|
||||
target=_physics_loop, args=(simulate, model, data))
|
||||
physics_thread.start()
|
||||
|
||||
# Load the initial model, if one is given.
|
||||
if model is not None:
|
||||
t = threading.Thread(target=simulate.load, args=(model, data))
|
||||
t.start()
|
||||
del t
|
||||
|
||||
simulate.renderloop()
|
||||
|
||||
if run_physics_thread:
|
||||
physics_thread.join()
|
||||
|
||||
|
||||
def launch_repl(model: mujoco.MjModel, data: mujoco.MjData) -> None:
|
||||
"""EXPERIMENTAL FEATURE: Launches the Simulate GUI in REPL mode."""
|
||||
if typing.TYPE_CHECKING:
|
||||
launch(model, data, run_physics_thread=False)
|
||||
return
|
||||
|
||||
try:
|
||||
import IPython # pylint: disable=g-import-not-at-top
|
||||
has_ipython = True
|
||||
except ImportError:
|
||||
has_ipython = False
|
||||
|
||||
def start_shell(global_variables):
|
||||
if has_ipython and IPython.get_ipython() is not None:
|
||||
locals().update(global_variables)
|
||||
IPython.embed()
|
||||
else:
|
||||
code.InteractiveConsole(locals=global_variables).interact()
|
||||
|
||||
repl_thread = threading.Thread(
|
||||
target=start_shell, args=(inspect.stack()[1][0].f_globals,))
|
||||
repl_thread.start()
|
||||
launch(model, data, run_physics_thread=False)
|
||||
repl_thread.join()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from absl import app # pylint: disable=g-import-not-at-top
|
||||
from absl import flags # pylint: disable=g-import-not-at-top
|
||||
|
||||
_MJCF_PATH = flags.DEFINE_string('mjcf', None, 'Path to MJCF file.')
|
||||
|
||||
def main(argv) -> None:
|
||||
del argv
|
||||
if _MJCF_PATH.value is not None:
|
||||
model = mujoco.MjModel.from_xml_path(os.path.expanduser(_MJCF_PATH.value))
|
||||
launch(model)
|
||||
else:
|
||||
launch()
|
||||
|
||||
app.run(main)
|
||||
+1
-1
@@ -70,7 +70,6 @@ def get_external_lib_patterns():
|
||||
else:
|
||||
return ['libmujoco.so.*']
|
||||
|
||||
|
||||
def get_plugin_lib_patterns():
|
||||
if platform.system() == 'Windows':
|
||||
return ['*.dll']
|
||||
@@ -322,6 +321,7 @@ setup(
|
||||
CMakeExtension('mujoco._functions'),
|
||||
CMakeExtension('mujoco._render'),
|
||||
CMakeExtension('mujoco._rollout'),
|
||||
CMakeExtension('mujoco._simulate'),
|
||||
CMakeExtension('mujoco._structs'),
|
||||
],
|
||||
python_requires='>=3.7',
|
||||
|
||||
+119
-109
@@ -41,6 +41,9 @@ if(APPLE)
|
||||
enable_language(OBJCXX)
|
||||
endif()
|
||||
|
||||
option(SIMULATE_BUILD_EXECUTABLE "Build the simulate executable binary." ON)
|
||||
option(SIMULATE_GLFW_DYNAMIC_SYMBOLS "Whether to resolve GLFW symbols dynamically." OFF)
|
||||
|
||||
# Check if we are building as standalone project.
|
||||
set(SIMULATE_STANDALONE OFF)
|
||||
set(_INSTALL_SIMULATE ON)
|
||||
@@ -114,7 +117,8 @@ target_sources(
|
||||
target_include_directories(libsimulate PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_definitions(libsimulate PUBLIC MJSIMULATE_STATIC)
|
||||
target_compile_options(libsimulate PUBLIC ${MUJOCO_SIMULATE_COMPILE_OPTIONS})
|
||||
target_link_libraries(libsimulate PUBLIC glfw lodepng mujoco::mujoco)
|
||||
target_link_libraries(libsimulate PUBLIC lodepng mujoco::mujoco)
|
||||
target_include_directories(libsimulate PUBLIC $<TARGET_PROPERTY:glfw,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
target_link_options(libsimulate PRIVATE ${MUJOCO_SIMULATE_LINK_OPTIONS})
|
||||
|
||||
if(APPLE)
|
||||
@@ -122,126 +126,132 @@ if(APPLE)
|
||||
target_link_libraries(libsimulate PUBLIC "-framework Cocoa")
|
||||
endif()
|
||||
|
||||
if(SIMULATE_GLFW_DYNAMIC_SYMBOLS)
|
||||
target_compile_definitions(libsimulate PUBLIC mjGLFW_DYNAMIC_SYMBOLS)
|
||||
endif()
|
||||
|
||||
# Build simulate executable
|
||||
if(APPLE)
|
||||
set(SIMULATE_RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../dist/mujoco.icns)
|
||||
elseif(WIN32)
|
||||
set(SIMULATE_RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../dist/simulate.rc)
|
||||
else()
|
||||
set(SIMULATE_RESOURCE_FILES "")
|
||||
endif()
|
||||
if(SIMULATE_BUILD_EXECUTABLE)
|
||||
if(APPLE)
|
||||
set(SIMULATE_RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../dist/mujoco.icns)
|
||||
elseif(WIN32)
|
||||
set(SIMULATE_RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../dist/simulate.rc)
|
||||
else()
|
||||
set(SIMULATE_RESOURCE_FILES "")
|
||||
endif()
|
||||
|
||||
add_executable(simulate main.cc array_safety.h ${SIMULATE_RESOURCE_FILES})
|
||||
target_compile_options(simulate PUBLIC ${MUJOCO_SIMULATE_COMPILE_OPTIONS})
|
||||
if(WIN32)
|
||||
add_custom_command(
|
||||
TARGET simulate
|
||||
PRE_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/../dist/mujoco.ico
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E rm ${CMAKE_CURRENT_SOURCE_DIR}/mujoco.ico
|
||||
)
|
||||
endif()
|
||||
add_executable(simulate main.cc array_safety.h ${SIMULATE_RESOURCE_FILES})
|
||||
target_compile_options(simulate PUBLIC ${MUJOCO_SIMULATE_COMPILE_OPTIONS})
|
||||
if(WIN32)
|
||||
add_custom_command(
|
||||
TARGET simulate
|
||||
PRE_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/../dist/mujoco.ico
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E rm ${CMAKE_CURRENT_SOURCE_DIR}/mujoco.ico
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
simulate
|
||||
libsimulate
|
||||
mujoco::mujoco
|
||||
glfw
|
||||
Threads::Threads
|
||||
lodepng
|
||||
)
|
||||
|
||||
target_link_options(simulate PRIVATE ${MUJOCO_SIMULATE_LINK_OPTIONS})
|
||||
|
||||
if(APPLE AND MUJOCO_BUILD_MACOS_FRAMEWORKS)
|
||||
set_target_properties(
|
||||
target_link_libraries(
|
||||
simulate
|
||||
PROPERTIES INSTALL_RPATH @executable_path/../Frameworks
|
||||
BUILD_WITH_INSTALL_RPATH TRUE
|
||||
RESOURCE ${SIMULATE_RESOURCE_FILES}
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../dist/Info.plist.simulate.in
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "MuJoCo"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "org.mujoco.mujoco"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_INFO_STRING ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_LONG_VERSION_STRING ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_ICON_FILE "mujoco.icns"
|
||||
MACOSX_BUNDLE_COPYRIGHT "Copyright 2021 DeepMind Technologies Limited."
|
||||
libsimulate
|
||||
mujoco::mujoco
|
||||
glfw
|
||||
Threads::Threads
|
||||
lodepng
|
||||
)
|
||||
|
||||
macro(embed_in_bundle target)
|
||||
add_dependencies(${target} simulate)
|
||||
target_link_options(simulate PRIVATE ${MUJOCO_SIMULATE_LINK_OPTIONS})
|
||||
|
||||
if(APPLE AND MUJOCO_BUILD_MACOS_FRAMEWORKS)
|
||||
set_target_properties(
|
||||
${target}
|
||||
simulate
|
||||
PROPERTIES INSTALL_RPATH @executable_path/../Frameworks
|
||||
BUILD_WITH_INSTALL_RPATH TRUE
|
||||
RUNTIME_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:simulate>
|
||||
RESOURCE ${SIMULATE_RESOURCE_FILES}
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../dist/Info.plist.simulate.in
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "MuJoCo"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "org.mujoco.mujoco"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_INFO_STRING ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_LONG_VERSION_STRING ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_ICON_FILE "mujoco.icns"
|
||||
MACOSX_BUNDLE_COPYRIGHT "Copyright 2021 DeepMind Technologies Limited."
|
||||
)
|
||||
endmacro()
|
||||
|
||||
# Embed mujoco.framework inside the App bundle ane move the icon file over too.
|
||||
add_custom_command(
|
||||
TARGET simulate
|
||||
POST_BUILD
|
||||
COMMAND mkdir -p $<TARGET_FILE_DIR:simulate>/../Frameworks
|
||||
COMMAND rm -rf $<TARGET_FILE_DIR:simulate>/../Frameworks/mujoco.framework
|
||||
COMMAND cp -a $<TARGET_FILE_DIR:mujoco::mujoco>/../../../mujoco.framework
|
||||
$<TARGET_FILE_DIR:simulate>/../Frameworks/
|
||||
# Delete the symlink and the TBD, otherwise we can't sign and notarize.
|
||||
COMMAND rm -rf $<TARGET_FILE_DIR:simulate>/../Frameworks/mujoco.framework/mujoco.tbd
|
||||
COMMAND rm -rf
|
||||
$<TARGET_FILE_DIR:simulate>/../Frameworks/mujoco.framework/Versions/A/libmujoco.dylib
|
||||
)
|
||||
endif()
|
||||
|
||||
# Do not install if macOS Bundles are created as RPATH is managed manually there.
|
||||
if(APPLE AND MUJOCO_BUILD_MACOS_FRAMEWORKS)
|
||||
set(_INSTALL_SIMULATE OFF)
|
||||
endif()
|
||||
|
||||
if(_INSTALL_SIMULATE)
|
||||
|
||||
include(TargetAddRpath)
|
||||
|
||||
# Add support to RPATH for the samples.
|
||||
target_add_rpath(
|
||||
TARGETS
|
||||
simulate
|
||||
INSTALL_DIRECTORY
|
||||
"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}"
|
||||
LIB_DIRS
|
||||
"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}"
|
||||
DEPENDS
|
||||
MUJOCO_ENABLE_RPATH
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS simulate
|
||||
EXPORT ${PROJECT_NAME}
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT simulate
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT simulate
|
||||
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT simulate
|
||||
BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT simulate
|
||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT simulate
|
||||
)
|
||||
|
||||
if(NOT MUJOCO_SIMULATE_USE_SYSTEM_GLFW)
|
||||
# We downloaded GLFW. Depending if it is a static or shared LIBRARY we might
|
||||
# need to install it.
|
||||
get_target_property(MJ_GLFW_LIBRARY_TYPE glfw TYPE)
|
||||
if(MJ_GLFW_LIBRARY_TYPE STREQUAL SHARED_LIBRARY)
|
||||
install(
|
||||
TARGETS glfw
|
||||
EXPORT ${PROJECT_NAME}
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT simulate
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT simulate
|
||||
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT simulate
|
||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT simulate
|
||||
macro(embed_in_bundle target)
|
||||
add_dependencies(${target} simulate)
|
||||
set_target_properties(
|
||||
${target}
|
||||
PROPERTIES INSTALL_RPATH @executable_path/../Frameworks
|
||||
BUILD_WITH_INSTALL_RPATH TRUE
|
||||
RUNTIME_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:simulate>
|
||||
)
|
||||
endmacro()
|
||||
|
||||
# Embed mujoco.framework inside the App bundle ane move the icon file over too.
|
||||
add_custom_command(
|
||||
TARGET simulate
|
||||
POST_BUILD
|
||||
COMMAND mkdir -p $<TARGET_FILE_DIR:simulate>/../Frameworks
|
||||
COMMAND rm -rf $<TARGET_FILE_DIR:simulate>/../Frameworks/mujoco.framework
|
||||
COMMAND cp -a $<TARGET_FILE_DIR:mujoco::mujoco>/../../../mujoco.framework
|
||||
$<TARGET_FILE_DIR:simulate>/../Frameworks/
|
||||
# Delete the symlink and the TBD, otherwise we can't sign and notarize.
|
||||
COMMAND rm -rf $<TARGET_FILE_DIR:simulate>/../Frameworks/mujoco.framework/mujoco.tbd
|
||||
COMMAND rm -rf
|
||||
$<TARGET_FILE_DIR:simulate>/../Frameworks/mujoco.framework/Versions/A/libmujoco.dylib
|
||||
)
|
||||
endif()
|
||||
|
||||
# Do not install if macOS Bundles are created as RPATH is managed manually there.
|
||||
if(APPLE AND MUJOCO_BUILD_MACOS_FRAMEWORKS)
|
||||
set(_INSTALL_SIMULATE OFF)
|
||||
endif()
|
||||
|
||||
if(_INSTALL_SIMULATE)
|
||||
|
||||
include(TargetAddRpath)
|
||||
|
||||
# Add support to RPATH for the samples.
|
||||
target_add_rpath(
|
||||
TARGETS
|
||||
simulate
|
||||
INSTALL_DIRECTORY
|
||||
"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}"
|
||||
LIB_DIRS
|
||||
"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}"
|
||||
DEPENDS
|
||||
MUJOCO_ENABLE_RPATH
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS simulate
|
||||
EXPORT ${PROJECT_NAME}
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT simulate
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT simulate
|
||||
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT simulate
|
||||
BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT simulate
|
||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT simulate
|
||||
)
|
||||
|
||||
if(NOT MUJOCO_SIMULATE_USE_SYSTEM_GLFW)
|
||||
# We downloaded GLFW. Depending if it is a static or shared LIBRARY we might
|
||||
# need to install it.
|
||||
get_target_property(MJ_GLFW_LIBRARY_TYPE glfw TYPE)
|
||||
if(MJ_GLFW_LIBRARY_TYPE STREQUAL SHARED_LIBRARY)
|
||||
install(
|
||||
TARGETS glfw
|
||||
EXPORT ${PROJECT_NAME}
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT simulate
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT simulate
|
||||
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT simulate
|
||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT simulate
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -80,6 +80,7 @@ const struct Glfw& Glfw(void* dlhandle) {
|
||||
|
||||
// go/keep-sorted start
|
||||
mjGLFW_INITIALIZE_SYMBOL(glfwCreateWindow);
|
||||
mjGLFW_INITIALIZE_SYMBOL(glfwDestroyWindow);
|
||||
mjGLFW_INITIALIZE_SYMBOL(glfwGetCursorPos);
|
||||
mjGLFW_INITIALIZE_SYMBOL(glfwGetFramebufferSize);
|
||||
mjGLFW_INITIALIZE_SYMBOL(glfwGetKey);
|
||||
@@ -115,10 +116,6 @@ const struct Glfw& Glfw(void* dlhandle) {
|
||||
|
||||
#undef mjGLFW_INITIALIZE_SYMBOL
|
||||
|
||||
#if defined(mjGLFW_DYNAMIC_SYMBOLS) && !defined(_MSC_VER)
|
||||
dlclose(dlhandle);
|
||||
#endif
|
||||
|
||||
return glfw;
|
||||
}();
|
||||
return glfw;
|
||||
|
||||
@@ -25,6 +25,7 @@ struct Glfw {
|
||||
#define mjGLFW_DECLARE_SYMBOL(func) decltype(&::func) func
|
||||
// go/keep-sorted start
|
||||
mjGLFW_DECLARE_SYMBOL(glfwCreateWindow);
|
||||
mjGLFW_DECLARE_SYMBOL(glfwDestroyWindow);
|
||||
mjGLFW_DECLARE_SYMBOL(glfwGetCursorPos);
|
||||
mjGLFW_DECLARE_SYMBOL(glfwGetFramebufferSize);
|
||||
mjGLFW_DECLARE_SYMBOL(glfwGetKey);
|
||||
|
||||
+9
-3
@@ -337,7 +337,10 @@ void PhysicsLoop(mj::Simulate& sim) {
|
||||
mjData* dnew = nullptr;
|
||||
if (mnew) dnew = mj_makeData(mnew);
|
||||
if (dnew) {
|
||||
sim.load(sim.dropfilename, mnew, dnew, true);
|
||||
sim.load(sim.dropfilename, mnew, dnew);
|
||||
|
||||
mj_deleteData(d);
|
||||
mj_deleteModel(m);
|
||||
|
||||
m = mnew;
|
||||
d = dnew;
|
||||
@@ -356,7 +359,10 @@ void PhysicsLoop(mj::Simulate& sim) {
|
||||
mjData* dnew = nullptr;
|
||||
if (mnew) dnew = mj_makeData(mnew);
|
||||
if (dnew) {
|
||||
sim.load(sim.filename, mnew, dnew, true);
|
||||
sim.load(sim.filename, mnew, dnew);
|
||||
|
||||
mj_deleteData(d);
|
||||
mj_deleteModel(m);
|
||||
|
||||
m = mnew;
|
||||
d = dnew;
|
||||
@@ -482,7 +488,7 @@ void PhysicsThread(mj::Simulate* sim, const char* filename) {
|
||||
m = LoadModel(filename, *sim);
|
||||
if (m) d = mj_makeData(m);
|
||||
if (d) {
|
||||
sim->load(filename, m, d, true);
|
||||
sim->load(filename, m, d);
|
||||
mj_forward(m, d);
|
||||
|
||||
// allocate ctrlnoise
|
||||
|
||||
+10
-14
@@ -1575,11 +1575,9 @@ void Simulate::applyforceperturbations() {
|
||||
//------------------------- Tell the render thread to load a file and wait -------------------------
|
||||
void Simulate::load(const char* file,
|
||||
mjModel* mnew,
|
||||
mjData* dnew,
|
||||
bool delete_old_m_d) {
|
||||
mjData* dnew) {
|
||||
this->mnew = mnew;
|
||||
this->dnew = dnew;
|
||||
this->delete_old_m_d = delete_old_m_d;
|
||||
mju::strcpy_arr(this->filename, file);
|
||||
|
||||
{
|
||||
@@ -1595,16 +1593,6 @@ void Simulate::load(const char* file,
|
||||
|
||||
//------------------------------------- load mjb or xml model --------------------------------------
|
||||
void Simulate::loadmodel() {
|
||||
if (this->delete_old_m_d) {
|
||||
// delete old model if requested
|
||||
if (this->d) {
|
||||
mj_deleteData(d);
|
||||
}
|
||||
if (this->m) {
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
}
|
||||
|
||||
this->m = this->mnew;
|
||||
this->d = this->dnew;
|
||||
|
||||
@@ -1618,7 +1606,8 @@ void Simulate::loadmodel() {
|
||||
this->pert.skinselect = -1;
|
||||
|
||||
// align and scale view unless reloading the same file
|
||||
if (mju::strcmp_arr(this->filename, this->previous_filename)) {
|
||||
if (this->filename[0] &&
|
||||
mju::strcmp_arr(this->filename, this->previous_filename)) {
|
||||
alignscale(this);
|
||||
mju::strcpy_arr(this->previous_filename, this->filename);
|
||||
}
|
||||
@@ -1980,6 +1969,13 @@ void Simulate::renderloop() {
|
||||
this->clearcallback();
|
||||
mjv_freeScene(&this->scn);
|
||||
mjr_freeContext(&this->con);
|
||||
|
||||
Glfw().glfwDestroyWindow(this->window);
|
||||
}
|
||||
|
||||
//------------------------------------ setup the glfw dispatch table -------------------------------
|
||||
void setglfwdlhandle(void* dlhandle) {
|
||||
Glfw(dlhandle);
|
||||
}
|
||||
|
||||
} // namespace mujoco
|
||||
|
||||
+5
-2
@@ -54,7 +54,7 @@ class MJSIMULATEAPI Simulate {
|
||||
|
||||
// Request that the Simulate UI thread render a new model
|
||||
// optionally delete the old model and data when done
|
||||
void load(const char* file, mjModel* m, mjData* d, bool delete_old_m_d);
|
||||
void load(const char* file, mjModel* m, mjData* d);
|
||||
|
||||
// functions below are used by the renderthread
|
||||
// load mjb or xml model that has been requested by load()
|
||||
@@ -79,7 +79,6 @@ class MJSIMULATEAPI Simulate {
|
||||
// model and data to be visualized
|
||||
mjModel* mnew = nullptr;
|
||||
mjData* dnew = nullptr;
|
||||
bool delete_old_m_d = false;
|
||||
|
||||
mjModel* m = nullptr;
|
||||
mjData* d = nullptr;
|
||||
@@ -228,6 +227,10 @@ class MJSIMULATEAPI Simulate {
|
||||
char info_content[Simulate::kMaxFilenameLength] = {0};
|
||||
};
|
||||
|
||||
// setup the glfw dispatch table
|
||||
// if set, must be called prior to other Simulate functions
|
||||
MJSIMULATEAPI void setglfwdlhandle(void* dlhandle);
|
||||
|
||||
} // namespace mujoco
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user