Skip to content

Commit

Permalink
Merge pull request #419 from aftersomemath:simulate-python
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 487496285
Change-Id: I17d15bd2a3886e5ce1631f4eb78afa94830d08cc
  • Loading branch information
copybara-github committed Nov 10, 2022
2 parents 0b45129 + 8cc983a commit f7b256d
Show file tree
Hide file tree
Showing 14 changed files with 606 additions and 129 deletions.
6 changes: 6 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^

Expand Down
32 changes: 32 additions & 0 deletions doc/python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion python/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions python/make_sdist.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions python/mujoco/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 ==================================================
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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>"
Expand All @@ -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>"
)
Expand Down Expand Up @@ -411,6 +424,7 @@ if(MUJOCO_PYTHON_MAKE_WHEEL)
_functions
_render
_rollout
_simulate
_structs
mujoco
)
Expand Down
143 changes: 143 additions & 0 deletions python/mujoco/simulate.cc
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit f7b256d

Please sign in to comment.