diff --git a/resources/images/OrcaSlicer_horizontal_dark.svg b/resources/images/OrcaSlicer_horizontal_dark.svg
new file mode 100644
index 0000000000..3e48b252b6
--- /dev/null
+++ b/resources/images/OrcaSlicer_horizontal_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/OrcaSlicer_horizontal_light.svg b/resources/images/OrcaSlicer_horizontal_light.svg
new file mode 100644
index 0000000000..d17593cf16
--- /dev/null
+++ b/resources/images/OrcaSlicer_horizontal_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp
index 51cb2e5787..f1bdd89712 100644
--- a/src/libslic3r/Utils.hpp
+++ b/src/libslic3r/Utils.hpp
@@ -194,6 +194,7 @@ std::string debug_out_path(const char *name, ...);
// smaller level means less log. level=5 means saving all logs.
void set_log_path_and_level(const std::string& file, unsigned int level);
void flush_logs();
+boost::filesystem::path get_log_file_name();
// A special type for strings encoded in the local Windows 8-bit code page.
// This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded.
diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp
index 675286d3a1..42dcc14878 100644
--- a/src/libslic3r/utils.cpp
+++ b/src/libslic3r/utils.cpp
@@ -392,6 +392,14 @@ void flush_logs()
return;
}
+// ORCA
+boost::filesystem::path get_log_file_name()
+{
+ if (g_log_sink)
+ return g_log_sink->locked_backend()->get_current_file_name();
+ return {};
+}
+
#ifdef _WIN32
// The following helpers are borrowed from the LLVM project https://github.com/llvm
namespace WindowsSupport
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 805cbafbd5..7acb698ee0 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -466,6 +466,8 @@ set(SLIC3R_GUI_SOURCES
GUI/TextLines.hpp
GUI/TickCode.cpp
GUI/TickCode.hpp
+ GUI/TroubleshootDialog.cpp
+ GUI/TroubleshootDialog.hpp
GUI/UnsavedChangesDialog.cpp
GUI/UnsavedChangesDialog.hpp
GUI/UpdateDialogs.cpp
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 82eeb85a10..80a2887586 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -116,6 +116,7 @@
#include "ParamsDialog.hpp"
#include "KBShortcutsDialog.hpp"
#include "DownloadProgressDialog.hpp"
+#include "TroubleshootDialog.hpp"
#include "BitmapCache.hpp"
#include "Notebook.hpp"
@@ -4116,6 +4117,12 @@ void GUI_App::keyboard_shortcuts()
dlg.ShowModal();
}
+void GUI_App::troubleshoot()
+{
+ TroubleshootDialog dlg;
+ if (dlg.ShowModal() == wxID_REMOVE)
+ wxGetApp().mainframe->Close(false);
+}
void GUI_App::ShowUserGuide() {
// BBS:Show NewUser Guide
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index 4d429eaadf..60ef782f51 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -467,6 +467,7 @@ public:
void recreate_GUI(const wxString& message);
void system_info();
void keyboard_shortcuts();
+ void troubleshoot();
void load_project(wxWindow *parent, wxString& input_file) const;
void import_model(wxWindow *parent, wxArrayString& input_files) const;
void import_zip(wxWindow* parent, wxString& input_file) const;
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 4ad146f577..f95be9f679 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -2546,10 +2546,24 @@ static wxMenu* generate_help_menu()
append_menu_item(helpMenu, wxID_ANY, _L("Setup Wizard"), _L("Setup Wizard"), [](wxCommandEvent &) {wxGetApp().ShowUserGuide();});
helpMenu->AppendSeparator();
+
// Open Config Folder
append_menu_item(helpMenu, wxID_ANY, _L("Show Configuration Folder"), _L("Show Configuration Folder"),
[](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); });
+ helpMenu->AppendSeparator();
+
+ // Troubleshoot center
+ append_menu_item(helpMenu, wxID_ANY, _L("Troubleshoot Center"), "",
+ [](wxCommandEvent&) { wxGetApp().troubleshoot(); });
+
+ append_menu_item(helpMenu, wxID_ANY, _L("Open Network Test"), _L("Open Network Test"), [](wxCommandEvent&) {
+ NetworkTestDialog dlg(wxGetApp().mainframe);
+ dlg.ShowModal();
+ });
+
+ helpMenu->AppendSeparator();
+
append_menu_item(helpMenu, wxID_ANY, _L("Show Tip of the Day"), _L("Show Tip of the Day"), [](wxCommandEvent&) {
wxGetApp().plater()->get_dailytips()->open();
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
@@ -2568,11 +2582,6 @@ static wxMenu* generate_help_menu()
return true;
});
- append_menu_item(helpMenu, wxID_ANY, _L("Open Network Test"), _L("Open Network Test"), [](wxCommandEvent&) {
- NetworkTestDialog dlg(wxGetApp().mainframe);
- dlg.ShowModal();
- });
-
// About
#ifndef __APPLE__
wxString about_title = wxString::Format(_L("&About %s"), SLIC3R_APP_FULL_NAME);
diff --git a/src/slic3r/GUI/TroubleshootDialog.cpp b/src/slic3r/GUI/TroubleshootDialog.cpp
new file mode 100644
index 0000000000..2a617251b2
--- /dev/null
+++ b/src/slic3r/GUI/TroubleshootDialog.cpp
@@ -0,0 +1,1399 @@
+#include "TroubleshootDialog.hpp"
+#include "I18N.hpp"
+
+#include "GUI.hpp"
+#include "GUI_App.hpp"
+#include "MainFrame.hpp"
+
+#include
+#include
+#include "wx/clipbrd.h"
+
+#include "libslic3r/libslic3r.h"
+#include "libslic3r/Utils.hpp"
+#include "libslic3r/AppConfig.hpp"
+#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/Preset.hpp"
+
+#include
+
+#ifdef __WINDOWS__
+#include
+#include
+#include
+#endif
+
+#ifdef __LINUX__
+#include
+#include
+#include
+#endif
+
+#ifdef __APPLE__
+#include
+#define Rect Mac_Rect
+#define RectPtr Mac_RectPtr
+#define Point Mac_Point
+#define Size Mac_Size
+#include
+#undef Rect
+#undef RectPtr
+#undef Point
+#undef Size
+#endif
+
+#include "NetworkTestDialog.hpp"
+
+#include "Widgets/StaticLine.hpp"
+#include "Widgets/HyperLink.hpp"
+#include "Widgets/Button.hpp"
+
+namespace Slic3r {
+namespace GUI {
+
+wxFlexGridSizer* TroubleshootDialog::create_item_loaded_profiles()
+{
+ auto create_label = [this](wxString title, wxFont font = Label::Body_14) {
+ auto label = new Label(this, font, title);
+ label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
+ return label;
+ };
+
+ auto g_sizer = new wxFlexGridSizer(1, 6, FromDIP(3), FromDIP(15));
+
+ g_sizer->AddSpacer(0);
+ g_sizer->Add(create_label(_L("Active"), Label::Body_12), 0, wxALIGN_CENTER);
+ g_sizer->AddSpacer(0);
+ g_sizer->Add(create_label(_L("System"), Label::Body_12), 0, wxALIGN_CENTER);
+ g_sizer->AddSpacer(0);
+ g_sizer->Add(create_label(_L("User"), Label::Body_12) , 0, wxALIGN_CENTER);
+
+ auto gen_stats = GetProfilesOverview();
+ gen_stats = ""; // clear mem. not needed after generating m_..._act, m_..._usr variables
+
+ auto add_sizer = [this, g_sizer, create_label](PresetCollection* col, wxString label, int in_use, int user) {
+ int sys = 0;
+ for (auto it = col->begin(); it != col->end(); it++) {
+ if (it->is_system)
+ sys++;
+ }
+ g_sizer->Add(create_label(label));
+ g_sizer->Add(create_label(wxString::Format("%d", in_use)), 0, wxALIGN_CENTER);
+ g_sizer->Add(create_label("/", Label::Body_12) , 0, wxALIGN_CENTER);
+ g_sizer->Add(create_label(wxString::Format("%d", sys )), 0, wxALIGN_CENTER);
+ g_sizer->Add(create_label("+", Label::Body_12) , 0, wxALIGN_CENTER);
+ g_sizer->Add(create_label(wxString::Format("%d", user )), 0, wxALIGN_CENTER);
+ };
+
+ auto preset_bundle = wxGetApp().preset_bundle;
+ add_sizer(&preset_bundle->printers , _L("Printers") , m_printers__act, m_printers__usr);
+ add_sizer(&preset_bundle->filaments, _L("Filaments"), m_filaments_act, m_filaments_usr);
+ add_sizer(&preset_bundle->prints , _L("Processes"), m_processes_act, m_processes_usr);
+
+ return g_sizer;
+}
+
+ComboBox *TroubleshootDialog::create_item_log_level_combo()
+{
+ auto combobox = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
+
+ for (const auto& item : std::vector{_L("fatal"), _L("error"), _L("warning"), _L("info"), _L("debug"), _L("trace")})
+ combobox->Append(item);
+
+ auto severity_level = wxGetApp().app_config->get("log_severity_level");
+ if (!severity_level.empty()) { combobox->SetValue(severity_level); }
+
+ combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [](wxCommandEvent &e) {
+ auto level = Slic3r::get_string_logging_level(e.GetSelection());
+ Slic3r::set_logging_level(Slic3r::level_string_to_boost(level));
+ wxGetApp().app_config->set("log_severity_level",level);
+ e.Skip();
+ });
+
+ return combobox;
+}
+
+TroubleshootDialog::TroubleshootDialog()
+ : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, _L("Troubleshoot Center"),
+ wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
+{
+ SetFont(wxGetApp().normal_font());
+ SetBackgroundColour(*wxWHITE);
+
+ auto data_dir = boost::filesystem::path(Slic3r::data_dir());
+ auto app_config = wxGetApp().app_config;
+ bool is_dark = app_config->get("dark_color_mode") == "1";
+
+ // LEFT SIZER //////////////////////
+
+ // HEADER
+ m_logo = ScalableBitmap(this, is_dark ? "OrcaSlicer_horizontal_dark" : "OrcaSlicer_horizontal_light", 64);
+ m_header_logo = new wxStaticBitmap(this, wxID_ANY, m_logo.bmp());
+ auto logo_line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(2)));
+ logo_line->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#009687")));
+ auto version = new Label(this, wxString(SoftFever_VERSION), wxALIGN_CENTRE_HORIZONTAL);
+ wxFont version_font = GetFont();
+ version_font = version_font.Scaled(1.65f); // SetPointSize(18) not works on macOS because it uses a 72 PPI reference
+ version->SetFont(version_font);
+ version->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
+
+ auto build = new Button(this, wxString(GIT_COMMIT_HASH));
+ build->SetStyle(ButtonStyle::Regular, ButtonType::Window);
+ auto hash_url = "https://github.com/OrcaSlicer/OrcaSlicer/commit/" + wxString(GIT_COMMIT_HASH);
+ build->SetToolTip(hash_url);
+ build->Bind(wxEVT_BUTTON, [hash_url](wxCommandEvent &e) {
+ wxLaunchDefaultBrowser(hash_url);
+ });
+
+ // SYSTEM INFO
+ auto sys_info_lines = [this](bool show_sys) {
+ if(!show_sys)
+ return std::vector{GetOStype()};
+ return std::vector{
+ GetOSinfo(),
+ GetPackageType(),
+ GetCPUinfo(),
+ GetRAMinfo() + " RAM",
+ GetGPUinfo(),
+ GetMONinfo()
+ };
+ };
+
+ auto* sys_panel = new CenteredMultiLinePanel(this, sys_info_lines(true));
+ sys_panel->SetBackgroundColour(*wxWHITE);
+ sys_panel->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
+
+ auto sys_less_btn = new Button(this, _L("Hide"));
+ sys_less_btn->SetStyle(ButtonStyle::Regular, ButtonType::Window);
+ sys_less_btn->SetToolTip(_L("Show/Hide system information"));
+
+ auto sys_copy_btn = new Button(this, _L("Copy"));
+ sys_copy_btn->SetStyle(ButtonStyle::Regular, ButtonType::Window);
+ sys_copy_btn->SetToolTip(_L("Copy system information to clipboard"));
+
+ sys_copy_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {
+ wxClipboardLocker lock;
+ if (!lock)
+ return false;
+ return wxTheClipboard->SetData(new wxTextDataObject(GetSysInfoAll()));
+ });
+
+ sys_less_btn->Bind(wxEVT_BUTTON, [this, sys_panel, sys_less_btn, sys_info_lines, sys_copy_btn](wxCommandEvent &e) {
+ m_sys_panel_mode = !m_sys_panel_mode;
+ sys_panel->SetText(sys_info_lines(m_sys_panel_mode));
+ sys_less_btn->SetLabel(m_sys_panel_mode ? _L("Hide") : _L("Show"));
+ Layout();
+ Fit();
+ });
+
+ auto link_wiki = new HyperLink(this, _L("Wiki Guide"));
+
+ // RIGHT SIZER //////////////////////
+
+ auto create_title = [this](wxString title) {
+ auto line = new StaticLine(this, false, title);
+ line->SetFont(Label::Head_16);
+ line->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
+ return line;
+ };
+
+ auto create_label = [this](wxString title, wxString tooltip, Label** label_out = nullptr) {
+ wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
+ auto label = new Label(this, title, LB_AUTO_WRAP, wxSize(FromDIP(275),-1)); // 400 - 120 - 5
+ label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
+ label->SetToolTip(tooltip);
+ sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(15));
+
+ if (label_out) *label_out = label;
+
+ return sizer;
+ };
+
+ auto create_btn = [this](wxString title, wxString tooltip) {
+ auto btn = new Button(this, title);
+ btn->SetToolTip(tooltip);
+ btn->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
+ return btn;
+ };
+
+ // INFORMATION
+ auto create_info_line = [this](wxString title) {
+ auto info = new Label(this, title);
+ info->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
+ info->SetMaxSize(wxSize(FromDIP(400),-1));
+ info->Wrap(FromDIP(400));
+ return info;
+ };
+
+ auto info_desc_1 = create_info_line(_L("We need information for diagnosing source of the issue. Check wiki page for detailed guide."));
+ auto info_desc_2 = create_info_line(_L("Pack button collects project file and logs of current session onto a zip file."));
+ auto info_desc_3 = create_info_line(_L("Any additional visual examples like images or screen recordings might be helpful while reporting the issue."));
+ wxBoxSizer *info_desc_sizer = new wxBoxSizer(wxVERTICAL);
+ info_desc_sizer->Add(info_desc_1, 0, wxEXPAND | wxBOTTOM, FromDIP(8));
+ info_desc_sizer->Add(info_desc_2, 0, wxEXPAND | wxBOTTOM, FromDIP(8));
+ info_desc_sizer->Add(info_desc_3, 0, wxEXPAND);
+
+ auto link_report = new HyperLink(this, _L("Report issue") + " ");
+ link_report->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) {
+ auto encodeStr = [](const wxString& text) {
+ wxString out;
+ const wxScopedCharBuffer utf8 = text.utf8_str();
+ for (const unsigned char* p = (const unsigned char*)utf8.data(); *p; ++p) {
+ unsigned char c = *p;
+ if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
+ out += (wxChar)c;
+ else
+ out += wxString::Format(wxT("%%%02X"), c);
+ }
+ return out;
+ };
+
+ wxString url = "https://github.com/OrcaSlicer/OrcaSlicer/issues/new?template=bug_report.yml";
+ wxString os = GetOStype();
+ if(!os.IsEmpty())
+ url += "&os_type=%22" + os +"%22";
+ url += "&version=" + encodeStr(wxString(SoftFever_VERSION));
+ url += "&os_version=" + encodeStr(GetOSinfo());
+ wxLaunchDefaultBrowser(url);
+ });
+
+ auto pack_btn = new Button(this, _L("Pack") + "...");
+ pack_btn->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
+ pack_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {
+ PackAll();
+ });
+
+ // PROFILES
+ auto prf_sys_cache_tip = _L("Cleans and rebuilds system profiles cache on next launch");
+ auto prf_sys_cache_szr = create_label(_L("Clean system profiles cache"), prf_sys_cache_tip);
+ auto prf_sys_cache_btn = create_btn(_L("Clean"), prf_sys_cache_tip);
+ prf_sys_cache_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {
+ RebuildSystemProfiles();
+ });
+ prf_sys_cache_szr->Add(prf_sys_cache_btn, 0, wxALIGN_CENTER_VERTICAL);
+
+ auto prf_loaded_szr = create_label(_L("Loaded profiles overview"), _L("This section shows information for loaded profiles"));
+ auto prf_loaded_btn = create_btn(_L("Export") + dots, _L("Exports detailed overview of loaded profiles in json format"));
+ prf_loaded_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {
+ return ExportAsJson(GetProfilesOverview(),"ProfilesOverview");
+ });
+ prf_loaded_szr->Add(prf_loaded_btn, 0, wxALIGN_CENTER_VERTICAL);
+
+ auto profiles_loaded = create_item_loaded_profiles();
+
+ // MORE
+ auto cfg_folder_szr = create_label(_L("Configurations folder"), "");
+ auto cfg_folder_btn = create_btn(_L("Browse") + "...", _L("Opens configurations folder"));
+ cfg_folder_btn->Bind(wxEVT_BUTTON, [this, data_dir](wxCommandEvent &e) {
+ BrowseFolder(data_dir.string());
+ });
+ cfg_folder_szr->Add(cfg_folder_btn, 0, wxALIGN_CENTER_VERTICAL);
+
+ auto log_level_szr = create_label(_L("Log level"), "");
+ log_level_szr->Add(create_item_log_level_combo(), 0, wxALIGN_CENTER_VERTICAL);
+
+ auto log_pack_szr = create_label(_L("Stored logs"), _L("Packs all stored logs onto a zip file."));
+ auto log_pack_btn = create_btn(_L("Pack") + "...", "");
+ log_pack_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {
+ auto data_dir = boost::filesystem::path(Slic3r::data_dir());
+ ExportAsZip({wxString((data_dir / "log").string())}, "OrcaSlicer_Logs_" + GetTimestamp());
+ });
+ log_pack_szr->Add(log_pack_btn, 0, wxALIGN_CENTER_VERTICAL);
+
+ auto log_clear_szr = create_label("-", "", &m_logs_storage);
+ auto log_clear_btn = create_btn(_L("Clear"), "");
+ log_clear_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {
+ ClearLogs();
+ });
+ log_clear_szr->Add(log_clear_btn, 0, wxALIGN_CENTER_VERTICAL);
+
+ UpdateLogsStorage();
+
+ // LAYOUT //////////////////////
+ wxBoxSizer *left_sizer = new wxBoxSizer(wxVERTICAL);
+
+ wxBoxSizer *sys_btn_sizer = new wxBoxSizer(wxHORIZONTAL);
+ sys_btn_sizer->Add(sys_less_btn, 0, wxLEFT | wxRIGHT, FromDIP(5));
+ sys_btn_sizer->AddStretchSpacer();
+ sys_btn_sizer->Add(sys_copy_btn, 0, wxLEFT | wxRIGHT, FromDIP(5));
+
+ left_sizer->Add(m_header_logo , 0, wxEXPAND | wxALIGN_CENTER);
+ left_sizer->Add(logo_line , 0, wxEXPAND | wxTOP, FromDIP(12));
+ left_sizer->Add(version , 0, wxEXPAND | wxTOP, FromDIP(6));
+ left_sizer->Add(build , 0, wxALIGN_CENTER | wxTOP, FromDIP(4));
+ left_sizer->Add(sys_panel , 0, wxEXPAND | wxTOP, FromDIP(15));
+ left_sizer->AddStretchSpacer();
+ left_sizer->Add(sys_btn_sizer , 0, wxEXPAND | wxTOP, FromDIP(15));
+ left_sizer->Add(link_wiki , 0, wxALIGN_CENTER | wxTOP, FromDIP(15));
+ left_sizer->AddSpacer(FromDIP(5));
+
+ wxBoxSizer *right_sizer = new wxBoxSizer(wxVERTICAL);
+
+ wxBoxSizer *pack_btn_sizer = new wxBoxSizer(wxHORIZONTAL);
+ pack_btn_sizer->Add(link_report , 0, wxRIGHT, FromDIP(5));
+ pack_btn_sizer->AddStretchSpacer();
+ pack_btn_sizer->Add(pack_btn);
+
+ right_sizer->Add(create_title(_L("Information")), 0, wxEXPAND);
+ right_sizer->Add(info_desc_sizer , 0, wxEXPAND | wxTOP, FromDIP(5));
+ right_sizer->Add(pack_btn_sizer , 0, wxEXPAND | wxTOP, FromDIP(12));
+
+ right_sizer->Add(create_title(_L("Profiles")) , 0, wxEXPAND | wxTOP, FromDIP(12));
+ right_sizer->Add(prf_sys_cache_szr , 0, wxEXPAND | wxTOP, FromDIP(8));
+ right_sizer->Add(prf_loaded_szr , 0, wxEXPAND | wxTOP, FromDIP(5));
+ right_sizer->Add(profiles_loaded , 0, wxEXPAND | wxTOP, FromDIP(5));
+
+ right_sizer->Add(create_title(_L("More")) , 0, wxEXPAND | wxTOP, FromDIP(12));
+ right_sizer->Add(cfg_folder_szr , 0, wxEXPAND | wxTOP, FromDIP(8));
+ right_sizer->Add(log_level_szr , 0, wxEXPAND | wxTOP, FromDIP(5));
+ right_sizer->Add(log_pack_szr , 0, wxEXPAND | wxTOP, FromDIP(5));
+ right_sizer->Add(log_clear_szr , 0, wxEXPAND | wxTOP, FromDIP(5));
+
+ wxBoxSizer *m_sizer = new wxBoxSizer(wxHORIZONTAL);
+ m_sizer->Add(left_sizer , 0, wxEXPAND | wxTOP | wxBOTTOM | wxLEFT , FromDIP(15));
+ m_sizer->AddSpacer(FromDIP(20));
+ m_sizer->Add(right_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM | wxRIGHT, FromDIP(15));
+
+ SetSizer(m_sizer);
+ Layout();
+ Fit();
+ CenterOnParent();
+ wxGetApp().UpdateDlgDarkUI(this);
+}
+
+wxString TroubleshootDialog::GetTimestamp()
+{
+ wxDateTime now = wxDateTime::Now();
+ return now.Format("%Y%m%d_%H%M"); // %S
+}
+
+wxString TroubleshootDialog::GetSysInfoAll()
+{
+ wxString info;
+ info += "Version : " + wxString(SoftFever_VERSION) + "\n"
+ + "Build : " + wxString(GIT_COMMIT_HASH) + "\n"
+ + "Package : " + GetPackageType() + "\n"
+ + "Platform : " + GetOSinfo() + "\n"
+ + "Processor : " + GetCPUinfo() + "\n"
+ + "Memory : " + GetRAMinfo() + "RAM" + "\n"
+ + "Renderer : " + GetGPUinfo() + "\n"
+ + "Monitors : " + GetMONinfo();
+ return info;
+};
+
+/*
+wxString TroubleshootDialog::GetConfigStr()
+{
+ wxString config_path = wxGetApp().app_config->config_path();
+ std::ifstream file(config_path.ToUTF8().data());
+ if (!file.is_open())
+ return "{}";
+ nlohmann::json root;
+ try {
+ file >> root;
+ } catch (const nlohmann::json::exception&) {
+ return "{}";
+ }
+ for (const auto& key : std::vector{"recent_projects", "recent", "custom_color_list", "orca_presets"})
+ root.erase(key);
+ if (root.contains("app")) {
+ for (const auto& key : std::vector{"last_backup_path", "last_export_path", "download_path", "slicer_uuid", "preset_folder"})
+ root["app"].erase(key);
+ }
+ return wxString::FromUTF8(root.dump(4));
+};
+*/
+
+wxString TroubleshootDialog::GetProfilesOverview()
+{
+ using ojson = nlohmann::ordered_json;
+
+ auto preset_bundle = wxGetApp().preset_bundle;
+ auto app_config = wxGetApp().app_config;
+ auto vendors = app_config->vendors();
+
+ ojson root = ojson::object();
+
+ m_printers__act = 0; m_printers__usr = 0;
+ m_filaments_act = 0; m_filaments_usr = 0;
+ m_processes_act = 0; m_processes_usr = 0;
+
+ { // OVERVIEW
+ root["Overview"] = "";
+ }
+ { // PRINTERS - enabled
+ ojson arr = ojson::array();
+ for (const auto& [vendor_name, models] : vendors)
+ for (const auto& [model_name, _] : models){
+ arr.push_back(model_name);
+ m_printers__act++;
+ }
+ root["printers_enabled"] = arr;
+ }
+ { // PRINTERS - user defined
+ ojson arr = ojson::array();
+ for (auto it = preset_bundle->printers.begin(); it != preset_bundle->printers.end(); ++it) {
+ if (!it->is_user()) continue;
+ ojson entry;
+ entry["name"] = it->name;
+ entry["inherits"] = it->inherits();
+ arr.push_back(entry);
+ m_printers__usr++;
+ }
+ root["printers_user"] = arr;
+ }
+ { // FILAMENTS - enabled
+ if(app_config->has_section("filaments")){
+ auto filaments = app_config->get_section("filaments");
+ if(!filaments.empty()){
+ ojson arr = ojson::array();
+ for (const auto& filament : app_config->get_section("filaments")){
+ arr.push_back(filament.first);
+ m_filaments_act++;
+ }
+ root["filaments_enabled"] = arr;
+ }
+ }
+ }
+ { // FILAMENTS - user defined
+ ojson arr = ojson::array();
+ for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); ++it) {
+ if (!it->is_user()) continue;
+ ojson entry;
+ entry["name"] = it->name;
+ entry["inherits"] = it->inherits();
+ auto *compatible_printers = dynamic_cast(it->config.option("compatible_printers"));
+ if(compatible_printers != nullptr && !compatible_printers->values.empty()){
+ ojson c_arr = ojson::array();
+ for (const auto& c_item : compatible_printers->values)
+ c_arr.push_back(c_item);
+ entry["compatible_printers"] = c_arr;
+ }
+ auto *compatible_processes = dynamic_cast(it->config.option("compatible_prints"));
+ if(compatible_processes != nullptr && !compatible_processes->values.empty()){
+ ojson c_arr = ojson::array();
+ for (const auto& c_item : compatible_processes->values)
+ c_arr.push_back(c_item);
+ entry["compatible_processes"] = c_arr;
+ }
+ arr.push_back(entry);
+ m_filaments_usr++;
+ }
+ root["filaments_user"] = arr;
+ }
+ { // PROCESSES - enabled, grouped by compatible printer
+ ojson obj = ojson::object();
+ for (auto it = preset_bundle->printers.begin(); it != preset_bundle->printers.end(); ++it) {
+ if (!it->is_visible || !it->is_compatible)
+ continue;
+ ojson arr = ojson::array();
+ for (auto jt = preset_bundle->prints.begin(); jt != preset_bundle->prints.end(); ++jt) {
+ if (!jt->is_visible || jt->is_user() || !jt->loaded) continue;
+ auto *compatible_printers = dynamic_cast(jt->config.option("compatible_printers"));
+ bool is_compatible = false;
+ if (compatible_printers == nullptr || compatible_printers->values.empty()) {
+ is_compatible = false;
+ }
+ else {
+ for (const auto& p : compatible_printers->values) {
+ if (p == it->name) {
+ is_compatible = true;
+ break;
+ }
+ }
+ }
+ if (!is_compatible)
+ continue;
+ arr.push_back(jt->name);
+ m_processes_act++;
+ }
+ if (!arr.empty())
+ obj[it->name] = arr;
+ }
+ root["processes_enabled"] = obj;
+ }
+ { // PROCESSES - user defined
+ ojson arr = ojson::array();
+ for (auto it = preset_bundle->prints.begin(); it != preset_bundle->prints.end(); ++it) {
+ if (!it->is_user()) continue;
+ ojson entry;
+ entry["name"] = it->name;
+ entry["inherits"] = it->inherits();
+ auto *compatible_printers = dynamic_cast(it->config.option("compatible_printers"));
+ if(compatible_printers != nullptr && !compatible_printers->values.empty()){
+ ojson c_arr = ojson::array();
+ for (const auto& c_item : compatible_printers->values)
+ c_arr.push_back(c_item);
+ entry["compatible_printers"] = c_arr;
+ }
+ arr.push_back(entry);
+ m_processes_usr++;
+ }
+ root["processes_user"] = arr;
+ }
+ { // OVERVIEW
+ ojson entry;
+ entry["printers__act"] = m_printers__act;
+ entry["printers__usr"] = m_printers__usr;
+ entry["filaments_act"] = m_filaments_act;
+ entry["filaments_usr"] = m_filaments_usr;
+ entry["processes_act"] = m_processes_act;
+ entry["processes_usr"] = m_processes_usr;
+ root["Overview"] = entry;
+ }
+ return wxString::FromUTF8(root.dump(4));
+}
+
+wxString TroubleshootDialog::GetOStype()
+{
+ wxString os = "";
+ #ifdef __WINDOWS__
+ os = "Windows";
+ #elif defined(__LINUX__)
+ os = "Linux";
+ #elif defined(__APPLE__)
+ os = "macOS";
+ #endif
+ return os;
+}
+
+wxString TroubleshootDialog::GetOSinfo()
+{
+ wxString result;
+#ifdef __WINDOWS__
+ result = GetWinVersion();
+#elif defined(__LINUX__)
+ result = GetLinuxDistroName() + " " + GetLinuxDisplayServer();
+#elif defined(__APPLE__)
+ result = wxGetOsDescription(); // returns "macOS Version 26.3 (Build 25D125)"
+ result.Replace("Version ", ""); // simplify naming
+ result.Replace("Build ", "Build-"); // dash for wrapping build info on next line
+
+ // No public API for naming
+ auto GetMacOSName = [](const wxString& ver) -> wxString {
+ if (ver.StartsWith("26")) return "Tahoe";
+ if (ver.StartsWith("15")) return "Sequoia";
+ if (ver.StartsWith("14")) return "Sonoma";
+ if (ver.StartsWith("13")) return "Ventura";
+ if (ver.StartsWith("12")) return "Monterey";
+ if (ver.StartsWith("11")) return "Big Sur";
+ return "";
+ };
+
+ wxRegEx reVer("([0-9]+\\.[0-9]+)");
+ if (reVer.Matches(result)) {
+ wxString ver = reVer.GetMatch(result, 1);
+ wxString name = GetMacOSName(ver);
+ if (!name.IsEmpty())
+ result.Replace("(Build-", name + " (Build-");
+ }
+#endif
+ return result;
+}
+
+#ifdef __WINDOWS__
+wxString TroubleshootDialog::GetWinDisplayVersion() {
+ HKEY hKey;
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey) != ERROR_SUCCESS)
+ return wxString();
+
+ wchar_t buf[64] = {};
+ DWORD size = sizeof(buf);
+ DWORD type = 0;
+
+ // "DisplayVersion" exists on Windows 10 20H2+ and Windows 11
+ LONG res = RegQueryValueExW(hKey, L"DisplayVersion", nullptr, &type, reinterpret_cast(buf), &size);
+ if (res != ERROR_SUCCESS || type != REG_SZ) { // Fallback: older builds use "ReleaseId" (e.g. "2004", "1909")
+ size = sizeof(buf);
+ RegQueryValueExW(hKey, L"ReleaseId", nullptr, &type, reinterpret_cast(buf), &size);
+ }
+
+ RegCloseKey(hKey);
+ return wxString(buf);
+}
+wxString TroubleshootDialog::GetWinVersion()
+{
+ typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
+ HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
+ if (hNtdll) {
+ auto RtlGetVersion = (RtlGetVersionPtr)GetProcAddress(hNtdll, "RtlGetVersion");
+ if (RtlGetVersion) {
+ RTL_OSVERSIONINFOW osvi = {};
+ osvi.dwOSVersionInfoSize = sizeof(osvi);
+ if (RtlGetVersion(&osvi) == 0) {
+ int build = osvi.dwBuildNumber;
+ wxString win = (build >= 22000) ? "11"
+ : (build >= 10240) ? "10"
+ : (build >= 9200) ? "8"
+ : (build >= 7601) ? "7"
+ : "?";
+
+ wxString displayVer = GetWinDisplayVersion();
+
+ if (!displayVer.IsEmpty())
+ return wxString::Format("Windows %s %s %d", win, displayVer, build);
+ else
+ return wxString::Format("Windows %s %d", win, build);
+ }
+ }
+ }
+ return "Windows (unknown)";
+}
+#elif defined(__LINUX__)
+wxString TroubleshootDialog::GetLinuxDistroName()
+{
+ // Try host os-release first (works when running as Flatpak)
+ wxArrayString candidates;
+ candidates.Add("/run/host/os-release");
+ candidates.Add("/run/host/etc/os-release");
+ candidates.Add("/etc/os-release");
+ candidates.Add("/usr/lib/os-release");
+
+ for (const wxString& path : candidates) {
+ if (!wxFileExists(path))
+ continue;
+ wxTextFile file;
+ if (!file.Open(path))
+ continue;
+ for (wxString line = file.GetFirstLine(); !file.Eof(); line = file.GetNextLine()) {
+ if (line.StartsWith("PRETTY_NAME=")) {
+ wxString value = line.Mid(12);
+ value.Replace("\"", "");
+ return value;
+ }
+ }
+ }
+ return "Linux";
+}
+
+wxString TroubleshootDialog::GetLinuxDisplayServer()
+{
+ const char* wayland = getenv("WAYLAND_DISPLAY");
+ if (wayland && wayland[0] != '\0') // WAYLAND_DISPLAY is set when running under Wayland
+ return "Wayland";
+
+ const char* sessionType = getenv("XDG_SESSION_TYPE");
+ if (sessionType) { // XDG_SESSION_TYPE is more explicit
+ if (wxString(sessionType).IsSameAs("wayland", false))
+ return "Wayland";
+ if (wxString(sessionType).IsSameAs("x11", false))
+ return "X11";
+ }
+
+ const char* display = getenv("DISPLAY");
+ if (display && display[0] != '\0')
+ return "X11"; // DISPLAY being set suggests X11
+
+ return "";
+}
+#endif
+
+wxString TroubleshootDialog::GetPackageType()
+{
+ wxString result;
+#ifdef __WINDOWS__
+ wxString path = wxStandardPaths::Get().GetExecutablePath();
+ wxString dir = wxPathOnly(path);
+
+ if (path.Contains("OrcaSlicer\\build"))
+ return "Local Build";
+
+ if (wxFileExists(dir + "\\Uninstall.exe"))
+ return "Installed";
+
+ return "Portable";
+#elif defined(__LINUX__)
+ if (wxGetEnv("APPIMAGE" , nullptr)) return "AppImage";
+ if (wxGetEnv("FLATPAK_ID", nullptr)) return "Flatpak";
+ //if (wxGetEnv("SNAP" , nullptr)) return "Snap";
+ if (wxFileExists("/.flatpak-info")) return "Flatpak";
+ //if (wxFileExists("/usr/bin/dpkg")) return "Debian/Ubuntu (deb)";
+ //if (wxFileExists("/usr/bin/rpm")) return "RPM-based (rpm)";
+ //if (wxFileExists("/usr/bin/pacman")) return "Arch (pacman)";
+
+ wxString path = wxStandardPaths::Get().GetExecutablePath();
+ if (path.Contains("OrcaSlicer/build")) return "Local Build";
+ //if (path.StartsWith("/usr/local")) return "Compiled (local)";
+ if (path.StartsWith("/opt")) return "Third-party";
+
+ return "Native Package"; // (deb/rpm/etc)
+#elif defined(__APPLE__)
+ wxString path = wxStandardPaths::Get().GetExecutablePath();
+ wxString dir = wxPathOnly(path);
+
+ if (path.Contains("OrcaSlicer/build"))
+ return "Local Build";
+
+ //if (wxDirExists(dir + "/../_MASReceipt"))
+ // return "Mac App Store";
+
+ if (path.Contains("/Cellar/") || wxGetEnv("HOMEBREW_PREFIX", nullptr))
+ return "Homebrew";
+
+ if (path.StartsWith("/Volumes/OrcaSlicer")) // running from .dmg
+ return "Temporary";
+
+ if (path.StartsWith("/Applications"))
+ return "Applications";
+
+ return "Custom"; // running from Downloads, Desktop or custom folder
+#endif
+ return result;
+}
+
+wxString TroubleshootDialog::GetCPUinfo()
+{
+ wxString info;
+#ifdef __WINDOWS__
+ info = get_cpu_info_from_registry();
+#elif __APPLE__
+ std::map cpu_info = parse_lscpu_etc("sysctl -a", ':');
+ info = wxString(cpu_info["machdep.cpu.brand_string"]);
+#else // linux/BSD
+ std::map cpu_info = parse_lscpu_etc("cat /proc/cpuinfo", ':');
+ info = wxString(cpu_info["model name"]);
+#endif
+ info.Trim();
+ return info;
+}
+
+#ifdef __WINDOWS__
+wxString TroubleshootDialog::get_cpu_info_from_registry()
+{
+ const std::string dir = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\";
+ char buf[500] = {};
+ DWORD bufsize = sizeof(buf) - 1;
+
+ for (const auto& path : { dir, dir + "0\\" })
+ if (RegGetValueA(HKEY_LOCAL_MACHINE, path.c_str(), "ProcessorNameString", RRF_RT_REG_SZ, NULL, buf, &bufsize) == ERROR_SUCCESS)
+ return buf;
+
+ return "Unknown";
+}
+#else // macOS / linux
+std::map TroubleshootDialog::parse_lscpu_etc(const std::string& name, char delimiter)
+{
+ std::map out;
+ constexpr size_t max_len = 1000;
+ char cline[max_len] = "";
+ FILE* fp = popen(name.data(), "r");
+ if (fp != NULL) {
+ while (fgets(cline, max_len, fp) != NULL) {
+ std::string line(cline);
+ line.erase(std::remove_if(line.begin(), line.end(),
+ [](char c) { return c == '\"' || c == '\r' || c == '\n'; }),
+ line.end());
+ size_t pos = line.find(delimiter);
+ if (pos < line.size() - 1) {
+ std::string key = line.substr(0, pos);
+ std::string value = line.substr(pos + 1);
+ boost::trim_all(key); // remove leading and trailing spaces
+ boost::trim_all(value);
+ out.emplace(key, value);
+ }
+ }
+ pclose(fp);
+ }
+ return out;
+}
+#endif
+
+wxString TroubleshootDialog::GetRAMinfo()
+{
+ size_t n = std::round(Slic3r::total_physical_memory() / 107374182.40);
+ return std::to_string(n / 10) + "." + std::to_string(n % 10) + " GB";
+}
+
+wxString TroubleshootDialog::GetGPUinfo()
+{
+ auto gl_info = OpenGLManager::get_gl_info();
+ return gl_info.get_renderer()+ " GLSL:" + gl_info.get_glsl_version();
+}
+
+wxString TroubleshootDialog::GetMONinfo()
+{
+ wxString m_str;
+ int d_count = wxDisplay::GetCount();
+ if (d_count <= 0)
+ return "Unknown";
+ double scale = 1.0;
+
+#if defined(__LINUX__)
+ for (int i = 0; i < d_count; ++i) {
+ wxDisplay disp(i);
+ if (!disp.IsOk()) continue;
+
+ scale = disp.GetScaleFactor();
+ wxRect rc = disp.GetGeometry();
+
+ wxString d_str = wxString::Format("%dx%d-%.0f%%", rc.width, rc.height, scale * 100.0);
+
+ m_str += ((i > 0) ? " " : "") + d_str;
+ }
+#elif defined(__APPLE__)
+ std::vector cgDisplays;
+ uint32_t displayCount = 0;
+ CGGetActiveDisplayList(0, nullptr, &displayCount);
+ if (displayCount > 0) {
+ cgDisplays.resize(displayCount);
+ CGGetActiveDisplayList(displayCount, cgDisplays.data(), &displayCount);
+ }
+ for (int i = 0; i < d_count; ++i) {
+ wxDisplay disp(i);
+ if (!disp.IsOk()) continue;
+ scale = disp.GetScaleFactor();
+ wxRect rc = disp.GetGeometry();
+ int physW = static_cast(std::round(rc.width * scale));
+ int physH = static_cast(std::round(rc.height * scale));
+ const char* type = "L-"; // Logical fallback. only visible when cant get native res
+
+ CGDirectDisplayID cgDispID = (i < static_cast(cgDisplays.size())) ? cgDisplays[i] : CGMainDisplayID();
+
+ CFArrayRef allModes = CGDisplayCopyAllDisplayModes(cgDispID, nullptr);
+ if (allModes) {
+ CFIndex count = CFArrayGetCount(allModes);
+ for (CFIndex m = 0; m < count; ++m) {
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, m);
+ if (CGDisplayModeGetIOFlags(mode) & kDisplayModeNativeFlag) {
+ size_t pw = CGDisplayModeGetPixelWidth(mode);
+ size_t ph = CGDisplayModeGetPixelHeight(mode);
+ if (pw >= 800 && ph >= 600 && pw <= 16384 && ph <= 16384) {
+ physW = static_cast(pw);
+ physH = static_cast(ph);
+ type = ""; // Native
+ }
+ break;
+ }
+ }
+ CFRelease(allModes);
+ }
+
+ wxString d_str = wxString::Format("%dx%d-%s%.0f%%", physW, physH, type, scale * 100.0);
+ m_str += ((i > 0) ? " " : "") + d_str;
+ }
+#elif defined(__WINDOWS__)
+ for (int i = 0; i < d_count; ++i) {
+ wxDisplay disp(i);
+ if (!disp.IsOk()) continue;
+
+ scale = disp.GetScaleFactor();
+ wxRect rc = disp.GetGeometry();
+
+ wxString d_str = wxString::Format("%dx%d-%.0f%%", rc.width, rc.height, scale * 100.0);
+
+ m_str += ((i > 0) ? " " : "") + d_str;
+ }
+
+ UINT dpi = 96;
+ HMODULE hUser = GetModuleHandleW(L"user32.dll");
+ if (hUser) {
+ auto fn = reinterpret_cast(GetProcAddress(hUser, "GetDpiForSystem"));
+ if (fn)
+ dpi = fn();
+ }
+ double text_scale = dpi / 96.0;
+ m_str += wxString::Format(" TextScaling-%.0f%%", text_scale * 100.0);
+#endif
+ return m_str;
+}
+
+void TroubleshootDialog::PackAll()
+{
+ std::vector include_zip;
+ //auto data_dir = boost::filesystem::path(Slic3r::data_dir());
+ //include_zip.emplace_back(wxString((data_dir / "log").string()));
+
+ // Collect logs for current session. does not includes debug_network_...
+ boost::filesystem::path current_log = get_log_file_name();
+ if (!current_log.empty()){
+ boost::filesystem::path base = current_log.parent_path() / current_log.stem(); // removes ".0"
+ std::vector result;
+ for (int n = 0; ; ++n) {
+ auto candidate = boost::filesystem::path(base.string() + "." + std::to_string(n));
+ if (!boost::filesystem::exists(candidate))
+ break;
+ include_zip.emplace_back(wxString(candidate.string()));
+ }
+ }
+
+ auto project_name = wxGetApp().plater()->get_project_filename(".3mf");
+ if(!project_name.IsEmpty()){
+ if (wxGetApp().plater()->is_project_dirty()) {
+ auto res = MessageDialog(this,
+ _L("The current project has unsaved changes, save it before continue?") +
+ "\n\n" +
+ _L("Select NO to close dialog and review project"),
+ wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Save"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE
+ ).ShowModal();
+ if (res == wxID_YES)
+ wxGetApp().plater()->save_project();
+ else {
+ if (res == wxID_NO)
+ EndModal(wxID_CLOSE);
+ return;
+ }
+ }
+ include_zip.emplace_back(project_name);
+ }
+
+ if (project_name.IsEmpty()){
+ MessageDialog(this, _L("No project file on current session. Only logs will be included to package"),
+ wxString(SLIC3R_APP_FULL_NAME), wxOK | wxICON_WARNING | wxCENTRE
+ ).ShowModal();
+ }
+
+ ExportAsZip(include_zip, "OrcaSlicer_PackedDebugInfo_" + GetTimestamp());
+}
+
+void TroubleshootDialog::RebuildSystemProfiles()
+{
+ if (wxGetApp().plater()->is_project_dirty()) {
+ auto res = MessageDialog(this,
+ _L("The current project has unsaved changes, save it before continue?") +
+ "\n\n" +
+ _L("Select NO to close dialog and review project."),
+ wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Save"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE
+ ).ShowModal();
+ if (res == wxID_YES)
+ wxGetApp().plater()->save_project();
+ else {
+ if (res == wxID_NO)
+ EndModal(wxID_CLOSE);
+ return;
+ }
+ }
+
+ MessageDialog msg(this,
+ _L("Restart Required") + "\n" +
+ _L("Please make sure any instances of OrcaSlicer are not running") + "\n" +
+ _L("Do you want to continue?")
+ , wxString(SLIC3R_APP_FULL_NAME), wxICON_QUESTION | wxOK | wxCANCEL
+ );
+ if (msg.ShowModal() == wxID_OK){
+ auto sys_folder = boost::filesystem::path(Slic3r::data_dir()) / "system";
+ if (boost::filesystem::exists(sys_folder)) {
+ bool is_deletable = true;
+ try {
+ for (const auto& entry : boost::filesystem::recursive_directory_iterator(sys_folder)) {
+ if (boost::filesystem::is_regular_file(entry.path())) {
+ std::ofstream file(entry.path().string(), std::ios::in | std::ios::out);
+ if (!file.is_open()) {
+ BOOST_LOG_TRIVIAL(warning) << "File is locked: " << entry.path().string();
+ is_deletable = false;
+ }
+ file.close();
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ is_deletable = false;
+ BOOST_LOG_TRIVIAL(warning) << e.what();
+ }
+ if (!is_deletable) {
+ MessageDialog(this, _L("System folder cannot be deleted because some files are in use by another application. Please close any applications using these files and try again."),
+ wxString(SLIC3R_APP_FULL_NAME), wxOK | wxICON_WARNING | wxCENTRE
+ ).ShowModal();
+ return;
+ }
+ try {
+ boost::filesystem::remove_all(sys_folder);
+ EndModal(wxID_REMOVE);
+ RestartApplication();
+ }
+ catch (const std::exception& e) {
+ BOOST_LOG_TRIVIAL(warning) << "Failed to delete system folder..." << e.what();
+ MessageDialog(this, _L("Failed to delete system folder..."),
+ wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Error"), wxOK | wxICON_WARNING | wxCENTRE
+ ).ShowModal();
+ }
+ }
+ }
+}
+
+bool TroubleshootDialog::RestartApplication()
+{
+ wxString execPath = wxStandardPaths::Get().GetExecutablePath();
+ if (execPath.IsEmpty()) {
+ MessageDialog(this,
+ _L("Failed to determine executable path."),
+ wxString(SLIC3R_APP_FULL_NAME),
+ wxICON_WARNING | wxOK
+ ).ShowModal();
+ return false;
+ }
+
+ // Reconstruct original argv if you need to forward launch arguments,
+ // e.g. "--datadir", profile flags, etc.
+ // wxString args = "--some-flag";
+ // wxString cmd = wxString::Format(R"("%s" %s)", execPath, args);
+
+#ifdef __WXMSW__
+ wxString cmd = wxString::Format(R"("%s")", execPath);
+
+ // CreateProcess needs a mutable buffer for lpCommandLine
+ std::vector cmdBuf(cmd.wc_str(), cmd.wc_str() + cmd.length() + 1);
+
+ STARTUPINFO si = { sizeof(si) };
+ PROCESS_INFORMATION pi = {};
+ if (!CreateProcess(nullptr, cmdBuf.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
+ MessageDialog(this, _L("Failed to launch a new instance."), wxString(SLIC3R_APP_FULL_NAME), wxICON_WARNING | wxOK).ShowModal();
+ return false;
+ }
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+#else
+ // macOS / Linux — wxExecute with ASYNC so it doesn't block
+ wxString cmd = wxString::Format(R"("%s")", execPath);
+ long pid = wxExecute(cmd, wxEXEC_ASYNC);
+ if (pid == 0) {
+ MessageDialog(this,
+ _L("Failed to launch a new instance."),
+ wxString(SLIC3R_APP_FULL_NAME),
+ wxICON_WARNING | wxOK
+ ).ShowModal();
+ return false;
+ }
+#endif
+ wxMilliSleep(500);
+
+ // Close this instance cleanly
+ wxGetApp().GetTopWindow()->Close(true); // triggers EVT_CLOSE, runs cleanup
+
+ return true;
+}
+
+void TroubleshootDialog::ClearLogs()
+{
+ // Same method with GUI_App::post_init() only LOG_FILES_MAX_NUM replaced with 1
+ auto data_dir = boost::filesystem::path(Slic3r::data_dir());
+ auto log_folder = data_dir / "log";
+ if (boost::filesystem::exists(log_folder)) {
+ std::vector> files_vec;
+ for (auto& it : boost::filesystem::directory_iterator(log_folder)) {
+ auto temp_path = it.path();
+ try {
+ if (it.status().type() == boost::filesystem::regular_file) {
+ std::time_t lw_t = boost::filesystem::last_write_time(temp_path) ;
+ files_vec.push_back({ lw_t, temp_path.filename().string() });
+ }
+ } catch (const std::exception &) {
+ }
+ }
+ std::sort(files_vec.begin(), files_vec.end(), [](
+ std::pair &a, std::pair &b) {
+ return a.first > b.first;
+ });
+
+ while (files_vec.size() > 1) {
+ auto full_path = log_folder / boost::filesystem::path(files_vec[files_vec.size() - 1].second);
+ BOOST_LOG_TRIVIAL(info) << "delete log file over " << LOG_FILES_MAX_NUM << ", filename: "<< files_vec[files_vec.size() - 1].second;
+ try {
+ boost::filesystem::remove(full_path);
+ }
+ catch (const std::exception& ex) {
+ BOOST_LOG_TRIVIAL(error) << "failed to delete log file: "<< files_vec[files_vec.size() - 1].second << ". Error: " << ex.what();
+ }
+ files_vec.pop_back();
+ }
+ }
+ UpdateLogsStorage();
+}
+
+void TroubleshootDialog::UpdateLogsStorage()
+{
+ boost::filesystem::path logs_path = boost::filesystem::path(Slic3r::data_dir()) / "log";
+
+ uintmax_t total_bytes = 0;
+ int file_count = 0;
+ if (boost::filesystem::exists(logs_path) && boost::filesystem::is_directory(logs_path)) {
+ for (const auto& entry : boost::filesystem::recursive_directory_iterator(logs_path)) {
+ if (boost::filesystem::is_regular_file(entry.path())){
+ total_bytes += boost::filesystem::file_size(entry.path());
+ file_count++;
+ }
+ }
+ }
+
+ bool is_mb = total_bytes >= 1024 * 1024;
+ wxString label = total_bytes > 0 ? wxString::Format("%d %s (%.2f %s)", file_count, _L("log(s)"), total_bytes / (1024.0 * (is_mb ? 1024.0 : 1.0)), is_mb ? "MB" : "KB") : "";
+ m_logs_storage->SetLabel(label);
+}
+
+void TroubleshootDialog::BrowseFolder(std::string path)
+{
+ wxString wxpath = wxString::FromUTF8(path);
+
+ if (!wxpath.IsEmpty() && !wxFileName::IsPathSeparator(wxpath.Last()))
+ wxpath += wxFileName::GetPathSeparator();
+
+ if (wxLaunchDefaultApplication(wxpath))
+ return;
+
+ auto ShellQuote = [](const wxString& arg) {
+ #ifdef __WXMSW__
+ wxString result = "\"";
+ for (wxChar c : arg) {
+ if (c == '"') result += '\\';
+ result += c;
+ }
+ result += "\"";
+ return result;
+ #else
+ wxString result = "'";
+ for (wxChar c : arg) {
+ if (c == '\'')
+ result += wxString("'\\''");
+ else
+ result += c;
+ }
+ result += "'";
+ return result;
+ #endif
+ };
+
+ #ifdef _WIN32
+ ::wxExecute(L"explorer.exe " + ShellQuote(wxpath), wxEXEC_ASYNC);
+ #elif defined(__APPLE__)
+ ::wxExecute(wxString::Format("open %s", ShellQuote(wxpath)), wxEXEC_ASYNC);
+ #else
+ const char* argv[] = { "xdg-open", nullptr, nullptr };
+
+ wxString utf8_path = wxpath.ToUTF8();
+ argv[1] = utf8_path.c_str();
+
+ if (wxGetEnv("APPIMAGE", nullptr)) {
+ wxEnvVariableHashMap env_vars;
+ wxGetEnvMap(&env_vars);
+
+ env_vars.erase("APPIMAGE");
+ env_vars.erase("APPDIR");
+ env_vars.erase("LD_LIBRARY_PATH");
+ env_vars.erase("LD_PRELOAD");
+ env_vars.erase("UNION_PRELOAD");
+
+ wxExecuteEnv exec_env;
+ exec_env.env = std::move(env_vars);
+
+ wxString owd;
+ if (wxGetEnv("OWD", &owd))
+ exec_env.cwd = std::move(owd);
+
+ ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr, &exec_env);
+ }
+ else
+ ::wxExecute(const_cast(argv), wxEXEC_ASYNC);
+ #endif
+}
+
+bool TroubleshootDialog::ExportAsJson(const wxString& json_data, const wxString& export_name)
+{
+ wxString home = wxGetHomeDir();
+
+ wxFileName desktop(home, "");
+ desktop.AppendDir("Desktop");
+ wxString defaultPath = wxDirExists(desktop.GetPath()) ? desktop.GetPath() : home;
+
+ wxFileDialog dialog(this, _L("Choose where to save the exported JSON file"), defaultPath,
+ export_name.IsEmpty() ? "export.json" : export_name + ".json",
+ "JSON files (*.json)|*.json",
+ wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+ );
+
+ if (dialog.ShowModal() != wxID_OK)
+ return false;
+
+ wxString jsonPath = dialog.GetPath();
+
+ wxFile file(jsonPath, wxFile::write);
+ if (!file.IsOpened()) {
+ MessageDialog(this, _L("Export failed\nPlease check write permissions or file in use by another application"),
+ wxString(SLIC3R_APP_FULL_NAME), wxICON_WARNING | wxOK
+ ).ShowModal();
+ return false;
+ }
+
+ bool success = file.Write(json_data, wxConvUTF8);
+ file.Close();
+
+ if (!success) {
+ wxRemoveFile(jsonPath);
+ MessageDialog(this, _L("Export failed\nPlease check write permissions or file in use by another application"),
+ wxString(SLIC3R_APP_FULL_NAME), wxICON_WARNING | wxOK
+ ).ShowModal();
+ return false;
+ }
+
+ MessageDialog(this, _L("Export successful"), wxString(SLIC3R_APP_FULL_NAME), wxICON_INFORMATION | wxOK).ShowModal();
+
+ return true;
+}
+
+bool TroubleshootDialog::ExportAsZip(const std::vector& sources, const wxString& export_name)
+{
+ wxString home = wxGetHomeDir();
+
+ wxFileName desktop(home, "");
+ desktop.AppendDir("Desktop");
+ wxString defaultPath = wxDirExists(desktop.GetPath()) ? desktop.GetPath() : home;
+
+ wxDirDialog dialog(this, _L("Choose where to save the exported ZIP file"), defaultPath, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
+ wxString destDir = (dialog.ShowModal() == wxID_OK) ? dialog.GetPath() : "";
+ if (destDir.IsEmpty())
+ return false;
+ wxString baseName = export_name.IsEmpty() ? wxFileName(sources[0]).GetFullName() : export_name;
+ wxString zipPath = wxFileName(destDir, baseName + ".zip").GetFullPath();
+ if (wxFileExists(zipPath)) {
+ MessageDialog msg(this, _L("File already exists. Overwrite?"),
+ wxString(SLIC3R_APP_FULL_NAME), wxICON_QUESTION | wxYES_NO
+ );
+ if (msg.ShowModal() != wxID_YES)
+ return false;
+ }
+ if (!SaveAsZip(sources, zipPath)) {
+ MessageDialog(this, _L("Export failed\nPlease check write permissions or file in use by another application"),
+ wxString(SLIC3R_APP_FULL_NAME), wxICON_WARNING | wxOK
+ ).ShowModal();
+ return false;
+ }
+ else {
+ MessageDialog(this, _L("Export successful"), wxString(SLIC3R_APP_FULL_NAME), wxICON_INFORMATION | wxOK).ShowModal();
+ }
+ return true;
+}
+
+bool TroubleshootDialog::AddToZip(wxZipOutputStream& zip, const wxString& fullPathOrTextData, const wxString& rootDir)
+{
+ if (fullPathOrTextData.StartsWith(wxT("TxtData:"))) { // add text to zip
+ // Format: "TxtData:|"
+ wxString payload = fullPathOrTextData.Mid(8); // strip "TxtData:"
+ int sep = payload.Find('|');
+ if (sep == wxNOT_FOUND)
+ return false;
+ wxString entryName = payload.Left(sep);
+ wxString content = payload.Mid(sep + 1);
+
+ if (!zip.PutNextEntry(entryName))
+ return false;
+ wxScopedCharBuffer buf = content.utf8_str();
+ zip.Write(buf.data(), buf.length());
+ return zip.CloseEntry();
+ }
+
+ wxString relPath = fullPathOrTextData.Mid(rootDir.length());
+ if (relPath.StartsWith(wxFileName::GetPathSeparator()))
+ relPath = relPath.Mid(1);
+ relPath.Replace(wxFileName::GetPathSeparator(), wxT("/"));
+ if (wxDirExists(fullPathOrTextData)) {
+ if (!relPath.IsEmpty()) {
+ if (!relPath.EndsWith(wxT("/")))
+ relPath += wxT("/");
+ if (!zip.PutNextDirEntry(relPath))
+ return false;
+ }
+
+ wxDir dir(fullPathOrTextData);
+ if (!dir.IsOpened())
+ return false;
+
+ wxString filename;
+ bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES | wxDIR_DIRS | wxDIR_HIDDEN);
+ while (cont) {
+ wxString childPath = fullPathOrTextData;
+ if (!wxEndsWithPathSeparator(childPath))
+ childPath += wxFileName::GetPathSeparator();
+ childPath += filename;
+
+ if (!AddToZip(zip, childPath, rootDir))
+ return false;
+
+ cont = dir.GetNext(&filename);
+ }
+ }
+ else if (wxFileExists(fullPathOrTextData)) {
+ wxFileInputStream in(fullPathOrTextData);
+ if (!in.IsOk())
+ return false;
+ if (!zip.PutNextEntry(relPath))
+ return false;
+ zip.Write(in);
+ if (!zip.CloseEntry())
+ return false;
+ }
+ else
+ return false;
+
+ return true;
+}
+
+bool TroubleshootDialog::SaveAsZip(const std::vector& sourcePaths, const wxString& zipFullPath)
+{
+ wxFileOutputStream out(zipFullPath);
+ if (!out.IsOk())
+ return false;
+ wxZipOutputStream zip(out);
+ if (!zip.IsOk()) {
+ out.Close();
+ return false;
+ }
+ bool success = true;
+ for (const auto& sourcePath : sourcePaths) {
+ if (sourcePath.StartsWith(wxT("TxtData:"))) {
+ wxString rootDir; // unused for virtual entries
+ if (!AddToZip(zip, sourcePath, rootDir)) {
+ success = false;
+ break;
+ }
+ continue;
+ }
+ if (!wxDirExists(sourcePath) && !wxFileExists(sourcePath)) {
+ success = false;
+ break;
+ }
+ wxString rootDir = wxFileName(sourcePath).GetPath();
+ if (!wxEndsWithPathSeparator(rootDir))
+ rootDir += wxFileName::GetPathSeparator();
+ if (!AddToZip(zip, sourcePath, rootDir)) {
+ success = false;
+ break;
+ }
+ }
+ if (!zip.Close()) success = false;
+ if (!out.Close()) success = false;
+ if (!success)
+ wxRemoveFile(zipFullPath);
+ return success;
+}
+
+void TroubleshootDialog::on_dpi_changed(const wxRect& suggested_rect)
+{
+ m_logo.msw_rescale();
+ m_header_logo->SetBitmap(m_logo.bmp());
+
+ auto processCtrls = [&](auto&& self, wxWindow* win) -> void {
+ if (!win)
+ return;
+
+ if (Button* btn = dynamic_cast