2828#include < pybind11/gil.h>
2929#include < pybind11/pybind11.h>
3030#include < pybind11/pytypes.h>
31+ #include < pybind11/stl.h>
3132
3233namespace mujoco ::python {
3334namespace {
@@ -69,11 +70,10 @@ class UIAdapterWithPyCallback : public Adapter {
6970class SimulateWrapper {
7071 public:
7172 SimulateWrapper (std::unique_ptr<PlatformUIAdapter> platform_ui_adapter,
72- py::object cam, py::object opt,
73- py::object pert, py::object user_scn, bool is_passive)
73+ py::object cam, py::object opt, py::object pert,
74+ py::object user_scn, bool is_passive)
7475 : simulate_(new mujoco::Simulate(
75- std::move (platform_ui_adapter),
76- cam.cast<MjvCameraWrapper&>().get(),
76+ std::move (platform_ui_adapter), cam.cast<MjvCameraWrapper&>().get(),
7777 opt.cast<MjvOptionWrapper&>().get(),
7878 pert.cast<MjvPerturbWrapper&>().get(), is_passive)),
7979 m_(py::none()),
@@ -126,6 +126,28 @@ class SimulateWrapper {
126126 py::object GetModel () const { return m_; }
127127 py::object GetData () const { return d_; }
128128
129+ mjrRect GetViewport () const {
130+ // Return the viewport corresponding to the 3D view, i.e. the viewer window
131+ // without the UI elements.
132+ return simulate_->uistate .rect [3 ];
133+ }
134+
135+ void SetFigures (
136+ const std::vector<std::pair<mjrRect, py::object>>& viewports_figures) {
137+ // Pairs of [viewport, figure], where viewport corresponds to the location
138+ // of the figure on the viewer window.
139+ std::vector<std::pair<mjrRect, mjvFigure>> user_figures;
140+ for (const auto & [viewport, figure] : viewports_figures) {
141+ mjvFigure casted_figure = *figure.cast <MjvFigureWrapper&>().get ();
142+ user_figures.push_back (std::make_pair (viewport, casted_figure));
143+ }
144+
145+ // Set them all at once to prevent figure flickering.
146+ simulate_->user_figures_ = user_figures;
147+ }
148+
149+ void ClearFigures () { simulate_->user_figures_ .clear (); }
150+
129151 private:
130152 mujoco::Simulate* simulate_;
131153 std::atomic_int destroyed_ = 0 ;
@@ -173,14 +195,14 @@ inline auto CallIfNotNull(void (mujoco::Simulate::*func)(Args...)) {
173195}
174196
175197template <typename T>
176- inline auto GetIfNotNull (T mujoco::Simulate::*member) {
198+ inline auto GetIfNotNull (T mujoco::Simulate::* member) {
177199 return [member](SimulateWrapper& wrapper) -> T& {
178200 return SimulateRefOrThrow (wrapper).*member;
179201 };
180202}
181203
182204template <typename T, typename ... Args>
183- inline auto SetIfNotNull (T mujoco::Simulate::*member) {
205+ inline auto SetIfNotNull (T mujoco::Simulate::* member) {
184206 return [member](SimulateWrapper& wrapper, const T& value) -> void {
185207 SimulateRefOrThrow (wrapper).*member = value;
186208 };
@@ -205,8 +227,7 @@ PYBIND11_MODULE(_simulate, pymodule) {
205227 py::object key_callback) {
206228 bool is_passive = !run_physics_thread;
207229 return std::make_unique<SimulateWrapper>(
208- std::make_unique<UIAdapterWithPyCallback<UIAdapter>>(
209- key_callback),
230+ std::make_unique<UIAdapterWithPyCallback<UIAdapter>>(key_callback),
210231 scn, cam, opt, pert, is_passive);
211232 }))
212233 .def (" destroy" , &SimulateWrapper::Destroy)
@@ -225,8 +246,12 @@ PYBIND11_MODULE(_simulate, pymodule) {
225246 .def (" lock" , GetIfNotNull (&mujoco::Simulate::mtx),
226247 py::call_guard<py::gil_scoped_release>(),
227248 py::return_value_policy::reference_internal)
249+ .def (" set_figures" , &SimulateWrapper::SetFigures,
250+ py::arg (" viewports_figures" ))
251+ .def (" clear_figures" , &SimulateWrapper::ClearFigures)
228252 .def_property_readonly (" m" , &SimulateWrapper::GetModel)
229253 .def_property_readonly (" d" , &SimulateWrapper::GetData)
254+ .def_property_readonly (" viewport" , &SimulateWrapper::GetViewport)
230255 .def_property_readonly (" ctrl_noise_std" ,
231256 GetIfNotNull (&mujoco::Simulate::ctrl_noise_std),
232257 py::call_guard<py::gil_scoped_release>())
@@ -260,18 +285,17 @@ PYBIND11_MODULE(_simulate, pymodule) {
260285 return sim.exitrequest .load ();
261286 }),
262287 py::call_guard<py::gil_scoped_release>())
263- .def (
264- " exit" ,
265- [](SimulateWrapper& wrapper) {
266- mujoco::Simulate* sim = wrapper.simulate ();
267- if (!sim) {
268- return ;
269- }
270-
271- int value = 0 ;
272- sim->exitrequest .compare_exchange_strong (value, 1 );
273- wrapper.WaitUntilExit ();
274- })
288+ .def (" exit" ,
289+ [](SimulateWrapper& wrapper) {
290+ mujoco::Simulate* sim = wrapper.simulate ();
291+ if (!sim) {
292+ return ;
293+ }
294+
295+ int value = 0 ;
296+ sim->exitrequest .compare_exchange_strong (value, 1 );
297+ wrapper.WaitUntilExit ();
298+ })
275299
276300 .def_property_readonly (" uiloadrequest" ,
277301 CallIfNotNull (+[](mujoco::Simulate& sim) {
0 commit comments