Replace functionally opaque Viscous Pause with functionally transparent "Viscous posing mode" toggle.

PiperOrigin-RevId: 947527204
Change-Id: I122a9730fa22ac9e8809762046a89fb0a774d827
This commit is contained in:
Yuval Tassa
2026-07-14 01:49:40 -07:00
committed by Copybara-Service
parent 3c6080b1c0
commit 0ce8f7b06b
6 changed files with 33 additions and 63 deletions
+1 -3
View File
@@ -32,14 +32,12 @@ PYBIND11_MODULE(sim, m, pybind11::mod_gil_not_used()) {
py::enum_<StepControl::Status>(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_<StepControl::PauseState>(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_<StepControl>(m, "StepControl")
.def(py::init<>())
@@ -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)
@@ -18,7 +18,6 @@
#include <chrono>
#include <cstdlib>
#include <functional>
#include <optional>
#include <ratio>
#include <mujoco/mujoco.h>
@@ -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<ViscousPauseState> 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
+1 -4
View File
@@ -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);
+24 -3
View File
@@ -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<mjtNum>(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);
+5 -15
View File
@@ -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 {