mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 22:42:37 +00:00
Harden the GL canvases against dropped frames and UI-thread freezes
Viewport: GLCanvas3D::on_idle() cleared m_dirty even when _refresh_if_shown_on_screen() rendered nothing because the canvas was not on screen yet — a frame requested while the notebook was still showing a page got silently swallowed and the viewport stayed blank until some later event dirtied it again. _refresh_if_shown_on_screen() now reports whether it rendered, and on_idle keeps the canvas dirty when it did not. Covers Design/Prepare/Preview. Freeze: a big STEP (17.8 MB) spent ~40-50 s inside OCCT on the UI thread (read_step_solids + recompute), so the window stopped repainting and the compositor marked the app unresponsive. Both now run on a worker thread behind an app-modal pulsing progress dialog: the UI keeps painting and the document cannot be touched while the worker owns it. OCCT's Standard_Failure is not a std::exception, so the worker catches it explicitly — an escaping exception would terminate the process. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6d9368276c
commit
01aa6903f0
@@ -27,6 +27,7 @@
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/colordlg.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/progdlg.h>
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
@@ -34,6 +35,8 @@
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#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<void()>& work)
|
||||
{
|
||||
wxProgressDialog dlg(_L("SnapOrca"), message, 100, parent,
|
||||
wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_SMOOTH);
|
||||
std::atomic<bool> 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<TopoDS_Shape> solids = GeometryEngine::read_step_solids(path, err);
|
||||
std::vector<TopoDS_Shape> 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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user