mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-10 04:20:08 +00:00
1399 lines
52 KiB
C++
1399 lines
52 KiB
C++
#include "TroubleshootDialog.hpp"
|
|
#include "I18N.hpp"
|
|
|
|
#include "GUI.hpp"
|
|
#include "GUI_App.hpp"
|
|
#include "MainFrame.hpp"
|
|
|
|
#include <wx/display.h>
|
|
#include <wx/wfstream.h>
|
|
#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 <nlohmann/json.hpp>
|
|
|
|
#ifdef __WINDOWS__
|
|
#include <windows.h>
|
|
#include <VersionHelpers.h>
|
|
#include <sysinfoapi.h>
|
|
#endif
|
|
|
|
#ifdef __LINUX__
|
|
#include <wx/textfile.h>
|
|
#include <wx/tokenzr.h>
|
|
#include <cstdlib>
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
#include <wx/regex.h>
|
|
#define Rect Mac_Rect
|
|
#define RectPtr Mac_RectPtr
|
|
#define Point Mac_Point
|
|
#define Size Mac_Size
|
|
#include <CoreGraphics/CoreGraphics.h>
|
|
#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<wxString>{_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<wxWindow*>(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<wxString>{GetOStype()};
|
|
return std::vector<wxString>{
|
|
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<std::string>{"recent_projects", "recent", "custom_color_list", "orca_presets"})
|
|
root.erase(key);
|
|
if (root.contains("app")) {
|
|
for (const auto& key : std::vector<std::string>{"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<const ConfigOptionStrings*>(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<const ConfigOptionStrings*>(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<const ConfigOptionStrings*>(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<const ConfigOptionStrings*>(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<LPBYTE>(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<LPBYTE>(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<std::string, std::string> cpu_info = parse_lscpu_etc("sysctl -a", ':');
|
|
info = wxString(cpu_info["machdep.cpu.brand_string"]);
|
|
#else // linux/BSD
|
|
std::map<std::string, std::string> 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<std::string, std::string> TroubleshootDialog::parse_lscpu_etc(const std::string& name, char delimiter)
|
|
{
|
|
std::map<std::string, std::string> 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<CGDirectDisplayID> 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<int>(std::round(rc.width * scale));
|
|
int physH = static_cast<int>(std::round(rc.height * scale));
|
|
const char* type = "L-"; // Logical fallback. only visible when cant get native res
|
|
|
|
CGDirectDisplayID cgDispID = (i < static_cast<int>(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<int>(pw);
|
|
physH = static_cast<int>(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<UINT(WINAPI*)()>(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<wxString> 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<boost::filesystem::path> 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. Would you like to save before continuing\?") +
|
|
"\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. Would you like to save before continuing\?") +
|
|
"\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<wchar_t> 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<std::pair<time_t, std::string>> 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<time_t, std::string> &a, std::pair<time_t, std::string> &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<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
|
|
}
|
|
else
|
|
::wxExecute(const_cast<char**>(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<wxString>& 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:<filename>|<content>"
|
|
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<wxString>& 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<Button*>(win))
|
|
btn->Rescale();
|
|
|
|
if (ComboBox* combo = dynamic_cast<ComboBox*>(win))
|
|
combo->Rescale();
|
|
|
|
wxWindowList children = win->GetChildren();
|
|
for (auto child : children)
|
|
self(self, child);
|
|
};
|
|
|
|
processCtrls(processCtrls, this);
|
|
|
|
Layout();
|
|
Fit();
|
|
Refresh();
|
|
}
|
|
|
|
//TroubleshootDialog::~TroubleshootDialog()
|
|
//{
|
|
//}
|
|
|
|
} // namespace GUI
|
|
} // namespace Slic3r
|