diff --git a/src/experimental/platform/sim/model_holder.cc b/src/experimental/platform/sim/model_holder.cc index 402dde8e..ab5ac0b0 100644 --- a/src/experimental/platform/sim/model_holder.cc +++ b/src/experimental/platform/sim/model_holder.cc @@ -17,7 +17,7 @@ #include #include #include -#include +#include // NOLINT #include #include @@ -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_) { diff --git a/src/experimental/platform/sim/model_holder.h b/src/experimental/platform/sim/model_holder.h index abb13b90..44794bd1 100644 --- a/src/experimental/platform/sim/model_holder.h +++ b/src/experimental/platform/sim/model_holder.h @@ -17,7 +17,8 @@ #include #include -#include +#include // NOLINT +#include #include #include @@ -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 diff --git a/src/experimental/platform/ux/imgui_widgets.cc b/src/experimental/platform/ux/imgui_widgets.cc index 673a811d..0a507771 100644 --- a/src/experimental/platform/ux/imgui_widgets.cc +++ b/src/experimental/platform/ux/imgui_widgets.cc @@ -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) { diff --git a/src/experimental/studio/app.cc b/src/experimental/studio/app.cc index d56e1767..ec9e6379 100644 --- a/src/experimental/studio/app.cc +++ b/src/experimental/studio/app.cc @@ -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()); diff --git a/src/experimental/studio/app.h b/src/experimental/studio/app.h index 2fae3e8e..f0441298 100644 --- a/src/experimental/studio/app.h +++ b/src/experimental/studio/app.h @@ -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 pending_load_; std::function pending_op_;