From 0ce8f7b06bd2bea80d7e927610955498a8e0a50a Mon Sep 17 00:00:00 2001 From: Yuval Tassa Date: Tue, 14 Jul 2026 01:49:40 -0700 Subject: [PATCH] Replace functionally opaque Viscous Pause with functionally transparent "Viscous posing mode" toggle. PiperOrigin-RevId: 947527204 Change-Id: I122a9730fa22ac9e8809762046a89fb0a774d827 --- python/mujoco/experimental/studio/sim.cc | 4 +-- .../experimental/studio/studio_app_events.py | 11 ++----- src/experimental/platform/sim/step_control.cc | 29 ------------------- src/experimental/platform/sim/step_control.h | 5 +--- src/experimental/platform/ux/gui.cc | 27 +++++++++++++++-- src/experimental/studio/app.cc | 20 ++++--------- 6 files changed, 33 insertions(+), 63 deletions(-) diff --git a/python/mujoco/experimental/studio/sim.cc b/python/mujoco/experimental/studio/sim.cc index 0dd285ed..a840cf21 100644 --- a/python/mujoco/experimental/studio/sim.cc +++ b/python/mujoco/experimental/studio/sim.cc @@ -32,14 +32,12 @@ PYBIND11_MODULE(sim, m, pybind11::mod_gil_not_used()) { py::enum_(m, "StepStatus") .value("OK", StepControl::Status::kOk) .value("PAUSED", StepControl::Status::kPaused) - .value("VISCOUS_PAUSED", StepControl::Status::kViscousPaused) .value("AUTO_RESET", StepControl::Status::kAutoReset) .value("DIVERGED", StepControl::Status::kDiverged); py::enum_(m, "PauseState") .value("UNPAUSED", StepControl::PauseState::kUnpaused) - .value("NORMAL_PAUSED", StepControl::PauseState::kNormalPaused) - .value("VISCOUS_PAUSED", StepControl::PauseState::kViscousPaused); + .value("NORMAL_PAUSED", StepControl::PauseState::kNormalPaused); py::class_(m, "StepControl") .def(py::init<>()) diff --git a/python/mujoco/experimental/studio/studio_app_events.py b/python/mujoco/experimental/studio/studio_app_events.py index 017a4673..97602f69 100644 --- a/python/mujoco/experimental/studio/studio_app_events.py +++ b/python/mujoco/experimental/studio/studio_app_events.py @@ -157,15 +157,8 @@ def handle_step_control_keyboard_events( pressed = imgui.IsKeyChordPressed - if pressed(int(imgui.Key.Ctrl) | int(imgui.Key.Space)): - if step_control.get_pause_state() == sim.PauseState.VISCOUS_PAUSED: - step_control.set_pause_state(sim.PauseState.UNPAUSED) - else: - step_control.set_pause_state(sim.PauseState.VISCOUS_PAUSED) - return True - elif pressed(imgui.Key.Space): - pause = step_control.get_pause_state() - if pause in (sim.PauseState.VISCOUS_PAUSED, sim.PauseState.UNPAUSED): + if pressed(imgui.Key.Space): + if step_control.get_pause_state() == sim.PauseState.UNPAUSED: step_control.set_pause_state(sim.PauseState.NORMAL_PAUSED) else: step_control.set_pause_state(sim.PauseState.UNPAUSED) diff --git a/src/experimental/platform/sim/step_control.cc b/src/experimental/platform/sim/step_control.cc index 8492845c..bbb0c7fa 100644 --- a/src/experimental/platform/sim/step_control.cc +++ b/src/experimental/platform/sim/step_control.cc @@ -18,7 +18,6 @@ #include #include #include -#include #include #include @@ -32,31 +31,7 @@ static mjtNum Timer() { return Milliseconds(Clock::now() - start).count(); } -// Updates key viscous pause parameters restores them when done. -struct ViscousPauseState { - ViscousPauseState(mjModel* model) : model(model) { - if (model) { - mju_copy3(gravity, model->opt.gravity); - viscosity = model->opt.viscosity; - disableflags = model->opt.disableflags; - mju_zero3(model->opt.gravity); - model->opt.viscosity = 10; - model->opt.disableflags |= mjDSBL_SPRING; - } - } - ~ViscousPauseState() { - if (model) { - mju_copy3(model->opt.gravity, gravity); - model->opt.viscosity = viscosity; - model->opt.disableflags = disableflags; - } - } - mjModel* model; - mjtNum gravity[3]; - mjtNum viscosity; - int disableflags; -}; StepControl::StepControl() { mjcb_time = Timer; } @@ -100,10 +75,6 @@ StepControl::Status StepControl::Advance(mjModel* m, mjData* d) { return Status::kOk; } - std::optional viscous_pause_state; - if (m && pause_state_ == PauseState::kViscousPaused) { - viscous_pause_state.emplace(m); - } if (pause_state_ == PauseState::kNormalPaused) { // When we eventually unpause, we need to make sure we sync to immediately diff --git a/src/experimental/platform/sim/step_control.h b/src/experimental/platform/sim/step_control.h index cafe7cb1..fdc82acf 100644 --- a/src/experimental/platform/sim/step_control.h +++ b/src/experimental/platform/sim/step_control.h @@ -38,9 +38,6 @@ class StepControl { // Simulation was not stepped because it is paused. kPaused, - // Simulation is viscously paused (stepping with zero gravity, high - // viscosity, and no spring forces). - kViscousPaused, // Simulation diverged with autoreset enabled. kAutoReset, @@ -73,7 +70,7 @@ class StepControl { void SetPreStepCallback(StepFn step_fn); void SetPostStepCallback(StepFn step_fn); - enum class PauseState { kUnpaused, kNormalPaused, kViscousPaused }; + enum class PauseState { kUnpaused, kNormalPaused }; // Sets the pause state of the simulation. void SetPauseState(PauseState state); diff --git a/src/experimental/platform/ux/gui.cc b/src/experimental/platform/ux/gui.cc index 33fab1f2..8e947711 100644 --- a/src/experimental/platform/ux/gui.cc +++ b/src/experimental/platform/ux/gui.cc @@ -511,9 +511,6 @@ void StepControlGui(StepControl* step_control, int& speed_index) { make_button(ICON_FA_PAUSE, StepControl::PauseState::kNormalPaused, yellow, ImDrawFlags_RoundCornersLeft, "Pause", .3f, 1.6f); ImGui::SameLine(0.f, 0.f); - make_button(ICON_FA_MAGIC, StepControl::PauseState::kViscousPaused, yellow, - ImDrawFlags_RoundCornersNone, "Viscous Pause", .3f, 1.3f); - ImGui::SameLine(0.f, 0.f); make_button(ICON_FA_PLAY, StepControl::PauseState::kUnpaused, green, ImDrawFlags_RoundCornersRight, "", .3f, 1.6f); @@ -983,6 +980,30 @@ void PhysicsGui(mjModel* model, float min_width) { } if (SectionHeader("Physical Parameters")) { + // Viscous posing mode toggle: disables gravity and passive springs, adds + // viscosity. All changes are directly visible in the parameters below. + { + constexpr mjtNum kPosingViscosity = 10; + bool active = (opt.disableflags & mjDSBL_GRAVITY) && + (opt.disableflags & mjDSBL_SPRING) && + (opt.viscosity >= kPosingViscosity); + if (ImGui_ButtonToggle("Viscous posing mode", &active)) { + if (active) { + opt.disableflags |= mjDSBL_GRAVITY; + opt.disableflags |= mjDSBL_SPRING; + opt.viscosity += kPosingViscosity; + } else { + opt.disableflags &= ~mjDSBL_GRAVITY; + opt.disableflags &= ~mjDSBL_SPRING; + opt.viscosity = std::max(0, opt.viscosity - kPosingViscosity); + } + } + ImGui::SetItemTooltip( + "Disable gravity and passive springs,\n" + "add viscosity for easier posing."); + } + ImGui::Spacing(); + ImGui_InputN("Gravity", opt.gravity, 3); ImGui_InputN("Wind", opt.wind, 3); ImGui_InputN("Magnetic", opt.magnetic, 3); diff --git a/src/experimental/studio/app.cc b/src/experimental/studio/app.cc index 721ce6a7..bcbc41f7 100644 --- a/src/experimental/studio/app.cc +++ b/src/experimental/studio/app.cc @@ -733,16 +733,8 @@ void App::HandleKeyboardEvents() { LoadHistory(sim_history_.GetIndex() + 1); } } - } else if (ImGui_IsChordJustPressed(ImGuiMod_Ctrl | ImGuiKey_Space)) { - if (step_control_.GetPauseState() == PauseState::kViscousPaused) { - step_control_.SetPauseState(PauseState::kUnpaused); - } else { - step_control_.SetPauseState(PauseState::kViscousPaused); - } } else if (ImGui_IsChordJustPressed(ImGuiKey_Space)) { - if (step_control_.GetPauseState() == PauseState::kViscousPaused) { - step_control_.SetPauseState(PauseState::kNormalPaused); - } else if (step_control_.GetPauseState() == PauseState::kUnpaused) { + if (step_control_.GetPauseState() == PauseState::kUnpaused) { step_control_.SetPauseState(PauseState::kNormalPaused); } else { step_control_.SetPauseState(PauseState::kUnpaused); @@ -1461,6 +1453,7 @@ void App::TimelineScrubberGui() { // Advance layout cursor past this row. ImGui::SetCursorScreenPos(ImVec2(cursor.x, cursor.y + total_h)); + ImGui::Dummy(ImVec2(0.0f, 0.0f)); } void App::ModelOptionsGui() { @@ -2178,8 +2171,8 @@ void App::ToolBarGui() { ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, right_width); ImGui::TableNextColumn(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + - ImGui::GetStyle().WindowPadding.x); + ImGui::Dummy(ImVec2(ImGui::GetStyle().WindowPadding.x, 0.0f)); + ImGui::SameLine(0.0f, 0.0f); const float btn_size = ImGui::GetFrameHeight(); const ImVec2 square_size(btn_size, btn_size); @@ -2200,7 +2193,7 @@ void App::ToolBarGui() { } ImGui::SetItemTooltip("%s", "Reset"); - // Combined (Normal Pause, Viscous Pause, Play) widget and Speed selection. + // Combined (Pause, Play) widget and Speed selection. ImGui::SameLine(0, separator_width); platform::StepControlGui(&step_control_, tmp_.speed_index); @@ -2228,9 +2221,6 @@ void App::StatusBarGui() { if (!has_model()) { ImGui::Text("No model loaded"); - } else if (step_control_.GetPauseState() == PauseState::kViscousPaused) { - ImGui::Text("Viscous Pause"); - ImGui::SetItemTooltip("Zero gravity, high viscosity, no spring forces"); } else if (step_control_.GetPauseState() == PauseState::kNormalPaused) { ImGui::Text("Paused"); } else {