Add update_{hfield,mesh,texture} methods to the passive viewer.

Fixes #812.
Fixes #958.
Fixes #965.

PiperOrigin-RevId: 548181282
Change-Id: I6eb0cd2ce52c66083e83dd08d69e0cfb4178dbba
This commit is contained in:
Saran Tunyasuvunakool
2023-07-14 11:42:55 -07:00
committed by Copybara-Service
parent b5bef1b639
commit 0ccfef7314
6 changed files with 153 additions and 55 deletions
+8
View File
@@ -15,6 +15,14 @@ General
"from-to" argument parametrization. :ref:`mjv_makeConnector` is now deprecated.
- Bump oldest supported MacOS from version 10.12 to 11. MacOS 11 is the oldest version still maintained by Apple.
Python bindings
^^^^^^^^^^^^^^^
- The :ref:`passive viewer<PyViewerPassive>` handle now exposes ``update_hfield``, ``update_mesh``, and
``update_texture`` methods to allow users to update renderable assets.
(`#812 <https://github.com/deepmind/mujoco/issues/812>`_, `#958 <https://github.com/deepmind/mujoco/issues/958>`_,
`#965 <https://github.com/deepmind/mujoco/issues/965>`_)
Version 2.3.6 (June 20, 2023)
-----------------------------
+6
View File
@@ -113,6 +113,12 @@ attributes:
also transfers user inputs from the GUI back into ``mjOption`` (inside ``mjModel``) and ``mjData``, including
enable/disable flags, control inputs, and mouse perturbations.
- ``update_hfield(hfieldid)``: updates the height field data at the specified ``hfieldid`` for subsequent renderings.
- ``update_mesh(meshid)``: updates the mesh data at the specified ``meshid`` for subsequent renderings.
- ``update_texture(texid)``: updates the texture data at the specified ``texid`` for subsequent renderings.
- ``close()``: programmatically closes the viewer window. This method can be safely called without locking.
- ``is_running()``: returns ``True`` if the viewer window is running and ``False`` if it is closed.
+12
View File
@@ -154,6 +154,18 @@ PYBIND11_MODULE(_simulate, pymodule) {
simulate.uiloadrequest.fetch_sub(1);
},
py::call_guard<py::gil_scoped_release>())
.def(
"update_hfield",
[](SimulateWrapper& simulate, int hfieldid) {
simulate.UpdateHField(hfieldid);
},
py::call_guard<py::gil_scoped_release>())
.def(
"update_texture",
[](SimulateWrapper& simulate, int texid) {
simulate.UpdateTexture(texid);
},
py::call_guard<py::gil_scoped_release>())
.def_property(
"droploadrequest",
+16 -2
View File
@@ -113,8 +113,22 @@ class Handle:
def sync(self):
sim = self._sim()
if sim is not None:
with sim.lock():
sim.sync()
sim.sync() # locks internally
def update_hfield(self, hfieldid: int):
sim = self._sim()
if sim is not None:
sim.update_hfield(hfieldid) # locks internally and blocks until done
def update_mesh(self, meshid: int):
sim = self._sim()
if sim is not None:
sim.update_mesh(meshid) # locks internally and blocks until done
def update_texture(self, texid: int):
sim = self._sim()
if sim is not None:
sim.update_texture(texid) # locks internally and blocks until done
def __enter__(self):
return self
+101 -53
View File
@@ -1620,6 +1620,8 @@ Simulate::Simulate(std::unique_ptr<PlatformUIAdapter> platform_ui,
}
void Simulate::Sync() {
MutexLock lock(this->mtx);
if (!m_) {
return;
}
@@ -1900,9 +1902,7 @@ void Simulate::Sync() {
}
//------------------------- Tell the render thread to load a file and wait -------------------------
void Simulate::Load(mjModel* m,
mjData* d,
const char* displayed_filename) {
void Simulate::Load(mjModel* m, mjData* d, const char* displayed_filename) {
this->mnew_ = m;
this->dnew_ = d;
mju::strcpy_arr(this->filename, displayed_filename);
@@ -1923,82 +1923,82 @@ void Simulate::LoadOnRenderThread() {
this->m_ = this->mnew_;
this->d_ = this->dnew_;
ncam_ = this->mnew_->ncam;
nkey_ = this->mnew_->nkey;
body_parentid_.resize(this->mnew_->nbody);
std::memcpy(body_parentid_.data(), this->mnew_->body_parentid,
sizeof(this->mnew_->body_parentid[0]) * this->mnew_->nbody);
ncam_ = this->m_->ncam;
nkey_ = this->m_->nkey;
body_parentid_.resize(this->m_->nbody);
std::memcpy(body_parentid_.data(), this->m_->body_parentid,
sizeof(this->m_->body_parentid[0]) * this->m_->nbody);
jnt_type_.resize(this->mnew_->njnt);
std::memcpy(jnt_type_.data(), this->mnew_->jnt_type,
sizeof(this->mnew_->jnt_type[0]) * this->mnew_->njnt);
jnt_type_.resize(this->m_->njnt);
std::memcpy(jnt_type_.data(), this->m_->jnt_type,
sizeof(this->m_->jnt_type[0]) * this->m_->njnt);
jnt_group_.resize(this->mnew_->njnt);
std::memcpy(jnt_group_.data(), this->mnew_->jnt_group,
sizeof(this->mnew_->jnt_group[0]) * this->mnew_->njnt);
jnt_group_.resize(this->m_->njnt);
std::memcpy(jnt_group_.data(), this->m_->jnt_group,
sizeof(this->m_->jnt_group[0]) * this->m_->njnt);
jnt_qposadr_.resize(this->mnew_->njnt);
std::memcpy(jnt_qposadr_.data(), this->mnew_->jnt_qposadr,
sizeof(this->mnew_->jnt_qposadr[0]) * this->mnew_->njnt);
jnt_qposadr_.resize(this->m_->njnt);
std::memcpy(jnt_qposadr_.data(), this->m_->jnt_qposadr,
sizeof(this->m_->jnt_qposadr[0]) * this->m_->njnt);
jnt_range_.clear();
jnt_range_.reserve(this->mnew_->njnt);
for (int i = 0; i < this->mnew_->njnt; ++i) {
if (this->mnew_->jnt_limited[i]) {
jnt_range_.reserve(this->m_->njnt);
for (int i = 0; i < this->m_->njnt; ++i) {
if (this->m_->jnt_limited[i]) {
jnt_range_.push_back(
std::make_pair(this->mnew_->jnt_range[2 * i], this->mnew_->jnt_range[2 * i + 1]));
std::make_pair(this->m_->jnt_range[2 * i], this->m_->jnt_range[2 * i + 1]));
} else {
jnt_range_.push_back(std::nullopt);
}
}
jnt_names_.clear();
jnt_names_.reserve(this->mnew_->njnt);
for (int i = 0; i < this->mnew_->njnt; ++i) {
jnt_names_.emplace_back(this->mnew_->names + this->mnew_->name_jntadr[i]);
jnt_names_.reserve(this->m_->njnt);
for (int i = 0; i < this->m_->njnt; ++i) {
jnt_names_.emplace_back(this->m_->names + this->m_->name_jntadr[i]);
}
actuator_group_.resize(this->mnew_->nu);
std::memcpy(actuator_group_.data(), this->mnew_->actuator_group,
sizeof(this->mnew_->actuator_group[0]) * this->mnew_->nu);
actuator_group_.resize(this->m_->nu);
std::memcpy(actuator_group_.data(), this->m_->actuator_group,
sizeof(this->m_->actuator_group[0]) * this->m_->nu);
actuator_ctrlrange_.clear();
actuator_ctrlrange_.reserve(this->mnew_->nu);
for (int i = 0; i < this->mnew_->nu; ++i) {
if (this->mnew_->actuator_ctrllimited[i]) {
actuator_ctrlrange_.reserve(this->m_->nu);
for (int i = 0; i < this->m_->nu; ++i) {
if (this->m_->actuator_ctrllimited[i]) {
actuator_ctrlrange_.push_back(std::make_pair(
this->mnew_->actuator_ctrlrange[2 * i], this->mnew_->actuator_ctrlrange[2 * i + 1]));
this->m_->actuator_ctrlrange[2 * i], this->m_->actuator_ctrlrange[2 * i + 1]));
} else {
actuator_ctrlrange_.push_back(std::nullopt);
}
}
actuator_names_.clear();
actuator_names_.reserve(this->mnew_->nu);
for (int i = 0; i < this->mnew_->nu; ++i) {
actuator_names_.emplace_back(this->mnew_->names + this->mnew_->name_actuatoradr[i]);
actuator_names_.reserve(this->m_->nu);
for (int i = 0; i < this->m_->nu; ++i) {
actuator_names_.emplace_back(this->m_->names + this->m_->name_actuatoradr[i]);
}
qpos_.resize(this->mnew_->nq);
std::memcpy(qpos_.data(), this->dnew_->qpos, sizeof(this->dnew_->qpos[0]) * this->mnew_->nq);
qpos_.resize(this->m_->nq);
std::memcpy(qpos_.data(), this->d_->qpos, sizeof(this->d_->qpos[0]) * this->m_->nq);
qpos_prev_ = qpos_;
ctrl_.resize(this->mnew_->nu);
std::memcpy(ctrl_.data(), this->dnew_->ctrl, sizeof(this->dnew_->ctrl[0]) * this->mnew_->nu);
ctrl_.resize(this->m_->nu);
std::memcpy(ctrl_.data(), this->d_->ctrl, sizeof(this->d_->ctrl[0]) * this->m_->nu);
ctrl_prev_ = ctrl_;
// re-create scene and context
if (this->fully_managed_) {
mjv_makeScene(this->mnew_, &this->scn, kMaxGeom);
mjv_makeScene(this->m_, &this->scn, kMaxGeom);
} else {
mjopt_prev_ = mnew_->opt;
mjopt_prev_ = m_->opt;
opt_prev_ = opt;
cam_prev_ = cam;
warn_vgeomfull_prev_ = dnew_->warning[mjWARN_VGEOMFULL].number;
mjv_makeSceneState(this->mnew_, this->dnew_, &this->scnstate_, kMaxGeom);
warn_vgeomfull_prev_ = d_->warning[mjWARN_VGEOMFULL].number;
mjv_makeSceneState(this->m_, this->d_, &this->scnstate_, kMaxGeom);
}
this->platform_ui->RefreshMjrContext(this->mnew_, 50*(this->font+1));
this->platform_ui->RefreshMjrContext(this->m_, 50*(this->font+1));
UiModify(&this->ui0, &this->uistate, &this->platform_ui->mjr_context());
UiModify(&this->ui1, &this->uistate, &this->platform_ui->mjr_context());
@@ -2015,37 +2015,37 @@ void Simulate::LoadOnRenderThread() {
// align and scale view unless reloading the same file
if (this->filename[0] &&
mju::strcmp_arr(this->filename, this->previous_filename)) {
AlignAndScaleView(this, this->mnew_);
AlignAndScaleView(this, this->m_);
mju::strcpy_arr(this->previous_filename, this->filename);
}
// update scene
if (fully_managed_) {
mjv_updateScene(this->mnew_, this->dnew_,
mjv_updateScene(this->m_, this->d_,
&this->opt, &this->pert, &this->cam, mjCAT_ALL, &this->scn);
} else {
mjv_updateSceneState(this->mnew_, this->dnew_, &this->opt, &this->scnstate_);
mjv_updateSceneState(this->m_, this->d_, &this->opt, &this->scnstate_);
}
// set window title to model name
if (this->mnew_->names) {
if (this->m_->names) {
char title[200] = "MuJoCo : ";
mju::strcat_arr(title, this->mnew_->names);
mju::strcat_arr(title, this->m_->names);
platform_ui->SetWindowTitle(title);
}
// set keyframe range and divisions
this->ui0.sect[SECT_SIMULATION].item[5].slider.range[0] = 0;
this->ui0.sect[SECT_SIMULATION].item[5].slider.range[1] = mjMAX(0, this->mnew_->nkey - 1);
this->ui0.sect[SECT_SIMULATION].item[5].slider.divisions = mjMAX(1, this->mnew_->nkey - 1);
this->ui0.sect[SECT_SIMULATION].item[5].slider.range[1] = mjMAX(0, this->m_->nkey - 1);
this->ui0.sect[SECT_SIMULATION].item[5].slider.divisions = mjMAX(1, this->m_->nkey - 1);
// rebuild UI sections
MakeUiSections(this, this->mnew_, this->dnew_);
MakeUiSections(this, this->m_, this->d_);
// full ui update
UiModify(&this->ui0, &this->uistate, &this->platform_ui->mjr_context());
UiModify(&this->ui1, &this->uistate, &this->platform_ui->mjr_context());
UpdateSettings(this, this->mnew_);
UpdateSettings(this, this->m_);
// clear request
this->loadrequest = 0;
@@ -2054,7 +2054,7 @@ void Simulate::LoadOnRenderThread() {
// set real time index
int numclicks = sizeof(this->percentRealTime) / sizeof(this->percentRealTime[0]);
float min_error = 1e6;
float desired = mju_log(100*this->mnew_->vis.global.realtime);
float desired = mju_log(100*this->m_->vis.global.realtime);
for (int click=0; click<numclicks; click++) {
float error = mju_abs(mju_log(this->percentRealTime[click]) - desired);
if (error < min_error) {
@@ -2383,6 +2383,27 @@ void Simulate::RenderLoop() {
// poll and handle events
this->platform_ui->PollEvents();
// upload assets if requested
bool upload_notify = false;
if (hfield_upload_ != -1) {
mjr_uploadHField(m_, &platform_ui->mjr_context(), hfield_upload_);
hfield_upload_ = -1;
upload_notify = true;
}
if (mesh_upload_ != -1) {
mjr_uploadMesh(m_, &platform_ui->mjr_context(), mesh_upload_);
mesh_upload_ = -1;
upload_notify = true;
}
if (texture_upload_ != -1) {
mjr_uploadTexture(m_, &platform_ui->mjr_context(), texture_upload_);
texture_upload_ = -1;
upload_notify = true;
}
if (upload_notify) {
cond_upload_.notify_all();
}
// update scene, doing a full sync if in fully managed mode
if (this->fully_managed_) {
Sync();
@@ -2414,4 +2435,31 @@ void Simulate::RenderLoop() {
this->exitrequest.store(2);
}
void Simulate::UpdateHField(int hfieldid) {
MutexLock lock(this->mtx);
if (!m_ || hfieldid < 0 || hfieldid >= m_->nhfield) {
return;
}
hfield_upload_ = hfieldid;
cond_upload_.wait(lock, [this]() { return hfield_upload_ == -1; });
}
void Simulate::UpdateMesh(int meshid) {
MutexLock lock(this->mtx);
if (!m_ || meshid < 0 || meshid >= m_->nmesh) {
return;
}
mesh_upload_ = meshid;
cond_upload_.wait(lock, [this]() { return mesh_upload_ == -1; });
}
void Simulate::UpdateTexture(int texid) {
MutexLock lock(this->mtx);
if (!m_ || texid < 0 || texid >= m_->ntex) {
return;
}
texture_upload_ = texid;
cond_upload_.wait(lock, [this]() { return texture_upload_ == -1; });
}
} // namespace mujoco
+10
View File
@@ -58,6 +58,10 @@ class Simulate {
// visualization.
void Sync();
void UpdateHField(int hfieldid);
void UpdateMesh(int meshid);
void UpdateTexture(int texid);
// Request that the Simulate UI thread render a new model
// optionally delete the old model and data when done
void Load(mjModel* m, mjData* d, const char* displayed_filename);
@@ -279,6 +283,12 @@ class Simulate {
// info strings
char info_title[Simulate::kMaxFilenameLength] = {0};
char info_content[Simulate::kMaxFilenameLength] = {0};
// pending uploads
std::condition_variable_any cond_upload_;
int texture_upload_ = -1;
int mesh_upload_ = -1;
int hfield_upload_ = -1;
};
} // namespace mujoco