diff --git a/resources/icons/notification_open.svg b/resources/icons/notification_open.svg
new file mode 100644
index 00000000000..a83138a887a
--- /dev/null
+++ b/resources/icons/notification_open.svg
@@ -0,0 +1,63 @@
+
+
diff --git a/resources/icons/notification_open_hover.svg b/resources/icons/notification_open_hover.svg
new file mode 100644
index 00000000000..77472808864
--- /dev/null
+++ b/resources/icons/notification_open_hover.svg
@@ -0,0 +1,64 @@
+
+
diff --git a/resources/icons/notification_pause.svg b/resources/icons/notification_pause.svg
new file mode 100644
index 00000000000..dc0d6131161
--- /dev/null
+++ b/resources/icons/notification_pause.svg
@@ -0,0 +1,75 @@
+
+
diff --git a/resources/icons/notification_pause_hover.svg b/resources/icons/notification_pause_hover.svg
new file mode 100644
index 00000000000..6654f3775e5
--- /dev/null
+++ b/resources/icons/notification_pause_hover.svg
@@ -0,0 +1,75 @@
+
+
diff --git a/resources/icons/notification_play.svg b/resources/icons/notification_play.svg
new file mode 100644
index 00000000000..5aa80cd94f5
--- /dev/null
+++ b/resources/icons/notification_play.svg
@@ -0,0 +1,75 @@
+
+
diff --git a/resources/icons/notification_play_hover.svg b/resources/icons/notification_play_hover.svg
new file mode 100644
index 00000000000..f0d07fc1231
--- /dev/null
+++ b/resources/icons/notification_play_hover.svg
@@ -0,0 +1,75 @@
+
+
diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp
index 918d2ca3c27..1ee4b0d347b 100644
--- a/src/PrusaSlicer.cpp
+++ b/src/PrusaSlicer.cpp
@@ -113,6 +113,9 @@ int CLI::run(int argc, char **argv)
std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() &&
std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() &&
std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end();
+ bool start_downloader = false;
+ bool delete_after_load = false;
+ std::string download_url;
bool start_as_gcodeviewer =
#ifdef _WIN32
false;
@@ -221,6 +224,11 @@ int CLI::run(int argc, char **argv)
}
if (!start_as_gcodeviewer) {
for (const std::string& file : m_input_files) {
+ if (boost::starts_with(file, "prusaslicer://")) {
+ start_downloader = true;
+ download_url = file;
+ continue;
+ }
if (!boost::filesystem::exists(file)) {
boost::nowide::cerr << "No such file: " << file << std::endl;
exit(1);
@@ -478,6 +486,9 @@ int CLI::run(int argc, char **argv)
// Models are repaired by default.
//for (auto &model : m_models)
// model.repair();
+
+ } else if (opt_key == "delete-after-load") {
+ delete_after_load = true;
} else {
boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl;
return 1;
@@ -663,9 +674,12 @@ int CLI::run(int argc, char **argv)
params.extra_config = std::move(m_extra_config);
params.input_files = std::move(m_input_files);
params.start_as_gcodeviewer = start_as_gcodeviewer;
+ params.start_downloader = start_downloader;
+ params.download_url = download_url;
+ params.delete_after_load = delete_after_load;
#if ENABLE_GL_CORE_PROFILE
- params.opengl_version = opengl_version;
#if ENABLE_OPENGL_DEBUG_OPTION
+ params.opengl_version = opengl_version;
params.opengl_debug = opengl_debug;
#endif // ENABLE_OPENGL_DEBUG_OPTION
#endif // ENABLE_GL_CORE_PROFILE
diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h
index 6dabace5b59..651df6183cf 100644
--- a/src/imgui/imconfig.h
+++ b/src/imgui/imconfig.h
@@ -158,6 +158,12 @@ namespace ImGui
const wchar_t SliderFloatEditBtnIcon = 0x2604;
const wchar_t SliderFloatEditBtnPressedIcon = 0x2605;
const wchar_t ClipboardBtnIcon = 0x2606;
+ const wchar_t PlayButton = 0x2618;
+ const wchar_t PlayHoverButton = 0x2619;
+ const wchar_t PauseButton = 0x261A;
+ const wchar_t PauseHoverButton = 0x261B;
+ const wchar_t OpenButton = 0x261C;
+ const wchar_t OpenHoverButton = 0x261D;
const wchar_t LegendTravel = 0x2701;
const wchar_t LegendWipe = 0x2702;
@@ -173,8 +179,8 @@ namespace ImGui
const wchar_t LegendToolMarker = 0x2712;
const wchar_t WarningMarkerSmall = 0x2713;
const wchar_t ExpandBtn = 0x2714;
- const wchar_t CollapseBtn = 0x2715;
const wchar_t InfoMarkerSmall = 0x2716;
+ const wchar_t CollapseBtn = 0x2715;
// void MyFunction(const char* name, const MyMatrix44& v);
}
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index fd752873d3d..af2ce3b66d7 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -4632,6 +4632,10 @@ CLITransformConfigDef::CLITransformConfigDef()
def->label = L("Scale to Fit");
def->tooltip = L("Scale to fit the given volume.");
def->set_default_value(new ConfigOptionPoint3(Vec3d(0,0,0)));
+
+ def = this->add("delete-after-load", coString);
+ def->label = L("Delete files after loading");
+ def->tooltip = L("Delete files after loading.");
}
CLIMiscConfigDef::CLIMiscConfigDef()
diff --git a/src/platform/osx/Info.plist.in b/src/platform/osx/Info.plist.in
index d3c91360497..60f09201677 100644
--- a/src/platform/osx/Info.plist.in
+++ b/src/platform/osx/Info.plist.in
@@ -110,6 +110,17 @@
Alternate
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+ PrusaSlicer Downloads
+ CFBundleURLSchemes
+
+ prusaslicer
+
+
+
LSMinimumSystemVersion
10.12
NSPrincipalClass
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 4fa51c48688..236aedf66f9 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -232,6 +232,12 @@ set(SLIC3R_GUI_SOURCES
GUI/DesktopIntegrationDialog.hpp
GUI/HintNotification.cpp
GUI/HintNotification.hpp
+ GUI/FileArchiveDialog.cpp
+ GUI/FileArchiveDialog.hpp
+ GUI/Downloader.cpp
+ GUI/Downloader.hpp
+ GUI/DownloaderFileGet.cpp
+ GUI/DownloaderFileGet.hpp
Utils/AppUpdater.cpp
Utils/AppUpdater.hpp
Utils/Http.cpp
diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp
index 204067a9516..ef6f53bf08c 100644
--- a/src/slic3r/GUI/ConfigWizard.cpp
+++ b/src/slic3r/GUI/ConfigWizard.cpp
@@ -1,3123 +1,3360 @@
-// FIXME: extract absolute units -> em
-
-#include "ConfigWizard_private.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#ifdef _MSW_DARK_MODE
-#include
-#endif // _MSW_DARK_MODE
-
-#include "libslic3r/Platform.hpp"
-#include "libslic3r/Utils.hpp"
-#include "libslic3r/Config.hpp"
-#include "libslic3r/libslic3r.h"
-#include "libslic3r/Model.hpp"
-#include "libslic3r/Color.hpp"
-#include "GUI.hpp"
-#include "GUI_App.hpp"
-#include "GUI_Utils.hpp"
-#include "GUI_ObjectManipulation.hpp"
-#include "Field.hpp"
-#include "DesktopIntegrationDialog.hpp"
-#include "slic3r/Config/Snapshot.hpp"
-#include "slic3r/Utils/PresetUpdater.hpp"
-#include "format.hpp"
-#include "MsgDialog.hpp"
-#include "UnsavedChangesDialog.hpp"
-
-#if defined(__linux__) && defined(__WXGTK3__)
-#define wxLinux_gtk3 true
-#else
-#define wxLinux_gtk3 false
-#endif //defined(__linux__) && defined(__WXGTK3__)
-
-namespace Slic3r {
-namespace GUI {
-
-
-using Config::Snapshot;
-using Config::SnapshotDB;
-
-
-// Configuration data structures extensions needed for the wizard
-
-bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle)
-{
- this->preset_bundle = std::make_unique();
- this->is_in_resources = ais_in_resources;
- this->is_prusa_bundle = ais_prusa_bundle;
-
- std::string path_string = source_path.string();
- // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
- auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(
- path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
- UNUSED(config_substitutions);
- // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
- assert(config_substitutions.empty());
- auto first_vendor = preset_bundle->vendors.begin();
- if (first_vendor == preset_bundle->vendors.end()) {
- BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
- return false;
- }
- if (presets_loaded == 0) {
- BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string;
- return false;
- }
-
- BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded;
- this->vendor_profile = &first_vendor->second;
- return true;
-}
-
-Bundle::Bundle(Bundle &&other)
- : preset_bundle(std::move(other.preset_bundle))
- , vendor_profile(other.vendor_profile)
- , is_in_resources(other.is_in_resources)
- , is_prusa_bundle(other.is_prusa_bundle)
-{
- other.vendor_profile = nullptr;
-}
-
-BundleMap BundleMap::load()
-{
- BundleMap res;
-
- const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred();
- const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
-
- auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
- auto prusa_bundle_rsrc = false;
- if (! boost::filesystem::exists(prusa_bundle_path)) {
- prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
- prusa_bundle_rsrc = true;
- }
- {
- Bundle prusa_bundle;
- if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true))
- res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle));
- }
-
- // Load the other bundles in the datadir/vendor directory
- // and then additionally from resources/profiles.
- bool is_in_resources = false;
- for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) {
- for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) {
- if (Slic3r::is_ini_file(dir_entry)) {
- std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part
-
- // Don't load this bundle if we've already loaded it.
- if (res.find(id) != res.end()) { continue; }
-
- Bundle bundle;
- if (bundle.load(dir_entry.path(), is_in_resources))
- res.emplace(std::move(id), std::move(bundle));
- }
- }
-
- is_in_resources = true;
- }
-
- return res;
-}
-
-Bundle& BundleMap::prusa_bundle()
-{
- auto it = find(PresetBundle::PRUSA_BUNDLE);
- if (it == end()) {
- throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded");
- }
-
- return it->second;
-}
-
-const Bundle& BundleMap::prusa_bundle() const
-{
- return const_cast(this)->prusa_bundle();
-}
-
-
-// Printer model picker GUI control
-
-struct PrinterPickerEvent : public wxEvent
-{
- std::string vendor_id;
- std::string model_id;
- std::string variant_name;
- bool enable;
-
- PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable)
- : wxEvent(winid, eventType)
- , vendor_id(std::move(vendor_id))
- , model_id(std::move(model_id))
- , variant_name(std::move(variant_name))
- , enable(enable)
- {}
-
- virtual wxEvent *Clone() const
- {
- return new PrinterPickerEvent(*this);
- }
-};
-
-wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent);
-
-const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png";
-
-PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter)
- : wxPanel(parent)
- , vendor_id(vendor.id)
- , width(0)
-{
- wxGetApp().UpdateDarkUI(this);
- const auto &models = vendor.models;
-
- auto *sizer = new wxBoxSizer(wxVERTICAL);
-
- const auto font_title = GetFont().MakeBold().Scaled(1.3f);
- const auto font_name = GetFont().MakeBold();
- const auto font_alt_nozzle = GetFont().Scaled(0.9f);
-
- // wxGrid appends widgets by rows, but we need to construct them in columns.
- // These vectors are used to hold the elements so that they can be appended in the right order.
- std::vector titles;
- std::vector bitmaps;
- std::vector variants_panels;
-
- int max_row_width = 0;
- int current_row_width = 0;
-
- bool is_variants = false;
-
- for (const auto &model : models) {
- if (! filter(model)) { continue; }
-
- wxBitmap bitmap;
- int bitmap_width = 0;
- auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool {
- if (wxFileExists(bitmap_file)) {
- bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG);
- bitmap_width = bitmap.GetWidth();
- return true;
- }
- return false;
- };
- if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
- if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
- BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead")
- % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png")
- % vendor.id
- % model.id;
- load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width);
- }
- }
- auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
- title->SetFont(font_name);
- const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width);
- title->Wrap(wrap_width);
-
- current_row_width += wrap_width;
- if (titles.size() % max_cols == max_cols - 1) {
- max_row_width = std::max(max_row_width, current_row_width);
- current_row_width = 0;
- }
-
- titles.push_back(title);
-
- auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap);
- bitmaps.push_back(bitmap_widget);
-
- auto *variants_panel = new wxPanel(this);
- wxGetApp().UpdateDarkUI(variants_panel);
- auto *variants_sizer = new wxBoxSizer(wxVERTICAL);
- variants_panel->SetSizer(variants_sizer);
- const auto model_id = model.id;
-
- for (size_t i = 0; i < model.variants.size(); i++) {
- const auto &variant = model.variants[i];
-
- const auto label = model.technology == ptFFF
- ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str())
- : from_u8(model.name);
-
- if (i == 1) {
- auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:"));
- alt_label->SetFont(font_alt_nozzle);
- variants_sizer->Add(alt_label, 0, wxBOTTOM, 3);
- is_variants = true;
- }
-
- auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name);
- i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox);
-
- const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name);
- cbox->SetValue(enabled);
-
- variants_sizer->Add(cbox, 0, wxBOTTOM, 3);
-
- cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) {
- on_checkbox(cbox, event.IsChecked());
- });
- }
-
- variants_panels.push_back(variants_panel);
- }
-
- width = std::max(max_row_width, current_row_width);
-
- const size_t cols = std::min(max_cols, titles.size());
-
- auto *printer_grid = new wxFlexGridSizer(cols, 0, 20);
- printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL);
-
- if (titles.size() > 0) {
- const size_t odd_items = titles.size() % cols;
-
- for (size_t i = 0; i < titles.size() - odd_items; i += cols) {
- for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); }
- for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); }
- for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); }
-
- // Add separator space to multiliners
- if (titles.size() > cols) {
- for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); }
- }
- }
- if (odd_items > 0) {
- const size_t rem = titles.size() - odd_items;
-
- for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); }
- for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); }
- for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); }
- for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); }
- for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); }
- }
- }
-
- auto *title_sizer = new wxBoxSizer(wxHORIZONTAL);
- if (! title.IsEmpty()) {
- auto *title_widget = new wxStaticText(this, wxID_ANY, title);
- title_widget->SetFont(font_title);
- title_sizer->Add(title_widget);
- }
- title_sizer->AddStretchSpacer();
-
- if (titles.size() > 1 || is_variants) {
- // It only makes sense to add the All / None buttons if there's multiple printers
- // All Standard button is added when there are more variants for at least one printer
- auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard"));
- auto *sel_all = new wxButton(this, wxID_ANY, _L("All"));
- auto *sel_none = new wxButton(this, wxID_ANY, _L("None"));
- if (is_variants)
- sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& event) { this->select_all(true, false); });
- sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); });
- sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); });
- if (is_variants)
- title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING);
- title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING);
- title_sizer->Add(sel_none);
-
- wxGetApp().UpdateDarkUI(sel_all_std);
- wxGetApp().UpdateDarkUI(sel_all);
- wxGetApp().UpdateDarkUI(sel_none);
-
- // fill button indexes used later for buttons rescaling
- if (is_variants)
- m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() };
- else {
- sel_all_std->Destroy();
- m_button_indexes = { sel_all->GetId(), sel_none->GetId() };
- }
- }
-
- sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING);
- sizer->Add(printer_grid);
-
- SetSizer(sizer);
-}
-
-PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig)
- : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; })
-{}
-
-void PrinterPicker::select_all(bool select, bool alternates)
-{
- for (const auto &cb : cboxes) {
- if (cb->GetValue() != select) {
- cb->SetValue(select);
- on_checkbox(cb, select);
- }
- }
-
- if (! select) { alternates = false; }
-
- for (const auto &cb : cboxes_alt) {
- if (cb->GetValue() != alternates) {
- cb->SetValue(alternates);
- on_checkbox(cb, alternates);
- }
- }
-}
-
-void PrinterPicker::select_one(size_t i, bool select)
-{
- if (i < cboxes.size() && cboxes[i]->GetValue() != select) {
- cboxes[i]->SetValue(select);
- on_checkbox(cboxes[i], select);
- }
-}
-
-bool PrinterPicker::any_selected() const
-{
- for (const auto &cb : cboxes) {
- if (cb->GetValue()) { return true; }
- }
-
- for (const auto &cb : cboxes_alt) {
- if (cb->GetValue()) { return true; }
- }
-
- return false;
-}
-
-std::set PrinterPicker::get_selected_models() const
-{
- std::set ret_set;
-
- for (const auto& cb : cboxes)
- if (cb->GetValue())
- ret_set.emplace(cb->model);
-
- for (const auto& cb : cboxes_alt)
- if (cb->GetValue())
- ret_set.emplace(cb->model);
-
- return ret_set;
-}
-
-void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
-{
- PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
- AddPendingEvent(evt);
-}
-
-
-// Wizard page base
-
-ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent)
- : wxPanel(parent->p->hscroll)
- , parent(parent)
- , shortname(std::move(shortname))
- , indent(indent)
-{
- wxGetApp().UpdateDarkUI(this);
-
- auto *sizer = new wxBoxSizer(wxVERTICAL);
-
- auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
- const auto font = GetFont().MakeBold().Scaled(1.5);
- text->SetFont(font);
- sizer->Add(text, 0, wxALIGN_LEFT, 0);
- sizer->AddSpacer(10);
-
- content = new wxBoxSizer(wxVERTICAL);
- sizer->Add(content, 1, wxEXPAND);
-
- SetSizer(sizer);
-
- // There is strange layout on Linux with GTK3,
- // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861
- // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages
- if (!wxLinux_gtk3)
- this->Hide();
-
- Bind(wxEVT_SIZE, [this](wxSizeEvent &event) {
- this->Layout();
- event.Skip();
- });
-}
-
-ConfigWizardPage::~ConfigWizardPage() {}
-
-wxStaticText* ConfigWizardPage::append_text(wxString text)
-{
- auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
- widget->Wrap(WRAP_WIDTH);
- widget->SetMinSize(wxSize(WRAP_WIDTH, -1));
- append(widget);
- return widget;
-}
-
-void ConfigWizardPage::append_spacer(int space)
-{
- // FIXME: scaling
- content->AddSpacer(space);
-}
-
-// Wizard pages
-
-PageWelcome::PageWelcome(ConfigWizard *parent)
- : ConfigWizardPage(parent, from_u8((boost::format(
-#ifdef __APPLE__
- _utf8(L("Welcome to the %s Configuration Assistant"))
-#else
- _utf8(L("Welcome to the %s Configuration Wizard"))
-#endif
- ) % SLIC3R_APP_NAME).str()), _L("Welcome"))
- , welcome_text(append_text(from_u8((boost::format(
- _utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")))
- % SLIC3R_APP_NAME
- % _utf8(ConfigWizard::name())).str())
- ))
- , cbox_reset(append(
- new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)"))
- ))
- , cbox_integrate(append(
- new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system)."))
- ))
-{
- welcome_text->Hide();
- cbox_reset->Hide();
- cbox_integrate->Hide();
-}
-
-void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
-{
- const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY;
- welcome_text->Show(data_empty);
- cbox_reset->Show(!data_empty);
-#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
- if (!DesktopIntegrationDialog::is_integrated())
- cbox_integrate->Show(true);
- else
- cbox_integrate->Hide();
-#else
- cbox_integrate->Hide();
-#endif
-}
-
-
-PagePrinters::PagePrinters(ConfigWizard *parent,
- wxString title,
- wxString shortname,
- const VendorProfile &vendor,
- unsigned indent,
- Technology technology)
- : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent)
- , technology(technology)
- , install(false) // only used for 3rd party vendors
-{
- enum {
- COL_SIZE = 200,
- };
-
- AppConfig *appconfig = &this->wizard_p()->appconfig_new;
-
- const auto families = vendor.families();
- for (const auto &family : families) {
- const auto filter = [&](const VendorProfile::PrinterModel &model) {
- return ((model.technology == ptFFF && technology & T_FFF)
- || (model.technology == ptSLA && technology & T_SLA))
- && model.family == family;
- };
-
- if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) {
- continue;
- }
-
- const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str());
- auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter);
-
- picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) {
- appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
- wizard_p()->on_printer_pick(this, evt);
- });
-
- append(new StaticLine(this));
-
- append(picker);
- printer_pickers.push_back(picker);
- has_printers = true;
- }
-
-}
-
-void PagePrinters::select_all(bool select, bool alternates)
-{
- for (auto picker : printer_pickers) {
- picker->select_all(select, alternates);
- }
-}
-
-int PagePrinters::get_width() const
-{
- return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0,
- [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); });
-}
-
-bool PagePrinters::any_selected() const
-{
- for (const auto *picker : printer_pickers) {
- if (picker->any_selected()) { return true; }
- }
-
- return false;
-}
-
-std::set PagePrinters::get_selected_models()
-{
- std::set ret_set;
-
- for (const auto *picker : printer_pickers)
- {
- std::set tmp_models = picker->get_selected_models();
- ret_set.insert(tmp_models.begin(), tmp_models.end());
- }
-
- return ret_set;
-}
-
-void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
-{
- if (is_primary_printer_page
- && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY)
- && printer_pickers.size() > 0
- && printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) {
- printer_pickers[0]->select_one(0, true);
- }
-}
-
-
-const std::string PageMaterials::EMPTY;
-
-PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name)
- : ConfigWizardPage(parent, std::move(title), std::move(shortname))
- , materials(materials)
- , list_printer(new StringList(this, wxLB_MULTIPLE))
- , list_type(new StringList(this))
- , list_vendor(new StringList(this))
- , list_profile(new PresetList(this))
-{
- append_spacer(VERTICAL_SPACING);
-
- const int em = parent->em_unit();
- const int list_h = 30*em;
-
-
- list_printer->SetMinSize(wxSize(23*em, list_h));
- list_type->SetMinSize(wxSize(13*em, list_h));
- list_vendor->SetMinSize(wxSize(13*em, list_h));
- list_profile->SetMinSize(wxSize(23*em, list_h));
-
-
-
- grid = new wxFlexGridSizer(4, em/2, em);
- grid->AddGrowableCol(3, 1);
- grid->AddGrowableRow(1, 1);
-
- grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:")));
- grid->Add(new wxStaticText(this, wxID_ANY, list1name));
- grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:")));
- grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:")));
-
- grid->Add(list_printer, 0, wxEXPAND);
- grid->Add(list_type, 0, wxEXPAND);
- grid->Add(list_vendor, 0, wxEXPAND);
- grid->Add(list_profile, 1, wxEXPAND);
-
- auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL);
- auto *sel_all = new wxButton(this, wxID_ANY, _L("All"));
- auto *sel_none = new wxButton(this, wxID_ANY, _L("None"));
- btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2);
- btn_sizer->Add(sel_none);
-
- wxGetApp().UpdateDarkUI(list_printer);
- wxGetApp().UpdateDarkUI(list_type);
- wxGetApp().UpdateDarkUI(list_vendor);
- wxGetApp().UpdateDarkUI(sel_all);
- wxGetApp().UpdateDarkUI(sel_none);
-
- grid->Add(new wxBoxSizer(wxHORIZONTAL));
- grid->Add(new wxBoxSizer(wxHORIZONTAL));
- grid->Add(new wxBoxSizer(wxHORIZONTAL));
- grid->Add(btn_sizer, 0, wxALIGN_RIGHT);
-
- append(grid, 1, wxEXPAND);
-
- append_spacer(VERTICAL_SPACING);
-
- html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition,
- wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO);
- append(html_window, 0, wxEXPAND);
-
- list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) {
- update_lists(list_type->GetSelection(), list_vendor->GetSelection(), evt.GetInt());
- });
- list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
- update_lists(list_type->GetSelection(), list_vendor->GetSelection());
- });
- list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
- update_lists(list_type->GetSelection(), list_vendor->GetSelection());
- });
-
- list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); });
- list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); });
-
- sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); });
- sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); });
- /*
- Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();});
-
- list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); });
- list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); });
- list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); });
- */
- reload_presets();
- set_compatible_printers_html_window(std::vector(), false);
-}
-void PageMaterials::on_paint()
-{
-}
-void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt)
-{
- const wxClientDC dc(list_profile);
- const wxPoint pos = evt.GetLogicalPosition(dc);
- int item = list_profile->HitTest(pos);
- on_material_hovered(item);
-}
-void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt)
-{}
-void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt)
-{
- on_material_hovered(-1);
-}
-void PageMaterials::reload_presets()
-{
- clear();
-
- list_printer->append(_L("(All)"), &EMPTY);
- //list_printer->SetLabelMarkup("bald");
- for (const Preset* printer : materials->printers) {
- list_printer->append(printer->name, &printer->name);
- }
- sort_list_data(list_printer, true, false);
- if (list_printer->GetCount() > 0) {
- list_printer->SetSelection(0);
- sel_printers_prev.Clear();
- sel_type_prev = wxNOT_FOUND;
- sel_vendor_prev = wxNOT_FOUND;
- update_lists(0, 0, 0);
- }
-
- presets_loaded = true;
-}
-
-void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers)
-{
- const auto bgr_clr =
-#if defined(__APPLE__)
- html_window->GetParent()->GetBackgroundColour();
-#else
-#if defined(_WIN32)
- wxGetApp().get_window_default_clr();
-#else
- wxSystemSettings::GetColour(wxSYS_COLOUR_MENU);
-#endif
-#endif
- const auto text_clr = wxGetApp().get_label_clr_default();
- const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
- const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
- wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials"));
- wxString text;
- if (all_printers) {
- wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material"));
- text = wxString::Format(
- ""
- ""
- ""
- ""
- ""
- "%s
%s"
- ""
- ""
- ""
- ""
- , bgr_clr_str
- , text_clr_str
- , first_line
- , second_line
- );
- } else {
- wxString second_line;
- if (!printer_names.empty())
- second_line = (materials->technology == T_FFF ?
- _L("Only the following installed printers are compatible with the selected filaments") :
- _L("Only the following installed printers are compatible with the selected SLA materials")) + ":";
- text = wxString::Format(
- ""
- ""
- ""
- ""
- ""
- "%s
%s"
- ""
- ""
- , bgr_clr_str
- , text_clr_str
- , first_line
- , second_line);
- for (size_t i = 0; i < printer_names.size(); ++i)
- {
- text += wxString::Format("%s | ", boost::nowide::widen(printer_names[i]));
- if (i % 3 == 2) {
- text += wxString::Format(
- "
"
- "");
- }
- }
- text += wxString::Format(
- "
"
- "
"
- ""
- ""
- ""
- ""
- );
- }
-
- wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this));
- const int fs = font.GetPointSize();
- int size[] = { fs,fs,fs,fs,fs,fs,fs };
- html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
- html_window->SetPage(text);
-}
-
-void PageMaterials::clear_compatible_printers_label()
-{
- set_compatible_printers_html_window(std::vector(), false);
-}
-
-void PageMaterials::on_material_hovered(int sel_material)
-{
-
-}
-
-void PageMaterials::on_material_highlighted(int sel_material)
-{
- if (sel_material == last_hovered_item)
- return;
- if (sel_material == -1) {
- clear_compatible_printers_label();
- return;
- }
- last_hovered_item = sel_material;
- std::vector tabs;
- tabs.push_back(std::string());
- tabs.push_back(std::string());
- tabs.push_back(std::string());
- //selected material string
- std::string material_name = list_profile->get_data(sel_material);
- // get material preset
- const std::vector matching_materials = materials->get_presets_by_alias(material_name);
- if (matching_materials.empty())
- {
- clear_compatible_printers_label();
- return;
- }
- //find matching printers
- std::vector names;
- for (const Preset* printer : materials->printers) {
- for (const Preset* material : matching_materials) {
- if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) {
- names.push_back(printer->name);
- break;
- }
- }
- }
- set_compatible_printers_html_window(names, names.size() == materials->printers.size());
-}
-
-void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected_printer/* = -1*/)
-{
- wxWindowUpdateLocker freeze_guard(this);
- (void)freeze_guard;
-
- wxArrayInt sel_printers;
- int sel_printers_count = list_printer->GetSelections(sel_printers);
-
- // Does our wxWidgets version support operator== for wxArrayInt ?
- // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614
-#if wxCHECK_VERSION(3, 1, 1)
- if (sel_printers != sel_printers_prev) {
-#else
- auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) {
- if (arr_first.GetCount() != arr_second.GetCount())
- return false;
- for (size_t i = 0; i < arr_first.GetCount(); i++)
- if (arr_first[i] != arr_second[i])
- return false;
- return true;
- };
- if (!are_equal(sel_printers, sel_printers_prev)) {
-#endif
-
- // Refresh type list
- list_type->Clear();
- list_type->append(_L("(All)"), &EMPTY);
- if (sel_printers_count > 0) {
- // If all is selected with other printers
- // unselect "all" or all printers depending on last value
- if (sel_printers[0] == 0 && sel_printers_count > 1) {
- if (last_selected_printer == 0) {
- list_printer->SetSelection(wxNOT_FOUND);
- list_printer->SetSelection(0);
- } else {
- list_printer->SetSelection(0, false);
- sel_printers_count = list_printer->GetSelections(sel_printers);
- }
- }
- if (sel_printers[0] != 0) {
- for (int i = 0; i < sel_printers_count; i++) {
- const std::string& printer_name = list_printer->get_data(sel_printers[i]);
- const Preset* printer = nullptr;
- for (const Preset* it : materials->printers) {
- if (it->name == printer_name) {
- printer = it;
- break;
- }
- }
- materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) {
- const std::string& type = this->materials->get_type(p);
- if (list_type->find(type) == wxNOT_FOUND) {
- list_type->append(type, &type);
- }
- });
- }
- } else {
- //clear selection except "ALL"
- list_printer->SetSelection(wxNOT_FOUND);
- list_printer->SetSelection(0);
- sel_printers_count = list_printer->GetSelections(sel_printers);
-
- materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) {
- const std::string& type = this->materials->get_type(p);
- if (list_type->find(type) == wxNOT_FOUND) {
- list_type->append(type, &type);
- }
- });
- }
- sort_list_data(list_type, true, true);
- }
-
- sel_printers_prev = sel_printers;
- sel_type = 0;
- sel_type_prev = wxNOT_FOUND;
- list_type->SetSelection(sel_type);
- list_profile->Clear();
- }
-
- if (sel_type != sel_type_prev) {
- // Refresh vendor list
-
- // XXX: The vendor list is created with quadratic complexity here,
- // but the number of vendors is going to be very small this shouldn't be a problem.
-
- list_vendor->Clear();
- list_vendor->append(_L("(All)"), &EMPTY);
- if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) {
- const std::string& type = list_type->get_data(sel_type);
- // find printer preset
- for (int i = 0; i < sel_printers_count; i++) {
- const std::string& printer_name = list_printer->get_data(sel_printers[i]);
- const Preset* printer = nullptr;
- for (const Preset* it : materials->printers) {
- if (it->name == printer_name) {
- printer = it;
- break;
- }
- }
- materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) {
- const std::string& vendor = this->materials->get_vendor(p);
- if (list_vendor->find(vendor) == wxNOT_FOUND) {
- list_vendor->append(vendor, &vendor);
- }
- });
- }
- sort_list_data(list_vendor, true, false);
- }
-
- sel_type_prev = sel_type;
- sel_vendor = 0;
- sel_vendor_prev = wxNOT_FOUND;
- list_vendor->SetSelection(sel_vendor);
- list_profile->Clear();
- }
-
- if (sel_vendor != sel_vendor_prev) {
- // Refresh material list
-
- list_profile->Clear();
- clear_compatible_printers_label();
- if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) {
- const std::string& type = list_type->get_data(sel_type);
- const std::string& vendor = list_vendor->get_data(sel_vendor);
- // finst printer preset
- std::vector to_list;
- for (int i = 0; i < sel_printers_count; i++) {
- const std::string& printer_name = list_printer->get_data(sel_printers[i]);
- const Preset* printer = nullptr;
- for (const Preset* it : materials->printers) {
- if (it->name == printer_name) {
- printer = it;
- break;
- }
- }
-
- materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) {
- const std::string& section = materials->appconfig_section();
- bool checked = wizard_p()->appconfig_new.has(section, p->name);
- bool was_checked = false;
-
- int cur_i = list_profile->find(p->alias);
- if (cur_i == wxNOT_FOUND) {
- cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
- to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked);
- }
- else {
- was_checked = list_profile->IsChecked(cur_i);
- to_list[cur_i].checked = checked || was_checked;
- }
- list_profile->Check(cur_i, checked || was_checked);
-
- /* Update preset selection in config.
- * If one preset from aliases bundle is selected,
- * than mark all presets with this aliases as selected
- * */
- if (checked && !was_checked)
- wizard_p()->update_presets_in_config(section, p->alias, true);
- else if (!checked && was_checked)
- wizard_p()->appconfig_new.set(section, p->name, "1");
- });
- }
- sort_list_data(list_profile, to_list);
- }
-
- sel_vendor_prev = sel_vendor;
- }
- wxGetApp().UpdateDarkUI(list_profile);
-}
-
-void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering)
-{
-// get data from list
-// sort data
-// first should be
-// then prusa profiles
-// then the rest
-// in alphabetical order
-
- std::vector> prusa_profiles;
- std::vector> other_profiles;
- for (int i = 0 ; i < list->size(); ++i) {
- const std::string& data = list->get_data(i);
- if (data == EMPTY) // do not sort item
- continue;
- if (!material_type_ordering && data.find("Prusa") != std::string::npos)
- prusa_profiles.push_back(data);
- else
- other_profiles.push_back(data);
- }
- if(material_type_ordering) {
-
- const ConfigOptionDef* def = print_config_def.get("filament_type");
- std::vectorenum_values = def->enum_values;
- size_t end_of_sorted = 0;
- for (size_t vals = 0; vals < enum_values.size(); vals++) {
- for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++)
- {
- // find instead compare because PET vs PETG
- if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) {
- //swap
- if(profs != end_of_sorted) {
- std::reference_wrapper aux = other_profiles[end_of_sorted];
- other_profiles[end_of_sorted] = other_profiles[profs];
- other_profiles[profs] = aux;
- }
- end_of_sorted++;
- break;
- }
- }
- }
- } else {
- std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) {
- return a.get() < b.get();
- });
- std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) {
- return a.get() < b.get();
- });
- }
-
- list->Clear();
- if (add_All_item)
- list->append(_L("(All)"), &EMPTY);
- for (const auto& item : prusa_profiles)
- list->append(item, &const_cast(item.get()));
- for (const auto& item : other_profiles)
- list->append(item, &const_cast(item.get()));
-}
-
-void PageMaterials::sort_list_data(PresetList* list, const std::vector& data)
-{
- // sort data
- // then prusa profiles
- // then the rest
- // in alphabetical order
- std::vector prusa_profiles;
- std::vector other_profiles;
- //for (int i = 0; i < data.size(); ++i) {
- for (const auto& item : data) {
- const std::string& name = item.name;
- if (name.find("Prusa") != std::string::npos)
- prusa_profiles.emplace_back(item);
- else
- other_profiles.emplace_back(item);
- }
- std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
- return a.name.get() < b.name.get();
- });
- std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
- return a.name.get() < b.name.get();
- });
- list->Clear();
- for (size_t i = 0; i < prusa_profiles.size(); ++i) {
- list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast(prusa_profiles[i].name.get()));
- list->Check(i, prusa_profiles[i].checked);
- }
- for (size_t i = 0; i < other_profiles.size(); ++i) {
- list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast(other_profiles[i].name.get()));
- list->Check(i + prusa_profiles.size(), other_profiles[i].checked);
- }
-}
-
-void PageMaterials::select_material(int i)
-{
- const bool checked = list_profile->IsChecked(i);
-
- const std::string& alias_key = list_profile->get_data(i);
- wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked);
-}
-
-void PageMaterials::select_all(bool select)
-{
- wxWindowUpdateLocker freeze_guard(this);
- (void)freeze_guard;
-
- for (unsigned i = 0; i < list_profile->GetCount(); i++) {
- const bool current = list_profile->IsChecked(i);
- if (current != select) {
- list_profile->Check(i, select);
- select_material(i);
- }
- }
-}
-
-void PageMaterials::clear()
-{
- list_printer->Clear();
- list_type->Clear();
- list_vendor->Clear();
- list_profile->Clear();
- sel_printers_prev.Clear();
- sel_type_prev = wxNOT_FOUND;
- sel_vendor_prev = wxNOT_FOUND;
- presets_loaded = false;
-}
-
-void PageMaterials::on_activate()
-{
- if (! presets_loaded) {
- wizard_p()->update_materials(materials->technology);
- reload_presets();
- }
- first_paint = true;
-}
-
-
-const char *PageCustom::default_profile_name = "My Settings";
-
-PageCustom::PageCustom(ConfigWizard *parent)
- : ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer"))
-{
- cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile"));
- auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:"));
-
- wxBoxSizer* profile_name_sizer = new wxBoxSizer(wxVERTICAL);
- profile_name_editor = new SavePresetDialog::Item{ this, profile_name_sizer, default_profile_name };
- profile_name_editor->Enable(false);
-
- cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) {
- profile_name_editor->Enable(custom_wanted());
- wizard_p()->on_custom_setup(custom_wanted());
- });
-
- append(cb_custom);
- append(label);
- append(profile_name_sizer);
-}
-
-PageUpdate::PageUpdate(ConfigWizard *parent)
- : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates"))
- , version_check(true)
- , preset_update(true)
-{
- const AppConfig *app_config = wxGetApp().app_config;
- auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
- boldfont.SetWeight(wxFONTWEIGHT_BOLD);
-
- auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates"));
- box_slic3r->SetValue(app_config->get("notify_release") != "none");
- append(box_slic3r);
- append_text(wxString::Format(_L(
- "If enabled, %s checks for new application versions online. When a new version becomes available, "
- "a notification is displayed at the next application startup (never during program usage). "
- "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME));
-
- append_spacer(VERTICAL_SPACING);
-
- auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically"));
- box_presets->SetValue(app_config->get("preset_update") == "1");
- append(box_presets);
- append_text(wxString::Format(_L(
- "If enabled, %s downloads updates of built-in system presets in the background."
- "These updates are downloaded into a separate temporary location."
- "When a new preset version becomes available it is offered at application startup."), SLIC3R_APP_NAME));
- const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings.");
- auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold);
- label_bold->SetFont(boldfont);
- label_bold->Wrap(WRAP_WIDTH);
- append(label_bold);
- append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied."));
-
- box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); });
- box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
-}
-
-PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent)
- : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk"))
- , full_pathnames(false)
-{
- auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files"));
- box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1");
- append(box_pathnames);
- append_text(_L(
- "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n"
- "If not enabled, the Reload from disk command will ask to select each file using an open file dialog."
- ));
-
- box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); });
-}
-
-#ifdef _WIN32
-PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent)
- : ConfigWizardPage(parent, _L("Files association"), _L("Files association"))
-{
- cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer"));
- cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer"));
-// cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer"));
-
- append(cb_3mf);
- append(cb_stl);
-// append(cb_gcode);
-}
-#endif // _WIN32
-
-PageMode::PageMode(ConfigWizard *parent)
- : ConfigWizardPage(parent, _L("View mode"), _L("View mode"))
-{
- append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n"
- "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. "
- "The other two offer progressively more sophisticated fine-tuning, "
- "they are suitable for advanced and expert users, respectively."));
-
- radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode"));
- radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode"));
- radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode"));
-
- std::string mode { "simple" };
- wxGetApp().app_config->get("", "view_mode", mode);
-
- if (mode == "advanced") { radio_advanced->SetValue(true); }
- else if (mode == "expert") { radio_expert->SetValue(true); }
- else { radio_simple->SetValue(true); }
-
- append(radio_simple);
- append(radio_advanced);
- append(radio_expert);
-
- append_text("\n" + _L("The size of the object can be specified in inches"));
- check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches"));
- check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1");
- append(check_inch);
-
- on_activate();
-}
-
-void PageMode::serialize_mode(AppConfig *app_config) const
-{
- std::string mode = "";
-
- if (radio_simple->GetValue()) { mode = "simple"; }
- if (radio_advanced->GetValue()) { mode = "advanced"; }
- if (radio_expert->GetValue()) { mode = "expert"; }
-
- app_config->set("view_mode", mode);
- app_config->set("use_inches", check_inch->GetValue() ? "1" : "0");
-}
-
-PageVendors::PageVendors(ConfigWizard *parent)
- : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors"))
-{
- const AppConfig &appconfig = this->wizard_p()->appconfig_new;
-
- append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":");
-
- auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
- boldfont.SetWeight(wxFONTWEIGHT_BOLD);
-
- for (const auto &pair : wizard_p()->bundles) {
- const VendorProfile *vendor = pair.second.vendor_profile;
- if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
-
- auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name);
- cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) {
- wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked());
- });
-
- const auto &vendors = appconfig.vendors();
- const bool enabled = vendors.find(pair.first) != vendors.end();
- if (enabled) {
- cbox->SetValue(true);
-
- auto pages = wizard_p()->pages_3rdparty.find(vendor->id);
- wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created");
-
- for (PagePrinters* page : { pages->second.first, pages->second.second })
- if (page) page->install = true;
- }
-
- append(cbox);
- }
-}
-
-PageFirmware::PageFirmware(ConfigWizard *parent)
- : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1)
- , gcode_opt(*print_config_def.get("gcode_flavor"))
- , gcode_picker(nullptr)
-{
- append_text(_L("Choose the type of firmware used by your printer."));
- append_text(_(gcode_opt.tooltip));
-
- wxArrayString choices;
- choices.Alloc(gcode_opt.enum_labels.size());
- for (const auto &label : gcode_opt.enum_labels) {
- choices.Add(label);
- }
-
- gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
- wxGetApp().UpdateDarkUI(gcode_picker);
- const auto &enum_values = gcode_opt.enum_values;
- auto needle = enum_values.cend();
- if (gcode_opt.default_value) {
- needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize());
- }
- if (needle != enum_values.cend()) {
- gcode_picker->SetSelection(needle - enum_values.cbegin());
- } else {
- gcode_picker->SetSelection(0);
- }
-
- append(gcode_picker);
-}
-
-void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
-{
- auto sel = gcode_picker->GetSelection();
- if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) {
- auto *opt = new ConfigOptionEnum(static_cast(sel));
- config.set_key_value("gcode_flavor", opt);
- }
-}
-
-static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value)
-{
- e.Skip();
- wxString str = ctrl->GetValue();
-
- const char dec_sep = is_decimal_separator_point() ? '.' : ',';
- const char dec_sep_alt = dec_sep == '.' ? ',' : '.';
- // Replace the first incorrect separator in decimal number.
- bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0;
-
- double val = 0.0;
- if (!str.ToDouble(&val)) {
- if (val == 0.0)
- val = def_value;
- ctrl->SetValue(double_to_string(val));
- show_error(nullptr, _L("Invalid numeric input."));
- // On Windows, this SetFocus creates an invisible marker.
- //ctrl->SetFocus();
- }
- else if (was_replaced)
- ctrl->SetValue(double_to_string(val));
-}
-
-class DiamTextCtrl : public wxTextCtrl
-{
-public:
- DiamTextCtrl(wxWindow* parent)
- {
-#ifdef _WIN32
- long style = wxBORDER_SIMPLE;
-#else
- long style = 0;
-#endif
- Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord), style);
- wxGetApp().UpdateDarkUI(this);
- }
- ~DiamTextCtrl() {}
-};
-
-PageBedShape::PageBedShape(ConfigWizard *parent)
- : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1)
- , shape_panel(new BedShapePanel(this))
-{
- append_text(_L("Set the shape of your printer's bed."));
-
- shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"),
- *wizard_p()->custom_config->option("bed_custom_texture"),
- *wizard_p()->custom_config->option("bed_custom_model"));
-
- append(shape_panel);
-}
-
-void PageBedShape::apply_custom_config(DynamicPrintConfig &config)
-{
- const std::vector& points = shape_panel->get_shape();
- const std::string& custom_texture = shape_panel->get_custom_texture();
- const std::string& custom_model = shape_panel->get_custom_model();
- config.set_key_value("bed_shape", new ConfigOptionPoints(points));
- config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture));
- config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model));
-}
-
-PageBuildVolume::PageBuildVolume(ConfigWizard* parent)
- : ConfigWizardPage(parent, _L("Build Volume"), _L("Build Volume"), 1)
- , build_volume(new DiamTextCtrl(this))
-{
- append_text(_L("Set verctical size of your printer."));
-
- wxString value = "200";
- build_volume->SetValue(value);
-
- build_volume->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) {
- double def_value = 200.0;
- double max_value = 1200.0;
- e.Skip();
- wxString str = build_volume->GetValue();
-
- const char dec_sep = is_decimal_separator_point() ? '.' : ',';
- const char dec_sep_alt = dec_sep == '.' ? ',' : '.';
- // Replace the first incorrect separator in decimal number.
- bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0;
-
- double val = 0.0;
- if (!str.ToDouble(&val)) {
- val = def_value;
- build_volume->SetValue(double_to_string(val));
- show_error(nullptr, _L("Invalid numeric input."));
- //build_volume->SetFocus();
- } else if (val < 0.0) {
- val = def_value;
- build_volume->SetValue(double_to_string(val));
- show_error(nullptr, _L("Invalid numeric input."));
- //build_volume->SetFocus();
- } else if (val > max_value) {
- val = max_value;
- build_volume->SetValue(double_to_string(val));
- show_error(nullptr, _L("Invalid numeric input."));
- //build_volume->SetFocus();
- } else if (was_replaced)
- build_volume->SetValue(double_to_string(val));
- }, build_volume->GetId());
-
- auto* sizer_volume = new wxFlexGridSizer(3, 5, 5);
- auto* text_volume = new wxStaticText(this, wxID_ANY, _L("Max print height:"));
- auto* unit_volume = new wxStaticText(this, wxID_ANY, _L("mm"));
- sizer_volume->AddGrowableCol(0, 1);
- sizer_volume->Add(text_volume, 0, wxALIGN_CENTRE_VERTICAL);
- sizer_volume->Add(build_volume);
- sizer_volume->Add(unit_volume, 0, wxALIGN_CENTRE_VERTICAL);
- append(sizer_volume);
-}
-
-void PageBuildVolume::apply_custom_config(DynamicPrintConfig& config)
-{
- double val = 0.0;
- build_volume->GetValue().ToDouble(&val);
- auto* opt_volume = new ConfigOptionFloat(val);
- config.set_key_value("max_print_height", opt_volume);
-}
-
-PageDiameters::PageDiameters(ConfigWizard *parent)
- : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1)
- , diam_nozzle(new DiamTextCtrl(this))
- , diam_filam (new DiamTextCtrl(this))
-{
- auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value();
- wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5);
- diam_nozzle->SetValue(value);
-
- auto *default_filam = print_config_def.get("filament_diameter")->get_default_value();
- value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0);
- diam_filam->SetValue(value);
-
- diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId());
- diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId());
-
- append_text(_L("Enter the diameter of your printer's hot end nozzle."));
-
- auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5);
- auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:"));
- auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm"));
- sizer_nozzle->AddGrowableCol(0, 1);
- sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
- sizer_nozzle->Add(diam_nozzle);
- sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
- append(sizer_nozzle);
-
- append_spacer(VERTICAL_SPACING);
-
- append_text(_L("Enter the diameter of your filament."));
- append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average."));
-
- auto *sizer_filam = new wxFlexGridSizer(3, 5, 5);
- auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:"));
- auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm"));
- sizer_filam->AddGrowableCol(0, 1);
- sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL);
- sizer_filam->Add(diam_filam, 0, wxALIGN_CENTRE_VERTICAL);
- sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL);
- append(sizer_filam);
-}
-
-void PageDiameters::apply_custom_config(DynamicPrintConfig &config)
-{
- double val = 0.0;
- diam_nozzle->GetValue().ToDouble(&val);
- auto *opt_nozzle = new ConfigOptionFloats(1, val);
- config.set_key_value("nozzle_diameter", opt_nozzle);
-
- val = 0.0;
- diam_filam->GetValue().ToDouble(&val);
- auto * opt_filam = new ConfigOptionFloats(1, val);
- config.set_key_value("filament_diameter", opt_filam);
-
- auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) {
- char buf[64]; // locales don't matter here (sprintf/atof)
- sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4);
- config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false));
- };
-
- set_extrusion_width("support_material_extrusion_width", 0.35);
- set_extrusion_width("top_infill_extrusion_width", 0.40);
- set_extrusion_width("first_layer_extrusion_width", 0.42);
-
- set_extrusion_width("extrusion_width", 0.45);
- set_extrusion_width("perimeter_extrusion_width", 0.45);
- set_extrusion_width("external_perimeter_extrusion_width", 0.45);
- set_extrusion_width("infill_extrusion_width", 0.45);
- set_extrusion_width("solid_infill_extrusion_width", 0.45);
-}
-
-class SpinCtrlDouble: public wxSpinCtrlDouble
-{
-public:
- SpinCtrlDouble(wxWindow* parent)
- {
-#ifdef _WIN32
- long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE;
-#else
- long style = wxSP_ARROW_KEYS;
-#endif
- Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, style);
-#ifdef _WIN32
- wxGetApp().UpdateDarkUI(this->GetText());
-#endif
- this->Refresh();
- }
- ~SpinCtrlDouble() {}
-};
-
-PageTemperatures::PageTemperatures(ConfigWizard *parent)
- : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1)
- , spin_extr(new SpinCtrlDouble(this))
- , spin_bed (new SpinCtrlDouble(this))
-{
- spin_extr->SetIncrement(5.0);
- const auto &def_extr = *print_config_def.get("temperature");
- spin_extr->SetRange(def_extr.min, def_extr.max);
- auto *default_extr = def_extr.get_default_value();
- spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200);
-
- spin_bed->SetIncrement(5.0);
- const auto &def_bed = *print_config_def.get("bed_temperature");
- spin_bed->SetRange(def_bed.min, def_bed.max);
- auto *default_bed = def_bed.get_default_value();
- spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0);
-
- append_text(_L("Enter the temperature needed for extruding your filament."));
- append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS."));
-
- auto *sizer_extr = new wxFlexGridSizer(3, 5, 5);
- auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:"));
- auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C"));
- sizer_extr->AddGrowableCol(0, 1);
- sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL);
- sizer_extr->Add(spin_extr);
- sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL);
- append(sizer_extr);
-
- append_spacer(VERTICAL_SPACING);
-
- append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed."));
- append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed."));
-
- auto *sizer_bed = new wxFlexGridSizer(3, 5, 5);
- auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:"));
- auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C"));
- sizer_bed->AddGrowableCol(0, 1);
- sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL);
- sizer_bed->Add(spin_bed);
- sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL);
- append(sizer_bed);
-}
-
-void PageTemperatures::apply_custom_config(DynamicPrintConfig &config)
-{
- auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue());
- config.set_key_value("temperature", opt_extr);
- auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue());
- config.set_key_value("first_layer_temperature", opt_extr1st);
- auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue());
- config.set_key_value("bed_temperature", opt_bed);
- auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue());
- config.set_key_value("first_layer_bed_temperature", opt_bed1st);
-}
-
-
-// Index
-
-ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent)
- : wxPanel(parent)
- , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192))
- , bullet_black(ScalableBitmap(parent, "bullet_black.png"))
- , bullet_blue(ScalableBitmap(parent, "bullet_blue.png"))
- , bullet_white(ScalableBitmap(parent, "bullet_white.png"))
- , item_active(NO_ITEM)
- , item_hover(NO_ITEM)
- , last_page((size_t)-1)
-{
-#ifndef __WXOSX__
- SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
-#endif //__WXOSX__
- SetMinSize(bg.GetSize());
-
- const wxSize size = GetTextExtent("m");
- em_w = size.x;
- em_h = size.y;
-
- Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this);
- Bind(wxEVT_SIZE, [this](wxEvent& e) { e.Skip(); Refresh(); });
- Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this);
-
- Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) {
- if (item_hover != -1) {
- item_hover = -1;
- Refresh();
- }
- evt.Skip();
- });
-
- Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) {
- if (item_hover >= 0) { go_to(item_hover); }
- });
-}
-
-wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
-
-void ConfigWizardIndex::add_page(ConfigWizardPage *page)
-{
- last_page = items.size();
- items.emplace_back(Item { page->shortname, page->indent, page });
- Refresh();
-}
-
-void ConfigWizardIndex::add_label(wxString label, unsigned indent)
-{
- items.emplace_back(Item { std::move(label), indent, nullptr });
- Refresh();
-}
-
-ConfigWizardPage* ConfigWizardIndex::active_page() const
-{
- if (item_active >= items.size()) { return nullptr; }
-
- return items[item_active].page;
-}
-
-void ConfigWizardIndex::go_prev()
-{
- // Search for a preceiding item that is a page (not a label, ie. page != nullptr)
-
- if (item_active == NO_ITEM) { return; }
-
- for (size_t i = item_active; i > 0; i--) {
- if (items[i - 1].page != nullptr) {
- go_to(i - 1);
- return;
- }
- }
-}
-
-void ConfigWizardIndex::go_next()
-{
- // Search for a next item that is a page (not a label, ie. page != nullptr)
-
- if (item_active == NO_ITEM) { return; }
-
- for (size_t i = item_active + 1; i < items.size(); i++) {
- if (items[i].page != nullptr) {
- go_to(i);
- return;
- }
- }
-}
-
-// This one actually performs the go-to op
-void ConfigWizardIndex::go_to(size_t i)
-{
- if (i != item_active
- && i < items.size()
- && items[i].page != nullptr) {
- auto *new_active = items[i].page;
- auto *former_active = active_page();
- if (former_active != nullptr) {
- former_active->Hide();
- }
-
- item_active = i;
- new_active->Show();
-
- wxCommandEvent evt(EVT_INDEX_PAGE, GetId());
- AddPendingEvent(evt);
-
- Refresh();
-
- new_active->on_activate();
- }
-}
-
-void ConfigWizardIndex::go_to(const ConfigWizardPage *page)
-{
- if (page == nullptr) { return; }
-
- for (size_t i = 0; i < items.size(); i++) {
- if (items[i].page == page) {
- go_to(i);
- return;
- }
- }
-}
-
-void ConfigWizardIndex::clear()
-{
- auto *former_active = active_page();
- if (former_active != nullptr) { former_active->Hide(); }
-
- items.clear();
- item_active = NO_ITEM;
-}
-
-void ConfigWizardIndex::on_paint(wxPaintEvent & evt)
-{
- const auto size = GetClientSize();
- if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; }
-
- wxPaintDC dc(this);
-
- const auto bullet_w = bullet_black.GetWidth();
- const auto bullet_h = bullet_black.GetHeight();
- const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0;
- const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0;
- const int yinc = item_height();
-
- int index_width = 0;
-
- unsigned y = 0;
- for (size_t i = 0; i < items.size(); i++) {
- const Item& item = items[i];
- unsigned x = em_w/2 + item.indent * em_w;
-
- if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) {
- dc.DrawBitmap(bullet_blue.get_bitmap(), x, y + yoff_icon, false);
- }
- else if (i < item_active) { dc.DrawBitmap(bullet_black.get_bitmap(), x, y + yoff_icon, false); }
- else if (i > item_active) { dc.DrawBitmap(bullet_white.get_bitmap(), x, y + yoff_icon, false); }
-
- x += + bullet_w + em_w/2;
- const auto text_size = dc.GetTextExtent(item.label);
- dc.SetTextForeground(wxGetApp().get_label_clr_default());
- dc.DrawText(item.label, x, y + yoff_text);
-
- y += yinc;
- index_width = std::max(index_width, (int)x + text_size.x);
- }
-
- //draw logo
- if (int y = size.y - bg.GetHeight(); y>=0) {
- dc.DrawBitmap(bg.get_bitmap(), 0, y, false);
- index_width = std::max(index_width, bg.GetWidth() + em_w / 2);
- }
-
- if (GetMinSize().x < index_width) {
- CallAfter([this, index_width]() {
- SetMinSize(wxSize(index_width, GetMinSize().y));
- Refresh();
- });
- }
-}
-
-void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt)
-{
- const wxClientDC dc(this);
- const wxPoint pos = evt.GetLogicalPosition(dc);
-
- const ssize_t item_hover_new = pos.y / item_height();
-
- if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) {
- item_hover = item_hover_new;
- Refresh();
- }
-
- evt.Skip();
-}
-
-void ConfigWizardIndex::msw_rescale()
-{
- const wxSize size = GetTextExtent("m");
- em_w = size.x;
- em_h = size.y;
-
- SetMinSize(bg.GetSize());
-
- Refresh();
-}
-
-
-// Materials
-
-const std::string Materials::UNKNOWN = "(Unknown)";
-
-void Materials::push(const Preset *preset)
-{
- presets.emplace_back(preset);
- types.insert(technology & T_FFF
- ? Materials::get_filament_type(preset)
- : Materials::get_material_type(preset));
-}
-
-void Materials::add_printer(const Preset* preset)
-{
- printers.insert(preset);
-}
-
-void Materials::clear()
-{
- presets.clear();
- types.clear();
- printers.clear();
- compatibility_counter.clear();
-}
-
-const std::string& Materials::appconfig_section() const
-{
- return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
-}
-
-const std::string& Materials::get_type(const Preset *preset) const
-{
- return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset);
-}
-
-const std::string& Materials::get_vendor(const Preset *preset) const
-{
- return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset);
-}
-
-const std::string& Materials::get_filament_type(const Preset *preset)
-{
- const auto *opt = preset->config.opt("filament_type");
- if (opt != nullptr && opt->values.size() > 0) {
- return opt->values[0];
- } else {
- return UNKNOWN;
- }
-}
-
-const std::string& Materials::get_filament_vendor(const Preset *preset)
-{
- const auto *opt = preset->config.opt("filament_vendor");
- return opt != nullptr ? opt->value : UNKNOWN;
-}
-
-const std::string& Materials::get_material_type(const Preset *preset)
-{
- const auto *opt = preset->config.opt("material_type");
- if (opt != nullptr) {
- return opt->value;
- } else {
- return UNKNOWN;
- }
-}
-
-const std::string& Materials::get_material_vendor(const Preset *preset)
-{
- const auto *opt = preset->config.opt("material_vendor");
- return opt != nullptr ? opt->value : UNKNOWN;
-}
-
-// priv
-
-static const std::unordered_map> legacy_preset_map {{
- { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") },
- { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") },
- { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
- { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") },
- { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
- { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") },
- { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") },
- { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") },
-}};
-
-void ConfigWizard::priv::load_pages()
-{
- wxWindowUpdateLocker freeze_guard(q);
- (void)freeze_guard;
-
- const ConfigWizardPage *former_active = index->active_page();
-
- index->clear();
-
- index->add_page(page_welcome);
-
- // Printers
- if (!only_sla_mode)
- index->add_page(page_fff);
- index->add_page(page_msla);
- if (!only_sla_mode) {
- index->add_page(page_vendors);
- for (const auto &pages : pages_3rdparty) {
- for ( PagePrinters* page : { pages.second.first, pages.second.second })
- if (page && page->install)
- index->add_page(page);
- }
-
- index->add_page(page_custom);
- if (page_custom->custom_wanted()) {
- index->add_page(page_firmware);
- index->add_page(page_bed);
- index->add_page(page_bvolume);
- index->add_page(page_diams);
- index->add_page(page_temps);
- }
-
- // Filaments & Materials
- if (any_fff_selected) { index->add_page(page_filaments); }
- }
- if (any_sla_selected) { index->add_page(page_sla_materials); }
-
- // there should to be selected at least one printer
- btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected);
-
- index->add_page(page_update);
- index->add_page(page_reload_from_disk);
-#ifdef _WIN32
- index->add_page(page_files_association);
-#endif // _WIN32
- index->add_page(page_mode);
-
- index->go_to(former_active); // Will restore the active item/page if possible
-
- q->Layout();
-// This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig
- q->Refresh();
-}
-
-void ConfigWizard::priv::init_dialog_size()
-{
- // Clamp the Wizard size based on screen dimensions
-
- const auto idx = wxDisplay::GetFromWindow(q);
- wxDisplay display(idx != wxNOT_FOUND ? idx : 0u);
-
- const auto disp_rect = display.GetClientArea();
- wxRect window_rect(
- disp_rect.x + disp_rect.width / 20,
- disp_rect.y + disp_rect.height / 20,
- 9*disp_rect.width / 10,
- 9*disp_rect.height / 10);
-
- const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em()); // XXX: magic constant, I found no better solution
- if (width_hint < window_rect.width) {
- window_rect.x += (window_rect.width - width_hint) / 2;
- window_rect.width = width_hint;
- }
-
- q->SetSize(window_rect);
-}
-
-void ConfigWizard::priv::load_vendors()
-{
- bundles = BundleMap::load();
-
- // Load up the set of vendors / models / variants the user has had enabled up till now
- AppConfig *app_config = wxGetApp().app_config;
- if (! app_config->legacy_datadir()) {
- appconfig_new.set_vendors(*app_config);
- } else {
- // In case of legacy datadir, try to guess the preference based on the printer preset files that are present
- const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer";
- for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir))
- if (Slic3r::is_ini_file(dir_entry)) {
- auto needle = legacy_preset_map.find(dir_entry.path().filename().string());
- if (needle == legacy_preset_map.end()) { continue; }
-
- const auto &model = needle->second.first;
- const auto &variant = needle->second.second;
- appconfig_new.set_variant("PrusaResearch", model, variant, true);
- }
- }
-
- // Initialize the is_visible flag in printer Presets
- for (auto &pair : bundles) {
- pair.second.preset_bundle->load_installed_printers(appconfig_new);
- }
-
- // Copy installed filaments and SLA material names from app_config to appconfig_new
- // while resolving current names of profiles, which were renamed in the meantime.
- for (PrinterTechnology technology : { ptFFF, ptSLA }) {
- const std::string §ion_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
- std::map section_new;
- if (app_config->has_section(section_name)) {
- const std::map §ion_old = app_config->get_section(section_name);
- for (const auto& material_name_and_installed : section_old)
- if (material_name_and_installed.second == "1") {
- // Material is installed. Resolve it in bundles.
- size_t num_found = 0;
- const std::string &material_name = material_name_and_installed.first;
- for (auto &bundle : bundles) {
- const PresetCollection &materials = bundle.second.preset_bundle->materials(technology);
- const Preset *preset = materials.find_preset(material_name);
- if (preset == nullptr) {
- // Not found. Maybe the material preset is there, bu it was was renamed?
- const std::string *new_name = materials.get_preset_name_renamed(material_name);
- if (new_name != nullptr)
- preset = materials.find_preset(*new_name);
- }
- if (preset != nullptr) {
- // Materal preset was found, mark it as installed.
- section_new[preset->name] = "1";
- ++ num_found;
- }
- }
- if (num_found == 0)
- BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name;
- else if (num_found > 1)
- BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found;
- }
- }
- appconfig_new.set_section(section_name, section_new);
- };
-}
-
-void ConfigWizard::priv::add_page(ConfigWizardPage *page)
-{
- const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0;
- hscroll_sizer->Add(page, proportion, wxEXPAND);
- all_pages.push_back(page);
-}
-
-void ConfigWizard::priv::enable_next(bool enable)
-{
- btn_next->Enable(enable);
- btn_finish->Enable(enable);
-}
-
-void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page)
-{
- switch (start_page) {
- case ConfigWizard::SP_PRINTERS:
- index->go_to(page_fff);
- btn_next->SetFocus();
- break;
- case ConfigWizard::SP_FILAMENTS:
- index->go_to(page_filaments);
- btn_finish->SetFocus();
- break;
- case ConfigWizard::SP_MATERIALS:
- index->go_to(page_sla_materials);
- btn_finish->SetFocus();
- break;
- default:
- index->go_to(page_welcome);
- btn_next->SetFocus();
- break;
- }
-}
-
-void ConfigWizard::priv::create_3rdparty_pages()
-{
- for (const auto &pair : bundles) {
- const VendorProfile *vendor = pair.second.vendor_profile;
- if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
-
- bool is_fff_technology = false;
- bool is_sla_technology = false;
-
- for (auto& model: vendor->models)
- {
- if (!is_fff_technology && model.technology == ptFFF)
- is_fff_technology = true;
- if (!is_sla_technology && model.technology == ptSLA)
- is_sla_technology = true;
- }
-
- PagePrinters* pageFFF = nullptr;
- PagePrinters* pageSLA = nullptr;
-
- if (is_fff_technology) {
- pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF);
- add_page(pageFFF);
- }
-
- if (is_sla_technology) {
- pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA);
- add_page(pageSLA);
- }
-
- pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}});
- }
-}
-
-void ConfigWizard::priv::set_run_reason(RunReason run_reason)
-{
- this->run_reason = run_reason;
- for (auto &page : all_pages) {
- page->set_run_reason(run_reason);
- }
-}
-
-void ConfigWizard::priv::update_materials(Technology technology)
-{
- if (any_fff_selected && (technology & T_FFF)) {
- filaments.clear();
- aliases_fff.clear();
- // Iterate filaments in all bundles
- for (const auto &pair : bundles) {
- for (const auto &filament : pair.second.preset_bundle->filaments) {
- // Check if filament is already added
- if (filaments.containts(&filament))
- continue;
- // Iterate printers in all bundles
- for (const auto &printer : pair.second.preset_bundle->printers) {
- if (!printer.is_visible || printer.printer_technology() != ptFFF)
- continue;
- // Filter out inapplicable printers
- if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) {
- if (!filaments.containts(&filament)) {
- filaments.push(&filament);
- if (!filament.alias.empty())
- aliases_fff[filament.alias].insert(filament.name);
- }
- filaments.add_printer(&printer);
- }
- }
-
- }
- }
- // count compatible printers
- for (const auto& preset : filaments.presets) {
-
- const auto filter = [preset](const std::pair element) {
- return preset->alias == element.first;
- };
- if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) {
- continue;
- }
- std::vector idx_with_same_alias;
- for (size_t i = 0; i < filaments.presets.size(); ++i) {
- if (preset->alias == filaments.presets[i]->alias)
- idx_with_same_alias.push_back(i);
- }
- size_t counter = 0;
- for (const auto& printer : filaments.printers) {
- if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF)
- continue;
- bool compatible = false;
- // Test otrher materials with same alias
- for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
- const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]);
- const Preset& prntr = *printer;
- if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
- compatible = true;
- break;
- }
- }
- if (compatible)
- counter++;
- }
- filaments.compatibility_counter.emplace_back(preset->alias, counter);
- }
- }
-
- if (any_sla_selected && (technology & T_SLA)) {
- sla_materials.clear();
- aliases_sla.clear();
-
- // Iterate SLA materials in all bundles
- for (const auto &pair : bundles) {
- for (const auto &material : pair.second.preset_bundle->sla_materials) {
- // Check if material is already added
- if (sla_materials.containts(&material))
- continue;
- // Iterate printers in all bundles
- // For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
- for (const auto& printer : pair.second.preset_bundle->printers) {
- if(!printer.is_visible || printer.printer_technology() != ptSLA)
- continue;
- // Filter out inapplicable printers
- if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
- // Check if material is already added
- if(!sla_materials.containts(&material)) {
- sla_materials.push(&material);
- if (!material.alias.empty())
- aliases_sla[material.alias].insert(material.name);
- }
- sla_materials.add_printer(&printer);
- }
- }
- }
- }
- // count compatible printers
- for (const auto& preset : sla_materials.presets) {
-
- const auto filter = [preset](const std::pair element) {
- return preset->alias == element.first;
- };
- if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) {
- continue;
- }
- std::vector idx_with_same_alias;
- for (size_t i = 0; i < sla_materials.presets.size(); ++i) {
- if(preset->alias == sla_materials.presets[i]->alias)
- idx_with_same_alias.push_back(i);
- }
- size_t counter = 0;
- for (const auto& printer : sla_materials.printers) {
- if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA)
- continue;
- bool compatible = false;
- // Test otrher materials with same alias
- for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
- const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]);
- const Preset& prntr = *printer;
- if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
- compatible = true;
- break;
- }
- }
- if (compatible)
- counter++;
- }
- sla_materials.compatibility_counter.emplace_back(preset->alias, counter);
- }
- }
-}
-
-void ConfigWizard::priv::on_custom_setup(const bool custom_wanted)
-{
- custom_printer_selected = custom_wanted;
- load_pages();
-}
-
-void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt)
-{
- if (check_sla_selected() != any_sla_selected ||
- check_fff_selected() != any_fff_selected) {
- any_fff_selected = check_fff_selected();
- any_sla_selected = check_sla_selected();
-
- load_pages();
- }
-
- // Update the is_visible flag on relevant printer profiles
- for (auto &pair : bundles) {
- if (pair.first != evt.vendor_id) { continue; }
-
- for (auto &preset : pair.second.preset_bundle->printers) {
- if (preset.config.opt_string("printer_model") == evt.model_id
- && preset.config.opt_string("printer_variant") == evt.variant_name) {
- preset.is_visible = evt.enable;
- }
- }
-
- // When a printer model is picked, but there is no material installed compatible with this printer model,
- // install default materials for selected printer model silently.
- check_and_install_missing_materials(page->technology, evt.model_id);
- }
-
- if (page->technology & T_FFF) {
- page_filaments->clear();
- } else if (page->technology & T_SLA) {
- page_sla_materials->clear();
- }
-}
-
-void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology)
-{
- PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
- for (const std::string& material : printer_model.default_materials)
- appconfig_new.set(page_materials->materials->appconfig_section(), material, "1");
-}
-
-void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set &printer_models)
-{
- PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
- const std::string &appconfig_section = page_materials->materials->appconfig_section();
-
- // Following block was unnecessary. Its enough to iterate printer_models once. Not for every vendor printer page.
- // Filament is selected on same page for all printers of same technology.
- /*
- auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models, technology](PagePrinters *page_printers, Technology technology)
- {
- const std::string vendor_id = page_printers->get_vendor_id();
- for (auto& pair : bundles)
- if (pair.first == vendor_id)
- for (const VendorProfile::PrinterModel *printer_model : printer_models)
- for (const std::string &material : printer_model->default_materials)
- appconfig_new.set(appconfig_section, material, "1");
- };
-
- PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla;
- select_default_materials_for_printer_page(page_printers, technology);
-
- for (const auto& printer : pages_3rdparty)
- {
- page_printers = technology & T_FFF ? printer.second.first : printer.second.second;
- if (page_printers)
- select_default_materials_for_printer_page(page_printers, technology);
- }
- */
-
- // Iterate printer_models and select default materials. If none available -> msg to user.
- std::vector models_without_default;
- for (const VendorProfile::PrinterModel* printer_model : printer_models) {
- if (printer_model->default_materials.empty()) {
- models_without_default.emplace_back(printer_model);
- } else {
- for (const std::string& material : printer_model->default_materials)
- appconfig_new.set(appconfig_section, material, "1");
- }
- }
-
- if (!models_without_default.empty()) {
- std::string printer_names = "\n\n";
- for (const VendorProfile::PrinterModel* printer_model : models_without_default) {
- printer_names += printer_model->name + "\n";
- }
- printer_names += "\n\n";
- std::string message = (technology & T_FFF ?
- GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) :
- GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names));
- MessageDialog msg(q, message, _L("Notice"), wxOK);
- msg.ShowModal();
- }
-
- update_materials(technology);
- ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets();
-}
-
-void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install)
-{
- auto it = pages_3rdparty.find(vendor->id);
- wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile");
-
- for (PagePrinters* page : { it->second.first, it->second.second })
- if (page) {
- if (page->install && !install)
- page->select_all(false);
- page->install = install;
- // if some 3rd vendor is selected, select first printer for them
- if (install)
- page->printer_pickers[0]->select_one(0, true);
- page->Layout();
- }
-
- load_pages();
-}
-
-bool ConfigWizard::priv::on_bnt_finish()
-{
- wxBusyCursor wait;
- /* When Filaments or Sla Materials pages are activated,
- * materials for this pages are automaticaly updated and presets are reloaded.
- *
- * But, if _Finish_ button was clicked without activation of those pages
- * (for example, just some printers were added/deleted),
- * than last changes wouldn't be updated for filaments/materials.
- * SO, do that before close of Wizard
- */
- update_materials(T_ANY);
- if (any_fff_selected)
- page_filaments->reload_presets();
- if (any_sla_selected)
- page_sla_materials->reload_presets();
-
- // theres no need to check that filament is selected if we have only custom printer
- if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true;
- // check, that there is selected at least one filament/material
- return check_and_install_missing_materials(T_ANY);
-}
-
-// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed
-// for each Printer preset of each Printer Model installed.
-//
-// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently.
-// Otherwise the user is quieried whether to install the missing default materials or not.
-//
-// Return true if the tested Printer Models already had materials installed.
-// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these
-// respective Printer Models or not.
-bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id)
-{
- // Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle,
- // which is compatible with it.
- const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string §ion)
- {
- const std::map &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map();
- std::set printer_models_without_material;
- for (const auto &pair : bundles) {
- const PresetCollection &materials = pair.second.preset_bundle->materials(technology);
- for (const auto &printer : pair.second.preset_bundle->printers) {
- if (printer.is_visible && printer.printer_technology() == technology) {
- const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer);
- assert(printer_model != nullptr);
- if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) &&
- printer_models_without_material.find(printer_model) == printer_models_without_material.end()) {
- bool has_material = false;
- for (const auto& preset : appconfig_presets) {
- if (preset.second == "1") {
- const Preset *material = materials.find_preset(preset.first, false);
- if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
- has_material = true;
- break;
- }
- }
- }
- if (! has_material)
- printer_models_without_material.insert(printer_model);
- }
- }
- }
- }
- assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id);
- return printer_models_without_material;
- };
-
- const auto ask_and_select_default_materials = [this](const wxString &message, const std::set &printer_models, Technology technology)
- {
- //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO);
- MessageDialog msg(q, message, _L("Notice"), wxYES_NO);
- if (msg.ShowModal() == wxID_YES)
- select_default_materials_for_printer_models(technology, printer_models);
- };
-
- const auto printer_model_list = [](const std::set &printer_models) -> wxString {
- wxString out;
- for (const VendorProfile::PrinterModel *printer_model : printer_models) {
- wxString name = from_u8(printer_model->name);
- out += "\t\t";
- out += name;
- out += "\n";
- }
- return out;
- };
-
- if (any_fff_selected && (technology & T_FFF)) {
- std::set printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS);
- if (! printer_models_without_material.empty()) {
- if (only_for_model_id.empty())
- ask_and_select_default_materials(
- _L("The following FFF printer models have no filament selected:") +
- "\n\n\t" +
- printer_model_list(printer_models_without_material) +
- "\n\n\t" +
- _L("Do you want to select default filaments for these FFF printer models?"),
- printer_models_without_material,
- T_FFF);
- else
- select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF);
- return false;
- }
- }
-
- if (any_sla_selected && (technology & T_SLA)) {
- std::set printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS);
- if (! printer_models_without_material.empty()) {
- if (only_for_model_id.empty())
- ask_and_select_default_materials(
- _L("The following SLA printer models have no materials selected:") +
- "\n\n\t" +
- printer_model_list(printer_models_without_material) +
- "\n\n\t" +
- _L("Do you want to select default SLA materials for these printer models?"),
- printer_models_without_material,
- T_SLA);
- else
- select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA);
- return false;
- }
- }
-
- return true;
-}
-
-static std::set get_new_added_presets(const std::map& old_data, const std::map& new_data)
-{
- auto get_aliases = [](const std::map& data) {
- std::set old_aliases;
- for (auto item : data) {
- const std::string& name = item.first;
- size_t pos = name.find("@");
- old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1));
- }
- return old_aliases;
- };
-
- std::set old_aliases = get_aliases(old_data);
- std::set new_aliases = get_aliases(new_data);
- std::set diff;
- std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin()));
-
- return diff;
-}
-
-static std::string get_first_added_preset(const std::map& old_data, const std::map& new_data)
-{
- std::set diff = get_new_added_presets(old_data, new_data);
- if (diff.empty())
- return std::string();
- return *diff.begin();
-}
-
-bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes)
-{
- wxString header, caption = _L("Configuration is edited in ConfigWizard");
- const auto enabled_vendors = appconfig_new.vendors();
- const auto enabled_vendors_old = app_config->vendors();
-
- bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model());
- PrinterTechnology preferred_pt = ptAny;
- auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) {
- const auto config = enabled_vendors.find(bundle_name);
- PrinterTechnology pt = ptAny;
- if (config != enabled_vendors.end()) {
- for (const auto& model : bundle.vendor_profile->models) {
- if (const auto model_it = config->second.find(model.id);
- model_it != config->second.end() && model_it->second.size() > 0) {
- pt = model.technology;
- const auto config_old = enabled_vendors_old.find(bundle_name);
- if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) {
- // if preferred printer model has SLA printer technology it's important to check the model for multi-part state
- if (pt == ptSLA && suppress_sla_printer)
- continue;
- return pt;
- }
-
- if (const auto model_it_old = config_old->second.find(model.id);
- model_it_old == config_old->second.end() || model_it_old->second != model_it->second) {
- // if preferred printer model has SLA printer technology it's important to check the model for multi-part state
- if (pt == ptSLA && suppress_sla_printer)
- continue;
- return pt;
- }
- }
- }
- }
- return pt;
- };
- // Prusa printers are considered first, then 3rd party.
- if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle());
- preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) {
- for (const auto& bundle : bundles) {
- if (bundle.second.is_prusa_bundle) { continue; }
- if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny)
- continue;
- else if (preferred_pt == ptAny)
- preferred_pt = pt;
- if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)))
- break;
- }
- }
-
- if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption))
- return false;
-
- bool check_unsaved_preset_changes = page_welcome->reset_user_profile();
- if (check_unsaved_preset_changes)
- header = _L("All user presets will be deleted.");
- int act_btns = ActionButtons::KEEP;
- if (!check_unsaved_preset_changes)
- act_btns |= ActionButtons::SAVE;
-
- // Install bundles from resources if needed:
- std::vector install_bundles;
- for (const auto &pair : bundles) {
- if (! pair.second.is_in_resources) { continue; }
-
- if (pair.second.is_prusa_bundle) {
- // Always install Prusa bundle, because it has a lot of filaments/materials
- // likely to be referenced by other profiles.
- install_bundles.emplace_back(pair.first);
- continue;
- }
-
- const auto vendor = enabled_vendors.find(pair.first);
- if (vendor == enabled_vendors.end()) { continue; }
-
- size_t size_sum = 0;
- for (const auto &model : vendor->second) { size_sum += model.second.size(); }
-
- if (size_sum > 0) {
- // This vendor needs to be installed
- install_bundles.emplace_back(pair.first);
- }
- }
- if (!check_unsaved_preset_changes)
- if ((check_unsaved_preset_changes = install_bundles.size() > 0))
- header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size());
-
-#ifdef __linux__
- // Desktop integration on Linux
- if (page_welcome->integrate_desktop())
- DesktopIntegrationDialog::perform_desktop_integration();
-#endif
-
- // Decide whether to create snapshot based on run_reason and the reset profile checkbox
- bool snapshot = true;
- Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE;
- switch (run_reason) {
- case ConfigWizard::RR_DATA_EMPTY:
- snapshot = false;
- break;
- case ConfigWizard::RR_DATA_LEGACY:
- snapshot = true;
- break;
- case ConfigWizard::RR_DATA_INCOMPAT:
- // In this case snapshot has already been taken by
- // PresetUpdater with the appropriate reason
- snapshot = false;
- break;
- case ConfigWizard::RR_USER:
- snapshot = page_welcome->reset_user_profile();
- snapshot_reason = Snapshot::SNAPSHOT_USER;
- break;
- }
-
- if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Do you want to continue changing the configuration?")))
- return false;
-
- if (check_unsaved_preset_changes &&
- !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
- return false;
-
- if (install_bundles.size() > 0) {
- // Install bundles from resources.
- // Don't create snapshot - we've already done that above if applicable.
- if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
- return false;
- } else {
- BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
- }
-
- if (page_welcome->reset_user_profile()) {
- BOOST_LOG_TRIVIAL(info) << "Resetting user profiles...";
- preset_bundle->reset(true);
- }
-
- std::string preferred_model;
- std::string preferred_variant;
- auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) {
- const auto config = enabled_vendors.find(bundle_name);
- if (config == enabled_vendors.end())
- return std::string();
- for (const auto& model : bundle.vendor_profile->models) {
- if (const auto model_it = config->second.find(model.id);
- model_it != config->second.end() && model_it->second.size() > 0 &&
- preferred_pt == model.technology) {
- variant = *model_it->second.begin();
- const auto config_old = enabled_vendors_old.find(bundle_name);
- if (config_old == enabled_vendors_old.end())
- return model.id;
- const auto model_it_old = config_old->second.find(model.id);
- if (model_it_old == config_old->second.end())
- return model.id;
- else if (model_it_old->second != model_it->second) {
- for (const auto& var : model_it->second)
- if (model_it_old->second.find(var) == model_it_old->second.end()) {
- variant = var;
- return model.id;
- }
- }
- }
- }
- if (!variant.empty())
- variant.clear();
- return std::string();
- };
- // Prusa printers are considered first, then 3rd party.
- if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant);
- preferred_model.empty()) {
- for (const auto& bundle : bundles) {
- if (bundle.second.is_prusa_bundle) { continue; }
- if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant);
- !preferred_model.empty())
- break;
- }
- }
-
- // if unsaved changes was not cheched till this moment
- if (!check_unsaved_preset_changes) {
- if ((check_unsaved_preset_changes = !preferred_model.empty())) {
- header = _L("A new Printer was installed and it will be activated.");
- if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
- return false;
- }
- else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) {
- header = _L("Some Printers were uninstalled.");
- if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
- return false;
- }
- }
-
- std::string first_added_filament, first_added_sla_material;
- auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) {
- if (appconfig_new.has_section(section_name)) {
- // get first of new added preset names
- const std::map& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map();
- first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name));
- }
- };
- get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament);
- get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material);
-
- // if unsaved changes was not cheched till this moment
- if (!check_unsaved_preset_changes) {
- if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) {
- header = !first_added_filament.empty() ?
- _L("A new filament was installed and it will be activated.") :
- _L("A new SLA material was installed and it will be activated.");
- if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
- return false;
- }
- else {
- auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) {
- return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map()) != appconfig_new.get_section(section_name);
- };
- bool is_filaments_changed = changed(AppConfig::SECTION_FILAMENTS);
- bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS);
- if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) {
- header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled.");
- if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
- return false;
- }
- }
- }
-
- // apply materials in app_config
- for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS})
- app_config->set_section(section_name, appconfig_new.get_section(section_name));
-
- app_config->set_vendors(appconfig_new);
-
- app_config->set("notify_release", page_update->version_check ? "all" : "none");
- app_config->set("preset_update", page_update->preset_update ? "1" : "0");
- app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
-
-#ifdef _WIN32
- app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0");
- app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0");
-// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0");
-
- if (wxGetApp().is_editor()) {
- if (page_files_association->associate_3mf())
- wxGetApp().associate_3mf_files();
- if (page_files_association->associate_stl())
- wxGetApp().associate_stl_files();
- }
-// else {
-// if (page_files_association->associate_gcode())
-// wxGetApp().associate_gcode_files();
-// }
-#endif // _WIN32
-
- page_mode->serialize_mode(app_config);
-
- if (check_unsaved_preset_changes)
- preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem,
- {preferred_model, preferred_variant, first_added_filament, first_added_sla_material});
-
- if (!only_sla_mode && page_custom->custom_wanted() && page_custom->is_valid_profile_name()) {
- // if unsaved changes was not cheched till this moment
- if (!check_unsaved_preset_changes &&
- !wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes))
- return false;
-
- page_firmware->apply_custom_config(*custom_config);
- page_bed->apply_custom_config(*custom_config);
- page_bvolume->apply_custom_config(*custom_config);
- page_diams->apply_custom_config(*custom_config);
- page_temps->apply_custom_config(*custom_config);
-
- copy_bed_model_and_texture_if_needed(*custom_config);
-
- const std::string profile_name = page_custom->profile_name();
- preset_bundle->load_config_from_wizard(profile_name, *custom_config);
- }
-
- // Update the selections from the compatibilty.
- preset_bundle->export_selections(*app_config);
-
- return true;
-}
-void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add)
-{
- const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla;
-
- auto update = [this, add](const std::string& s, const std::string& key) {
- assert(! s.empty());
- if (add)
- appconfig_new.set(s, key, "1");
- else
- appconfig_new.erase(s, key);
- };
-
- // add or delete presets had a same alias
- auto it = aliases.find(alias_key);
- if (it != aliases.end())
- for (const std::string& name : it->second)
- update(section, name);
-}
-
-bool ConfigWizard::priv::check_fff_selected()
-{
- bool ret = page_fff->any_selected();
- for (const auto& printer: pages_3rdparty)
- if (printer.second.first) // FFF page
- ret |= printer.second.first->any_selected();
- return ret;
-}
-
-bool ConfigWizard::priv::check_sla_selected()
-{
- bool ret = page_msla->any_selected();
- for (const auto& printer: pages_3rdparty)
- if (printer.second.second) // SLA page
- ret |= printer.second.second->any_selected();
- return ret;
-}
-
-
-// Public
-
-ConfigWizard::ConfigWizard(wxWindow *parent)
- : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
- , p(new priv(this))
-{
- this->SetFont(wxGetApp().normal_font());
-
- p->load_vendors();
- p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({
- "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature",
- }));
-
- p->index = new ConfigWizardIndex(this);
-
- auto *vsizer = new wxBoxSizer(wxVERTICAL);
- auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
- auto* hline = new StaticLine(this);
- p->btnsizer = new wxBoxSizer(wxHORIZONTAL);
-
- // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling.
- // Later, we compare that to the size of the current screen and set minimum width based on that (see below).
- p->hscroll = new wxScrolledWindow(this);
- p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL);
- p->hscroll->SetSizer(p->hscroll_sizer);
-
- topsizer->Add(p->index, 0, wxEXPAND);
- topsizer->AddSpacer(INDEX_MARGIN);
- topsizer->Add(p->hscroll, 1, wxEXPAND);
-
- p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers"));
- p->btnsizer->Add(p->btn_sel_all);
-
- p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back"));
- p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >"));
- p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish"));
- p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel")); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac
- p->btnsizer->AddStretchSpacer();
- p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING);
- p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING);
- p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING);
- p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING);
-
- wxGetApp().UpdateDarkUI(p->btn_sel_all);
- wxGetApp().UpdateDarkUI(p->btn_prev);
- wxGetApp().UpdateDarkUI(p->btn_next);
- wxGetApp().UpdateDarkUI(p->btn_finish);
- wxGetApp().UpdateDarkUI(p->btn_cancel);
-
- const auto prusa_it = p->bundles.find("PrusaResearch");
- wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found");
- const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile;
-
- p->add_page(p->page_welcome = new PageWelcome(this));
-
-
- p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF);
- p->only_sla_mode = !p->page_fff->has_printers;
- if (!p->only_sla_mode) {
- p->add_page(p->page_fff);
- p->page_fff->is_primary_printer_page = true;
- }
-
-
- p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA);
- p->add_page(p->page_msla);
- if (p->only_sla_mode) {
- p->page_msla->is_primary_printer_page = true;
- }
-
- if (!p->only_sla_mode) {
- // Pages for 3rd party vendors
- p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors
- p->add_page(p->page_vendors = new PageVendors(this));
- p->add_page(p->page_custom = new PageCustom(this));
- p->custom_printer_selected = p->page_custom->custom_wanted();
- }
-
- p->any_sla_selected = p->check_sla_selected();
- p->any_fff_selected = ! p->only_sla_mode && p->check_fff_selected();
-
- p->update_materials(T_ANY);
- if (!p->only_sla_mode)
- p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
- _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") ));
-
- p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
- _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") ));
-
-
- p->add_page(p->page_update = new PageUpdate(this));
- p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this));
-#ifdef _WIN32
- p->add_page(p->page_files_association = new PageFilesAssociation(this));
-#endif // _WIN32
- p->add_page(p->page_mode = new PageMode(this));
- p->add_page(p->page_firmware = new PageFirmware(this));
- p->add_page(p->page_bed = new PageBedShape(this));
- p->add_page(p->page_bvolume = new PageBuildVolume(this));
- p->add_page(p->page_diams = new PageDiameters(this));
- p->add_page(p->page_temps = new PageTemperatures(this));
-
- p->load_pages();
- p->index->go_to(size_t{0});
-
- vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
- vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING);
- vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN);
-
- SetSizer(vsizer);
- SetSizerAndFit(vsizer);
-
- // We can now enable scrolling on hscroll
- p->hscroll->SetScrollRate(30, 30);
-
- on_window_geometry(this, [this]() {
- p->init_dialog_size();
- });
-
- p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); });
-
- p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
- {
- // check, that there is selected at least one filament/material
- ConfigWizardPage* active_page = this->p->index->active_page();
- if (// Leaving the filaments or SLA materials page and
- (active_page == p->page_filaments || active_page == p->page_sla_materials) &&
- // some Printer models had no filament or SLA material selected.
- ! p->check_and_install_missing_materials(dynamic_cast(active_page)->materials->technology))
- // In that case don't leave the page and the function above queried the user whether to install default materials.
- return;
- this->p->index->go_next();
- });
-
- p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
- {
- if (p->on_bnt_finish())
- this->EndModal(wxID_OK);
- });
-
- p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) {
- p->any_sla_selected = true;
- p->load_pages();
- p->page_fff->select_all(true, false);
- p->page_msla->select_all(true, false);
- p->index->go_to(p->page_mode);
- });
-
- p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) {
- const bool is_last = p->index->active_is_last();
- p->btn_next->Show(! is_last);
- if (is_last)
- p->btn_finish->SetFocus();
-
- Layout();
- });
-
- if (wxLinux_gtk3)
- this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) {
- ConfigWizardPage* active_page = p->index->active_page();
- if (!active_page)
- return;
- for (auto page : p->all_pages)
- if (page != active_page)
- page->Hide();
- // update best size for the dialog after hiding of the non-active pages
- vsizer->SetSizeHints(this);
- // set initial dialog size
- p->init_dialog_size();
- });
-}
-
-ConfigWizard::~ConfigWizard() {}
-
-bool ConfigWizard::run(RunReason reason, StartPage start_page)
-{
- BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page;
-
- GUI_App &app = wxGetApp();
-
- p->set_run_reason(reason);
- p->set_start_page(start_page);
-
- if (ShowModal() == wxID_OK) {
- bool apply_keeped_changes = false;
- if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes))
- return false;
-
- if (apply_keeped_changes)
- app.apply_keeped_preset_modifications();
-
- app.app_config->set_legacy_datadir(false);
- app.update_mode();
- app.obj_manipul()->update_ui_from_settings();
- BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied";
- return true;
- } else {
- BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled";
- return false;
- }
-}
-
-const wxString& ConfigWizard::name(const bool from_menu/* = false*/)
-{
- // A different naming convention is used for the Wizard on Windows & GTK vs. OSX.
- // Note: Don't call _() macro here.
- // This function just return the current name according to the OS.
- // Translation is implemented inside GUI_App::add_config_menu()
-#if __APPLE__
- static const wxString config_wizard_name = L("Configuration Assistant");
- static const wxString config_wizard_name_menu = L("Configuration &Assistant");
-#else
- static const wxString config_wizard_name = L("Configuration Wizard");
- static const wxString config_wizard_name_menu = L("Configuration &Wizard");
-#endif
- return from_menu ? config_wizard_name_menu : config_wizard_name;
-}
-
-void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect)
-{
- p->index->msw_rescale();
-
- const int em = em_unit();
-
- msw_buttons_rescale(this, em, { wxID_APPLY,
- wxID_CANCEL,
- p->btn_sel_all->GetId(),
- p->btn_next->GetId(),
- p->btn_prev->GetId() });
-
- for (auto printer_picker: p->page_fff->printer_pickers)
- msw_buttons_rescale(this, em, printer_picker->get_button_indexes());
-
- p->init_dialog_size();
-
- Refresh();
-}
-
-void ConfigWizard::on_sys_color_changed()
-{
- wxGetApp().UpdateDlgDarkUI(this);
- Refresh();
-}
-
-}
-}
+// FIXME: extract absolute units -> em
+
+#include "ConfigWizard_private.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef WIN32
+#include
+#include
+#include
+#endif // WIN32
+
+#ifdef _MSW_DARK_MODE
+#include
+#endif // _MSW_DARK_MODE
+
+#include "libslic3r/Platform.hpp"
+#include "libslic3r/Utils.hpp"
+#include "libslic3r/Config.hpp"
+#include "libslic3r/libslic3r.h"
+#include "libslic3r/Model.hpp"
+#include "libslic3r/Color.hpp"
+#include "GUI.hpp"
+#include "GUI_App.hpp"
+#include "GUI_Utils.hpp"
+#include "GUI_ObjectManipulation.hpp"
+#include "Field.hpp"
+#include "DesktopIntegrationDialog.hpp"
+#include "slic3r/Config/Snapshot.hpp"
+#include "slic3r/Utils/PresetUpdater.hpp"
+#include "format.hpp"
+#include "MsgDialog.hpp"
+#include "UnsavedChangesDialog.hpp"
+#include "slic3r/Utils/AppUpdater.hpp"
+
+#if defined(__linux__) && defined(__WXGTK3__)
+#define wxLinux_gtk3 true
+#else
+#define wxLinux_gtk3 false
+#endif //defined(__linux__) && defined(__WXGTK3__)
+
+namespace Slic3r {
+namespace GUI {
+
+
+using Config::Snapshot;
+using Config::SnapshotDB;
+
+
+// Configuration data structures extensions needed for the wizard
+
+bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle)
+{
+ this->preset_bundle = std::make_unique();
+ this->is_in_resources = ais_in_resources;
+ this->is_prusa_bundle = ais_prusa_bundle;
+
+ std::string path_string = source_path.string();
+ // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
+ auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(
+ path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
+ UNUSED(config_substitutions);
+ // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
+ assert(config_substitutions.empty());
+ auto first_vendor = preset_bundle->vendors.begin();
+ if (first_vendor == preset_bundle->vendors.end()) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
+ return false;
+ }
+ if (presets_loaded == 0) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string;
+ return false;
+ }
+
+ BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded;
+ this->vendor_profile = &first_vendor->second;
+ return true;
+}
+
+Bundle::Bundle(Bundle &&other)
+ : preset_bundle(std::move(other.preset_bundle))
+ , vendor_profile(other.vendor_profile)
+ , is_in_resources(other.is_in_resources)
+ , is_prusa_bundle(other.is_prusa_bundle)
+{
+ other.vendor_profile = nullptr;
+}
+
+BundleMap BundleMap::load()
+{
+ BundleMap res;
+
+ const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred();
+ const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
+
+ auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
+ auto prusa_bundle_rsrc = false;
+ if (! boost::filesystem::exists(prusa_bundle_path)) {
+ prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
+ prusa_bundle_rsrc = true;
+ }
+ {
+ Bundle prusa_bundle;
+ if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true))
+ res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle));
+ }
+
+ // Load the other bundles in the datadir/vendor directory
+ // and then additionally from resources/profiles.
+ bool is_in_resources = false;
+ for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) {
+ for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) {
+ if (Slic3r::is_ini_file(dir_entry)) {
+ std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part
+
+ // Don't load this bundle if we've already loaded it.
+ if (res.find(id) != res.end()) { continue; }
+
+ Bundle bundle;
+ if (bundle.load(dir_entry.path(), is_in_resources))
+ res.emplace(std::move(id), std::move(bundle));
+ }
+ }
+
+ is_in_resources = true;
+ }
+
+ return res;
+}
+
+Bundle& BundleMap::prusa_bundle()
+{
+ auto it = find(PresetBundle::PRUSA_BUNDLE);
+ if (it == end()) {
+ throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded");
+ }
+
+ return it->second;
+}
+
+const Bundle& BundleMap::prusa_bundle() const
+{
+ return const_cast(this)->prusa_bundle();
+}
+
+
+// Printer model picker GUI control
+
+struct PrinterPickerEvent : public wxEvent
+{
+ std::string vendor_id;
+ std::string model_id;
+ std::string variant_name;
+ bool enable;
+
+ PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable)
+ : wxEvent(winid, eventType)
+ , vendor_id(std::move(vendor_id))
+ , model_id(std::move(model_id))
+ , variant_name(std::move(variant_name))
+ , enable(enable)
+ {}
+
+ virtual wxEvent *Clone() const
+ {
+ return new PrinterPickerEvent(*this);
+ }
+};
+
+wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent);
+
+const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png";
+
+PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter)
+ : wxPanel(parent)
+ , vendor_id(vendor.id)
+ , width(0)
+{
+ wxGetApp().UpdateDarkUI(this);
+ const auto &models = vendor.models;
+
+ auto *sizer = new wxBoxSizer(wxVERTICAL);
+
+ const auto font_title = GetFont().MakeBold().Scaled(1.3f);
+ const auto font_name = GetFont().MakeBold();
+ const auto font_alt_nozzle = GetFont().Scaled(0.9f);
+
+ // wxGrid appends widgets by rows, but we need to construct them in columns.
+ // These vectors are used to hold the elements so that they can be appended in the right order.
+ std::vector titles;
+ std::vector bitmaps;
+ std::vector variants_panels;
+
+ int max_row_width = 0;
+ int current_row_width = 0;
+
+ bool is_variants = false;
+
+ for (const auto &model : models) {
+ if (! filter(model)) { continue; }
+
+ wxBitmap bitmap;
+ int bitmap_width = 0;
+ auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool {
+ if (wxFileExists(bitmap_file)) {
+ bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG);
+ bitmap_width = bitmap.GetWidth();
+ return true;
+ }
+ return false;
+ };
+ if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
+ if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
+ BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead")
+ % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png")
+ % vendor.id
+ % model.id;
+ load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width);
+ }
+ }
+ auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
+ title->SetFont(font_name);
+ const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width);
+ title->Wrap(wrap_width);
+
+ current_row_width += wrap_width;
+ if (titles.size() % max_cols == max_cols - 1) {
+ max_row_width = std::max(max_row_width, current_row_width);
+ current_row_width = 0;
+ }
+
+ titles.push_back(title);
+
+ auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap);
+ bitmaps.push_back(bitmap_widget);
+
+ auto *variants_panel = new wxPanel(this);
+ wxGetApp().UpdateDarkUI(variants_panel);
+ auto *variants_sizer = new wxBoxSizer(wxVERTICAL);
+ variants_panel->SetSizer(variants_sizer);
+ const auto model_id = model.id;
+
+ for (size_t i = 0; i < model.variants.size(); i++) {
+ const auto &variant = model.variants[i];
+
+ const auto label = model.technology == ptFFF
+ ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str())
+ : from_u8(model.name);
+
+ if (i == 1) {
+ auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:"));
+ alt_label->SetFont(font_alt_nozzle);
+ variants_sizer->Add(alt_label, 0, wxBOTTOM, 3);
+ is_variants = true;
+ }
+
+ auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name);
+ i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox);
+
+ const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name);
+ cbox->SetValue(enabled);
+
+ variants_sizer->Add(cbox, 0, wxBOTTOM, 3);
+
+ cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) {
+ on_checkbox(cbox, event.IsChecked());
+ });
+ }
+
+ variants_panels.push_back(variants_panel);
+ }
+
+ width = std::max(max_row_width, current_row_width);
+
+ const size_t cols = std::min(max_cols, titles.size());
+
+ auto *printer_grid = new wxFlexGridSizer(cols, 0, 20);
+ printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL);
+
+ if (titles.size() > 0) {
+ const size_t odd_items = titles.size() % cols;
+
+ for (size_t i = 0; i < titles.size() - odd_items; i += cols) {
+ for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); }
+ for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); }
+ for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); }
+
+ // Add separator space to multiliners
+ if (titles.size() > cols) {
+ for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); }
+ }
+ }
+ if (odd_items > 0) {
+ const size_t rem = titles.size() - odd_items;
+
+ for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); }
+ for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); }
+ for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); }
+ for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); }
+ for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); }
+ }
+ }
+
+ auto *title_sizer = new wxBoxSizer(wxHORIZONTAL);
+ if (! title.IsEmpty()) {
+ auto *title_widget = new wxStaticText(this, wxID_ANY, title);
+ title_widget->SetFont(font_title);
+ title_sizer->Add(title_widget);
+ }
+ title_sizer->AddStretchSpacer();
+
+ if (titles.size() > 1 || is_variants) {
+ // It only makes sense to add the All / None buttons if there's multiple printers
+ // All Standard button is added when there are more variants for at least one printer
+ auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard"));
+ auto *sel_all = new wxButton(this, wxID_ANY, _L("All"));
+ auto *sel_none = new wxButton(this, wxID_ANY, _L("None"));
+ if (is_variants)
+ sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& event) { this->select_all(true, false); });
+ sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); });
+ sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); });
+ if (is_variants)
+ title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING);
+ title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING);
+ title_sizer->Add(sel_none);
+
+ wxGetApp().UpdateDarkUI(sel_all_std);
+ wxGetApp().UpdateDarkUI(sel_all);
+ wxGetApp().UpdateDarkUI(sel_none);
+
+ // fill button indexes used later for buttons rescaling
+ if (is_variants)
+ m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() };
+ else {
+ sel_all_std->Destroy();
+ m_button_indexes = { sel_all->GetId(), sel_none->GetId() };
+ }
+ }
+
+ sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING);
+ sizer->Add(printer_grid);
+
+ SetSizer(sizer);
+}
+
+PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig)
+ : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; })
+{}
+
+void PrinterPicker::select_all(bool select, bool alternates)
+{
+ for (const auto &cb : cboxes) {
+ if (cb->GetValue() != select) {
+ cb->SetValue(select);
+ on_checkbox(cb, select);
+ }
+ }
+
+ if (! select) { alternates = false; }
+
+ for (const auto &cb : cboxes_alt) {
+ if (cb->GetValue() != alternates) {
+ cb->SetValue(alternates);
+ on_checkbox(cb, alternates);
+ }
+ }
+}
+
+void PrinterPicker::select_one(size_t i, bool select)
+{
+ if (i < cboxes.size() && cboxes[i]->GetValue() != select) {
+ cboxes[i]->SetValue(select);
+ on_checkbox(cboxes[i], select);
+ }
+}
+
+bool PrinterPicker::any_selected() const
+{
+ for (const auto &cb : cboxes) {
+ if (cb->GetValue()) { return true; }
+ }
+
+ for (const auto &cb : cboxes_alt) {
+ if (cb->GetValue()) { return true; }
+ }
+
+ return false;
+}
+
+std::set PrinterPicker::get_selected_models() const
+{
+ std::set ret_set;
+
+ for (const auto& cb : cboxes)
+ if (cb->GetValue())
+ ret_set.emplace(cb->model);
+
+ for (const auto& cb : cboxes_alt)
+ if (cb->GetValue())
+ ret_set.emplace(cb->model);
+
+ return ret_set;
+}
+
+void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
+{
+ PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
+ AddPendingEvent(evt);
+}
+
+
+// Wizard page base
+
+ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent)
+ : wxPanel(parent->p->hscroll)
+ , parent(parent)
+ , shortname(std::move(shortname))
+ , indent(indent)
+{
+ wxGetApp().UpdateDarkUI(this);
+
+ auto *sizer = new wxBoxSizer(wxVERTICAL);
+
+ auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
+ const auto font = GetFont().MakeBold().Scaled(1.5);
+ text->SetFont(font);
+ sizer->Add(text, 0, wxALIGN_LEFT, 0);
+ sizer->AddSpacer(10);
+
+ content = new wxBoxSizer(wxVERTICAL);
+ sizer->Add(content, 1, wxEXPAND);
+
+ SetSizer(sizer);
+
+ // There is strange layout on Linux with GTK3,
+ // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861
+ // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages
+ if (!wxLinux_gtk3)
+ this->Hide();
+
+ Bind(wxEVT_SIZE, [this](wxSizeEvent &event) {
+ this->Layout();
+ event.Skip();
+ });
+}
+
+ConfigWizardPage::~ConfigWizardPage() {}
+
+wxStaticText* ConfigWizardPage::append_text(wxString text)
+{
+ auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
+ widget->Wrap(WRAP_WIDTH);
+ widget->SetMinSize(wxSize(WRAP_WIDTH, -1));
+ append(widget);
+ return widget;
+}
+
+void ConfigWizardPage::append_spacer(int space)
+{
+ // FIXME: scaling
+ content->AddSpacer(space);
+}
+
+// Wizard pages
+
+PageWelcome::PageWelcome(ConfigWizard *parent)
+ : ConfigWizardPage(parent, from_u8((boost::format(
+#ifdef __APPLE__
+ _utf8(L("Welcome to the %s Configuration Assistant"))
+#else
+ _utf8(L("Welcome to the %s Configuration Wizard"))
+#endif
+ ) % SLIC3R_APP_NAME).str()), _L("Welcome"))
+ , welcome_text(append_text(from_u8((boost::format(
+ _utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")))
+ % SLIC3R_APP_NAME
+ % _utf8(ConfigWizard::name())).str())
+ ))
+ , cbox_reset(append(
+ new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)"))
+ ))
+ , cbox_integrate(append(
+ new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system)."))
+ ))
+{
+ welcome_text->Hide();
+ cbox_reset->Hide();
+ cbox_integrate->Hide();
+}
+
+void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
+{
+ const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY;
+ welcome_text->Show(data_empty);
+ cbox_reset->Show(!data_empty);
+#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
+ if (!DesktopIntegrationDialog::is_integrated())
+ cbox_integrate->Show(true);
+ else
+ cbox_integrate->Hide();
+#else
+ cbox_integrate->Hide();
+#endif
+}
+
+
+PagePrinters::PagePrinters(ConfigWizard *parent,
+ wxString title,
+ wxString shortname,
+ const VendorProfile &vendor,
+ unsigned indent,
+ Technology technology)
+ : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent)
+ , technology(technology)
+ , install(false) // only used for 3rd party vendors
+{
+ enum {
+ COL_SIZE = 200,
+ };
+
+ AppConfig *appconfig = &this->wizard_p()->appconfig_new;
+
+ const auto families = vendor.families();
+ for (const auto &family : families) {
+ const auto filter = [&](const VendorProfile::PrinterModel &model) {
+ return ((model.technology == ptFFF && technology & T_FFF)
+ || (model.technology == ptSLA && technology & T_SLA))
+ && model.family == family;
+ };
+
+ if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) {
+ continue;
+ }
+
+ const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str());
+ auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter);
+
+ picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) {
+ appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
+ wizard_p()->on_printer_pick(this, evt);
+ });
+
+ append(new StaticLine(this));
+
+ append(picker);
+ printer_pickers.push_back(picker);
+ has_printers = true;
+ }
+
+}
+
+void PagePrinters::select_all(bool select, bool alternates)
+{
+ for (auto picker : printer_pickers) {
+ picker->select_all(select, alternates);
+ }
+}
+
+int PagePrinters::get_width() const
+{
+ return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0,
+ [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); });
+}
+
+bool PagePrinters::any_selected() const
+{
+ for (const auto *picker : printer_pickers) {
+ if (picker->any_selected()) { return true; }
+ }
+
+ return false;
+}
+
+std::set PagePrinters::get_selected_models()
+{
+ std::set ret_set;
+
+ for (const auto *picker : printer_pickers)
+ {
+ std::set tmp_models = picker->get_selected_models();
+ ret_set.insert(tmp_models.begin(), tmp_models.end());
+ }
+
+ return ret_set;
+}
+
+void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
+{
+ if (is_primary_printer_page
+ && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY)
+ && printer_pickers.size() > 0
+ && printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) {
+ printer_pickers[0]->select_one(0, true);
+ }
+}
+
+
+const std::string PageMaterials::EMPTY;
+
+PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name)
+ : ConfigWizardPage(parent, std::move(title), std::move(shortname))
+ , materials(materials)
+ , list_printer(new StringList(this, wxLB_MULTIPLE))
+ , list_type(new StringList(this))
+ , list_vendor(new StringList(this))
+ , list_profile(new PresetList(this))
+{
+ append_spacer(VERTICAL_SPACING);
+
+ const int em = parent->em_unit();
+ const int list_h = 30*em;
+
+
+ list_printer->SetMinSize(wxSize(23*em, list_h));
+ list_type->SetMinSize(wxSize(13*em, list_h));
+ list_vendor->SetMinSize(wxSize(13*em, list_h));
+ list_profile->SetMinSize(wxSize(23*em, list_h));
+
+
+
+ grid = new wxFlexGridSizer(4, em/2, em);
+ grid->AddGrowableCol(3, 1);
+ grid->AddGrowableRow(1, 1);
+
+ grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:")));
+ grid->Add(new wxStaticText(this, wxID_ANY, list1name));
+ grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:")));
+ grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:")));
+
+ grid->Add(list_printer, 0, wxEXPAND);
+ grid->Add(list_type, 0, wxEXPAND);
+ grid->Add(list_vendor, 0, wxEXPAND);
+ grid->Add(list_profile, 1, wxEXPAND);
+
+ auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL);
+ auto *sel_all = new wxButton(this, wxID_ANY, _L("All"));
+ auto *sel_none = new wxButton(this, wxID_ANY, _L("None"));
+ btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2);
+ btn_sizer->Add(sel_none);
+
+ wxGetApp().UpdateDarkUI(list_printer);
+ wxGetApp().UpdateDarkUI(list_type);
+ wxGetApp().UpdateDarkUI(list_vendor);
+ wxGetApp().UpdateDarkUI(sel_all);
+ wxGetApp().UpdateDarkUI(sel_none);
+
+ grid->Add(new wxBoxSizer(wxHORIZONTAL));
+ grid->Add(new wxBoxSizer(wxHORIZONTAL));
+ grid->Add(new wxBoxSizer(wxHORIZONTAL));
+ grid->Add(btn_sizer, 0, wxALIGN_RIGHT);
+
+ append(grid, 1, wxEXPAND);
+
+ append_spacer(VERTICAL_SPACING);
+
+ html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition,
+ wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO);
+ append(html_window, 0, wxEXPAND);
+
+ list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) {
+ update_lists(list_type->GetSelection(), list_vendor->GetSelection(), evt.GetInt());
+ });
+ list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
+ update_lists(list_type->GetSelection(), list_vendor->GetSelection());
+ });
+ list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
+ update_lists(list_type->GetSelection(), list_vendor->GetSelection());
+ });
+
+ list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); });
+ list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); });
+
+ sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); });
+ sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); });
+ /*
+ Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();});
+
+ list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); });
+ list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); });
+ list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); });
+ */
+ reload_presets();
+ set_compatible_printers_html_window(std::vector(), false);
+}
+void PageMaterials::on_paint()
+{
+}
+void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt)
+{
+ const wxClientDC dc(list_profile);
+ const wxPoint pos = evt.GetLogicalPosition(dc);
+ int item = list_profile->HitTest(pos);
+ on_material_hovered(item);
+}
+void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt)
+{}
+void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt)
+{
+ on_material_hovered(-1);
+}
+void PageMaterials::reload_presets()
+{
+ clear();
+
+ list_printer->append(_L("(All)"), &EMPTY);
+ //list_printer->SetLabelMarkup("bald");
+ for (const Preset* printer : materials->printers) {
+ list_printer->append(printer->name, &printer->name);
+ }
+ sort_list_data(list_printer, true, false);
+ if (list_printer->GetCount() > 0) {
+ list_printer->SetSelection(0);
+ sel_printers_prev.Clear();
+ sel_type_prev = wxNOT_FOUND;
+ sel_vendor_prev = wxNOT_FOUND;
+ update_lists(0, 0, 0);
+ }
+
+ presets_loaded = true;
+}
+
+void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers)
+{
+ const auto bgr_clr =
+#if defined(__APPLE__)
+ html_window->GetParent()->GetBackgroundColour();
+#else
+#if defined(_WIN32)
+ wxGetApp().get_window_default_clr();
+#else
+ wxSystemSettings::GetColour(wxSYS_COLOUR_MENU);
+#endif
+#endif
+ const auto text_clr = wxGetApp().get_label_clr_default();
+ const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
+ const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
+ wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials"));
+ wxString text;
+ if (all_printers) {
+ wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material"));
+ text = wxString::Format(
+ ""
+ ""
+ ""
+ ""
+ ""
+ "%s
%s"
+ ""
+ ""
+ ""
+ ""
+ , bgr_clr_str
+ , text_clr_str
+ , first_line
+ , second_line
+ );
+ } else {
+ wxString second_line;
+ if (!printer_names.empty())
+ second_line = (materials->technology == T_FFF ?
+ _L("Only the following installed printers are compatible with the selected filaments") :
+ _L("Only the following installed printers are compatible with the selected SLA materials")) + ":";
+ text = wxString::Format(
+ ""
+ ""
+ ""
+ ""
+ ""
+ "%s
%s"
+ ""
+ ""
+ , bgr_clr_str
+ , text_clr_str
+ , first_line
+ , second_line);
+ for (size_t i = 0; i < printer_names.size(); ++i)
+ {
+ text += wxString::Format("%s | ", boost::nowide::widen(printer_names[i]));
+ if (i % 3 == 2) {
+ text += wxString::Format(
+ "
"
+ "");
+ }
+ }
+ text += wxString::Format(
+ "
"
+ "
"
+ ""
+ ""
+ ""
+ ""
+ );
+ }
+
+ wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this));
+ const int fs = font.GetPointSize();
+ int size[] = { fs,fs,fs,fs,fs,fs,fs };
+ html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
+ html_window->SetPage(text);
+}
+
+void PageMaterials::clear_compatible_printers_label()
+{
+ set_compatible_printers_html_window(std::vector(), false);
+}
+
+void PageMaterials::on_material_hovered(int sel_material)
+{
+
+}
+
+void PageMaterials::on_material_highlighted(int sel_material)
+{
+ if (sel_material == last_hovered_item)
+ return;
+ if (sel_material == -1) {
+ clear_compatible_printers_label();
+ return;
+ }
+ last_hovered_item = sel_material;
+ std::vector tabs;
+ tabs.push_back(std::string());
+ tabs.push_back(std::string());
+ tabs.push_back(std::string());
+ //selected material string
+ std::string material_name = list_profile->get_data(sel_material);
+ // get material preset
+ const std::vector matching_materials = materials->get_presets_by_alias(material_name);
+ if (matching_materials.empty())
+ {
+ clear_compatible_printers_label();
+ return;
+ }
+ //find matching printers
+ std::vector names;
+ for (const Preset* printer : materials->printers) {
+ for (const Preset* material : matching_materials) {
+ if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) {
+ names.push_back(printer->name);
+ break;
+ }
+ }
+ }
+ set_compatible_printers_html_window(names, names.size() == materials->printers.size());
+}
+
+void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected_printer/* = -1*/)
+{
+ wxWindowUpdateLocker freeze_guard(this);
+ (void)freeze_guard;
+
+ wxArrayInt sel_printers;
+ int sel_printers_count = list_printer->GetSelections(sel_printers);
+
+ // Does our wxWidgets version support operator== for wxArrayInt ?
+ // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614
+#if wxCHECK_VERSION(3, 1, 1)
+ if (sel_printers != sel_printers_prev) {
+#else
+ auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) {
+ if (arr_first.GetCount() != arr_second.GetCount())
+ return false;
+ for (size_t i = 0; i < arr_first.GetCount(); i++)
+ if (arr_first[i] != arr_second[i])
+ return false;
+ return true;
+ };
+ if (!are_equal(sel_printers, sel_printers_prev)) {
+#endif
+
+ // Refresh type list
+ list_type->Clear();
+ list_type->append(_L("(All)"), &EMPTY);
+ if (sel_printers_count > 0) {
+ // If all is selected with other printers
+ // unselect "all" or all printers depending on last value
+ if (sel_printers[0] == 0 && sel_printers_count > 1) {
+ if (last_selected_printer == 0) {
+ list_printer->SetSelection(wxNOT_FOUND);
+ list_printer->SetSelection(0);
+ } else {
+ list_printer->SetSelection(0, false);
+ sel_printers_count = list_printer->GetSelections(sel_printers);
+ }
+ }
+ if (sel_printers[0] != 0) {
+ for (int i = 0; i < sel_printers_count; i++) {
+ const std::string& printer_name = list_printer->get_data(sel_printers[i]);
+ const Preset* printer = nullptr;
+ for (const Preset* it : materials->printers) {
+ if (it->name == printer_name) {
+ printer = it;
+ break;
+ }
+ }
+ materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) {
+ const std::string& type = this->materials->get_type(p);
+ if (list_type->find(type) == wxNOT_FOUND) {
+ list_type->append(type, &type);
+ }
+ });
+ }
+ } else {
+ //clear selection except "ALL"
+ list_printer->SetSelection(wxNOT_FOUND);
+ list_printer->SetSelection(0);
+ sel_printers_count = list_printer->GetSelections(sel_printers);
+
+ materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) {
+ const std::string& type = this->materials->get_type(p);
+ if (list_type->find(type) == wxNOT_FOUND) {
+ list_type->append(type, &type);
+ }
+ });
+ }
+ sort_list_data(list_type, true, true);
+ }
+
+ sel_printers_prev = sel_printers;
+ sel_type = 0;
+ sel_type_prev = wxNOT_FOUND;
+ list_type->SetSelection(sel_type);
+ list_profile->Clear();
+ }
+
+ if (sel_type != sel_type_prev) {
+ // Refresh vendor list
+
+ // XXX: The vendor list is created with quadratic complexity here,
+ // but the number of vendors is going to be very small this shouldn't be a problem.
+
+ list_vendor->Clear();
+ list_vendor->append(_L("(All)"), &EMPTY);
+ if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) {
+ const std::string& type = list_type->get_data(sel_type);
+ // find printer preset
+ for (int i = 0; i < sel_printers_count; i++) {
+ const std::string& printer_name = list_printer->get_data(sel_printers[i]);
+ const Preset* printer = nullptr;
+ for (const Preset* it : materials->printers) {
+ if (it->name == printer_name) {
+ printer = it;
+ break;
+ }
+ }
+ materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) {
+ const std::string& vendor = this->materials->get_vendor(p);
+ if (list_vendor->find(vendor) == wxNOT_FOUND) {
+ list_vendor->append(vendor, &vendor);
+ }
+ });
+ }
+ sort_list_data(list_vendor, true, false);
+ }
+
+ sel_type_prev = sel_type;
+ sel_vendor = 0;
+ sel_vendor_prev = wxNOT_FOUND;
+ list_vendor->SetSelection(sel_vendor);
+ list_profile->Clear();
+ }
+
+ if (sel_vendor != sel_vendor_prev) {
+ // Refresh material list
+
+ list_profile->Clear();
+ clear_compatible_printers_label();
+ if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) {
+ const std::string& type = list_type->get_data(sel_type);
+ const std::string& vendor = list_vendor->get_data(sel_vendor);
+ // finst printer preset
+ std::vector to_list;
+ for (int i = 0; i < sel_printers_count; i++) {
+ const std::string& printer_name = list_printer->get_data(sel_printers[i]);
+ const Preset* printer = nullptr;
+ for (const Preset* it : materials->printers) {
+ if (it->name == printer_name) {
+ printer = it;
+ break;
+ }
+ }
+
+ materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) {
+ const std::string& section = materials->appconfig_section();
+ bool checked = wizard_p()->appconfig_new.has(section, p->name);
+ bool was_checked = false;
+
+ int cur_i = list_profile->find(p->alias);
+ if (cur_i == wxNOT_FOUND) {
+ cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
+ to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked);
+ }
+ else {
+ was_checked = list_profile->IsChecked(cur_i);
+ to_list[cur_i].checked = checked || was_checked;
+ }
+ list_profile->Check(cur_i, checked || was_checked);
+
+ /* Update preset selection in config.
+ * If one preset from aliases bundle is selected,
+ * than mark all presets with this aliases as selected
+ * */
+ if (checked && !was_checked)
+ wizard_p()->update_presets_in_config(section, p->alias, true);
+ else if (!checked && was_checked)
+ wizard_p()->appconfig_new.set(section, p->name, "1");
+ });
+ }
+ sort_list_data(list_profile, to_list);
+ }
+
+ sel_vendor_prev = sel_vendor;
+ }
+ wxGetApp().UpdateDarkUI(list_profile);
+}
+
+void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering)
+{
+// get data from list
+// sort data
+// first should be
+// then prusa profiles
+// then the rest
+// in alphabetical order
+
+ std::vector> prusa_profiles;
+ std::vector> other_profiles;
+ for (int i = 0 ; i < list->size(); ++i) {
+ const std::string& data = list->get_data(i);
+ if (data == EMPTY) // do not sort item
+ continue;
+ if (!material_type_ordering && data.find("Prusa") != std::string::npos)
+ prusa_profiles.push_back(data);
+ else
+ other_profiles.push_back(data);
+ }
+ if(material_type_ordering) {
+
+ const ConfigOptionDef* def = print_config_def.get("filament_type");
+ std::vectorenum_values = def->enum_values;
+ size_t end_of_sorted = 0;
+ for (size_t vals = 0; vals < enum_values.size(); vals++) {
+ for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++)
+ {
+ // find instead compare because PET vs PETG
+ if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) {
+ //swap
+ if(profs != end_of_sorted) {
+ std::reference_wrapper aux = other_profiles[end_of_sorted];
+ other_profiles[end_of_sorted] = other_profiles[profs];
+ other_profiles[profs] = aux;
+ }
+ end_of_sorted++;
+ break;
+ }
+ }
+ }
+ } else {
+ std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) {
+ return a.get() < b.get();
+ });
+ std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) {
+ return a.get() < b.get();
+ });
+ }
+
+ list->Clear();
+ if (add_All_item)
+ list->append(_L("(All)"), &EMPTY);
+ for (const auto& item : prusa_profiles)
+ list->append(item, &const_cast(item.get()));
+ for (const auto& item : other_profiles)
+ list->append(item, &const_cast(item.get()));
+}
+
+void PageMaterials::sort_list_data(PresetList* list, const std::vector& data)
+{
+ // sort data
+ // then prusa profiles
+ // then the rest
+ // in alphabetical order
+ std::vector prusa_profiles;
+ std::vector other_profiles;
+ //for (int i = 0; i < data.size(); ++i) {
+ for (const auto& item : data) {
+ const std::string& name = item.name;
+ if (name.find("Prusa") != std::string::npos)
+ prusa_profiles.emplace_back(item);
+ else
+ other_profiles.emplace_back(item);
+ }
+ std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
+ return a.name.get() < b.name.get();
+ });
+ std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
+ return a.name.get() < b.name.get();
+ });
+ list->Clear();
+ for (size_t i = 0; i < prusa_profiles.size(); ++i) {
+ list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast(prusa_profiles[i].name.get()));
+ list->Check(i, prusa_profiles[i].checked);
+ }
+ for (size_t i = 0; i < other_profiles.size(); ++i) {
+ list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast(other_profiles[i].name.get()));
+ list->Check(i + prusa_profiles.size(), other_profiles[i].checked);
+ }
+}
+
+void PageMaterials::select_material(int i)
+{
+ const bool checked = list_profile->IsChecked(i);
+
+ const std::string& alias_key = list_profile->get_data(i);
+ wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked);
+}
+
+void PageMaterials::select_all(bool select)
+{
+ wxWindowUpdateLocker freeze_guard(this);
+ (void)freeze_guard;
+
+ for (unsigned i = 0; i < list_profile->GetCount(); i++) {
+ const bool current = list_profile->IsChecked(i);
+ if (current != select) {
+ list_profile->Check(i, select);
+ select_material(i);
+ }
+ }
+}
+
+void PageMaterials::clear()
+{
+ list_printer->Clear();
+ list_type->Clear();
+ list_vendor->Clear();
+ list_profile->Clear();
+ sel_printers_prev.Clear();
+ sel_type_prev = wxNOT_FOUND;
+ sel_vendor_prev = wxNOT_FOUND;
+ presets_loaded = false;
+}
+
+void PageMaterials::on_activate()
+{
+ if (! presets_loaded) {
+ wizard_p()->update_materials(materials->technology);
+ reload_presets();
+ }
+ first_paint = true;
+}
+
+
+const char *PageCustom::default_profile_name = "My Settings";
+
+PageCustom::PageCustom(ConfigWizard *parent)
+ : ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer"))
+{
+ cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile"));
+ auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:"));
+
+ wxBoxSizer* profile_name_sizer = new wxBoxSizer(wxVERTICAL);
+ profile_name_editor = new SavePresetDialog::Item{ this, profile_name_sizer, default_profile_name };
+ profile_name_editor->Enable(false);
+
+ cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) {
+ profile_name_editor->Enable(custom_wanted());
+ wizard_p()->on_custom_setup(custom_wanted());
+ });
+
+ append(cb_custom);
+ append(label);
+ append(profile_name_sizer);
+}
+
+PageUpdate::PageUpdate(ConfigWizard *parent)
+ : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates"))
+ , version_check(true)
+ , preset_update(true)
+{
+ const AppConfig *app_config = wxGetApp().app_config;
+ auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
+ boldfont.SetWeight(wxFONTWEIGHT_BOLD);
+
+ auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates"));
+ box_slic3r->SetValue(app_config->get("notify_release") != "none");
+ append(box_slic3r);
+ append_text(wxString::Format(_L(
+ "If enabled, %s checks for new application versions online. When a new version becomes available, "
+ "a notification is displayed at the next application startup (never during program usage). "
+ "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME));
+
+ append_spacer(VERTICAL_SPACING);
+
+ auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically"));
+ box_presets->SetValue(app_config->get("preset_update") == "1");
+ append(box_presets);
+ append_text(wxString::Format(_L(
+ "If enabled, %s downloads updates of built-in system presets in the background."
+ "These updates are downloaded into a separate temporary location."
+ "When a new preset version becomes available it is offered at application startup."), SLIC3R_APP_NAME));
+ const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings.");
+ auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold);
+ label_bold->SetFont(boldfont);
+ label_bold->Wrap(WRAP_WIDTH);
+ append(label_bold);
+ append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied."));
+
+ box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); });
+ box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
+}
+
+namespace DownloaderUtils
+{
+#ifdef _WIN32
+
+ wxString get_downloads_path()
+ {
+ wxString ret;
+ PWSTR path = NULL;
+ HRESULT hr = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &path);
+ if (SUCCEEDED(hr)) {
+ ret = wxString(path);
+ }
+ CoTaskMemFree(path);
+ return ret;
+ }
+
+#elif __APPLE__
+ wxString get_downloads_path()
+ {
+ // call objective-c implementation
+ return wxString::FromUTF8(get_downloads_path_mac());
+ }
+#else
+ wxString get_downloads_path()
+ {
+ wxString command = "xdg-user-dir DOWNLOAD";
+ wxArrayString output;
+ GUI::desktop_execute_get_result(command, output);
+ if (output.GetCount() > 0) {
+ return output[0];
+ }
+ return wxString();
+ }
+
+#endif
+
+Worker::Worker(wxWindow* parent)
+: wxBoxSizer(wxHORIZONTAL)
+, m_parent(parent)
+{
+ m_input_path = new wxTextCtrl(m_parent, wxID_ANY);
+ set_path_name(get_app_config()->get("url_downloader_dest"));
+
+ auto* path_label = new wxStaticText(m_parent, wxID_ANY, _L("Download path") + ":");
+
+ this->Add(path_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
+ this->Add(m_input_path, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5);
+
+ auto* button_path = new wxButton(m_parent, wxID_ANY, _L("Browse"));
+ this->Add(button_path, 0, wxEXPAND | wxTOP | wxLEFT, 5);
+ button_path->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) {
+ boost::filesystem::path chosen_dest(boost::nowide::narrow(m_input_path->GetValue()));
+
+ wxDirDialog dialog(m_parent, L("Choose folder:"), chosen_dest.string() );
+ if (dialog.ShowModal() == wxID_OK)
+ this->m_input_path->SetValue(dialog.GetPath());
+ });
+
+ for (wxSizerItem* item : this->GetChildren())
+ if (item->IsWindow()) {
+ wxWindow* win = item->GetWindow();
+ wxGetApp().UpdateDarkUI(win);
+ }
+}
+
+void Worker::set_path_name(wxString path)
+{
+ if (path.empty())
+ path = boost::nowide::widen(get_app_config()->get("url_downloader_dest"));
+
+ if (path.empty()) {
+ // What should be default path? Each system has Downloads folder, that could be good one.
+ // Other would be program location folder - not so good: access rights, apple bin is inside bundle...
+ // default_path = boost::dll::program_location().parent_path().string();
+ path = get_downloads_path();
+ }
+
+ m_input_path->SetValue(path);
+}
+
+void Worker::set_path_name(const std::string& name)
+{
+ if (!m_input_path)
+ return;
+
+ set_path_name(boost::nowide::widen(name));
+}
+
+} // DownLoader
+
+PageDownloader::PageDownloader(ConfigWizard* parent)
+ : ConfigWizardPage(parent, _L("Downloads from URL"), _L("Downloads"))
+{
+ const AppConfig* app_config = wxGetApp().app_config;
+ auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
+ boldfont.SetWeight(wxFONTWEIGHT_BOLD);
+
+ append_spacer(VERTICAL_SPACING);
+
+ auto* box_allow_downloads = new wxCheckBox(this, wxID_ANY, _L("Allow build-in downloader"));
+ // TODO: Do we want it like this? The downloader is allowed for very first time the wizard is run.
+ bool box_allow_value = (app_config->has("downloader_url_registered") ? app_config->get("downloader_url_registered") == "1" : true);
+ box_allow_downloads->SetValue(box_allow_value);
+ append(box_allow_downloads);
+
+ append_text(wxString::Format(_L(
+ "If enabled, %s registers to start on custom URL on www.printables.com."
+ " You will be able to use button with %s logo to open models in this %s."
+ " The model will be downloaded into folder you choose bellow."
+ ), SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME));
+
+#ifdef __linux__
+ append_text(wxString::Format(_L(
+ "On Linux systems the process of registration also creates desktop integration files for this version of application."
+ )));
+#endif
+
+ box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->downloader->allow(event.IsChecked()); });
+
+ downloader = new DownloaderUtils::Worker(this);
+ append(downloader);
+ downloader->allow(box_allow_value);
+}
+
+bool PageDownloader::on_finish_downloader() const
+{
+ return downloader->on_finish();
+}
+
+bool DownloaderUtils::Worker::perform_register()
+{
+ //boost::filesystem::path chosen_dest/*(path_text_ctrl->GetValue());*/(boost::nowide::narrow(path_text_ctrl->GetValue()));
+ boost::filesystem::path chosen_dest (GUI::format(path_name()));
+ boost::system::error_code ec;
+ if (chosen_dest.empty() || !boost::filesystem::is_directory(chosen_dest, ec) || ec) {
+ std::string err_msg = GUI::format("%1%\n\n%2%",_L("Chosen directory for downloads does not Exists.") ,chosen_dest.string());
+ BOOST_LOG_TRIVIAL(error) << err_msg;
+ show_error(m_parent, err_msg);
+ return false;
+ }
+ BOOST_LOG_TRIVIAL(info) << "Downloader registration: Directory for downloads: " << chosen_dest.string();
+ wxGetApp().app_config->set("url_downloader_dest", chosen_dest.string());
+#ifdef _WIN32
+ // Registry key creation for "prusaslicer://" URL
+
+ boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location()));
+ // the path to binary needs to be correctly saved in string with respect to localized characters
+ wxString wbinary = wxString::FromUTF8(binary_path.string());
+ std::string binary_string = (boost::format("%1%") % wbinary).str();
+ BOOST_LOG_TRIVIAL(info) << "Downloader registration: Path of binary: " << binary_string;
+
+ //std::string key_string = "\"" + binary_string + "\" \"-u\" \"%1\"";
+ //std::string key_string = "\"" + binary_string + "\" \"%1\"";
+ std::string key_string = "\"" + binary_string + "\" \"--single-instance\" \"%1\"";
+
+ wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\prusaslicer");
+ wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command");
+ if (!key_first.Exists()) {
+ key_first.Create(false);
+ }
+ key_first.SetValue("URL Protocol", "");
+
+ if (!key_full.Exists()) {
+ key_full.Create(false);
+ }
+ //key_full = "\"C:\\Program Files\\Prusa3D\\PrusaSlicer\\prusa-slicer-console.exe\" \"%1\"";
+ key_full = key_string;
+#elif __APPLE__
+ // Apple registers for custom url in info.plist thus it has to be already registered since build.
+ // The url will always trigger opening of prusaslicer and we have to check that user has allowed it. (GUI_App::MacOpenURL is the triggered method)
+#else
+ // the performation should be called later during desktop integration
+ perform_registration_linux = true;
+#endif
+ return true;
+}
+
+void DownloaderUtils::Worker::deregister()
+{
+#ifdef _WIN32
+ std::string key_string = "";
+ wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command");
+ if (!key_full.Exists()) {
+ return;
+ }
+ key_full = key_string;
+#elif __APPLE__
+ // TODO
+#else
+ BOOST_LOG_TRIVIAL(debug) << "DesktopIntegrationDialog::undo_downloader_registration";
+ DesktopIntegrationDialog::undo_downloader_registration();
+ perform_registration_linux = false;
+#endif
+}
+
+bool DownloaderUtils::Worker::on_finish() {
+ AppConfig* app_config = wxGetApp().app_config;
+ bool ac_value = app_config->get("downloader_url_registered") == "1";
+ BOOST_LOG_TRIVIAL(debug) << "PageDownloader::on_finish_downloader ac_value " << ac_value << " downloader_checked " << downloader_checked;
+ if (ac_value && downloader_checked) {
+ // already registered but we need to do it again
+ if (!perform_register())
+ return false;
+ app_config->set("downloader_url_registered", "1");
+ } else if (!ac_value && downloader_checked) {
+ // register
+ if (!perform_register())
+ return false;
+ app_config->set("downloader_url_registered", "1");
+ } else if (ac_value && !downloader_checked) {
+ // deregister, downloads are banned now
+ deregister();
+ app_config->set("downloader_url_registered", "0");
+ } /*else if (!ac_value && !downloader_checked) {
+ // not registered and we dont want to do it
+ // do not deregister as other instance might be registered
+ } */
+ return true;
+}
+
+
+PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent)
+ : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk"))
+ , full_pathnames(false)
+{
+ auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files"));
+ box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1");
+ append(box_pathnames);
+ append_text(_L(
+ "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n"
+ "If not enabled, the Reload from disk command will ask to select each file using an open file dialog."
+ ));
+
+ box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); });
+}
+
+#ifdef _WIN32
+PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent)
+ : ConfigWizardPage(parent, _L("Files association"), _L("Files association"))
+{
+ cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer"));
+ cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer"));
+// cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer"));
+
+ append(cb_3mf);
+ append(cb_stl);
+// append(cb_gcode);
+}
+#endif // _WIN32
+
+PageMode::PageMode(ConfigWizard *parent)
+ : ConfigWizardPage(parent, _L("View mode"), _L("View mode"))
+{
+ append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n"
+ "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. "
+ "The other two offer progressively more sophisticated fine-tuning, "
+ "they are suitable for advanced and expert users, respectively."));
+
+ radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode"));
+ radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode"));
+ radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode"));
+
+ std::string mode { "simple" };
+ wxGetApp().app_config->get("", "view_mode", mode);
+
+ if (mode == "advanced") { radio_advanced->SetValue(true); }
+ else if (mode == "expert") { radio_expert->SetValue(true); }
+ else { radio_simple->SetValue(true); }
+
+ append(radio_simple);
+ append(radio_advanced);
+ append(radio_expert);
+
+ append_text("\n" + _L("The size of the object can be specified in inches"));
+ check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches"));
+ check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1");
+ append(check_inch);
+
+ on_activate();
+}
+
+void PageMode::serialize_mode(AppConfig *app_config) const
+{
+ std::string mode = "";
+
+ if (radio_simple->GetValue()) { mode = "simple"; }
+ if (radio_advanced->GetValue()) { mode = "advanced"; }
+ if (radio_expert->GetValue()) { mode = "expert"; }
+
+ app_config->set("view_mode", mode);
+ app_config->set("use_inches", check_inch->GetValue() ? "1" : "0");
+}
+
+PageVendors::PageVendors(ConfigWizard *parent)
+ : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors"))
+{
+ const AppConfig &appconfig = this->wizard_p()->appconfig_new;
+
+ append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":");
+
+ auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
+ boldfont.SetWeight(wxFONTWEIGHT_BOLD);
+
+ for (const auto &pair : wizard_p()->bundles) {
+ const VendorProfile *vendor = pair.second.vendor_profile;
+ if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
+
+ auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name);
+ cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) {
+ wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked());
+ });
+
+ const auto &vendors = appconfig.vendors();
+ const bool enabled = vendors.find(pair.first) != vendors.end();
+ if (enabled) {
+ cbox->SetValue(true);
+
+ auto pages = wizard_p()->pages_3rdparty.find(vendor->id);
+ wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created");
+
+ for (PagePrinters* page : { pages->second.first, pages->second.second })
+ if (page) page->install = true;
+ }
+
+ append(cbox);
+ }
+}
+
+PageFirmware::PageFirmware(ConfigWizard *parent)
+ : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1)
+ , gcode_opt(*print_config_def.get("gcode_flavor"))
+ , gcode_picker(nullptr)
+{
+ append_text(_L("Choose the type of firmware used by your printer."));
+ append_text(_(gcode_opt.tooltip));
+
+ wxArrayString choices;
+ choices.Alloc(gcode_opt.enum_labels.size());
+ for (const auto &label : gcode_opt.enum_labels) {
+ choices.Add(label);
+ }
+
+ gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
+ wxGetApp().UpdateDarkUI(gcode_picker);
+ const auto &enum_values = gcode_opt.enum_values;
+ auto needle = enum_values.cend();
+ if (gcode_opt.default_value) {
+ needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize());
+ }
+ if (needle != enum_values.cend()) {
+ gcode_picker->SetSelection(needle - enum_values.cbegin());
+ } else {
+ gcode_picker->SetSelection(0);
+ }
+
+ append(gcode_picker);
+}
+
+void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
+{
+ auto sel = gcode_picker->GetSelection();
+ if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) {
+ auto *opt = new ConfigOptionEnum(static_cast(sel));
+ config.set_key_value("gcode_flavor", opt);
+ }
+}
+
+static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value)
+{
+ e.Skip();
+ wxString str = ctrl->GetValue();
+
+ const char dec_sep = is_decimal_separator_point() ? '.' : ',';
+ const char dec_sep_alt = dec_sep == '.' ? ',' : '.';
+ // Replace the first incorrect separator in decimal number.
+ bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0;
+
+ double val = 0.0;
+ if (!str.ToDouble(&val)) {
+ if (val == 0.0)
+ val = def_value;
+ ctrl->SetValue(double_to_string(val));
+ show_error(nullptr, _L("Invalid numeric input."));
+ // On Windows, this SetFocus creates an invisible marker.
+ //ctrl->SetFocus();
+ }
+ else if (was_replaced)
+ ctrl->SetValue(double_to_string(val));
+}
+
+class DiamTextCtrl : public wxTextCtrl
+{
+public:
+ DiamTextCtrl(wxWindow* parent)
+ {
+#ifdef _WIN32
+ long style = wxBORDER_SIMPLE;
+#else
+ long style = 0;
+#endif
+ Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord), style);
+ wxGetApp().UpdateDarkUI(this);
+ }
+ ~DiamTextCtrl() {}
+};
+
+PageBedShape::PageBedShape(ConfigWizard *parent)
+ : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1)
+ , shape_panel(new BedShapePanel(this))
+{
+ append_text(_L("Set the shape of your printer's bed."));
+
+ shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"),
+ *wizard_p()->custom_config->option("bed_custom_texture"),
+ *wizard_p()->custom_config->option("bed_custom_model"));
+
+ append(shape_panel);
+}
+
+void PageBedShape::apply_custom_config(DynamicPrintConfig &config)
+{
+ const std::vector& points = shape_panel->get_shape();
+ const std::string& custom_texture = shape_panel->get_custom_texture();
+ const std::string& custom_model = shape_panel->get_custom_model();
+ config.set_key_value("bed_shape", new ConfigOptionPoints(points));
+ config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture));
+ config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model));
+}
+
+PageBuildVolume::PageBuildVolume(ConfigWizard* parent)
+ : ConfigWizardPage(parent, _L("Build Volume"), _L("Build Volume"), 1)
+ , build_volume(new DiamTextCtrl(this))
+{
+ append_text(_L("Set verctical size of your printer."));
+
+ wxString value = "200";
+ build_volume->SetValue(value);
+
+ build_volume->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) {
+ double def_value = 200.0;
+ double max_value = 1200.0;
+ e.Skip();
+ wxString str = build_volume->GetValue();
+
+ const char dec_sep = is_decimal_separator_point() ? '.' : ',';
+ const char dec_sep_alt = dec_sep == '.' ? ',' : '.';
+ // Replace the first incorrect separator in decimal number.
+ bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0;
+
+ double val = 0.0;
+ if (!str.ToDouble(&val)) {
+ val = def_value;
+ build_volume->SetValue(double_to_string(val));
+ show_error(nullptr, _L("Invalid numeric input."));
+ //build_volume->SetFocus();
+ } else if (val < 0.0) {
+ val = def_value;
+ build_volume->SetValue(double_to_string(val));
+ show_error(nullptr, _L("Invalid numeric input."));
+ //build_volume->SetFocus();
+ } else if (val > max_value) {
+ val = max_value;
+ build_volume->SetValue(double_to_string(val));
+ show_error(nullptr, _L("Invalid numeric input."));
+ //build_volume->SetFocus();
+ } else if (was_replaced)
+ build_volume->SetValue(double_to_string(val));
+ }, build_volume->GetId());
+
+ auto* sizer_volume = new wxFlexGridSizer(3, 5, 5);
+ auto* text_volume = new wxStaticText(this, wxID_ANY, _L("Max print height:"));
+ auto* unit_volume = new wxStaticText(this, wxID_ANY, _L("mm"));
+ sizer_volume->AddGrowableCol(0, 1);
+ sizer_volume->Add(text_volume, 0, wxALIGN_CENTRE_VERTICAL);
+ sizer_volume->Add(build_volume);
+ sizer_volume->Add(unit_volume, 0, wxALIGN_CENTRE_VERTICAL);
+ append(sizer_volume);
+}
+
+void PageBuildVolume::apply_custom_config(DynamicPrintConfig& config)
+{
+ double val = 0.0;
+ build_volume->GetValue().ToDouble(&val);
+ auto* opt_volume = new ConfigOptionFloat(val);
+ config.set_key_value("max_print_height", opt_volume);
+}
+
+PageDiameters::PageDiameters(ConfigWizard *parent)
+ : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1)
+ , diam_nozzle(new DiamTextCtrl(this))
+ , diam_filam (new DiamTextCtrl(this))
+{
+ auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value();
+ wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5);
+ diam_nozzle->SetValue(value);
+
+ auto *default_filam = print_config_def.get("filament_diameter")->get_default_value();
+ value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0);
+ diam_filam->SetValue(value);
+
+ diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId());
+ diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId());
+
+ append_text(_L("Enter the diameter of your printer's hot end nozzle."));
+
+ auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5);
+ auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:"));
+ auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm"));
+ sizer_nozzle->AddGrowableCol(0, 1);
+ sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
+ sizer_nozzle->Add(diam_nozzle);
+ sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
+ append(sizer_nozzle);
+
+ append_spacer(VERTICAL_SPACING);
+
+ append_text(_L("Enter the diameter of your filament."));
+ append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average."));
+
+ auto *sizer_filam = new wxFlexGridSizer(3, 5, 5);
+ auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:"));
+ auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm"));
+ sizer_filam->AddGrowableCol(0, 1);
+ sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL);
+ sizer_filam->Add(diam_filam, 0, wxALIGN_CENTRE_VERTICAL);
+ sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL);
+ append(sizer_filam);
+}
+
+void PageDiameters::apply_custom_config(DynamicPrintConfig &config)
+{
+ double val = 0.0;
+ diam_nozzle->GetValue().ToDouble(&val);
+ auto *opt_nozzle = new ConfigOptionFloats(1, val);
+ config.set_key_value("nozzle_diameter", opt_nozzle);
+
+ val = 0.0;
+ diam_filam->GetValue().ToDouble(&val);
+ auto * opt_filam = new ConfigOptionFloats(1, val);
+ config.set_key_value("filament_diameter", opt_filam);
+
+ auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) {
+ char buf[64]; // locales don't matter here (sprintf/atof)
+ sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4);
+ config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false));
+ };
+
+ set_extrusion_width("support_material_extrusion_width", 0.35);
+ set_extrusion_width("top_infill_extrusion_width", 0.40);
+ set_extrusion_width("first_layer_extrusion_width", 0.42);
+
+ set_extrusion_width("extrusion_width", 0.45);
+ set_extrusion_width("perimeter_extrusion_width", 0.45);
+ set_extrusion_width("external_perimeter_extrusion_width", 0.45);
+ set_extrusion_width("infill_extrusion_width", 0.45);
+ set_extrusion_width("solid_infill_extrusion_width", 0.45);
+}
+
+class SpinCtrlDouble: public wxSpinCtrlDouble
+{
+public:
+ SpinCtrlDouble(wxWindow* parent)
+ {
+#ifdef _WIN32
+ long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE;
+#else
+ long style = wxSP_ARROW_KEYS;
+#endif
+ Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, style);
+#ifdef _WIN32
+ wxGetApp().UpdateDarkUI(this->GetText());
+#endif
+ this->Refresh();
+ }
+ ~SpinCtrlDouble() {}
+};
+
+PageTemperatures::PageTemperatures(ConfigWizard *parent)
+ : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1)
+ , spin_extr(new SpinCtrlDouble(this))
+ , spin_bed (new SpinCtrlDouble(this))
+{
+ spin_extr->SetIncrement(5.0);
+ const auto &def_extr = *print_config_def.get("temperature");
+ spin_extr->SetRange(def_extr.min, def_extr.max);
+ auto *default_extr = def_extr.get_default_value();
+ spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200);
+
+ spin_bed->SetIncrement(5.0);
+ const auto &def_bed = *print_config_def.get("bed_temperature");
+ spin_bed->SetRange(def_bed.min, def_bed.max);
+ auto *default_bed = def_bed.get_default_value();
+ spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0);
+
+ append_text(_L("Enter the temperature needed for extruding your filament."));
+ append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS."));
+
+ auto *sizer_extr = new wxFlexGridSizer(3, 5, 5);
+ auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:"));
+ auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C"));
+ sizer_extr->AddGrowableCol(0, 1);
+ sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL);
+ sizer_extr->Add(spin_extr);
+ sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL);
+ append(sizer_extr);
+
+ append_spacer(VERTICAL_SPACING);
+
+ append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed."));
+ append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed."));
+
+ auto *sizer_bed = new wxFlexGridSizer(3, 5, 5);
+ auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:"));
+ auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C"));
+ sizer_bed->AddGrowableCol(0, 1);
+ sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL);
+ sizer_bed->Add(spin_bed);
+ sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL);
+ append(sizer_bed);
+}
+
+void PageTemperatures::apply_custom_config(DynamicPrintConfig &config)
+{
+ auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue());
+ config.set_key_value("temperature", opt_extr);
+ auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue());
+ config.set_key_value("first_layer_temperature", opt_extr1st);
+ auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue());
+ config.set_key_value("bed_temperature", opt_bed);
+ auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue());
+ config.set_key_value("first_layer_bed_temperature", opt_bed1st);
+}
+
+
+// Index
+
+ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent)
+ : wxPanel(parent)
+ , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192))
+ , bullet_black(ScalableBitmap(parent, "bullet_black.png"))
+ , bullet_blue(ScalableBitmap(parent, "bullet_blue.png"))
+ , bullet_white(ScalableBitmap(parent, "bullet_white.png"))
+ , item_active(NO_ITEM)
+ , item_hover(NO_ITEM)
+ , last_page((size_t)-1)
+{
+#ifndef __WXOSX__
+ SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
+#endif //__WXOSX__
+ SetMinSize(bg.GetSize());
+
+ const wxSize size = GetTextExtent("m");
+ em_w = size.x;
+ em_h = size.y;
+
+ Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this);
+ Bind(wxEVT_SIZE, [this](wxEvent& e) { e.Skip(); Refresh(); });
+ Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this);
+
+ Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) {
+ if (item_hover != -1) {
+ item_hover = -1;
+ Refresh();
+ }
+ evt.Skip();
+ });
+
+ Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) {
+ if (item_hover >= 0) { go_to(item_hover); }
+ });
+}
+
+wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
+
+void ConfigWizardIndex::add_page(ConfigWizardPage *page)
+{
+ last_page = items.size();
+ items.emplace_back(Item { page->shortname, page->indent, page });
+ Refresh();
+}
+
+void ConfigWizardIndex::add_label(wxString label, unsigned indent)
+{
+ items.emplace_back(Item { std::move(label), indent, nullptr });
+ Refresh();
+}
+
+ConfigWizardPage* ConfigWizardIndex::active_page() const
+{
+ if (item_active >= items.size()) { return nullptr; }
+
+ return items[item_active].page;
+}
+
+void ConfigWizardIndex::go_prev()
+{
+ // Search for a preceiding item that is a page (not a label, ie. page != nullptr)
+
+ if (item_active == NO_ITEM) { return; }
+
+ for (size_t i = item_active; i > 0; i--) {
+ if (items[i - 1].page != nullptr) {
+ go_to(i - 1);
+ return;
+ }
+ }
+}
+
+void ConfigWizardIndex::go_next()
+{
+ // Search for a next item that is a page (not a label, ie. page != nullptr)
+
+ if (item_active == NO_ITEM) { return; }
+
+ for (size_t i = item_active + 1; i < items.size(); i++) {
+ if (items[i].page != nullptr) {
+ go_to(i);
+ return;
+ }
+ }
+}
+
+// This one actually performs the go-to op
+void ConfigWizardIndex::go_to(size_t i)
+{
+ if (i != item_active
+ && i < items.size()
+ && items[i].page != nullptr) {
+ auto *new_active = items[i].page;
+ auto *former_active = active_page();
+ if (former_active != nullptr) {
+ former_active->Hide();
+ }
+
+ item_active = i;
+ new_active->Show();
+
+ wxCommandEvent evt(EVT_INDEX_PAGE, GetId());
+ AddPendingEvent(evt);
+
+ Refresh();
+
+ new_active->on_activate();
+ }
+}
+
+void ConfigWizardIndex::go_to(const ConfigWizardPage *page)
+{
+ if (page == nullptr) { return; }
+
+ for (size_t i = 0; i < items.size(); i++) {
+ if (items[i].page == page) {
+ go_to(i);
+ return;
+ }
+ }
+}
+
+void ConfigWizardIndex::clear()
+{
+ auto *former_active = active_page();
+ if (former_active != nullptr) { former_active->Hide(); }
+
+ items.clear();
+ item_active = NO_ITEM;
+}
+
+void ConfigWizardIndex::on_paint(wxPaintEvent & evt)
+{
+ const auto size = GetClientSize();
+ if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; }
+
+ wxPaintDC dc(this);
+
+ const auto bullet_w = bullet_black.GetWidth();
+ const auto bullet_h = bullet_black.GetHeight();
+ const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0;
+ const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0;
+ const int yinc = item_height();
+
+ int index_width = 0;
+
+ unsigned y = 0;
+ for (size_t i = 0; i < items.size(); i++) {
+ const Item& item = items[i];
+ unsigned x = em_w/2 + item.indent * em_w;
+
+ if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) {
+ dc.DrawBitmap(bullet_blue.get_bitmap(), x, y + yoff_icon, false);
+ }
+ else if (i < item_active) { dc.DrawBitmap(bullet_black.get_bitmap(), x, y + yoff_icon, false); }
+ else if (i > item_active) { dc.DrawBitmap(bullet_white.get_bitmap(), x, y + yoff_icon, false); }
+
+ x += + bullet_w + em_w/2;
+ const auto text_size = dc.GetTextExtent(item.label);
+ dc.SetTextForeground(wxGetApp().get_label_clr_default());
+ dc.DrawText(item.label, x, y + yoff_text);
+
+ y += yinc;
+ index_width = std::max(index_width, (int)x + text_size.x);
+ }
+
+ //draw logo
+ if (int y = size.y - bg.GetHeight(); y>=0) {
+ dc.DrawBitmap(bg.get_bitmap(), 0, y, false);
+ index_width = std::max(index_width, bg.GetWidth() + em_w / 2);
+ }
+
+ if (GetMinSize().x < index_width) {
+ CallAfter([this, index_width]() {
+ SetMinSize(wxSize(index_width, GetMinSize().y));
+ Refresh();
+ });
+ }
+}
+
+void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt)
+{
+ const wxClientDC dc(this);
+ const wxPoint pos = evt.GetLogicalPosition(dc);
+
+ const ssize_t item_hover_new = pos.y / item_height();
+
+ if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) {
+ item_hover = item_hover_new;
+ Refresh();
+ }
+
+ evt.Skip();
+}
+
+void ConfigWizardIndex::msw_rescale()
+{
+ const wxSize size = GetTextExtent("m");
+ em_w = size.x;
+ em_h = size.y;
+
+ SetMinSize(bg.GetSize());
+
+ Refresh();
+}
+
+
+// Materials
+
+const std::string Materials::UNKNOWN = "(Unknown)";
+
+void Materials::push(const Preset *preset)
+{
+ presets.emplace_back(preset);
+ types.insert(technology & T_FFF
+ ? Materials::get_filament_type(preset)
+ : Materials::get_material_type(preset));
+}
+
+void Materials::add_printer(const Preset* preset)
+{
+ printers.insert(preset);
+}
+
+void Materials::clear()
+{
+ presets.clear();
+ types.clear();
+ printers.clear();
+ compatibility_counter.clear();
+}
+
+const std::string& Materials::appconfig_section() const
+{
+ return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
+}
+
+const std::string& Materials::get_type(const Preset *preset) const
+{
+ return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset);
+}
+
+const std::string& Materials::get_vendor(const Preset *preset) const
+{
+ return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset);
+}
+
+const std::string& Materials::get_filament_type(const Preset *preset)
+{
+ const auto *opt = preset->config.opt("filament_type");
+ if (opt != nullptr && opt->values.size() > 0) {
+ return opt->values[0];
+ } else {
+ return UNKNOWN;
+ }
+}
+
+const std::string& Materials::get_filament_vendor(const Preset *preset)
+{
+ const auto *opt = preset->config.opt("filament_vendor");
+ return opt != nullptr ? opt->value : UNKNOWN;
+}
+
+const std::string& Materials::get_material_type(const Preset *preset)
+{
+ const auto *opt = preset->config.opt("material_type");
+ if (opt != nullptr) {
+ return opt->value;
+ } else {
+ return UNKNOWN;
+ }
+}
+
+const std::string& Materials::get_material_vendor(const Preset *preset)
+{
+ const auto *opt = preset->config.opt("material_vendor");
+ return opt != nullptr ? opt->value : UNKNOWN;
+}
+
+// priv
+
+static const std::unordered_map> legacy_preset_map {{
+ { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") },
+ { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") },
+ { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
+ { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") },
+ { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
+ { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") },
+ { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") },
+ { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") },
+}};
+
+void ConfigWizard::priv::load_pages()
+{
+ wxWindowUpdateLocker freeze_guard(q);
+ (void)freeze_guard;
+
+ const ConfigWizardPage *former_active = index->active_page();
+
+ index->clear();
+
+ index->add_page(page_welcome);
+
+ // Printers
+ if (!only_sla_mode)
+ index->add_page(page_fff);
+ index->add_page(page_msla);
+ if (!only_sla_mode) {
+ index->add_page(page_vendors);
+ for (const auto &pages : pages_3rdparty) {
+ for ( PagePrinters* page : { pages.second.first, pages.second.second })
+ if (page && page->install)
+ index->add_page(page);
+ }
+
+ index->add_page(page_custom);
+ if (page_custom->custom_wanted()) {
+ index->add_page(page_firmware);
+ index->add_page(page_bed);
+ index->add_page(page_bvolume);
+ index->add_page(page_diams);
+ index->add_page(page_temps);
+ }
+
+ // Filaments & Materials
+ if (any_fff_selected) { index->add_page(page_filaments); }
+ }
+ if (any_sla_selected) { index->add_page(page_sla_materials); }
+
+ // there should to be selected at least one printer
+ btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected);
+
+ index->add_page(page_update);
+ index->add_page(page_downloader);
+ index->add_page(page_reload_from_disk);
+#ifdef _WIN32
+ index->add_page(page_files_association);
+#endif // _WIN32
+ index->add_page(page_mode);
+
+ index->go_to(former_active); // Will restore the active item/page if possible
+
+ q->Layout();
+// This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig
+ q->Refresh();
+}
+
+void ConfigWizard::priv::init_dialog_size()
+{
+ // Clamp the Wizard size based on screen dimensions
+
+ const auto idx = wxDisplay::GetFromWindow(q);
+ wxDisplay display(idx != wxNOT_FOUND ? idx : 0u);
+
+ const auto disp_rect = display.GetClientArea();
+ wxRect window_rect(
+ disp_rect.x + disp_rect.width / 20,
+ disp_rect.y + disp_rect.height / 20,
+ 9*disp_rect.width / 10,
+ 9*disp_rect.height / 10);
+
+ const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em()); // XXX: magic constant, I found no better solution
+ if (width_hint < window_rect.width) {
+ window_rect.x += (window_rect.width - width_hint) / 2;
+ window_rect.width = width_hint;
+ }
+
+ q->SetSize(window_rect);
+}
+
+void ConfigWizard::priv::load_vendors()
+{
+ bundles = BundleMap::load();
+
+ // Load up the set of vendors / models / variants the user has had enabled up till now
+ AppConfig *app_config = wxGetApp().app_config;
+ if (! app_config->legacy_datadir()) {
+ appconfig_new.set_vendors(*app_config);
+ } else {
+ // In case of legacy datadir, try to guess the preference based on the printer preset files that are present
+ const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer";
+ for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir))
+ if (Slic3r::is_ini_file(dir_entry)) {
+ auto needle = legacy_preset_map.find(dir_entry.path().filename().string());
+ if (needle == legacy_preset_map.end()) { continue; }
+
+ const auto &model = needle->second.first;
+ const auto &variant = needle->second.second;
+ appconfig_new.set_variant("PrusaResearch", model, variant, true);
+ }
+ }
+
+ // Initialize the is_visible flag in printer Presets
+ for (auto &pair : bundles) {
+ pair.second.preset_bundle->load_installed_printers(appconfig_new);
+ }
+
+ // Copy installed filaments and SLA material names from app_config to appconfig_new
+ // while resolving current names of profiles, which were renamed in the meantime.
+ for (PrinterTechnology technology : { ptFFF, ptSLA }) {
+ const std::string §ion_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
+ std::map section_new;
+ if (app_config->has_section(section_name)) {
+ const std::map §ion_old = app_config->get_section(section_name);
+ for (const auto& material_name_and_installed : section_old)
+ if (material_name_and_installed.second == "1") {
+ // Material is installed. Resolve it in bundles.
+ size_t num_found = 0;
+ const std::string &material_name = material_name_and_installed.first;
+ for (auto &bundle : bundles) {
+ const PresetCollection &materials = bundle.second.preset_bundle->materials(technology);
+ const Preset *preset = materials.find_preset(material_name);
+ if (preset == nullptr) {
+ // Not found. Maybe the material preset is there, bu it was was renamed?
+ const std::string *new_name = materials.get_preset_name_renamed(material_name);
+ if (new_name != nullptr)
+ preset = materials.find_preset(*new_name);
+ }
+ if (preset != nullptr) {
+ // Materal preset was found, mark it as installed.
+ section_new[preset->name] = "1";
+ ++ num_found;
+ }
+ }
+ if (num_found == 0)
+ BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name;
+ else if (num_found > 1)
+ BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found;
+ }
+ }
+ appconfig_new.set_section(section_name, section_new);
+ };
+}
+
+void ConfigWizard::priv::add_page(ConfigWizardPage *page)
+{
+ const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0;
+ hscroll_sizer->Add(page, proportion, wxEXPAND);
+ all_pages.push_back(page);
+}
+
+void ConfigWizard::priv::enable_next(bool enable)
+{
+ btn_next->Enable(enable);
+ btn_finish->Enable(enable);
+}
+
+void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page)
+{
+ switch (start_page) {
+ case ConfigWizard::SP_PRINTERS:
+ index->go_to(page_fff);
+ btn_next->SetFocus();
+ break;
+ case ConfigWizard::SP_FILAMENTS:
+ index->go_to(page_filaments);
+ btn_finish->SetFocus();
+ break;
+ case ConfigWizard::SP_MATERIALS:
+ index->go_to(page_sla_materials);
+ btn_finish->SetFocus();
+ break;
+ default:
+ index->go_to(page_welcome);
+ btn_next->SetFocus();
+ break;
+ }
+}
+
+void ConfigWizard::priv::create_3rdparty_pages()
+{
+ for (const auto &pair : bundles) {
+ const VendorProfile *vendor = pair.second.vendor_profile;
+ if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
+
+ bool is_fff_technology = false;
+ bool is_sla_technology = false;
+
+ for (auto& model: vendor->models)
+ {
+ if (!is_fff_technology && model.technology == ptFFF)
+ is_fff_technology = true;
+ if (!is_sla_technology && model.technology == ptSLA)
+ is_sla_technology = true;
+ }
+
+ PagePrinters* pageFFF = nullptr;
+ PagePrinters* pageSLA = nullptr;
+
+ if (is_fff_technology) {
+ pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF);
+ add_page(pageFFF);
+ }
+
+ if (is_sla_technology) {
+ pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA);
+ add_page(pageSLA);
+ }
+
+ pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}});
+ }
+}
+
+void ConfigWizard::priv::set_run_reason(RunReason run_reason)
+{
+ this->run_reason = run_reason;
+ for (auto &page : all_pages) {
+ page->set_run_reason(run_reason);
+ }
+}
+
+void ConfigWizard::priv::update_materials(Technology technology)
+{
+ if (any_fff_selected && (technology & T_FFF)) {
+ filaments.clear();
+ aliases_fff.clear();
+ // Iterate filaments in all bundles
+ for (const auto &pair : bundles) {
+ for (const auto &filament : pair.second.preset_bundle->filaments) {
+ // Check if filament is already added
+ if (filaments.containts(&filament))
+ continue;
+ // Iterate printers in all bundles
+ for (const auto &printer : pair.second.preset_bundle->printers) {
+ if (!printer.is_visible || printer.printer_technology() != ptFFF)
+ continue;
+ // Filter out inapplicable printers
+ if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) {
+ if (!filaments.containts(&filament)) {
+ filaments.push(&filament);
+ if (!filament.alias.empty())
+ aliases_fff[filament.alias].insert(filament.name);
+ }
+ filaments.add_printer(&printer);
+ }
+ }
+
+ }
+ }
+ // count compatible printers
+ for (const auto& preset : filaments.presets) {
+
+ const auto filter = [preset](const std::pair element) {
+ return preset->alias == element.first;
+ };
+ if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) {
+ continue;
+ }
+ std::vector idx_with_same_alias;
+ for (size_t i = 0; i < filaments.presets.size(); ++i) {
+ if (preset->alias == filaments.presets[i]->alias)
+ idx_with_same_alias.push_back(i);
+ }
+ size_t counter = 0;
+ for (const auto& printer : filaments.printers) {
+ if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF)
+ continue;
+ bool compatible = false;
+ // Test otrher materials with same alias
+ for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
+ const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]);
+ const Preset& prntr = *printer;
+ if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
+ compatible = true;
+ break;
+ }
+ }
+ if (compatible)
+ counter++;
+ }
+ filaments.compatibility_counter.emplace_back(preset->alias, counter);
+ }
+ }
+
+ if (any_sla_selected && (technology & T_SLA)) {
+ sla_materials.clear();
+ aliases_sla.clear();
+
+ // Iterate SLA materials in all bundles
+ for (const auto &pair : bundles) {
+ for (const auto &material : pair.second.preset_bundle->sla_materials) {
+ // Check if material is already added
+ if (sla_materials.containts(&material))
+ continue;
+ // Iterate printers in all bundles
+ // For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
+ for (const auto& printer : pair.second.preset_bundle->printers) {
+ if(!printer.is_visible || printer.printer_technology() != ptSLA)
+ continue;
+ // Filter out inapplicable printers
+ if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
+ // Check if material is already added
+ if(!sla_materials.containts(&material)) {
+ sla_materials.push(&material);
+ if (!material.alias.empty())
+ aliases_sla[material.alias].insert(material.name);
+ }
+ sla_materials.add_printer(&printer);
+ }
+ }
+ }
+ }
+ // count compatible printers
+ for (const auto& preset : sla_materials.presets) {
+
+ const auto filter = [preset](const std::pair element) {
+ return preset->alias == element.first;
+ };
+ if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) {
+ continue;
+ }
+ std::vector idx_with_same_alias;
+ for (size_t i = 0; i < sla_materials.presets.size(); ++i) {
+ if(preset->alias == sla_materials.presets[i]->alias)
+ idx_with_same_alias.push_back(i);
+ }
+ size_t counter = 0;
+ for (const auto& printer : sla_materials.printers) {
+ if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA)
+ continue;
+ bool compatible = false;
+ // Test otrher materials with same alias
+ for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
+ const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]);
+ const Preset& prntr = *printer;
+ if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
+ compatible = true;
+ break;
+ }
+ }
+ if (compatible)
+ counter++;
+ }
+ sla_materials.compatibility_counter.emplace_back(preset->alias, counter);
+ }
+ }
+}
+
+void ConfigWizard::priv::on_custom_setup(const bool custom_wanted)
+{
+ custom_printer_selected = custom_wanted;
+ load_pages();
+}
+
+void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt)
+{
+ if (check_sla_selected() != any_sla_selected ||
+ check_fff_selected() != any_fff_selected) {
+ any_fff_selected = check_fff_selected();
+ any_sla_selected = check_sla_selected();
+
+ load_pages();
+ }
+
+ // Update the is_visible flag on relevant printer profiles
+ for (auto &pair : bundles) {
+ if (pair.first != evt.vendor_id) { continue; }
+
+ for (auto &preset : pair.second.preset_bundle->printers) {
+ if (preset.config.opt_string("printer_model") == evt.model_id
+ && preset.config.opt_string("printer_variant") == evt.variant_name) {
+ preset.is_visible = evt.enable;
+ }
+ }
+
+ // When a printer model is picked, but there is no material installed compatible with this printer model,
+ // install default materials for selected printer model silently.
+ check_and_install_missing_materials(page->technology, evt.model_id);
+ }
+
+ if (page->technology & T_FFF) {
+ page_filaments->clear();
+ } else if (page->technology & T_SLA) {
+ page_sla_materials->clear();
+ }
+}
+
+void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology)
+{
+ PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
+ for (const std::string& material : printer_model.default_materials)
+ appconfig_new.set(page_materials->materials->appconfig_section(), material, "1");
+}
+
+void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set &printer_models)
+{
+ PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
+ const std::string &appconfig_section = page_materials->materials->appconfig_section();
+
+ // Following block was unnecessary. Its enough to iterate printer_models once. Not for every vendor printer page.
+ // Filament is selected on same page for all printers of same technology.
+ /*
+ auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models, technology](PagePrinters *page_printers, Technology technology)
+ {
+ const std::string vendor_id = page_printers->get_vendor_id();
+ for (auto& pair : bundles)
+ if (pair.first == vendor_id)
+ for (const VendorProfile::PrinterModel *printer_model : printer_models)
+ for (const std::string &material : printer_model->default_materials)
+ appconfig_new.set(appconfig_section, material, "1");
+ };
+
+ PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla;
+ select_default_materials_for_printer_page(page_printers, technology);
+
+ for (const auto& printer : pages_3rdparty)
+ {
+ page_printers = technology & T_FFF ? printer.second.first : printer.second.second;
+ if (page_printers)
+ select_default_materials_for_printer_page(page_printers, technology);
+ }
+ */
+
+ // Iterate printer_models and select default materials. If none available -> msg to user.
+ std::vector models_without_default;
+ for (const VendorProfile::PrinterModel* printer_model : printer_models) {
+ if (printer_model->default_materials.empty()) {
+ models_without_default.emplace_back(printer_model);
+ } else {
+ for (const std::string& material : printer_model->default_materials)
+ appconfig_new.set(appconfig_section, material, "1");
+ }
+ }
+
+ if (!models_without_default.empty()) {
+ std::string printer_names = "\n\n";
+ for (const VendorProfile::PrinterModel* printer_model : models_without_default) {
+ printer_names += printer_model->name + "\n";
+ }
+ printer_names += "\n\n";
+ std::string message = (technology & T_FFF ?
+ GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) :
+ GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names));
+ MessageDialog msg(q, message, _L("Notice"), wxOK);
+ msg.ShowModal();
+ }
+
+ update_materials(technology);
+ ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets();
+}
+
+void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install)
+{
+ auto it = pages_3rdparty.find(vendor->id);
+ wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile");
+
+ for (PagePrinters* page : { it->second.first, it->second.second })
+ if (page) {
+ if (page->install && !install)
+ page->select_all(false);
+ page->install = install;
+ // if some 3rd vendor is selected, select first printer for them
+ if (install)
+ page->printer_pickers[0]->select_one(0, true);
+ page->Layout();
+ }
+
+ load_pages();
+}
+
+bool ConfigWizard::priv::on_bnt_finish()
+{
+ wxBusyCursor wait;
+
+ if (!page_downloader->on_finish_downloader()) {
+ index->go_to(page_downloader);
+ return false;
+ }
+ /* When Filaments or Sla Materials pages are activated,
+ * materials for this pages are automaticaly updated and presets are reloaded.
+ *
+ * But, if _Finish_ button was clicked without activation of those pages
+ * (for example, just some printers were added/deleted),
+ * than last changes wouldn't be updated for filaments/materials.
+ * SO, do that before close of Wizard
+ */
+ update_materials(T_ANY);
+ if (any_fff_selected)
+ page_filaments->reload_presets();
+ if (any_sla_selected)
+ page_sla_materials->reload_presets();
+
+ // theres no need to check that filament is selected if we have only custom printer
+ if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true;
+ // check, that there is selected at least one filament/material
+ return check_and_install_missing_materials(T_ANY);
+}
+
+// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed
+// for each Printer preset of each Printer Model installed.
+//
+// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently.
+// Otherwise the user is quieried whether to install the missing default materials or not.
+//
+// Return true if the tested Printer Models already had materials installed.
+// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these
+// respective Printer Models or not.
+bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id)
+{
+ // Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle,
+ // which is compatible with it.
+ const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string §ion)
+ {
+ const std::map &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map();
+ std::set printer_models_without_material;
+ for (const auto &pair : bundles) {
+ const PresetCollection &materials = pair.second.preset_bundle->materials(technology);
+ for (const auto &printer : pair.second.preset_bundle->printers) {
+ if (printer.is_visible && printer.printer_technology() == technology) {
+ const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer);
+ assert(printer_model != nullptr);
+ if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) &&
+ printer_models_without_material.find(printer_model) == printer_models_without_material.end()) {
+ bool has_material = false;
+ for (const auto& preset : appconfig_presets) {
+ if (preset.second == "1") {
+ const Preset *material = materials.find_preset(preset.first, false);
+ if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
+ has_material = true;
+ break;
+ }
+ }
+ }
+ if (! has_material)
+ printer_models_without_material.insert(printer_model);
+ }
+ }
+ }
+ }
+ assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id);
+ return printer_models_without_material;
+ };
+
+ const auto ask_and_select_default_materials = [this](const wxString &message, const std::set &printer_models, Technology technology)
+ {
+ //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO);
+ MessageDialog msg(q, message, _L("Notice"), wxYES_NO);
+ if (msg.ShowModal() == wxID_YES)
+ select_default_materials_for_printer_models(technology, printer_models);
+ };
+
+ const auto printer_model_list = [](const std::set &printer_models) -> wxString {
+ wxString out;
+ for (const VendorProfile::PrinterModel *printer_model : printer_models) {
+ wxString name = from_u8(printer_model->name);
+ out += "\t\t";
+ out += name;
+ out += "\n";
+ }
+ return out;
+ };
+
+ if (any_fff_selected && (technology & T_FFF)) {
+ std::set printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS);
+ if (! printer_models_without_material.empty()) {
+ if (only_for_model_id.empty())
+ ask_and_select_default_materials(
+ _L("The following FFF printer models have no filament selected:") +
+ "\n\n\t" +
+ printer_model_list(printer_models_without_material) +
+ "\n\n\t" +
+ _L("Do you want to select default filaments for these FFF printer models?"),
+ printer_models_without_material,
+ T_FFF);
+ else
+ select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF);
+ return false;
+ }
+ }
+
+ if (any_sla_selected && (technology & T_SLA)) {
+ std::set printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS);
+ if (! printer_models_without_material.empty()) {
+ if (only_for_model_id.empty())
+ ask_and_select_default_materials(
+ _L("The following SLA printer models have no materials selected:") +
+ "\n\n\t" +
+ printer_model_list(printer_models_without_material) +
+ "\n\n\t" +
+ _L("Do you want to select default SLA materials for these printer models?"),
+ printer_models_without_material,
+ T_SLA);
+ else
+ select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static std::set get_new_added_presets(const std::map& old_data, const std::map& new_data)
+{
+ auto get_aliases = [](const std::map& data) {
+ std::set old_aliases;
+ for (auto item : data) {
+ const std::string& name = item.first;
+ size_t pos = name.find("@");
+ old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1));
+ }
+ return old_aliases;
+ };
+
+ std::set old_aliases = get_aliases(old_data);
+ std::set new_aliases = get_aliases(new_data);
+ std::set diff;
+ std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin()));
+
+ return diff;
+}
+
+static std::string get_first_added_preset(const std::map& old_data, const std::map& new_data)
+{
+ std::set diff = get_new_added_presets(old_data, new_data);
+ if (diff.empty())
+ return std::string();
+ return *diff.begin();
+}
+
+bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes)
+{
+ wxString header, caption = _L("Configuration is edited in ConfigWizard");
+ const auto enabled_vendors = appconfig_new.vendors();
+ const auto enabled_vendors_old = app_config->vendors();
+
+ bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model());
+ PrinterTechnology preferred_pt = ptAny;
+ auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) {
+ const auto config = enabled_vendors.find(bundle_name);
+ PrinterTechnology pt = ptAny;
+ if (config != enabled_vendors.end()) {
+ for (const auto& model : bundle.vendor_profile->models) {
+ if (const auto model_it = config->second.find(model.id);
+ model_it != config->second.end() && model_it->second.size() > 0) {
+ pt = model.technology;
+ const auto config_old = enabled_vendors_old.find(bundle_name);
+ if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) {
+ // if preferred printer model has SLA printer technology it's important to check the model for multi-part state
+ if (pt == ptSLA && suppress_sla_printer)
+ continue;
+ return pt;
+ }
+
+ if (const auto model_it_old = config_old->second.find(model.id);
+ model_it_old == config_old->second.end() || model_it_old->second != model_it->second) {
+ // if preferred printer model has SLA printer technology it's important to check the model for multi-part state
+ if (pt == ptSLA && suppress_sla_printer)
+ continue;
+ return pt;
+ }
+ }
+ }
+ }
+ return pt;
+ };
+ // Prusa printers are considered first, then 3rd party.
+ if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle());
+ preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) {
+ for (const auto& bundle : bundles) {
+ if (bundle.second.is_prusa_bundle) { continue; }
+ if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny)
+ continue;
+ else if (preferred_pt == ptAny)
+ preferred_pt = pt;
+ if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)))
+ break;
+ }
+ }
+
+ if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption))
+ return false;
+
+ bool check_unsaved_preset_changes = page_welcome->reset_user_profile();
+ if (check_unsaved_preset_changes)
+ header = _L("All user presets will be deleted.");
+ int act_btns = ActionButtons::KEEP;
+ if (!check_unsaved_preset_changes)
+ act_btns |= ActionButtons::SAVE;
+
+ // Install bundles from resources if needed:
+ std::vector install_bundles;
+ for (const auto &pair : bundles) {
+ if (! pair.second.is_in_resources) { continue; }
+
+ if (pair.second.is_prusa_bundle) {
+ // Always install Prusa bundle, because it has a lot of filaments/materials
+ // likely to be referenced by other profiles.
+ install_bundles.emplace_back(pair.first);
+ continue;
+ }
+
+ const auto vendor = enabled_vendors.find(pair.first);
+ if (vendor == enabled_vendors.end()) { continue; }
+
+ size_t size_sum = 0;
+ for (const auto &model : vendor->second) { size_sum += model.second.size(); }
+
+ if (size_sum > 0) {
+ // This vendor needs to be installed
+ install_bundles.emplace_back(pair.first);
+ }
+ }
+ if (!check_unsaved_preset_changes)
+ if ((check_unsaved_preset_changes = install_bundles.size() > 0))
+ header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size());
+
+#ifdef __linux__
+ // Desktop integration on Linux
+ BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->downloader->get_perform_registration_linux();
+ if (page_welcome->integrate_desktop() || page_downloader->downloader->get_perform_registration_linux())
+ DesktopIntegrationDialog::perform_desktop_integration(page_downloader->downloader->get_perform_registration_linux());
+#endif
+
+ // Decide whether to create snapshot based on run_reason and the reset profile checkbox
+ bool snapshot = true;
+ Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE;
+ switch (run_reason) {
+ case ConfigWizard::RR_DATA_EMPTY:
+ snapshot = false;
+ break;
+ case ConfigWizard::RR_DATA_LEGACY:
+ snapshot = true;
+ break;
+ case ConfigWizard::RR_DATA_INCOMPAT:
+ // In this case snapshot has already been taken by
+ // PresetUpdater with the appropriate reason
+ snapshot = false;
+ break;
+ case ConfigWizard::RR_USER:
+ snapshot = page_welcome->reset_user_profile();
+ snapshot_reason = Snapshot::SNAPSHOT_USER;
+ break;
+ }
+
+ if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Do you want to continue changing the configuration?")))
+ return false;
+
+ if (check_unsaved_preset_changes &&
+ !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
+ return false;
+
+ if (install_bundles.size() > 0) {
+ // Install bundles from resources.
+ // Don't create snapshot - we've already done that above if applicable.
+ if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
+ return false;
+ } else {
+ BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
+ }
+
+ if (page_welcome->reset_user_profile()) {
+ BOOST_LOG_TRIVIAL(info) << "Resetting user profiles...";
+ preset_bundle->reset(true);
+ }
+
+ std::string preferred_model;
+ std::string preferred_variant;
+ auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) {
+ const auto config = enabled_vendors.find(bundle_name);
+ if (config == enabled_vendors.end())
+ return std::string();
+ for (const auto& model : bundle.vendor_profile->models) {
+ if (const auto model_it = config->second.find(model.id);
+ model_it != config->second.end() && model_it->second.size() > 0 &&
+ preferred_pt == model.technology) {
+ variant = *model_it->second.begin();
+ const auto config_old = enabled_vendors_old.find(bundle_name);
+ if (config_old == enabled_vendors_old.end())
+ return model.id;
+ const auto model_it_old = config_old->second.find(model.id);
+ if (model_it_old == config_old->second.end())
+ return model.id;
+ else if (model_it_old->second != model_it->second) {
+ for (const auto& var : model_it->second)
+ if (model_it_old->second.find(var) == model_it_old->second.end()) {
+ variant = var;
+ return model.id;
+ }
+ }
+ }
+ }
+ if (!variant.empty())
+ variant.clear();
+ return std::string();
+ };
+ // Prusa printers are considered first, then 3rd party.
+ if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant);
+ preferred_model.empty()) {
+ for (const auto& bundle : bundles) {
+ if (bundle.second.is_prusa_bundle) { continue; }
+ if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant);
+ !preferred_model.empty())
+ break;
+ }
+ }
+
+ // if unsaved changes was not cheched till this moment
+ if (!check_unsaved_preset_changes) {
+ if ((check_unsaved_preset_changes = !preferred_model.empty())) {
+ header = _L("A new Printer was installed and it will be activated.");
+ if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
+ return false;
+ }
+ else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) {
+ header = _L("Some Printers were uninstalled.");
+ if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
+ return false;
+ }
+ }
+
+ std::string first_added_filament, first_added_sla_material;
+ auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) {
+ if (appconfig_new.has_section(section_name)) {
+ // get first of new added preset names
+ const std::map& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map();
+ first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name));
+ }
+ };
+ get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament);
+ get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material);
+
+ // if unsaved changes was not cheched till this moment
+ if (!check_unsaved_preset_changes) {
+ if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) {
+ header = !first_added_filament.empty() ?
+ _L("A new filament was installed and it will be activated.") :
+ _L("A new SLA material was installed and it will be activated.");
+ if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
+ return false;
+ }
+ else {
+ auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) {
+ return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map()) != appconfig_new.get_section(section_name);
+ };
+ bool is_filaments_changed = changed(AppConfig::SECTION_FILAMENTS);
+ bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS);
+ if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) {
+ header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled.");
+ if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
+ return false;
+ }
+ }
+ }
+
+ // apply materials in app_config
+ for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS})
+ app_config->set_section(section_name, appconfig_new.get_section(section_name));
+
+ app_config->set_vendors(appconfig_new);
+
+ app_config->set("notify_release", page_update->version_check ? "all" : "none");
+ app_config->set("preset_update", page_update->preset_update ? "1" : "0");
+ app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
+
+#ifdef _WIN32
+ app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0");
+ app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0");
+// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0");
+
+ if (wxGetApp().is_editor()) {
+ if (page_files_association->associate_3mf())
+ wxGetApp().associate_3mf_files();
+ if (page_files_association->associate_stl())
+ wxGetApp().associate_stl_files();
+ }
+// else {
+// if (page_files_association->associate_gcode())
+// wxGetApp().associate_gcode_files();
+// }
+#endif // _WIN32
+
+ page_mode->serialize_mode(app_config);
+
+ if (check_unsaved_preset_changes)
+ preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem,
+ {preferred_model, preferred_variant, first_added_filament, first_added_sla_material});
+
+ if (!only_sla_mode && page_custom->custom_wanted() && page_custom->is_valid_profile_name()) {
+ // if unsaved changes was not cheched till this moment
+ if (!check_unsaved_preset_changes &&
+ !wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes))
+ return false;
+
+ page_firmware->apply_custom_config(*custom_config);
+ page_bed->apply_custom_config(*custom_config);
+ page_bvolume->apply_custom_config(*custom_config);
+ page_diams->apply_custom_config(*custom_config);
+ page_temps->apply_custom_config(*custom_config);
+
+ copy_bed_model_and_texture_if_needed(*custom_config);
+
+ const std::string profile_name = page_custom->profile_name();
+ preset_bundle->load_config_from_wizard(profile_name, *custom_config);
+ }
+
+ // Update the selections from the compatibilty.
+ preset_bundle->export_selections(*app_config);
+
+ return true;
+}
+void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add)
+{
+ const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla;
+
+ auto update = [this, add](const std::string& s, const std::string& key) {
+ assert(! s.empty());
+ if (add)
+ appconfig_new.set(s, key, "1");
+ else
+ appconfig_new.erase(s, key);
+ };
+
+ // add or delete presets had a same alias
+ auto it = aliases.find(alias_key);
+ if (it != aliases.end())
+ for (const std::string& name : it->second)
+ update(section, name);
+}
+
+bool ConfigWizard::priv::check_fff_selected()
+{
+ bool ret = page_fff->any_selected();
+ for (const auto& printer: pages_3rdparty)
+ if (printer.second.first) // FFF page
+ ret |= printer.second.first->any_selected();
+ return ret;
+}
+
+bool ConfigWizard::priv::check_sla_selected()
+{
+ bool ret = page_msla->any_selected();
+ for (const auto& printer: pages_3rdparty)
+ if (printer.second.second) // SLA page
+ ret |= printer.second.second->any_selected();
+ return ret;
+}
+
+
+// Public
+
+ConfigWizard::ConfigWizard(wxWindow *parent)
+ : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+ , p(new priv(this))
+{
+ this->SetFont(wxGetApp().normal_font());
+
+ p->load_vendors();
+ p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({
+ "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature",
+ }));
+
+ p->index = new ConfigWizardIndex(this);
+
+ auto *vsizer = new wxBoxSizer(wxVERTICAL);
+ auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
+ auto* hline = new StaticLine(this);
+ p->btnsizer = new wxBoxSizer(wxHORIZONTAL);
+
+ // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling.
+ // Later, we compare that to the size of the current screen and set minimum width based on that (see below).
+ p->hscroll = new wxScrolledWindow(this);
+ p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL);
+ p->hscroll->SetSizer(p->hscroll_sizer);
+
+ topsizer->Add(p->index, 0, wxEXPAND);
+ topsizer->AddSpacer(INDEX_MARGIN);
+ topsizer->Add(p->hscroll, 1, wxEXPAND);
+
+ p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers"));
+ p->btnsizer->Add(p->btn_sel_all);
+
+ p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back"));
+ p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >"));
+ p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish"));
+ p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel")); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac
+ p->btnsizer->AddStretchSpacer();
+ p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING);
+ p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING);
+ p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING);
+ p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING);
+
+ wxGetApp().UpdateDarkUI(p->btn_sel_all);
+ wxGetApp().UpdateDarkUI(p->btn_prev);
+ wxGetApp().UpdateDarkUI(p->btn_next);
+ wxGetApp().UpdateDarkUI(p->btn_finish);
+ wxGetApp().UpdateDarkUI(p->btn_cancel);
+
+ const auto prusa_it = p->bundles.find("PrusaResearch");
+ wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found");
+ const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile;
+
+ p->add_page(p->page_welcome = new PageWelcome(this));
+
+
+ p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF);
+ p->only_sla_mode = !p->page_fff->has_printers;
+ if (!p->only_sla_mode) {
+ p->add_page(p->page_fff);
+ p->page_fff->is_primary_printer_page = true;
+ }
+
+
+ p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA);
+ p->add_page(p->page_msla);
+ if (p->only_sla_mode) {
+ p->page_msla->is_primary_printer_page = true;
+ }
+
+ if (!p->only_sla_mode) {
+ // Pages for 3rd party vendors
+ p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors
+ p->add_page(p->page_vendors = new PageVendors(this));
+ p->add_page(p->page_custom = new PageCustom(this));
+ p->custom_printer_selected = p->page_custom->custom_wanted();
+ }
+
+ p->any_sla_selected = p->check_sla_selected();
+ p->any_fff_selected = ! p->only_sla_mode && p->check_fff_selected();
+
+ p->update_materials(T_ANY);
+ if (!p->only_sla_mode)
+ p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
+ _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") ));
+
+ p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
+ _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") ));
+
+
+ p->add_page(p->page_update = new PageUpdate(this));
+ p->add_page(p->page_downloader = new PageDownloader(this));
+ p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this));
+#ifdef _WIN32
+ p->add_page(p->page_files_association = new PageFilesAssociation(this));
+#endif // _WIN32
+ p->add_page(p->page_mode = new PageMode(this));
+ p->add_page(p->page_firmware = new PageFirmware(this));
+ p->add_page(p->page_bed = new PageBedShape(this));
+ p->add_page(p->page_bvolume = new PageBuildVolume(this));
+ p->add_page(p->page_diams = new PageDiameters(this));
+ p->add_page(p->page_temps = new PageTemperatures(this));
+
+ p->load_pages();
+ p->index->go_to(size_t{0});
+
+ vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
+ vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING);
+ vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN);
+
+ SetSizer(vsizer);
+ SetSizerAndFit(vsizer);
+
+ // We can now enable scrolling on hscroll
+ p->hscroll->SetScrollRate(30, 30);
+
+ on_window_geometry(this, [this]() {
+ p->init_dialog_size();
+ });
+
+ p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); });
+
+ p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
+ {
+ // check, that there is selected at least one filament/material
+ ConfigWizardPage* active_page = this->p->index->active_page();
+ if (// Leaving the filaments or SLA materials page and
+ (active_page == p->page_filaments || active_page == p->page_sla_materials) &&
+ // some Printer models had no filament or SLA material selected.
+ ! p->check_and_install_missing_materials(dynamic_cast(active_page)->materials->technology))
+ // In that case don't leave the page and the function above queried the user whether to install default materials.
+ return;
+ this->p->index->go_next();
+ });
+
+ p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
+ {
+ if (p->on_bnt_finish())
+ this->EndModal(wxID_OK);
+ });
+
+ p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) {
+ p->any_sla_selected = true;
+ p->load_pages();
+ p->page_fff->select_all(true, false);
+ p->page_msla->select_all(true, false);
+ p->index->go_to(p->page_mode);
+ });
+
+ p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) {
+ const bool is_last = p->index->active_is_last();
+ p->btn_next->Show(! is_last);
+ if (is_last)
+ p->btn_finish->SetFocus();
+
+ Layout();
+ });
+
+ if (wxLinux_gtk3)
+ this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) {
+ ConfigWizardPage* active_page = p->index->active_page();
+ if (!active_page)
+ return;
+ for (auto page : p->all_pages)
+ if (page != active_page)
+ page->Hide();
+ // update best size for the dialog after hiding of the non-active pages
+ vsizer->SetSizeHints(this);
+ // set initial dialog size
+ p->init_dialog_size();
+ });
+}
+
+ConfigWizard::~ConfigWizard() {}
+
+bool ConfigWizard::run(RunReason reason, StartPage start_page)
+{
+ BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page;
+
+ GUI_App &app = wxGetApp();
+
+ p->set_run_reason(reason);
+ p->set_start_page(start_page);
+
+ if (ShowModal() == wxID_OK) {
+ bool apply_keeped_changes = false;
+ if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes))
+ return false;
+
+ if (apply_keeped_changes)
+ app.apply_keeped_preset_modifications();
+
+ app.app_config->set_legacy_datadir(false);
+ app.update_mode();
+ app.obj_manipul()->update_ui_from_settings();
+ BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied";
+ return true;
+ } else {
+ BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled";
+ return false;
+ }
+}
+
+const wxString& ConfigWizard::name(const bool from_menu/* = false*/)
+{
+ // A different naming convention is used for the Wizard on Windows & GTK vs. OSX.
+ // Note: Don't call _() macro here.
+ // This function just return the current name according to the OS.
+ // Translation is implemented inside GUI_App::add_config_menu()
+#if __APPLE__
+ static const wxString config_wizard_name = L("Configuration Assistant");
+ static const wxString config_wizard_name_menu = L("Configuration &Assistant");
+#else
+ static const wxString config_wizard_name = L("Configuration Wizard");
+ static const wxString config_wizard_name_menu = L("Configuration &Wizard");
+#endif
+ return from_menu ? config_wizard_name_menu : config_wizard_name;
+}
+
+void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect)
+{
+ p->index->msw_rescale();
+
+ const int em = em_unit();
+
+ msw_buttons_rescale(this, em, { wxID_APPLY,
+ wxID_CANCEL,
+ p->btn_sel_all->GetId(),
+ p->btn_next->GetId(),
+ p->btn_prev->GetId() });
+
+ for (auto printer_picker: p->page_fff->printer_pickers)
+ msw_buttons_rescale(this, em, printer_picker->get_button_indexes());
+
+ p->init_dialog_size();
+
+ Refresh();
+}
+
+void ConfigWizard::on_sys_color_changed()
+{
+ wxGetApp().UpdateDlgDarkUI(this);
+ Refresh();
+}
+
+}
+}
diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp
index c1e848a63a7..a8ac09d9b1f 100644
--- a/src/slic3r/GUI/ConfigWizard_private.hpp
+++ b/src/slic3r/GUI/ConfigWizard_private.hpp
@@ -392,10 +392,56 @@ struct PageUpdate: ConfigWizardPage
{
bool version_check;
bool preset_update;
+ wxTextCtrl* path_text_ctrl;
PageUpdate(ConfigWizard *parent);
};
+namespace DownloaderUtils {
+ wxString get_downloads_path();
+
+class Worker : public wxBoxSizer
+{
+ wxWindow* m_parent {nullptr};
+ wxTextCtrl* m_input_path {nullptr};
+ bool downloader_checked {false};
+#ifdef __linux__
+ bool perform_registration_linux { false };
+#endif // __linux__
+
+ bool perform_register();
+ void deregister();
+
+public:
+ Worker(wxWindow* parent);
+ ~Worker(){}
+
+ void allow(bool allow_) { downloader_checked = allow_; }
+ bool is_checked() const { return downloader_checked; }
+ wxString path_name() const { return m_input_path ? m_input_path->GetValue() : wxString(); }
+
+ void set_path_name(wxString name);
+ void set_path_name(const std::string& name);
+
+ bool on_finish();
+
+#ifdef __linux__
+ bool get_perform_registration_linux() { return perform_registration_linux; }
+#endif // __linux__
+};
+
+}
+
+
+struct PageDownloader : ConfigWizardPage
+{
+ DownloaderUtils::Worker* downloader{ nullptr };
+
+ PageDownloader(ConfigWizard* parent);
+
+ bool on_finish_downloader() const ;
+};
+
struct PageReloadFromDisk : ConfigWizardPage
{
bool full_pathnames;
@@ -583,7 +629,8 @@ struct ConfigWizard::priv
PageMaterials *page_filaments = nullptr;
PageMaterials *page_sla_materials = nullptr;
PageCustom *page_custom = nullptr;
- PageUpdate *page_update = nullptr;
+ PageUpdate* page_update = nullptr;
+ PageDownloader* page_downloader = nullptr;
PageReloadFromDisk *page_reload_from_disk = nullptr;
#ifdef _WIN32
PageFilesAssociation* page_files_association = nullptr;
@@ -631,9 +678,9 @@ struct ConfigWizard::priv
bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes);
// #ys_FIXME_alise
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
-#ifdef __linux__
- void perform_desktop_integration() const;
-#endif
+//#ifdef __linux__
+// void perform_desktop_integration() const;
+//#endif
bool check_fff_selected(); // Used to decide whether to display Filaments page
bool check_sla_selected(); // Used to decide whether to display SLA Materials page
diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.cpp b/src/slic3r/GUI/DesktopIntegrationDialog.cpp
index 7f99a505c68..26a8f60e5c1 100644
--- a/src/slic3r/GUI/DesktopIntegrationDialog.cpp
+++ b/src/slic3r/GUI/DesktopIntegrationDialog.cpp
@@ -218,10 +218,9 @@ bool DesktopIntegrationDialog::integration_possible()
{
return true;
}
-void DesktopIntegrationDialog::perform_desktop_integration()
+void DesktopIntegrationDialog::perform_desktop_integration(bool perform_downloader)
{
- BOOST_LOG_TRIVIAL(debug) << "performing desktop integration";
-
+ BOOST_LOG_TRIVIAL(debug) << "performing desktop integration. With downloader integration: " << perform_downloader;
// Path to appimage
const char *appimage_env = std::getenv("APPIMAGE");
std::string excutable_path;
@@ -287,7 +286,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
std::string target_dir_icons;
std::string target_dir_desktop;
-
+
// slicer icon
// iterate thru target_candidates to find icons folder
for (size_t i = 0; i < target_candidates.size(); ++i) {
@@ -300,20 +299,20 @@ void DesktopIntegrationDialog::perform_desktop_integration()
break; // success
else
target_dir_icons.clear(); // copying failed
- // if all failed - try creating default home folder
- if (i == target_candidates.size() - 1) {
- // create $HOME/.local/share
- create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs);
- // copy icon
- target_dir_icons = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
- std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir());
- std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
- if (!contains_path_dir(target_dir_icons, "icons")
- || !copy_icon(icon_path, dest_path)) {
- // every attempt failed - icon wont be present
- target_dir_icons.clear();
- }
- }
+ }
+ // if all failed - try creating default home folder
+ if (i == target_candidates.size() - 1) {
+ // create $HOME/.local/share
+ create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs);
+ // copy icon
+ target_dir_icons = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
+ std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir());
+ std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
+ if (!contains_path_dir(target_dir_icons, "icons")
+ || !copy_icon(icon_path, dest_path)) {
+ // every attempt failed - icon wont be present
+ target_dir_icons.clear();
+ }
}
}
if(target_dir_icons.empty()) {
@@ -324,25 +323,25 @@ void DesktopIntegrationDialog::perform_desktop_integration()
// desktop file
// iterate thru target_candidates to find applications folder
- for (size_t i = 0; i < target_candidates.size(); ++i)
- {
+
+ std::string desktop_file = GUI::format(
+ "[Desktop Entry]\n"
+ "Name=PrusaSlicer%1%\n"
+ "GenericName=3D Printing Software\n"
+ "Icon=PrusaSlicer%2%\n"
+ "Exec=\"%3%\" %%F\n"
+ "Terminal=false\n"
+ "Type=Application\n"
+ "MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n"
+ "Categories=Graphics;3DGraphics;Engineering;\n"
+ "Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n"
+ "StartupNotify=false\n"
+ "StartupWMClass=prusa-slicer\n", name_suffix, version_suffix, excutable_path);
+
+ for (size_t i = 0; i < target_candidates.size(); ++i) {
if (contains_path_dir(target_candidates[i], "applications")) {
target_dir_desktop = target_candidates[i];
// Write slicer desktop file
- std::string desktop_file = GUI::format(
- "[Desktop Entry]\n"
- "Name=PrusaSlicer%1%\n"
- "GenericName=3D Printing Software\n"
- "Icon=PrusaSlicer%2%\n"
- "Exec=\"%3%\" %%F\n"
- "Terminal=false\n"
- "Type=Application\n"
- "MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n"
- "Categories=Graphics;3DGraphics;Engineering;\n"
- "Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n"
- "StartupNotify=false\n"
- "StartupWMClass=prusa-slicer\n", name_suffix, version_suffix, excutable_path);
-
std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(path, desktop_file)){
BOOST_LOG_TRIVIAL(debug) << "PrusaSlicer.desktop file installation success.";
@@ -352,24 +351,24 @@ void DesktopIntegrationDialog::perform_desktop_integration()
BOOST_LOG_TRIVIAL(debug) << "Attempt to PrusaSlicer.desktop file installation failed. failed path: " << target_candidates[i];
target_dir_desktop.clear();
}
- // if all failed - try creating default home folder
- if (i == target_candidates.size() - 1) {
- // create $HOME/.local/share
- create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
- // create desktop file
- target_dir_desktop = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
- std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix);
- if (contains_path_dir(target_dir_desktop, "applications")) {
- if (!create_desktop_file(path, desktop_file)) {
- // Desktop file not written - end desktop integration
- BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create desktop file";
- return;
- }
- } else {
- // Desktop file not written - end desktop integration
- BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found.";
+ }
+ // if all failed - try creating default home folder
+ if (i == target_candidates.size() - 1) {
+ // create $HOME/.local/share
+ create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
+ // create desktop file
+ target_dir_desktop = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
+ std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix);
+ if (contains_path_dir(target_dir_desktop, "applications")) {
+ if (!create_desktop_file(path, desktop_file)) {
+ // Desktop file not written - end desktop integration
+ BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create desktop file";
return;
}
+ } else {
+ // Desktop file not written - end desktop integration
+ BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found.";
+ return;
}
}
}
@@ -398,7 +397,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
}
// Desktop file
- std::string desktop_file = GUI::format(
+ std::string desktop_file_viewer = GUI::format(
"[Desktop Entry]\n"
"Name=Prusa Gcode Viewer%1%\n"
"GenericName=3D Printing Software\n"
@@ -410,9 +409,8 @@ void DesktopIntegrationDialog::perform_desktop_integration()
"Categories=Graphics;3DGraphics;\n"
"Keywords=3D;Printing;Slicer;\n"
"StartupNotify=false\n", name_suffix, version_suffix, excutable_path);
-
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerGcodeViewer%2%.desktop", target_dir_desktop, version_suffix);
- if (create_desktop_file(desktop_path, desktop_file))
+ if (create_desktop_file(desktop_path, desktop_file_viewer))
// save path to desktop file
app_config->set("desktop_integration_app_viewer_path", desktop_path);
else {
@@ -421,6 +419,37 @@ void DesktopIntegrationDialog::perform_desktop_integration()
}
}
+ if (perform_downloader)
+ {
+ std::string desktop_file_downloader = GUI::format(
+ "[Desktop Entry]\n"
+ "Name=PrusaSlicer URL Protocol%1%\n"
+ "Exec=\"%3%\" --single-instance %%u\n"
+ "Icon=PrusaSlicer%4%\n"
+ "Terminal=false\n"
+ "Type=Application\n"
+ "MimeType=x-scheme-handler/prusaslicer;\n"
+ "StartupNotify=false\n"
+ , name_suffix, version_suffix, excutable_path, version_suffix);
+
+ // desktop file for downloader as part of main app
+ std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
+ if (create_desktop_file(desktop_path, desktop_file_downloader)) {
+ // save path to desktop file
+ app_config->set("desktop_integration_URL_path", desktop_path);
+ // finish registration on mime type
+ std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
+ BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
+ int r = system(command.c_str());
+ BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
+
+ } else {
+ BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create URL Protocol desktop file";
+ show_error(nullptr, _L("Performing desktop integration failed - could not create URL Protocol desktop file."));
+ return;
+ }
+ }
+
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
}
void DesktopIntegrationDialog::undo_desktop_intgration()
@@ -453,9 +482,26 @@ void DesktopIntegrationDialog::undo_desktop_intgration()
std::remove(path.c_str());
}
}
+ // URL Protocol
+ path = std::string(app_config->get("desktop_integration_URL_path"));
+ if (!path.empty()) {
+ BOOST_LOG_TRIVIAL(debug) << "removing " << path;
+ std::remove(path.c_str());
+ }
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess);
}
+void DesktopIntegrationDialog::undo_downloader_registration()
+{
+ const AppConfig *app_config = wxGetApp().app_config;
+ std::string path = std::string(app_config->get("desktop_integration_URL_path"));
+ if (!path.empty()) {
+ BOOST_LOG_TRIVIAL(debug) << "removing " << path;
+ std::remove(path.c_str());
+ }
+ // There is no need to undo xdg-mime default command. It is done automatically when desktop file is deleted.
+}
+
DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent)
: wxDialog(parent, wxID_ANY, _(L("Desktop Integration")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
{
@@ -481,7 +527,7 @@ DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent)
wxButton *btn_perform = new wxButton(this, wxID_ANY, _L("Perform"));
btn_szr->Add(btn_perform, 0, wxALL, 10);
- btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(); EndModal(wxID_ANY); });
+ btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(false); EndModal(wxID_ANY); });
if (can_undo){
wxButton *btn_undo = new wxButton(this, wxID_ANY, _L("Undo"));
diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.hpp b/src/slic3r/GUI/DesktopIntegrationDialog.hpp
index 74a0a68f995..08c984083b6 100644
--- a/src/slic3r/GUI/DesktopIntegrationDialog.hpp
+++ b/src/slic3r/GUI/DesktopIntegrationDialog.hpp
@@ -26,9 +26,14 @@ class DesktopIntegrationDialog : public wxDialog
// Creates Desktop files and icons for both PrusaSlicer and GcodeViewer.
// Stores paths into App Config.
// Rewrites if files already existed.
- static void perform_desktop_integration();
+ // if perform_downloader:
+ // Creates Destktop files for PrusaSlicer downloader feature
+ // Regiters PrusaSlicer to start on prusaslicer:// URL
+ static void perform_desktop_integration(bool perform_downloader);
// Deletes Desktop files and icons for both PrusaSlicer and GcodeViewer at paths stored in App Config.
static void undo_desktop_intgration();
+
+ static void undo_downloader_registration();
private:
};
diff --git a/src/slic3r/GUI/Downloader.cpp b/src/slic3r/GUI/Downloader.cpp
new file mode 100644
index 00000000000..157233076d5
--- /dev/null
+++ b/src/slic3r/GUI/Downloader.cpp
@@ -0,0 +1,245 @@
+#include "Downloader.hpp"
+#include "GUI_App.hpp"
+#include "NotificationManager.hpp"
+
+#include
+#include
+
+namespace Slic3r {
+namespace GUI {
+
+namespace {
+void open_folder(const std::string& path)
+{
+ // Code taken from NotificationManager.cpp
+
+ // Execute command to open a file explorer, platform dependent.
+ // FIXME: The const_casts aren't needed in wxWidgets 3.1, remove them when we upgrade.
+
+#ifdef _WIN32
+ const wxString widepath = from_u8(path);
+ const wchar_t* argv[] = { L"explorer", widepath.GetData(), nullptr };
+ ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr);
+#elif __APPLE__
+ const char* argv[] = { "open", path.data(), nullptr };
+ ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr);
+#else
+ const char* argv[] = { "xdg-open", path.data(), nullptr };
+
+ // Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
+ // because they may mess up the environment expected by the file manager.
+ // Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
+ if (wxGetEnv("APPIMAGE", nullptr)) {
+ // We're running from AppImage
+ 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)) {
+ // This is the original work directory from which the AppImage image was run,
+ // set it as CWD for the child process:
+ exec_env.cwd = std::move(owd);
+ }
+
+ ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr, &exec_env);
+ }
+ else {
+ // Looks like we're NOT running from AppImage, we'll make no changes to the environment.
+ ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr, nullptr);
+ }
+#endif
+}
+
+std::string filename_from_url(const std::string& url)
+{
+ // TODO: can it be done with curl?
+ size_t slash = url.find_last_of("/");
+ if (slash == std::string::npos && slash != url.size() - 1)
+ return std::string();
+ return url.substr(slash + 1, url.size() - slash + 1);
+}
+}
+
+Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
+ : m_id(ID)
+ , m_filename(filename_from_url(url))
+ , m_dest_folder(dest_folder)
+{
+ assert(boost::filesystem::is_directory(dest_folder));
+ m_final_path = dest_folder / m_filename;
+ m_file_get = std::make_shared(ID, std::move(url), m_filename, evt_handler, dest_folder);
+}
+
+void Download::start()
+{
+ m_state = DownloadState::DownloadOngoing;
+ m_file_get->get();
+}
+void Download::cancel()
+{
+ m_state = DownloadState::DownloadStopped;
+ m_file_get->cancel();
+}
+void Download::pause()
+{
+ //assert(m_state == DownloadState::DownloadOngoing);
+ // if instead of assert - it can happen that user clicks on pause several times before the pause happens
+ if (m_state != DownloadState::DownloadOngoing)
+ return;
+ m_state = DownloadState::DownloadPaused;
+ m_file_get->pause();
+}
+void Download::resume()
+{
+ //assert(m_state == DownloadState::DownloadPaused);
+ if (m_state != DownloadState::DownloadPaused)
+ return;
+ m_state = DownloadState::DownloadOngoing;
+ m_file_get->resume();
+}
+
+
+Downloader::Downloader()
+ : wxEvtHandler()
+{
+ //Bind(EVT_DWNLDR_FILE_COMPLETE, [](const wxCommandEvent& evt) {});
+ //Bind(EVT_DWNLDR_FILE_PROGRESS, [](const wxCommandEvent& evt) {});
+ //Bind(EVT_DWNLDR_FILE_ERROR, [](const wxCommandEvent& evt) {});
+ //Bind(EVT_DWNLDR_FILE_NAME_CHANGE, [](const wxCommandEvent& evt) {});
+
+ Bind(EVT_DWNLDR_FILE_COMPLETE, &Downloader::on_complete, this);
+ Bind(EVT_DWNLDR_FILE_PROGRESS, &Downloader::on_progress, this);
+ Bind(EVT_DWNLDR_FILE_ERROR, &Downloader::on_error, this);
+ Bind(EVT_DWNLDR_FILE_NAME_CHANGE, &Downloader::on_name_change, this);
+ Bind(EVT_DWNLDR_FILE_PAUSED, &Downloader::on_paused, this);
+ Bind(EVT_DWNLDR_FILE_CANCELED, &Downloader::on_canceled, this);
+}
+
+void Downloader::start_download(const std::string& full_url)
+{
+ assert(m_initialized);
+
+ // TODO: There is a misterious slash appearing in recieved msg on windows
+#ifdef _WIN32
+ if (!boost::starts_with(full_url, "prusaslicer://open/?file=")) {
+#else
+ if (!boost::starts_with(full_url, "prusaslicer://open?file=")) {
+#endif
+ BOOST_LOG_TRIVIAL(error) << "Could not start download due to wrong URL: " << full_url;
+ // TODO: show error?
+ return;
+ }
+ size_t id = get_next_id();
+ // TODO: still same mistery
+#ifdef _WIN32
+ std::string escaped_url = FileGet::escape_url(full_url.substr(25));
+#else
+ std::string escaped_url = FileGet::escape_url(full_url.substr(24));
+#endif
+ // TODO: enable after testing
+ /*
+ if (!boost::starts_with(escaped_url, "https://media.printables.com/")) {
+ BOOST_LOG_TRIVIAL(error) << "Download won't start. Download URL doesn't point to https://media.printables.com : " << escaped_url;
+ // TODO: show error?
+ return;
+ }
+ */
+ std::string text(escaped_url);
+ m_downloads.emplace_back(std::make_unique(id, std::move(escaped_url), this, m_dest_folder));
+ NotificationManager* ntf_mngr = wxGetApp().notification_manager();
+ ntf_mngr->push_download_URL_progress_notification(id, m_downloads.back()->get_filename(), std::bind(&Downloader::user_action_callback, this, std::placeholders::_1, std::placeholders::_2));
+ m_downloads.back()->start();
+ BOOST_LOG_TRIVIAL(debug) << "started download";
+}
+
+void Downloader::on_progress(wxCommandEvent& event)
+{
+ size_t id = event.GetInt();
+ float percent = (float)std::stoi(boost::nowide::narrow(event.GetString())) / 100.f;
+ //BOOST_LOG_TRIVIAL(error) << "progress " << id << ": " << percent;
+ NotificationManager* ntf_mngr = wxGetApp().notification_manager();
+ BOOST_LOG_TRIVIAL(trace) << "Download "<< id << ": " << percent;
+ ntf_mngr->set_download_URL_progress(id, percent);
+}
+void Downloader::on_error(wxCommandEvent& event)
+{
+ size_t id = event.GetInt();
+ set_download_state(event.GetInt(), DownloadState::DownloadError);
+ BOOST_LOG_TRIVIAL(error) << "Download error: " << event.GetString();
+ NotificationManager* ntf_mngr = wxGetApp().notification_manager();
+ ntf_mngr->set_download_URL_error(id, boost::nowide::narrow(event.GetString()));
+}
+void Downloader::on_complete(wxCommandEvent& event)
+{
+ // TODO: is this always true? :
+ // here we open the file itself, notification should get 1.f progress from on progress.
+ set_download_state(event.GetInt(), DownloadState::DownloadDone);
+ wxArrayString paths;
+ paths.Add(event.GetString());
+ wxGetApp().plater()->load_files(paths);
+}
+bool Downloader::user_action_callback(DownloaderUserAction action, int id)
+{
+ for (size_t i = 0; i < m_downloads.size(); ++i) {
+ if (m_downloads[i]->get_id() == id) {
+ switch (action) {
+ case DownloadUserCanceled:
+ m_downloads[i]->cancel();
+ return true;
+ case DownloadUserPaused:
+ m_downloads[i]->pause();
+ return true;
+ case DownloadUserContinued:
+ m_downloads[i]->resume();
+ return true;
+ case DownloadUserOpenedFolder:
+ open_folder(m_downloads[i]->get_dest_folder());
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+void Downloader::on_name_change(wxCommandEvent& event)
+{
+
+}
+
+void Downloader::on_paused(wxCommandEvent& event)
+{
+ size_t id = event.GetInt();
+ NotificationManager* ntf_mngr = wxGetApp().notification_manager();
+ ntf_mngr->set_download_URL_paused(id);
+}
+
+void Downloader::on_canceled(wxCommandEvent& event)
+{
+ size_t id = event.GetInt();
+ NotificationManager* ntf_mngr = wxGetApp().notification_manager();
+ ntf_mngr->set_download_URL_canceled(id);
+}
+
+void Downloader::set_download_state(int id, DownloadState state)
+{
+ for (size_t i = 0; i < m_downloads.size(); ++i) {
+ if (m_downloads[i]->get_id() == id) {
+ m_downloads[i]->set_state(state);
+ return;
+ }
+ }
+}
+
+}
+}
\ No newline at end of file
diff --git a/src/slic3r/GUI/Downloader.hpp b/src/slic3r/GUI/Downloader.hpp
new file mode 100644
index 00000000000..84a9a956974
--- /dev/null
+++ b/src/slic3r/GUI/Downloader.hpp
@@ -0,0 +1,99 @@
+#ifndef slic3r_Downloader_hpp_
+#define slic3r_Downloader_hpp_
+
+#include "DownloaderFileGet.hpp"
+#include
+#include
+
+namespace Slic3r {
+namespace GUI {
+
+class NotificationManager;
+
+enum DownloadState
+{
+ DownloadPending = 0,
+ DownloadOngoing,
+ DownloadStopped,
+ DownloadDone,
+ DownloadError,
+ DownloadPaused,
+ DownloadStateUnknown
+};
+
+enum DownloaderUserAction
+{
+ DownloadUserCanceled,
+ DownloadUserPaused,
+ DownloadUserContinued,
+ DownloadUserOpenedFolder
+};
+
+class Download {
+public:
+ Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
+ void start();
+ void cancel();
+ void pause();
+ void resume();
+
+ int get_id() const { return m_id; }
+ boost::filesystem::path get_final_path() const { return m_final_path; }
+ std::string get_filename() const { return m_filename; }
+ DownloadState get_state() const { return m_state; }
+ void set_state(DownloadState state) { m_state = state; }
+ std::string get_dest_folder() { return m_dest_folder.string(); }
+private:
+ const int m_id;
+ std::string m_filename;
+ boost::filesystem::path m_final_path;
+ boost::filesystem::path m_dest_folder;
+ std::shared_ptr m_file_get;
+ DownloadState m_state { DownloadState::DownloadPending };
+};
+
+class Downloader : public wxEvtHandler {
+public:
+ Downloader();
+
+ bool get_initialized() { return m_initialized; }
+ void init(const boost::filesystem::path& dest_folder)
+ {
+ m_dest_folder = dest_folder;
+ m_initialized = true;
+ }
+ void start_download(const std::string& full_url);
+ // cancel = false -> just pause
+ bool user_action_callback(DownloaderUserAction action, int id);
+private:
+ bool m_initialized { false };
+
+ std::vector> m_downloads;
+ boost::filesystem::path m_dest_folder;
+
+ size_t m_next_id { 0 };
+ size_t get_next_id() { return ++m_next_id; }
+
+ void on_progress(wxCommandEvent& event);
+ void on_error(wxCommandEvent& event);
+ void on_complete(wxCommandEvent& event);
+ void on_name_change(wxCommandEvent& event);
+ void on_paused(wxCommandEvent& event);
+ void on_canceled(wxCommandEvent& event);
+
+ void set_download_state(int id, DownloadState state);
+ /*
+ bool is_in_state(int id, DownloadState state) const;
+ DownloadState get_download_state(int id) const;
+ bool cancel_download(int id);
+ bool pause_download(int id);
+ bool resume_download(int id);
+ bool delete_download(int id);
+ wxString get_path_of(int id) const;
+ wxString get_folder_path_of(int id) const;
+ */
+};
+
+}
+}
+#endif
\ No newline at end of file
diff --git a/src/slic3r/GUI/DownloaderFileGet.cpp b/src/slic3r/GUI/DownloaderFileGet.cpp
new file mode 100644
index 00000000000..10142c7c12e
--- /dev/null
+++ b/src/slic3r/GUI/DownloaderFileGet.cpp
@@ -0,0 +1,322 @@
+#include "DownloaderFileGet.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "format.hpp"
+
+namespace Slic3r {
+namespace GUI {
+
+const size_t DOWNLOAD_MAX_CHUNK_SIZE = 10 * 1024 * 1024;
+const size_t DOWNLOAD_SIZE_LIMIT = 1024 * 1024 * 1024;
+
+std::string FileGet::escape_url(const std::string& unescaped)
+{
+ std::string ret_val;
+ CURL* curl = curl_easy_init();
+ if (curl) {
+ int decodelen;
+ char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen);
+ if (decoded) {
+ ret_val = std::string(decoded);
+ curl_free(decoded);
+ }
+ curl_easy_cleanup(curl);
+ }
+ return ret_val;
+}
+namespace {
+unsigned get_current_pid()
+{
+#ifdef WIN32
+ return GetCurrentProcessId();
+#else
+ return ::getpid();
+#endif
+}
+}
+
+// int = DOWNLOAD ID; string = file path
+wxDEFINE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent);
+// int = DOWNLOAD ID; string = error msg
+wxDEFINE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent);
+// int = DOWNLOAD ID; string = progress percent
+wxDEFINE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent);
+// int = DOWNLOAD ID; string = name
+wxDEFINE_EVENT(EVT_DWNLDR_FILE_NAME_CHANGE, wxCommandEvent);
+// int = DOWNLOAD ID;
+wxDEFINE_EVENT(EVT_DWNLDR_FILE_PAUSED, wxCommandEvent);
+// int = DOWNLOAD ID;
+wxDEFINE_EVENT(EVT_DWNLDR_FILE_CANCELED, wxCommandEvent);
+
+struct FileGet::priv
+{
+ const int m_id;
+ std::string m_url;
+ std::string m_filename;
+ std::thread m_io_thread;
+ wxEvtHandler* m_evt_handler;
+ boost::filesystem::path m_dest_folder;
+ boost::filesystem::path m_tmp_path; // path when ongoing download
+ std::atomic_bool m_cancel { false };
+ std::atomic_bool m_pause { false };
+ std::atomic_bool m_stopped { false }; // either canceled or paused - download is not running
+ size_t m_written { 0 };
+ size_t m_absolute_size { 0 };
+ priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
+
+ void get_perform();
+};
+
+FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
+ : m_id(ID)
+ , m_url(std::move(url))
+ , m_filename(filename)
+ , m_evt_handler(evt_handler)
+ , m_dest_folder(dest_folder)
+{
+}
+
+void FileGet::priv::get_perform()
+{
+ assert(m_evt_handler);
+ assert(!m_url.empty());
+ assert(!m_filename.empty());
+ assert(boost::filesystem::is_directory(m_dest_folder));
+
+ m_stopped = false;
+
+ // open dest file
+ if (m_written == 0)
+ {
+ boost::filesystem::path dest_path = m_dest_folder / m_filename;
+ std::string extension = boost::filesystem::extension(dest_path);
+ std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size());
+ std::string final_filename = just_filename;
+
+ size_t version = 0;
+ while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download")))
+ {
+ ++version;
+ final_filename = just_filename + "(" + std::to_string(version) + ")";
+ }
+ m_filename = final_filename + extension;
+
+ m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download");
+
+ wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE);
+ evt->SetString(boost::nowide::widen(m_filename));
+ evt->SetInt(m_id);
+ m_evt_handler->QueueEvent(evt);
+ }
+
+ boost::filesystem::path dest_path = m_dest_folder / m_filename;
+
+ wxString temp_path_wstring(m_tmp_path.wstring());
+
+ std::cout << "dest_path: " << dest_path.string() << std::endl;
+ std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl;
+
+ BOOST_LOG_TRIVIAL(info) << GUI::format("Starting download from %1% to %2%. Temp path is %3%",m_url, dest_path, m_tmp_path);
+
+ FILE* file;
+ // open file for writting
+ if (m_written == 0)
+ file = fopen(temp_path_wstring.c_str(), "wb");
+ else
+ file = fopen(temp_path_wstring.c_str(), "a");
+
+ assert(file != NULL);
+
+ std:: string range_string = std::to_string(m_written) + "-";
+
+ size_t written_previously = m_written;
+ size_t written_this_session = 0;
+ Http::get(m_url)
+ .size_limit(DOWNLOAD_SIZE_LIMIT) //more?
+ .set_range(range_string)
+ .on_progress([&](Http::Progress progress, bool& cancel) {
+ // to prevent multiple calls into following ifs (m_cancel / m_pause)
+ if (m_stopped){
+ cancel = true;
+ return;
+ }
+ if (m_cancel) {
+ m_stopped = true;
+ fclose(file);
+ // remove canceled file
+ std::remove(m_tmp_path.string().c_str());
+ m_written = 0;
+ cancel = true;
+ wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_CANCELED);
+ evt->SetInt(m_id);
+ m_evt_handler->QueueEvent(evt);
+ return;
+ // TODO: send canceled event?
+ }
+ if (m_pause) {
+ m_stopped = true;
+ fclose(file);
+ cancel = true;
+ wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PAUSED);
+ evt->SetInt(m_id);
+ m_evt_handler->QueueEvent(evt);
+ return;
+ }
+
+ if (m_absolute_size < progress.dltotal) {
+ m_absolute_size = progress.dltotal;
+ }
+
+ if (progress.dlnow != 0) {
+ if (progress.dlnow - written_this_session > DOWNLOAD_MAX_CHUNK_SIZE || progress.dlnow == progress.dltotal) {
+ try
+ {
+ std::string part_for_write = progress.buffer.substr(written_this_session, progress.dlnow);
+ fwrite(part_for_write.c_str(), 1, part_for_write.size(), file);
+ }
+ catch (const std::exception& e)
+ {
+ // fclose(file); do it?
+ wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
+ evt->SetString(e.what());
+ evt->SetInt(m_id);
+ m_evt_handler->QueueEvent(evt);
+ cancel = true;
+ return;
+ }
+ written_this_session = progress.dlnow;
+ m_written = written_previously + written_this_session;
+ }
+ wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PROGRESS);
+ int percent_total = (written_previously + progress.dlnow) * 100 / m_absolute_size;
+ evt->SetString(std::to_string(percent_total));
+ evt->SetInt(m_id);
+ m_evt_handler->QueueEvent(evt);
+ }
+
+ })
+ .on_error([&](std::string body, std::string error, unsigned http_status) {
+ if (file != NULL)
+ fclose(file);
+ wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
+ evt->SetString(error);
+ evt->SetInt(m_id);
+ m_evt_handler->QueueEvent(evt);
+ })
+ .on_complete([&](std::string body, unsigned /* http_status */) {
+
+ // TODO: perform a body size check
+ //
+ //size_t body_size = body.size();
+ //if (body_size != expected_size) {
+ // return;
+ //}
+ try
+ {
+ /*
+ if (m_written < body.size())
+ {
+ // this code should never be entered. As there should be on_progress call after last bit downloaded.
+ std::string part_for_write = body.substr(m_written);
+ fwrite(part_for_write.c_str(), 1, part_for_write.size(), file);
+ }
+ */
+ fclose(file);
+ boost::filesystem::rename(m_tmp_path, dest_path);
+ }
+ catch (const std::exception& /*e*/)
+ {
+ //TODO: report?
+ //error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path);
+ wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
+ evt->SetString("Failed to write and move.");
+ evt->SetInt(m_id);
+ m_evt_handler->QueueEvent(evt);
+ return;
+ }
+
+ wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_COMPLETE);
+ evt->SetString(dest_path.wstring());
+ evt->SetInt(m_id);
+ m_evt_handler->QueueEvent(evt);
+ })
+ .perform_sync();
+
+}
+
+FileGet::FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
+ : p(new priv(ID, std::move(url), filename, evt_handler, dest_folder))
+{}
+
+FileGet::FileGet(FileGet&& other) : p(std::move(other.p)) {}
+
+FileGet::~FileGet()
+{
+ if (p && p->m_io_thread.joinable()) {
+ p->m_cancel = true;
+ p->m_io_thread.join();
+ }
+}
+
+void FileGet::get()
+{
+ assert(p);
+ if (p->m_io_thread.joinable()) {
+ // This will stop transfers being done by the thread, if any.
+ // Cancelling takes some time, but should complete soon enough.
+ p->m_cancel = true;
+ p->m_io_thread.join();
+ }
+ p->m_cancel = false;
+ p->m_pause = false;
+ p->m_io_thread = std::thread([this]() {
+ p->get_perform();
+ });
+}
+
+void FileGet::cancel()
+{
+ if(p && p->m_stopped) {
+ if (p->m_io_thread.joinable()) {
+ p->m_cancel = true;
+ p->m_io_thread.join();
+ wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_CANCELED);
+ evt->SetInt(p->m_id);
+ p->m_evt_handler->QueueEvent(evt);
+ }
+ }
+
+ if (p)
+ p->m_cancel = true;
+
+}
+
+void FileGet::pause()
+{
+ if (p) {
+ p->m_pause = true;
+ }
+}
+void FileGet::resume()
+{
+ assert(p);
+ if (p->m_io_thread.joinable()) {
+ // This will stop transfers being done by the thread, if any.
+ // Cancelling takes some time, but should complete soon enough.
+ p->m_cancel = true;
+ p->m_io_thread.join();
+ }
+ p->m_cancel = false;
+ p->m_pause = false;
+ p->m_io_thread = std::thread([this]() {
+ p->get_perform();
+ });
+}
+}
+}
diff --git a/src/slic3r/GUI/DownloaderFileGet.hpp b/src/slic3r/GUI/DownloaderFileGet.hpp
new file mode 100644
index 00000000000..38ddd9af02e
--- /dev/null
+++ b/src/slic3r/GUI/DownloaderFileGet.hpp
@@ -0,0 +1,44 @@
+#ifndef slic3r_DownloaderFileGet_hpp_
+#define slic3r_DownloaderFileGet_hpp_
+
+#include "../Utils/Http.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Slic3r {
+namespace GUI {
+class FileGet : public std::enable_shared_from_this {
+private:
+ struct priv;
+public:
+ FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder);
+ FileGet(FileGet&& other);
+ ~FileGet();
+
+ void get();
+ void cancel();
+ void pause();
+ void resume();
+ static std::string escape_url(const std::string& url);
+private:
+ std::unique_ptr p;
+};
+// int = DOWNLOAD ID; string = file path
+wxDECLARE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent);
+// int = DOWNLOAD ID; string = error msg
+wxDECLARE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent);
+// int = DOWNLOAD ID; string = progress percent
+wxDECLARE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent);
+// int = DOWNLOAD ID; string = name
+wxDECLARE_EVENT(EVT_DWNLDR_FILE_NAME_CHANGE, wxCommandEvent);
+// int = DOWNLOAD ID;
+wxDECLARE_EVENT(EVT_DWNLDR_FILE_PAUSED, wxCommandEvent);
+// int = DOWNLOAD ID;
+wxDECLARE_EVENT(EVT_DWNLDR_FILE_CANCELED, wxCommandEvent);
+}
+}
+#endif
diff --git a/src/slic3r/GUI/FileArchiveDialog.cpp b/src/slic3r/GUI/FileArchiveDialog.cpp
new file mode 100644
index 00000000000..2813887dff6
--- /dev/null
+++ b/src/slic3r/GUI/FileArchiveDialog.cpp
@@ -0,0 +1,363 @@
+#include "FileArchiveDialog.hpp"
+
+#include "I18N.hpp"
+#include "GUI_App.hpp"
+#include "GUI.hpp"
+#include "MainFrame.hpp"
+#include "ExtraRenderers.hpp"
+#include "format.hpp"
+#include
+#include
+#include
+
+namespace Slic3r {
+namespace GUI {
+
+ArchiveViewModel::ArchiveViewModel(wxWindow* parent)
+ :m_parent(parent)
+{}
+ArchiveViewModel::~ArchiveViewModel()
+{}
+
+std::shared_ptr ArchiveViewModel::AddFile(std::shared_ptr parent, const wxString& name, bool container)
+{
+ std::shared_ptr node = std::make_shared(ArchiveViewNode(name));
+ node->set_container(container);
+
+ if (parent.get() != nullptr) {
+ parent->get_children().push_back(node);
+ node->set_parent(parent);
+ parent->set_is_folder(true);
+ } else {
+ m_top_children.emplace_back(node);
+ }
+
+ wxDataViewItem child = wxDataViewItem((void*)node.get());
+ wxDataViewItem parent_item= wxDataViewItem((void*)parent.get());
+ ItemAdded(parent_item, child);
+
+ if (parent)
+ m_ctrl->Expand(parent_item);
+ return node;
+}
+
+wxString ArchiveViewModel::GetColumnType(unsigned int col) const
+{
+ if (col == 0)
+ return "bool";
+ return "string";//"DataViewBitmapText";
+}
+
+void ArchiveViewModel::Rescale()
+{
+ // There should be no pictures rendered
+}
+
+void ArchiveViewModel::Delete(const wxDataViewItem& item)
+{
+ assert(item.IsOk());
+ ArchiveViewNode* node = static_cast(item.GetID());
+ assert(node->get_parent() != nullptr);
+ for (std::shared_ptr child : node->get_children())
+ {
+ Delete(wxDataViewItem((void*)child.get()));
+ }
+ delete [] node;
+}
+void ArchiveViewModel::Clear()
+{
+}
+
+wxDataViewItem ArchiveViewModel::GetParent(const wxDataViewItem& item) const
+{
+ assert(item.IsOk());
+ ArchiveViewNode* node = static_cast(item.GetID());
+ return wxDataViewItem((void*)node->get_parent().get());
+}
+unsigned int ArchiveViewModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const
+{
+ if (!parent.IsOk()) {
+ for (std::shared_ptrchild : m_top_children) {
+ array.push_back(wxDataViewItem((void*)child.get()));
+ }
+ return m_top_children.size();
+ }
+
+ ArchiveViewNode* node = static_cast(parent.GetID());
+ for (std::shared_ptr child : node->get_children()) {
+ array.push_back(wxDataViewItem((void*)child.get()));
+ }
+ return node->get_children().size();
+}
+
+void ArchiveViewModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const
+{
+ assert(item.IsOk());
+ ArchiveViewNode* node = static_cast(item.GetID());
+ if (col == 0) {
+ variant = node->get_toggle();
+ } else {
+ variant = node->get_name();
+ }
+}
+
+void ArchiveViewModel::untoggle_folders(const wxDataViewItem& item)
+{
+ assert(item.IsOk());
+ ArchiveViewNode* node = static_cast(item.GetID());
+ node->set_toggle(false);
+ if (node->get_parent().get() != nullptr)
+ untoggle_folders(wxDataViewItem((void*)node->get_parent().get()));
+}
+
+bool ArchiveViewModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col)
+{
+ assert(item.IsOk());
+ ArchiveViewNode* node = static_cast(item.GetID());
+ if (col == 0) {
+ node->set_toggle(variant.GetBool());
+ // if folder recursivelly check all children
+ for (std::shared_ptr child : node->get_children()) {
+ SetValue(variant, wxDataViewItem((void*)child.get()), col);
+ }
+ if(!variant.GetBool() && node->get_parent())
+ untoggle_folders(wxDataViewItem((void*)node->get_parent().get()));
+ } else {
+ node->set_name(variant.GetString());
+ }
+ m_parent->Refresh();
+ return true;
+}
+bool ArchiveViewModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const
+{
+ // As of now, all items are always enabled.
+ // Returning false for col 1 would gray out text.
+ return true;
+}
+
+bool ArchiveViewModel::IsContainer(const wxDataViewItem& item) const
+{
+ if(!item.IsOk())
+ return true;
+ ArchiveViewNode* node = static_cast