From fbf031a7d5afb87bc252346276e8c983c81ab407 Mon Sep 17 00:00:00 2001 From: Google DeepMind Date: Sat, 11 Jul 2026 13:47:08 -0700 Subject: [PATCH] Replace functionally opaque Viscous Pause with functionally transparent "Viscous posing mode" toggle. PiperOrigin-RevId: 946298154 Change-Id: Ia06dd6346ac1002000f2d0b1cf9498ffb0d5f8bd --- 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, 63 insertions(+), 33 deletions(-) diff --git a/python/mujoco/experimental/studio/sim.cc b/python/mujoco/experimental/studio/sim.cc index a840cf21..0dd285ed 100644 --- a/python/mujoco/experimental/studio/sim.cc +++ b/python/mujoco/experimental/studio/sim.cc @@ -32,12 +32,14 @@ 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("NORMAL_PAUSED", StepControl::PauseState::kNormalPaused) + .value("VISCOUS_PAUSED", StepControl::PauseState::kViscousPaused); 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 97602f69..017a4673 100644 --- a/python/mujoco/experimental/studio/studio_app_events.py +++ b/python/mujoco/experimental/studio/studio_app_events.py @@ -157,8 +157,15 @@ def handle_step_control_keyboard_events( pressed = imgui.IsKeyChordPressed - if pressed(imgui.Key.Space): - if step_control.get_pause_state() == sim.PauseState.UNPAUSED: + 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): 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 bbb0c7fa..8492845c 100644 --- a/src/experimental/platform/sim/step_control.cc +++ b/src/experimental/platform/sim/step_control.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -31,7 +32,31 @@ 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; } @@ -75,6 +100,10 @@ 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 fdc82acf..cafe7cb1 100644 --- a/src/experimental/platform/sim/step_control.h +++ b/src/experimental/platform/sim/step_control.h @@ -38,6 +38,9 @@ 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, @@ -70,7 +73,7 @@ class StepControl { void SetPreStepCallback(StepFn step_fn); void SetPostStepCallback(StepFn step_fn); - enum class PauseState { kUnpaused, kNormalPaused }; + enum class PauseState { kUnpaused, kNormalPaused, kViscousPaused }; // 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 8e947711..33fab1f2 100644 --- a/src/experimental/platform/ux/gui.cc +++ b/src/experimental/platform/ux/gui.cc @@ -511,6 +511,9 @@ 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); @@ -980,30 +983,6 @@ 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 bcbc41f7..721ce6a7 100644 --- a/src/experimental/studio/app.cc +++ b/src/experimental/studio/app.cc @@ -733,8 +733,16 @@ 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::kUnpaused) { + if (step_control_.GetPauseState() == PauseState::kViscousPaused) { + step_control_.SetPauseState(PauseState::kNormalPaused); + } else if (step_control_.GetPauseState() == PauseState::kUnpaused) { step_control_.SetPauseState(PauseState::kNormalPaused); } else { step_control_.SetPauseState(PauseState::kUnpaused); @@ -1453,7 +1461,6 @@ 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() { @@ -2171,8 +2178,8 @@ void App::ToolBarGui() { ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, right_width); ImGui::TableNextColumn(); - ImGui::Dummy(ImVec2(ImGui::GetStyle().WindowPadding.x, 0.0f)); - ImGui::SameLine(0.0f, 0.0f); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + + ImGui::GetStyle().WindowPadding.x); const float btn_size = ImGui::GetFrameHeight(); const ImVec2 square_size(btn_size, btn_size); @@ -2193,7 +2200,7 @@ void App::ToolBarGui() { } ImGui::SetItemTooltip("%s", "Reset"); - // Combined (Pause, Play) widget and Speed selection. + // Combined (Normal Pause, Viscous Pause, Play) widget and Speed selection. ImGui::SameLine(0, separator_width); platform::StepControlGui(&step_control_, tmp_.speed_index); @@ -2221,6 +2228,9 @@ 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 {