diff --git a/src/slic3r/GUI/DesignPanel.cpp b/src/slic3r/GUI/DesignPanel.cpp index fcabd05561..28bcba4021 100644 --- a/src/slic3r/GUI/DesignPanel.cpp +++ b/src/slic3r/GUI/DesignPanel.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -34,6 +35,8 @@ #include #include #include +#include +#include #include "slic3r/GUI/wxExtensions.hpp" // ScalableButton, create_scaled_bitmap #include "Widgets/Label.hpp" // HarmonyOS Sans fonts (Head_*/Body_*) shared with the rest of Orca @@ -2369,6 +2372,28 @@ void DesignPanel::on_import_svg() add_imported_sketch(svg_to_regions(path, 1.0), _L("SVG")); } +// Run a long CAD computation off the UI thread. A big STEP costs tens of seconds in OCCT +// (parse + tessellate); running it inline froze the whole window — the compositor marked the +// app unresponsive and nothing repainted. The dialog is app-modal, so the document cannot be +// touched while the worker owns it. Exceptions must not escape the worker: `work` is expected +// to swallow them (OCCT throws Standard_Failure, which is not a std::exception). +static void run_off_ui_thread(wxWindow* parent, const wxString& message, const std::function& work) +{ + wxProgressDialog dlg(_L("SnapOrca"), message, 100, parent, + wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_SMOOTH); + std::atomic done{false}; + std::thread worker([&work, &done]() { + work(); + done.store(true, std::memory_order_release); + }); + while (!done.load(std::memory_order_acquire)) { + dlg.Pulse(); + wxYield(); // keep painting; app-modal blocks input to the rest of the UI + wxMilliSleep(30); + } + worker.join(); +} + void DesignPanel::on_import_step() { wxFileDialog dlg(this, _L("Import STEP"), wxEmptyString, wxEmptyString, @@ -2380,7 +2405,17 @@ void DesignPanel::on_import_step() std::string err; // Keep the OCCT B-rep (don't mesh it like the slicer importer): each top-level solid // becomes a coexisting CadBody, fully editable by the on-face/edge feature tools. - const std::vector solids = GeometryEngine::read_step_solids(path, err); + std::vector solids; + run_off_ui_thread(this, _L("Reading STEP…"), [&]() { + try { + solids = GeometryEngine::read_step_solids(path, err); + } catch (const Standard_Failure& e) { + const char* what = e.GetMessageString(); + err = (what != nullptr && *what != '\0') ? what : "OCCT failure"; + } catch (const std::exception& e) { + err = e.what(); + } + }); if (solids.empty()) { m_status->SetForegroundColour(wxColour(235, 110, 110)); m_status->SetLabel(err.empty() ? _L("No solids found in STEP") @@ -2398,7 +2433,18 @@ void DesignPanel::on_import_step() f.mode = BooleanMode::New; // each solid is its own coexisting body m_doc.features.push_back(f); } - if (!m_doc.recompute()) { + bool rebuilt = false; + run_off_ui_thread(this, _L("Rebuilding model…"), [&]() { + try { + rebuilt = m_doc.recompute(); + } catch (const Standard_Failure& e) { + const char* what = e.GetMessageString(); + m_doc.error = (what != nullptr && *what != '\0') ? what : "OCCT failure"; + } catch (const std::exception& e) { + m_doc.error = e.what(); + } + }); + if (!rebuilt) { m_status->SetForegroundColour(wxColour(235, 110, 110)); m_status->SetLabel(_L("STEP import failed: ") + wxString::FromUTF8(m_doc.error)); m_status->Refresh(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 74638c8212..738afc628e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3267,7 +3267,10 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) m_last_frame_start_time = now; } - _refresh_if_shown_on_screen(); + // Not on screen yet (e.g. the notebook is still showing this page): nothing was rendered, + // so keep the frame pending instead of dropping it — clearing m_dirty here leaves the + // canvas blank until some later event happens to dirty it again. + const bool rendered = _refresh_if_shown_on_screen(); #if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) { @@ -3279,7 +3282,7 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) evt.RequestMore(); } else - m_dirty = false; + m_dirty = !rendered; } void GLCanvas3D::on_char(wxKeyEvent& evt) @@ -7251,16 +7254,18 @@ void GLCanvas3D::_update_camera_zoom(double zoom) m_dirty = true; } -void GLCanvas3D::_refresh_if_shown_on_screen() +bool GLCanvas3D::_refresh_if_shown_on_screen() { - if (_is_shown_on_screen()) { - const Size& cnv_size = get_canvas_size(); - _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + if (!_is_shown_on_screen()) + return false; - // Because of performance problems on macOS, where PaintEvents are not delivered - // frequently enough, we call render() here directly when we can. - render(); - } + const Size& cnv_size = get_canvas_size(); + _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + + // Because of performance problems on macOS, where PaintEvents are not delivered + // frequently enough, we call render() here directly when we can. + render(); + return true; } void GLCanvas3D::_picking_pass() diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 5eb21e06ed..24d0a7179c 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1256,7 +1256,9 @@ private: void _zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultCameraZoomToBoxMarginFactor); void _update_camera_zoom(double zoom); - void _refresh_if_shown_on_screen(); + // Returns false when the canvas is not on screen yet and therefore nothing was rendered, + // so callers can keep it dirty instead of dropping the pending frame. + bool _refresh_if_shown_on_screen(); void _picking_pass(); void _rectangular_selection_picking_pass();