Add support for user-defined figures in the MuJoCo viewer.
PiperOrigin-RevId: 726983516 Change-Id: Ifb821f3cafb163d4f3077d76b0668037b4bc5a9c
This commit is contained in:
committed by
Copybara-Service
parent
6f0bd0a5f8
commit
37d7591ce0
@@ -25,6 +25,8 @@ Feature promotion
|
|||||||
|
|
||||||
General
|
General
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
- Add support for custom plots in the MuJoCo viewer by exposing a ``viewport`` property, a ``set_figures`` method,
|
||||||
|
and a ``clear_figures`` method.
|
||||||
- Separate collision and deformation meshes for :ref:`flex<deformable-flex>`. This enables a fixed cost for the soft
|
- Separate collision and deformation meshes for :ref:`flex<deformable-flex>`. This enables a fixed cost for the soft
|
||||||
body computations, while preserving the fidelity of high-resolution collisions.
|
body computations, while preserving the fidelity of high-resolution collisions.
|
||||||
- Added :ref:`mjs_setDeepCopy` API function. When the deep copy flag is 0, attaching a model will not copy it to the
|
- Added :ref:`mjs_setDeepCopy` API function. When the deep copy flag is 0, attaching a model will not copy it to the
|
||||||
|
|||||||
+43
-19
@@ -28,6 +28,7 @@
|
|||||||
#include <pybind11/gil.h>
|
#include <pybind11/gil.h>
|
||||||
#include <pybind11/pybind11.h>
|
#include <pybind11/pybind11.h>
|
||||||
#include <pybind11/pytypes.h>
|
#include <pybind11/pytypes.h>
|
||||||
|
#include <pybind11/stl.h>
|
||||||
|
|
||||||
namespace mujoco::python {
|
namespace mujoco::python {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -69,11 +70,10 @@ class UIAdapterWithPyCallback : public Adapter {
|
|||||||
class SimulateWrapper {
|
class SimulateWrapper {
|
||||||
public:
|
public:
|
||||||
SimulateWrapper(std::unique_ptr<PlatformUIAdapter> platform_ui_adapter,
|
SimulateWrapper(std::unique_ptr<PlatformUIAdapter> platform_ui_adapter,
|
||||||
py::object cam, py::object opt,
|
py::object cam, py::object opt, py::object pert,
|
||||||
py::object pert, py::object user_scn, bool is_passive)
|
py::object user_scn, bool is_passive)
|
||||||
: simulate_(new mujoco::Simulate(
|
: simulate_(new mujoco::Simulate(
|
||||||
std::move(platform_ui_adapter),
|
std::move(platform_ui_adapter), cam.cast<MjvCameraWrapper&>().get(),
|
||||||
cam.cast<MjvCameraWrapper&>().get(),
|
|
||||||
opt.cast<MjvOptionWrapper&>().get(),
|
opt.cast<MjvOptionWrapper&>().get(),
|
||||||
pert.cast<MjvPerturbWrapper&>().get(), is_passive)),
|
pert.cast<MjvPerturbWrapper&>().get(), is_passive)),
|
||||||
m_(py::none()),
|
m_(py::none()),
|
||||||
@@ -126,6 +126,28 @@ class SimulateWrapper {
|
|||||||
py::object GetModel() const { return m_; }
|
py::object GetModel() const { return m_; }
|
||||||
py::object GetData() const { return d_; }
|
py::object GetData() const { return d_; }
|
||||||
|
|
||||||
|
mjrRect GetViewport() const {
|
||||||
|
// Return the viewport corresponding to the 3D view, i.e. the viewer window
|
||||||
|
// without the UI elements.
|
||||||
|
return simulate_->uistate.rect[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFigures(
|
||||||
|
const std::vector<std::pair<mjrRect, py::object>>& viewports_figures) {
|
||||||
|
// Pairs of [viewport, figure], where viewport corresponds to the location
|
||||||
|
// of the figure on the viewer window.
|
||||||
|
std::vector<std::pair<mjrRect, mjvFigure>> user_figures;
|
||||||
|
for (const auto& [viewport, figure] : viewports_figures) {
|
||||||
|
mjvFigure casted_figure = *figure.cast<MjvFigureWrapper&>().get();
|
||||||
|
user_figures.push_back(std::make_pair(viewport, casted_figure));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set them all at once to prevent figure flickering.
|
||||||
|
simulate_->user_figures_ = user_figures;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearFigures() { simulate_->user_figures_.clear(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mujoco::Simulate* simulate_;
|
mujoco::Simulate* simulate_;
|
||||||
std::atomic_int destroyed_ = 0;
|
std::atomic_int destroyed_ = 0;
|
||||||
@@ -173,14 +195,14 @@ inline auto CallIfNotNull(void (mujoco::Simulate::*func)(Args...)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline auto GetIfNotNull(T mujoco::Simulate::*member) {
|
inline auto GetIfNotNull(T mujoco::Simulate::* member) {
|
||||||
return [member](SimulateWrapper& wrapper) -> T& {
|
return [member](SimulateWrapper& wrapper) -> T& {
|
||||||
return SimulateRefOrThrow(wrapper).*member;
|
return SimulateRefOrThrow(wrapper).*member;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename... Args>
|
template <typename T, typename... Args>
|
||||||
inline auto SetIfNotNull(T mujoco::Simulate::*member) {
|
inline auto SetIfNotNull(T mujoco::Simulate::* member) {
|
||||||
return [member](SimulateWrapper& wrapper, const T& value) -> void {
|
return [member](SimulateWrapper& wrapper, const T& value) -> void {
|
||||||
SimulateRefOrThrow(wrapper).*member = value;
|
SimulateRefOrThrow(wrapper).*member = value;
|
||||||
};
|
};
|
||||||
@@ -205,8 +227,7 @@ PYBIND11_MODULE(_simulate, pymodule) {
|
|||||||
py::object key_callback) {
|
py::object key_callback) {
|
||||||
bool is_passive = !run_physics_thread;
|
bool is_passive = !run_physics_thread;
|
||||||
return std::make_unique<SimulateWrapper>(
|
return std::make_unique<SimulateWrapper>(
|
||||||
std::make_unique<UIAdapterWithPyCallback<UIAdapter>>(
|
std::make_unique<UIAdapterWithPyCallback<UIAdapter>>(key_callback),
|
||||||
key_callback),
|
|
||||||
scn, cam, opt, pert, is_passive);
|
scn, cam, opt, pert, is_passive);
|
||||||
}))
|
}))
|
||||||
.def("destroy", &SimulateWrapper::Destroy)
|
.def("destroy", &SimulateWrapper::Destroy)
|
||||||
@@ -225,8 +246,12 @@ PYBIND11_MODULE(_simulate, pymodule) {
|
|||||||
.def("lock", GetIfNotNull(&mujoco::Simulate::mtx),
|
.def("lock", GetIfNotNull(&mujoco::Simulate::mtx),
|
||||||
py::call_guard<py::gil_scoped_release>(),
|
py::call_guard<py::gil_scoped_release>(),
|
||||||
py::return_value_policy::reference_internal)
|
py::return_value_policy::reference_internal)
|
||||||
|
.def("set_figures", &SimulateWrapper::SetFigures,
|
||||||
|
py::arg("viewports_figures"))
|
||||||
|
.def("clear_figures", &SimulateWrapper::ClearFigures)
|
||||||
.def_property_readonly("m", &SimulateWrapper::GetModel)
|
.def_property_readonly("m", &SimulateWrapper::GetModel)
|
||||||
.def_property_readonly("d", &SimulateWrapper::GetData)
|
.def_property_readonly("d", &SimulateWrapper::GetData)
|
||||||
|
.def_property_readonly("viewport", &SimulateWrapper::GetViewport)
|
||||||
.def_property_readonly("ctrl_noise_std",
|
.def_property_readonly("ctrl_noise_std",
|
||||||
GetIfNotNull(&mujoco::Simulate::ctrl_noise_std),
|
GetIfNotNull(&mujoco::Simulate::ctrl_noise_std),
|
||||||
py::call_guard<py::gil_scoped_release>())
|
py::call_guard<py::gil_scoped_release>())
|
||||||
@@ -260,18 +285,17 @@ PYBIND11_MODULE(_simulate, pymodule) {
|
|||||||
return sim.exitrequest.load();
|
return sim.exitrequest.load();
|
||||||
}),
|
}),
|
||||||
py::call_guard<py::gil_scoped_release>())
|
py::call_guard<py::gil_scoped_release>())
|
||||||
.def(
|
.def("exit",
|
||||||
"exit",
|
[](SimulateWrapper& wrapper) {
|
||||||
[](SimulateWrapper& wrapper) {
|
mujoco::Simulate* sim = wrapper.simulate();
|
||||||
mujoco::Simulate* sim = wrapper.simulate();
|
if (!sim) {
|
||||||
if (!sim) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int value = 0;
|
int value = 0;
|
||||||
sim->exitrequest.compare_exchange_strong(value, 1);
|
sim->exitrequest.compare_exchange_strong(value, 1);
|
||||||
wrapper.WaitUntilExit();
|
wrapper.WaitUntilExit();
|
||||||
})
|
})
|
||||||
|
|
||||||
.def_property_readonly("uiloadrequest",
|
.def_property_readonly("uiloadrequest",
|
||||||
CallIfNotNull(+[](mujoco::Simulate& sim) {
|
CallIfNotNull(+[](mujoco::Simulate& sim) {
|
||||||
|
|||||||
@@ -108,6 +108,23 @@ class Handle:
|
|||||||
return sim.d
|
return sim.d
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def viewport(self):
|
||||||
|
sim = self._sim()
|
||||||
|
if sim is not None:
|
||||||
|
return sim.viewport
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_figures(self, viewports_figures):
|
||||||
|
sim = self._sim()
|
||||||
|
if sim is not None:
|
||||||
|
sim.set_figures(viewports_figures)
|
||||||
|
|
||||||
|
def clear_figures(self):
|
||||||
|
sim = self._sim()
|
||||||
|
if sim is not None:
|
||||||
|
sim.clear_figures()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
sim = self._sim()
|
sim = self._sim()
|
||||||
if sim is not None:
|
if sim is not None:
|
||||||
|
|||||||
@@ -542,6 +542,10 @@ void ShowSensor(mj::Simulate* sim, mjrRect rect) {
|
|||||||
mjr_figure(viewport, &sim->figsensor, &sim->platform_ui->mjr_context());
|
mjr_figure(viewport, &sim->figsensor, &sim->platform_ui->mjr_context());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShowFigure(mj::Simulate* sim, mjrRect viewport, mjvFigure* fig){
|
||||||
|
mjr_figure(viewport, fig, &sim->platform_ui->mjr_context());
|
||||||
|
}
|
||||||
|
|
||||||
// load state from history buffer
|
// load state from history buffer
|
||||||
static void LoadScrubState(mj::Simulate* sim) {
|
static void LoadScrubState(mj::Simulate* sim) {
|
||||||
// get index into circular buffer
|
// get index into circular buffer
|
||||||
@@ -2588,6 +2592,11 @@ void Simulate::Render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// user figures
|
||||||
|
for (auto& [viewport, figure] : this->user_figures_) {
|
||||||
|
ShowFigure(this, viewport, &figure);
|
||||||
|
}
|
||||||
|
|
||||||
// finalize
|
// finalize
|
||||||
this->platform_ui->SwapBuffers();
|
this->platform_ui->SwapBuffers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ class Simulate {
|
|||||||
// additional user-defined visualization geoms (used in passive mode)
|
// additional user-defined visualization geoms (used in passive mode)
|
||||||
mjvScene* user_scn = nullptr;
|
mjvScene* user_scn = nullptr;
|
||||||
mjtByte user_scn_flags_prev_[mjNRNDFLAG];
|
mjtByte user_scn_flags_prev_[mjNRNDFLAG];
|
||||||
|
std::vector<std::pair<mjrRect, mjvFigure>> user_figures_;
|
||||||
|
|
||||||
// OpenGL rendering and UI
|
// OpenGL rendering and UI
|
||||||
int refresh_rate = 60;
|
int refresh_rate = 60;
|
||||||
|
|||||||
Reference in New Issue
Block a user