Improve error/warning display in studio.

PiperOrigin-RevId: 940299502
Change-Id: I8b13e2f984627a638a01b52f7dadca43f2c2ca80
This commit is contained in:
Yuval Tassa
2026-06-30 01:11:25 -07:00
committed by Copybara-Service
parent 407be2fc0b
commit 584ccd73d5
5 changed files with 60 additions and 19 deletions
@@ -17,7 +17,7 @@
#include <cstddef>
#include <cstring>
#include <memory>
#include <span>
#include <span> // NOLINT
#include <string_view>
#include <mujoco/mujoco.h>
@@ -89,6 +89,11 @@ void ModelHolder::PostInit() {
SetLoadError(mjs_getError(spec_));
return;
}
int num_warnings = mjs_numWarnings(spec_);
for (int i = 0; i < num_warnings; ++i) {
if (!warning_.empty()) warning_ += '\n';
warning_ += mjs_getWarning(spec_, i);
}
}
data_ = mj_makeData(model_);
if (!data_) {
+6 -1
View File
@@ -17,7 +17,8 @@
#include <memory>
#include <cstddef>
#include <span>
#include <span> // NOLINT
#include <string>
#include <string_view>
#include <mujoco/mujoco.h>
@@ -56,6 +57,9 @@ class ModelHolder {
// Returns the error message if the model failed to load.
std::string_view error() const { return error_; }
// Returns the warning message(s), newline-separated.
std::string_view warning() const { return warning_; }
// Releases ownership of the underlying MuJoCo structures. Callers are now
// responsible for calling the appropriate `mj_delete*` function on these
// objects.
@@ -78,6 +82,7 @@ class ModelHolder {
mjModel* model_ = nullptr;
mjData* data_ = nullptr;
char error_[1000] = "";
std::string warning_;
};
} // namespace mujoco::platform
+1 -13
View File
@@ -505,18 +505,6 @@ void DrawTextAt(const char* text, float x, float y, float z) {
}
namespace {
float MeasureTextWidth(const char* text) {
float max_width = 0.0f;
const char* start = text;
while (*start) {
const char* end = start;
while (*end && *end != '\n') ++end;
max_width = std::max(max_width, ImGui::CalcTextSize(start, end).x);
start = *end ? end + 1 : end;
}
return max_width;
}
void SetNextWindowPosInside(OverlayPos pos, ImVec4 rect) {
// compute anchor point and pivot from position enum
ImVec2 anchor, pivot;
@@ -584,7 +572,7 @@ void TextOverlay(const char* id, OverlayPos pos, ImVec4 workspace_rect,
float scale = ImGui::GetWindowDpiScale();
float padding = 30.0f * scale;
float max_width = std::max(0.0f, workspace_rect.z - 20.0f);
float text_width = MeasureTextWidth(text) * font_scale;
float text_width = ImGui::CalcTextSize(text).x * font_scale;
float min_target = std::min(min_width * scale, max_width);
float target = 0.0f;
if (text_width + padding > max_width || min_width > 0.0f) {
+45 -4
View File
@@ -196,13 +196,16 @@ void App::OnModelLoaded(std::string filename, ModelKind model_kind) {
step_error_ = "";
edit_error_ = "";
if (!model_holder_->warning().empty()) {
load_error_ = model_holder_->warning();
}
model_path_ = std::move(filename);
if (model_kind_ == kEmptyModel) {
step_control_.SetPauseState(PauseState::kUnpaused);
}
model_kind_ = model_kind;
if (model_kind_ == kEmptyModel) {
if (model_kind_ == kEmptyModel || !load_error_.empty()) {
step_control_.SetPauseState(PauseState::kNormalPaused);
}
@@ -240,6 +243,7 @@ void App::OnModelLoaded(std::string filename, ModelKind model_kind) {
}
});
tmp_.update_threadpool = true;
last_pause_state_ = step_control_.GetPauseState();
}
void App::UpdateFilePaths(const std::string& resolved_path) {
@@ -377,6 +381,12 @@ bool App::Update() {
ProcessPendingLoads();
PauseState current_pause = step_control_.GetPauseState();
if (current_pause != last_pause_state_) {
load_error_ = "";
last_pause_state_ = current_pause;
}
// Only update the simulation if a popup window is not open. Note that the
// simulation itself will only update if it is not paused.
if (tmp_.file_dialog == UiTempState::FileDialog_None) {
@@ -987,6 +997,40 @@ void App::BuildGui() {
ImGui::End();
}
if (!load_error_.empty()) {
const float scale = ImGui::GetWindowDpiScale();
const float max_line_width =
ImGui::CalcTextSize(load_error_.c_str()).x;
const float padding = 30.0f * scale;
const float max_workspace_width = std::max(0.0f, workspace_rect.z - 20.0f);
const float min_target_width = std::min(350.0f * scale, max_workspace_width);
const float target_width = std::clamp(max_line_width + padding,
min_target_width, max_workspace_width);
if (platform::BeginOverlay("WarningOverlay", platform::OverlayPos::kBottom,
workspace_rect, target_width, 0.8f)) {
const bool is_dark = ImGui::GetStyle().Colors[ImGuiCol_WindowBg].x < 0.5f;
if (!model_holder_->warning().empty()) {
const ImVec4 warning_color = is_dark ? ImVec4(1.0f, 0.8f, 0.2f, 1.0f)
: ImVec4(0.7f, 0.45f, 0.0f, 1.0f);
ImGui::TextColored(warning_color, "Warning:");
} else {
const ImVec4 error_color = is_dark ? ImVec4(1.0f, 0.4f, 0.4f, 1.0f)
: ImVec4(0.8f, 0.1f, 0.1f, 1.0f);
ImGui::TextColored(error_color, "Compiler Error:");
}
ImGui::Separator();
if (max_line_width + padding > max_workspace_width) {
ImGui::PushTextWrapPos(max_workspace_width - padding);
ImGui::TextUnformatted(load_error_.c_str());
ImGui::PopTextWrapPos();
} else {
ImGui::TextUnformatted(load_error_.c_str());
}
}
platform::EndOverlay();
}
if (tmp_.info) {
const float scale = ImGui::GetWindowDpiScale();
if (platform::BeginOverlay("Info", platform::OverlayPos::kBottomLeft,
@@ -1890,9 +1934,6 @@ void App::StatusBarGui() {
if (!step_error_.empty()) {
ImGui::SameLine();
ImGui::Text(" | Step Error: %s", step_error_.c_str());
} else if (!load_error_.empty()) {
ImGui::SameLine();
ImGui::Text(" | Load Error: %s", load_error_.c_str());
} else if (!edit_error_.empty()) {
ImGui::SameLine();
ImGui::Text(" | Edit Error: %s", edit_error_.c_str());
+2
View File
@@ -247,6 +247,8 @@ class App {
std::string load_error_;
std::string step_error_;
std::string edit_error_;
platform::StepControl::PauseState last_pause_state_ =
platform::StepControl::PauseState::kNormalPaused;
std::optional<std::string> pending_load_;
std::function<void()> pending_op_;