From cdd41e230d4a439d8b5a44dffb40cf8a01d259bb Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Fri, 28 Aug 2026 14:03:25 +0200 Subject: [PATCH] The Design tab's MCP socket must not wait for someone to click the tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Building the Design tab on first use (1750c52211) leaves m_design_panel null until a human selects the tab. McpControl::handle_on_main refused every verb while it was null, so start_mcp_control_if_enabled() opened the socket and then answered "Design panel not ready" to everything — for the whole session if nobody clicked. That is precisely the headless case the socket exists for: the click-test rig drives this app over it with no window manager and no user. MainFrame::ensure_design_panel() now builds the panel on demand and returns it; the tab activation and the MCP dispatcher both go through it, so there is one construction site rather than two. Safe to build wx controls there: that handler is dispatched on the main thread by CallAfter, as its own comment says. The startup saving is untouched — a launch that never opens the tab and never speaks MCP still builds nothing. Verified: libslic3r_gui builds clean 745/745; fork parity green with snaporca, which carries the same fix (McpControl.cpp is a byte-identical shared source). --- src/slic3r/GUI/CAD/McpControl.cpp | 7 +++++-- src/slic3r/GUI/MainFrame.cpp | 20 ++++++++++++++------ src/slic3r/GUI/MainFrame.hpp | 5 +++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/CAD/McpControl.cpp b/src/slic3r/GUI/CAD/McpControl.cpp index 9004ab934c..81f22ecfef 100644 --- a/src/slic3r/GUI/CAD/McpControl.cpp +++ b/src/slic3r/GUI/CAD/McpControl.cpp @@ -1990,9 +1990,12 @@ json action_set_feature_expr(DesignPanel* panel, const json& params) std::string handle_on_main(const std::string& method, const json& params, const json& id) { MainFrame* mf = wxGetApp().mainframe; - if (!mf || !mf->m_design_panel) + // The panel is built on first use, and in a headless session nobody clicks the tab that + // would build it -- so build it here rather than refusing. Safe: this runs on the main + // thread (see the CallAfter that dispatches us). + DesignPanel* panel = mf ? mf->ensure_design_panel() : nullptr; + if (!panel) return rpc_error(id, -32001, "Design panel not ready"); - DesignPanel* panel = mf->m_design_panel; // Stale-id guard, checked here rather than in each handler. // diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 4490cbe2bb..101715dea7 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1245,6 +1245,19 @@ void MainFrame::show_option(bool show) } } +#ifdef SLIC3R_CAD +DesignPanel* MainFrame::ensure_design_panel() +{ + if (m_design_panel == nullptr && m_design_page != nullptr) { + wxBusyCursor busy; + m_design_panel = new DesignPanel(m_design_page); + m_design_page->GetSizer()->Add(m_design_panel, 1, wxEXPAND); + m_design_page->Layout(); + } + return m_design_panel; +} +#endif + void MainFrame::init_tabpanel() { // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on // Windows 10 with multiple high resolution displays connected. @@ -1290,12 +1303,7 @@ void MainFrame::init_tabpanel() { // Built on first activation, never at startup: the panel creates several hundred // controls and its own GL canvas, which a user who does not open the tab should // not pay for. - if (m_design_panel == nullptr) { - wxBusyCursor busy; - m_design_panel = new DesignPanel(m_design_page); - m_design_page->GetSizer()->Add(m_design_panel, 1, wxEXPAND); - m_design_page->Layout(); - } + ensure_design_panel(); // Re-sync the Design bed to the active printer: the panel is built before the // printer profile is fully applied, so its bed must refresh on activation or the // grid (true bed) spills past the stale default bed quad. diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 8578ed87b5..b647acfadd 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -391,6 +391,11 @@ public: // selected, so everything the Design panel builds stays off the startup path. wxPanel* m_design_page { nullptr }; DesignPanel* m_design_panel { nullptr }; + // Builds the Design panel if it does not exist yet and returns it (null only before the + // placeholder page itself exists). Main thread only -- it creates wx controls. Both the + // tab activation and the MCP socket go through this: the socket is driven headlessly, + // with nobody to click the tab, and without this every verb would answer "not ready". + DesignPanel* ensure_design_panel(); #endif //BBS: GUI refactor MonitorPanel* m_monitor{ nullptr };