1 // FIXME: extract absolute units -> em
2
3 #include "ConfigWizard_private.hpp"
4
5 #include <algorithm>
6 #include <numeric>
7 #include <utility>
8 #include <unordered_map>
9 #include <stdexcept>
10 #include <boost/format.hpp>
11 #include <boost/log/trivial.hpp>
12 #include <boost/algorithm/string/predicate.hpp>
13 #include <boost/nowide/convert.hpp>
14
15 #include <wx/settings.h>
16 #include <wx/stattext.h>
17 #include <wx/textctrl.h>
18 #include <wx/dcclient.h>
19 #include <wx/statbmp.h>
20 #include <wx/checkbox.h>
21 #include <wx/statline.h>
22 #include <wx/dataview.h>
23 #include <wx/notebook.h>
24 #include <wx/display.h>
25 #include <wx/filefn.h>
26 #include <wx/wupdlock.h>
27 #include <wx/debug.h>
28
29 #include "libslic3r/Utils.hpp"
30 #include "libslic3r/Config.hpp"
31 #include "GUI.hpp"
32 #include "GUI_App.hpp"
33 #include "GUI_Utils.hpp"
34 #include "GUI_ObjectManipulation.hpp"
35 #include "slic3r/Config/Snapshot.hpp"
36 #include "slic3r/Utils/PresetUpdater.hpp"
37
38 #if defined(__linux__) && defined(__WXGTK3__)
39 #define wxLinux_gtk3 true
40 #else
41 #define wxLinux_gtk3 false
42 #endif //defined(__linux__) && defined(__WXGTK3__)
43
44 namespace Slic3r {
45 namespace GUI {
46
47
48 using Config::Snapshot;
49 using Config::SnapshotDB;
50
51
52 // Configuration data structures extensions needed for the wizard
53
load(fs::path source_path,bool ais_in_resources,bool ais_prusa_bundle)54 bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle)
55 {
56 this->preset_bundle = std::make_unique<PresetBundle>();
57 this->is_in_resources = ais_in_resources;
58 this->is_prusa_bundle = ais_prusa_bundle;
59
60 std::string path_string = source_path.string();
61 // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
62 auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(
63 path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
64 // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
65 assert(config_substitutions.empty());
66 auto first_vendor = preset_bundle->vendors.begin();
67 if (first_vendor == preset_bundle->vendors.end()) {
68 BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
69 return false;
70 }
71 if (presets_loaded == 0) {
72 BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string;
73 return false;
74 }
75
76 BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded;
77 this->vendor_profile = &first_vendor->second;
78 return true;
79 }
80
Bundle(Bundle && other)81 Bundle::Bundle(Bundle &&other)
82 : preset_bundle(std::move(other.preset_bundle))
83 , vendor_profile(other.vendor_profile)
84 , is_in_resources(other.is_in_resources)
85 , is_prusa_bundle(other.is_prusa_bundle)
86 {
87 other.vendor_profile = nullptr;
88 }
89
load()90 BundleMap BundleMap::load()
91 {
92 BundleMap res;
93
94 const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred();
95 const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
96
97 auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
98 auto prusa_bundle_rsrc = false;
99 if (! boost::filesystem::exists(prusa_bundle_path)) {
100 prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
101 prusa_bundle_rsrc = true;
102 }
103 {
104 Bundle prusa_bundle;
105 if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true))
106 res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle));
107 }
108
109 // Load the other bundles in the datadir/vendor directory
110 // and then additionally from resources/profiles.
111 bool is_in_resources = false;
112 for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) {
113 for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) {
114 if (Slic3r::is_ini_file(dir_entry)) {
115 std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part
116
117 // Don't load this bundle if we've already loaded it.
118 if (res.find(id) != res.end()) { continue; }
119
120 Bundle bundle;
121 if (bundle.load(dir_entry.path(), is_in_resources))
122 res.emplace(std::move(id), std::move(bundle));
123 }
124 }
125
126 is_in_resources = true;
127 }
128
129 return res;
130 }
131
prusa_bundle()132 Bundle& BundleMap::prusa_bundle()
133 {
134 auto it = find(PresetBundle::PRUSA_BUNDLE);
135 if (it == end()) {
136 throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded");
137 }
138
139 return it->second;
140 }
141
prusa_bundle() const142 const Bundle& BundleMap::prusa_bundle() const
143 {
144 return const_cast<BundleMap*>(this)->prusa_bundle();
145 }
146
147
148 // Printer model picker GUI control
149
150 struct PrinterPickerEvent : public wxEvent
151 {
152 std::string vendor_id;
153 std::string model_id;
154 std::string variant_name;
155 bool enable;
156
PrinterPickerEventSlic3r::GUI::PrinterPickerEvent157 PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable)
158 : wxEvent(winid, eventType)
159 , vendor_id(std::move(vendor_id))
160 , model_id(std::move(model_id))
161 , variant_name(std::move(variant_name))
162 , enable(enable)
163 {}
164
CloneSlic3r::GUI::PrinterPickerEvent165 virtual wxEvent *Clone() const
166 {
167 return new PrinterPickerEvent(*this);
168 }
169 };
170
171 wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent);
172
173 const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png";
174
PrinterPicker(wxWindow * parent,const VendorProfile & vendor,wxString title,size_t max_cols,const AppConfig & appconfig,const ModelFilter & filter)175 PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter)
176 : wxPanel(parent)
177 , vendor_id(vendor.id)
178 , width(0)
179 {
180 const auto &models = vendor.models;
181
182 auto *sizer = new wxBoxSizer(wxVERTICAL);
183
184 const auto font_title = GetFont().MakeBold().Scaled(1.3f);
185 const auto font_name = GetFont().MakeBold();
186 const auto font_alt_nozzle = GetFont().Scaled(0.9f);
187
188 // wxGrid appends widgets by rows, but we need to construct them in columns.
189 // These vectors are used to hold the elements so that they can be appended in the right order.
190 std::vector<wxStaticText*> titles;
191 std::vector<wxStaticBitmap*> bitmaps;
192 std::vector<wxPanel*> variants_panels;
193
194 int max_row_width = 0;
195 int current_row_width = 0;
196
197 bool is_variants = false;
198
199 for (const auto &model : models) {
200 if (! filter(model)) { continue; }
201
202 wxBitmap bitmap;
203 int bitmap_width = 0;
204 auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool {
205 if (wxFileExists(bitmap_file)) {
206 bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG);
207 bitmap_width = bitmap.GetWidth();
208 return true;
209 }
210 return false;
211 };
212 if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
213 if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
214 BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead")
215 % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png")
216 % vendor.id
217 % model.id;
218 load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width);
219 }
220 }
221 auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
222 title->SetFont(font_name);
223 const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width);
224 title->Wrap(wrap_width);
225
226 current_row_width += wrap_width;
227 if (titles.size() % max_cols == max_cols - 1) {
228 max_row_width = std::max(max_row_width, current_row_width);
229 current_row_width = 0;
230 }
231
232 titles.push_back(title);
233
234 auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap);
235 bitmaps.push_back(bitmap_widget);
236
237 auto *variants_panel = new wxPanel(this);
238 auto *variants_sizer = new wxBoxSizer(wxVERTICAL);
239 variants_panel->SetSizer(variants_sizer);
240 const auto model_id = model.id;
241
242 for (size_t i = 0; i < model.variants.size(); i++) {
243 const auto &variant = model.variants[i];
244
245 const auto label = model.technology == ptFFF
246 ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str())
247 : from_u8(model.name);
248
249 if (i == 1) {
250 auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:"));
251 alt_label->SetFont(font_alt_nozzle);
252 variants_sizer->Add(alt_label, 0, wxBOTTOM, 3);
253 is_variants = true;
254 }
255
256 auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name);
257 i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox);
258
259 const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name);
260 cbox->SetValue(enabled);
261
262 variants_sizer->Add(cbox, 0, wxBOTTOM, 3);
263
264 cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) {
265 on_checkbox(cbox, event.IsChecked());
266 });
267 }
268
269 variants_panels.push_back(variants_panel);
270 }
271
272 width = std::max(max_row_width, current_row_width);
273
274 const size_t cols = std::min(max_cols, titles.size());
275
276 auto *printer_grid = new wxFlexGridSizer(cols, 0, 20);
277 printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL);
278
279 if (titles.size() > 0) {
280 const size_t odd_items = titles.size() % cols;
281
282 for (size_t i = 0; i < titles.size() - odd_items; i += cols) {
283 for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); }
284 for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); }
285 for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); }
286
287 // Add separator space to multiliners
288 if (titles.size() > cols) {
289 for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); }
290 }
291 }
292 if (odd_items > 0) {
293 const size_t rem = titles.size() - odd_items;
294
295 for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); }
296 for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); }
297 for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); }
298 for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); }
299 for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); }
300 }
301 }
302
303 auto *title_sizer = new wxBoxSizer(wxHORIZONTAL);
304 if (! title.IsEmpty()) {
305 auto *title_widget = new wxStaticText(this, wxID_ANY, title);
306 title_widget->SetFont(font_title);
307 title_sizer->Add(title_widget);
308 }
309 title_sizer->AddStretchSpacer();
310
311 if (/*titles.size() > 1*/is_variants) {
312 // It only makes sense to add the All / None buttons if there's multiple printers
313
314 auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard"));
315 auto *sel_all = new wxButton(this, wxID_ANY, _L("All"));
316 auto *sel_none = new wxButton(this, wxID_ANY, _L("None"));
317 sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, false); });
318 sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); });
319 sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); });
320 title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING);
321 title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING);
322 title_sizer->Add(sel_none);
323
324 // fill button indexes used later for buttons rescaling
325 m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() };
326 }
327
328 sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING);
329 sizer->Add(printer_grid);
330
331 SetSizer(sizer);
332 }
333
PrinterPicker(wxWindow * parent,const VendorProfile & vendor,wxString title,size_t max_cols,const AppConfig & appconfig)334 PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig)
335 : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; })
336 {}
337
select_all(bool select,bool alternates)338 void PrinterPicker::select_all(bool select, bool alternates)
339 {
340 for (const auto &cb : cboxes) {
341 if (cb->GetValue() != select) {
342 cb->SetValue(select);
343 on_checkbox(cb, select);
344 }
345 }
346
347 if (! select) { alternates = false; }
348
349 for (const auto &cb : cboxes_alt) {
350 if (cb->GetValue() != alternates) {
351 cb->SetValue(alternates);
352 on_checkbox(cb, alternates);
353 }
354 }
355 }
356
select_one(size_t i,bool select)357 void PrinterPicker::select_one(size_t i, bool select)
358 {
359 if (i < cboxes.size() && cboxes[i]->GetValue() != select) {
360 cboxes[i]->SetValue(select);
361 on_checkbox(cboxes[i], select);
362 }
363 }
364
any_selected() const365 bool PrinterPicker::any_selected() const
366 {
367 for (const auto &cb : cboxes) {
368 if (cb->GetValue()) { return true; }
369 }
370
371 for (const auto &cb : cboxes_alt) {
372 if (cb->GetValue()) { return true; }
373 }
374
375 return false;
376 }
377
get_selected_models() const378 std::set<std::string> PrinterPicker::get_selected_models() const
379 {
380 std::set<std::string> ret_set;
381
382 for (const auto& cb : cboxes)
383 if (cb->GetValue())
384 ret_set.emplace(cb->model);
385
386 for (const auto& cb : cboxes_alt)
387 if (cb->GetValue())
388 ret_set.emplace(cb->model);
389
390 return ret_set;
391 }
392
on_checkbox(const Checkbox * cbox,bool checked)393 void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
394 {
395 PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
396 AddPendingEvent(evt);
397 }
398
399
400 // Wizard page base
401
ConfigWizardPage(ConfigWizard * parent,wxString title,wxString shortname,unsigned indent)402 ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent)
403 : wxPanel(parent->p->hscroll)
404 , parent(parent)
405 , shortname(std::move(shortname))
406 , indent(indent)
407 {
408 auto *sizer = new wxBoxSizer(wxVERTICAL);
409
410 auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
411 const auto font = GetFont().MakeBold().Scaled(1.5);
412 text->SetFont(font);
413 sizer->Add(text, 0, wxALIGN_LEFT, 0);
414 sizer->AddSpacer(10);
415
416 content = new wxBoxSizer(wxVERTICAL);
417 sizer->Add(content, 1, wxEXPAND);
418
419 SetSizer(sizer);
420
421 // There is strange layout on Linux with GTK3,
422 // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861
423 // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages
424 if (!wxLinux_gtk3)
425 this->Hide();
426
427 Bind(wxEVT_SIZE, [this](wxSizeEvent &event) {
428 this->Layout();
429 event.Skip();
430 });
431 }
432
~ConfigWizardPage()433 ConfigWizardPage::~ConfigWizardPage() {}
434
append_text(wxString text)435 wxStaticText* ConfigWizardPage::append_text(wxString text)
436 {
437 auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
438 widget->Wrap(WRAP_WIDTH);
439 widget->SetMinSize(wxSize(WRAP_WIDTH, -1));
440 append(widget);
441 return widget;
442 }
443
append_spacer(int space)444 void ConfigWizardPage::append_spacer(int space)
445 {
446 // FIXME: scaling
447 content->AddSpacer(space);
448 }
449
450
451 // Wizard pages
452
PageWelcome(ConfigWizard * parent)453 PageWelcome::PageWelcome(ConfigWizard *parent)
454 : ConfigWizardPage(parent, from_u8((boost::format(
455 #ifdef __APPLE__
456 _utf8(L("Welcome to the %s Configuration Assistant"))
457 #else
458 _utf8(L("Welcome to the %s Configuration Wizard"))
459 #endif
460 ) % SLIC3R_APP_NAME).str()), _L("Welcome"))
461 , welcome_text(append_text(from_u8((boost::format(
462 _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.")))
463 % SLIC3R_APP_NAME
464 % _utf8(ConfigWizard::name())).str())
465 ))
466 , cbox_reset(append(
467 new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)"))
468 ))
469 {
470 welcome_text->Hide();
471 cbox_reset->Hide();
472 }
473
set_run_reason(ConfigWizard::RunReason run_reason)474 void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
475 {
476 const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY;
477 welcome_text->Show(data_empty);
478 cbox_reset->Show(!data_empty);
479 }
480
481
PagePrinters(ConfigWizard * parent,wxString title,wxString shortname,const VendorProfile & vendor,unsigned indent,Technology technology)482 PagePrinters::PagePrinters(ConfigWizard *parent,
483 wxString title,
484 wxString shortname,
485 const VendorProfile &vendor,
486 unsigned indent,
487 Technology technology)
488 : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent)
489 , technology(technology)
490 , install(false) // only used for 3rd party vendors
491 {
492 enum {
493 COL_SIZE = 200,
494 };
495
496 AppConfig *appconfig = &this->wizard_p()->appconfig_new;
497
498 const auto families = vendor.families();
499 for (const auto &family : families) {
500 const auto filter = [&](const VendorProfile::PrinterModel &model) {
501 return ((model.technology == ptFFF && technology & T_FFF)
502 || (model.technology == ptSLA && technology & T_SLA))
503 && model.family == family;
504 };
505
506 if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) {
507 continue;
508 }
509
510 const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str());
511 auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter);
512
513 picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) {
514 appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
515 wizard_p()->on_printer_pick(this, evt);
516 });
517
518 append(new wxStaticLine(this));
519
520 append(picker);
521 printer_pickers.push_back(picker);
522 }
523 }
524
select_all(bool select,bool alternates)525 void PagePrinters::select_all(bool select, bool alternates)
526 {
527 for (auto picker : printer_pickers) {
528 picker->select_all(select, alternates);
529 }
530 }
531
get_width() const532 int PagePrinters::get_width() const
533 {
534 return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0,
535 [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); });
536 }
537
any_selected() const538 bool PagePrinters::any_selected() const
539 {
540 for (const auto *picker : printer_pickers) {
541 if (picker->any_selected()) { return true; }
542 }
543
544 return false;
545 }
546
get_selected_models()547 std::set<std::string> PagePrinters::get_selected_models()
548 {
549 std::set<std::string> ret_set;
550
551 for (const auto *picker : printer_pickers)
552 {
553 std::set<std::string> tmp_models = picker->get_selected_models();
554 ret_set.insert(tmp_models.begin(), tmp_models.end());
555 }
556
557 return ret_set;
558 }
559
set_run_reason(ConfigWizard::RunReason run_reason)560 void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
561 {
562 if (technology == T_FFF
563 && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY)
564 && printer_pickers.size() > 0
565 && printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) {
566 printer_pickers[0]->select_one(0, true);
567 }
568 }
569
570
571 const std::string PageMaterials::EMPTY;
572
PageMaterials(ConfigWizard * parent,Materials * materials,wxString title,wxString shortname,wxString list1name)573 PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name)
574 : ConfigWizardPage(parent, std::move(title), std::move(shortname))
575 , materials(materials)
576 , list_printer(new StringList(this, wxLB_MULTIPLE))
577 , list_type(new StringList(this))
578 , list_vendor(new StringList(this))
579 , list_profile(new PresetList(this))
580 {
581 append_spacer(VERTICAL_SPACING);
582
583 const int em = parent->em_unit();
584 const int list_h = 30*em;
585
586
587 list_printer->SetMinSize(wxSize(23*em, list_h));
588 list_type->SetMinSize(wxSize(8*em, list_h));
589 list_vendor->SetMinSize(wxSize(13*em, list_h));
590 list_profile->SetMinSize(wxSize(23*em, list_h));
591
592
593
594 grid = new wxFlexGridSizer(4, em/2, em);
595 grid->AddGrowableCol(3, 1);
596 grid->AddGrowableRow(1, 1);
597
598 grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:")));
599 grid->Add(new wxStaticText(this, wxID_ANY, list1name));
600 grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:")));
601 grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:")));
602
603 grid->Add(list_printer, 0, wxEXPAND);
604 grid->Add(list_type, 0, wxEXPAND);
605 grid->Add(list_vendor, 0, wxEXPAND);
606 grid->Add(list_profile, 1, wxEXPAND);
607
608 auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL);
609 auto *sel_all = new wxButton(this, wxID_ANY, _L("All"));
610 auto *sel_none = new wxButton(this, wxID_ANY, _L("None"));
611 btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2);
612 btn_sizer->Add(sel_none);
613
614
615 grid->Add(new wxBoxSizer(wxHORIZONTAL));
616 grid->Add(new wxBoxSizer(wxHORIZONTAL));
617 grid->Add(new wxBoxSizer(wxHORIZONTAL));
618 grid->Add(btn_sizer, 0, wxALIGN_RIGHT);
619
620 append(grid, 1, wxEXPAND);
621
622 append_spacer(VERTICAL_SPACING);
623
624 html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition,
625 wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO);
626 append(html_window, 0, wxEXPAND);
627
628 list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) {
629 update_lists(evt.GetInt(), list_type->GetSelection(), list_vendor->GetSelection());
630 });
631 list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
632 update_lists(list_printer->GetSelection(), list_type->GetSelection(), list_vendor->GetSelection());
633 });
634 list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
635 update_lists(list_printer->GetSelection(), list_type->GetSelection(), list_vendor->GetSelection());
636 });
637
638 list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); });
639 list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); });
640
641 sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); });
642 sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); });
643 /*
644 Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();});
645
646 list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); });
647 list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); });
648 list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); });
649 */
650 reload_presets();
651 set_compatible_printers_html_window(std::vector<std::string>(), false);
652 }
on_paint()653 void PageMaterials::on_paint()
654 {
655 }
on_mouse_move_on_profiles(wxMouseEvent & evt)656 void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt)
657 {
658 const wxClientDC dc(list_profile);
659 const wxPoint pos = evt.GetLogicalPosition(dc);
660 int item = list_profile->HitTest(pos);
661 on_material_hovered(item);
662 }
on_mouse_enter_profiles(wxMouseEvent & evt)663 void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt)
664 {}
on_mouse_leave_profiles(wxMouseEvent & evt)665 void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt)
666 {
667 on_material_hovered(-1);
668 }
reload_presets()669 void PageMaterials::reload_presets()
670 {
671 clear();
672
673 list_printer->append(_L("(All)"), &EMPTY);
674 //list_printer->SetLabelMarkup("<b>bald</b>");
675 for (const Preset* printer : materials->printers) {
676 list_printer->append(printer->name, &printer->name);
677 }
678 sort_list_data(list_printer, true, false);
679 if (list_printer->GetCount() > 0) {
680 list_printer->SetSelection(0);
681 sel_printer_count_prev = wxNOT_FOUND;
682 sel_printer_item_prev = wxNOT_FOUND;
683 sel_type_prev = wxNOT_FOUND;
684 sel_vendor_prev = wxNOT_FOUND;
685 update_lists(0, 0, 0);
686 }
687
688 presets_loaded = true;
689 }
690
set_compatible_printers_html_window(const std::vector<std::string> & printer_names,bool all_printers)691 void PageMaterials::set_compatible_printers_html_window(const std::vector<std::string>& printer_names, bool all_printers)
692 {
693 const auto bgr_clr =
694 #if defined(__APPLE__)
695 wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
696 #else
697 wxSystemSettings::GetColour(wxSYS_COLOUR_MENU);
698 #endif
699 const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
700 const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
701 const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
702 wxString first_line = _L("Filaments marked with <b>*</b> are <b>not</b> compatible with some installed printers.");
703 wxString text;
704 if (all_printers) {
705 wxString second_line = _L("All installed printers are compatible with the selected filament.");
706 text = wxString::Format(
707 "<html>"
708 "<style>"
709 "table{border-spacing: 1px;}"
710 "</style>"
711 "<body bgcolor= %s>"
712 "<font color=%s>"
713 "<font size=\"3\">"
714 "%s<br /><br />%s"
715 "</font>"
716 "</font>"
717 "</body>"
718 "</html>"
719 , bgr_clr_str
720 , text_clr_str
721 , first_line
722 , second_line
723 );
724 } else {
725 wxString second_line = _L("Only the following installed printers are compatible with the selected filament:");
726 text = wxString::Format(
727 "<html>"
728 "<style>"
729 "table{border-spacing: 1px;}"
730 "</style>"
731 "<body bgcolor= %s>"
732 "<font color=%s>"
733 "<font size=\"3\">"
734 "%s<br /><br />%s"
735 "<table>"
736 "<tr>"
737 , bgr_clr_str
738 , text_clr_str
739 , first_line
740 , second_line);
741 for (int i = 0; i < printer_names.size(); ++i)
742 {
743 text += wxString::Format("<td>%s</td>", boost::nowide::widen(printer_names[i]));
744 if (i % 3 == 2) {
745 text += wxString::Format(
746 "</tr>"
747 "<tr>");
748 }
749 }
750 text += wxString::Format(
751 "</tr>"
752 "</table>"
753 "</font>"
754 "</font>"
755 "</body>"
756 "</html>"
757 );
758 }
759
760 wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this));
761 const int fs = font.GetPointSize();
762 int size[] = { fs,fs,fs,fs,fs,fs,fs };
763 html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
764 html_window->SetPage(text);
765 }
766
clear_compatible_printers_label()767 void PageMaterials::clear_compatible_printers_label()
768 {
769 set_compatible_printers_html_window(std::vector<std::string>(), false);
770 }
771
on_material_hovered(int sel_material)772 void PageMaterials::on_material_hovered(int sel_material)
773 {
774
775 }
776
on_material_highlighted(int sel_material)777 void PageMaterials::on_material_highlighted(int sel_material)
778 {
779 if (sel_material == last_hovered_item)
780 return;
781 if (sel_material == -1) {
782 clear_compatible_printers_label();
783 return;
784 }
785 last_hovered_item = sel_material;
786 std::vector<std::string> tabs;
787 tabs.push_back(std::string());
788 tabs.push_back(std::string());
789 tabs.push_back(std::string());
790 //selected material string
791 std::string material_name = list_profile->get_data(sel_material);
792 // get material preset
793 const std::vector<const Preset*> matching_materials = materials->get_presets_by_alias(material_name);
794 if (matching_materials.empty())
795 {
796 clear_compatible_printers_label();
797 return;
798 }
799 //find matching printers
800 std::vector<std::string> names;
801 for (const Preset* printer : materials->printers) {
802 for (const Preset* material : matching_materials) {
803 if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) {
804 names.push_back(printer->name);
805 break;
806 }
807 }
808 }
809 set_compatible_printers_html_window(names, names.size() == materials->printers.size());
810 }
811
update_lists(int sel_printer,int sel_type,int sel_vendor)812 void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor)
813 {
814 wxWindowUpdateLocker freeze_guard(this);
815 (void)freeze_guard;
816
817 wxArrayInt sel_printers;
818 int sel_printers_count = list_printer->GetSelections(sel_printers);
819
820 if (sel_printers_count != sel_printer_count_prev || (sel_printers_count == 1 && sel_printer_item_prev != sel_printer && sel_printer != -1)) {
821 // Refresh type list
822 list_type->Clear();
823 list_type->append(_L("(All)"), &EMPTY);
824 if (sel_printers_count > 0) {
825 // If all is selected with other printers
826 // unselect "all" or all printers depending on last value
827 if (sel_printers[0] == 0 && sel_printers_count > 1) {
828 if (sel_printer == 0) {
829 list_printer->SetSelection(wxNOT_FOUND);
830 list_printer->SetSelection(0);
831 } else {
832 list_printer->SetSelection(0, false);
833 sel_printers_count = list_printer->GetSelections(sel_printers);
834 }
835 }
836 if (sel_printers[0] != 0) {
837 for (size_t i = 0; i < sel_printers_count; i++) {
838 const std::string& printer_name = list_printer->get_data(sel_printers[i]);
839 const Preset* printer = nullptr;
840 for (const Preset* it : materials->printers) {
841 if (it->name == printer_name) {
842 printer = it;
843 break;
844 }
845 }
846 materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) {
847 const std::string& type = this->materials->get_type(p);
848 if (list_type->find(type) == wxNOT_FOUND) {
849 list_type->append(type, &type);
850 }
851 });
852 }
853 } else {
854 //clear selection except "ALL"
855 list_printer->SetSelection(wxNOT_FOUND);
856 list_printer->SetSelection(0);
857 sel_printers_count = list_printer->GetSelections(sel_printers);
858
859 materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) {
860 const std::string& type = this->materials->get_type(p);
861 if (list_type->find(type) == wxNOT_FOUND) {
862 list_type->append(type, &type);
863 }
864 });
865 }
866 sort_list_data(list_type, true, true);
867 }
868
869 sel_printer_count_prev = sel_printers_count;
870 sel_printer_item_prev = sel_printer;
871 sel_type = 0;
872 sel_type_prev = wxNOT_FOUND;
873 list_type->SetSelection(sel_type);
874 list_profile->Clear();
875 }
876
877 if (sel_type != sel_type_prev) {
878 // Refresh vendor list
879
880 // XXX: The vendor list is created with quadratic complexity here,
881 // but the number of vendors is going to be very small this shouldn't be a problem.
882
883 list_vendor->Clear();
884 list_vendor->append(_L("(All)"), &EMPTY);
885 if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) {
886 const std::string& type = list_type->get_data(sel_type);
887 // find printer preset
888 for (size_t i = 0; i < sel_printers_count; i++) {
889 const std::string& printer_name = list_printer->get_data(sel_printers[i]);
890 const Preset* printer = nullptr;
891 for (const Preset* it : materials->printers) {
892 if (it->name == printer_name) {
893 printer = it;
894 break;
895 }
896 }
897 materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) {
898 const std::string& vendor = this->materials->get_vendor(p);
899 if (list_vendor->find(vendor) == wxNOT_FOUND) {
900 list_vendor->append(vendor, &vendor);
901 }
902 });
903 }
904 sort_list_data(list_vendor, true, false);
905 }
906
907 sel_type_prev = sel_type;
908 sel_vendor = 0;
909 sel_vendor_prev = wxNOT_FOUND;
910 list_vendor->SetSelection(sel_vendor);
911 list_profile->Clear();
912 }
913
914 if (sel_vendor != sel_vendor_prev) {
915 // Refresh material list
916
917 list_profile->Clear();
918 clear_compatible_printers_label();
919 if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) {
920 const std::string& type = list_type->get_data(sel_type);
921 const std::string& vendor = list_vendor->get_data(sel_vendor);
922 // finst printer preset
923 std::vector<ProfilePrintData> to_list;
924 for (size_t i = 0; i < sel_printers_count; i++) {
925 const std::string& printer_name = list_printer->get_data(sel_printers[i]);
926 const Preset* printer = nullptr;
927 for (const Preset* it : materials->printers) {
928 if (it->name == printer_name) {
929 printer = it;
930 break;
931 }
932 }
933
934 materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) {
935 bool was_checked = false;
936 //size_t printer_counter = materials->get_printer_counter(p);
937 int cur_i = list_profile->find(p->alias);
938 bool emplace_to_to_list = false;
939 if (cur_i == wxNOT_FOUND) {
940 cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
941 emplace_to_to_list = true;
942 } else
943 was_checked = list_profile->IsChecked(cur_i);
944
945 const std::string& section = materials->appconfig_section();
946
947 const bool checked = wizard_p()->appconfig_new.has(section, p->name);
948 list_profile->Check(cur_i, checked || was_checked);
949 if (emplace_to_to_list)
950 to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked || was_checked);
951
952 /* Update preset selection in config.
953 * If one preset from aliases bundle is selected,
954 * than mark all presets with this aliases as selected
955 * */
956 if (checked && !was_checked)
957 wizard_p()->update_presets_in_config(section, p->alias, true);
958 else if (!checked && was_checked)
959 wizard_p()->appconfig_new.set(section, p->name, "1");
960 });
961 }
962 sort_list_data(list_profile, to_list);
963 }
964
965 sel_vendor_prev = sel_vendor;
966 }
967 }
968
sort_list_data(StringList * list,bool add_All_item,bool material_type_ordering)969 void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering)
970 {
971 // get data from list
972 // sort data
973 // first should be <all>
974 // then prusa profiles
975 // then the rest
976 // in alphabetical order
977
978 std::vector<std::reference_wrapper<const std::string>> prusa_profiles;
979 std::vector<std::reference_wrapper<const std::string>> other_profiles;
980 for (int i = 0 ; i < list->size(); ++i) {
981 const std::string& data = list->get_data(i);
982 if (data == EMPTY) // do not sort <all> item
983 continue;
984 if (!material_type_ordering && data.find("Prusa") != std::string::npos)
985 prusa_profiles.push_back(data);
986 else
987 other_profiles.push_back(data);
988 }
989 if(material_type_ordering) {
990
991 const ConfigOptionDef* def = print_config_def.get("filament_type");
992 std::vector<std::string>enum_values = def->enum_values;
993 int end_of_sorted = 0;
994 for (size_t vals = 0; vals < enum_values.size(); vals++) {
995 for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++)
996 {
997 // find instead compare because PET vs PETG
998 if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) {
999 //swap
1000 if(profs != end_of_sorted) {
1001 std::reference_wrapper<const std::string> aux = other_profiles[end_of_sorted];
1002 other_profiles[end_of_sorted] = other_profiles[profs];
1003 other_profiles[profs] = aux;
1004 }
1005 end_of_sorted++;
1006 break;
1007 }
1008 }
1009 }
1010 } else {
1011 std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper<const std::string> a, std::reference_wrapper<const std::string> b) {
1012 return a.get() < b.get();
1013 });
1014 std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper<const std::string> a, std::reference_wrapper<const std::string> b) {
1015 return a.get() < b.get();
1016 });
1017 }
1018
1019 list->Clear();
1020 if (add_All_item)
1021 list->append(_L("(All)"), &EMPTY);
1022 for (const auto& item : prusa_profiles)
1023 list->append(item, &const_cast<std::string&>(item.get()));
1024 for (const auto& item : other_profiles)
1025 list->append(item, &const_cast<std::string&>(item.get()));
1026 }
1027
sort_list_data(PresetList * list,const std::vector<ProfilePrintData> & data)1028 void PageMaterials::sort_list_data(PresetList* list, const std::vector<ProfilePrintData>& data)
1029 {
1030 // sort data
1031 // then prusa profiles
1032 // then the rest
1033 // in alphabetical order
1034 std::vector<ProfilePrintData> prusa_profiles;
1035 std::vector<ProfilePrintData> other_profiles;
1036 //for (int i = 0; i < data.size(); ++i) {
1037 for (const auto& item : data) {
1038 const std::string& name = item.name;
1039 if (name.find("Prusa") != std::string::npos)
1040 prusa_profiles.emplace_back(item);
1041 else
1042 other_profiles.emplace_back(item);
1043 }
1044 std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
1045 return a.name.get() < b.name.get();
1046 });
1047 std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
1048 return a.name.get() < b.name.get();
1049 });
1050 list->Clear();
1051 //for (const auto& item : prusa_profiles)
1052 for (int i = 0; i < prusa_profiles.size(); ++i) {
1053 list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast<std::string&>(prusa_profiles[i].name.get()));
1054 list->Check(i, prusa_profiles[i].checked);
1055 }
1056 //for (const auto& item : other_profiles)
1057 for (int i = 0; i < other_profiles.size(); ++i) {
1058 list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast<std::string&>(other_profiles[i].name.get()));
1059 list->Check(i + prusa_profiles.size(), other_profiles[i].checked);
1060 }
1061 }
1062
select_material(int i)1063 void PageMaterials::select_material(int i)
1064 {
1065 const bool checked = list_profile->IsChecked(i);
1066
1067 const std::string& alias_key = list_profile->get_data(i);
1068 wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked);
1069 }
1070
select_all(bool select)1071 void PageMaterials::select_all(bool select)
1072 {
1073 wxWindowUpdateLocker freeze_guard(this);
1074 (void)freeze_guard;
1075
1076 for (unsigned i = 0; i < list_profile->GetCount(); i++) {
1077 const bool current = list_profile->IsChecked(i);
1078 if (current != select) {
1079 list_profile->Check(i, select);
1080 select_material(i);
1081 }
1082 }
1083 }
1084
clear()1085 void PageMaterials::clear()
1086 {
1087 list_printer->Clear();
1088 list_type->Clear();
1089 list_vendor->Clear();
1090 list_profile->Clear();
1091 sel_printer_count_prev = wxNOT_FOUND;
1092 sel_printer_item_prev = wxNOT_FOUND;
1093 sel_type_prev = wxNOT_FOUND;
1094 sel_vendor_prev = wxNOT_FOUND;
1095 presets_loaded = false;
1096 }
1097
on_activate()1098 void PageMaterials::on_activate()
1099 {
1100 if (! presets_loaded) {
1101 wizard_p()->update_materials(materials->technology);
1102 reload_presets();
1103 }
1104 first_paint = true;
1105 }
1106
1107
1108 const char *PageCustom::default_profile_name = "My Settings";
1109
PageCustom(ConfigWizard * parent)1110 PageCustom::PageCustom(ConfigWizard *parent)
1111 : ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer"))
1112 {
1113 cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile"));
1114 tc_profile_name = new wxTextCtrl(this, wxID_ANY, default_profile_name);
1115 auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:"));
1116
1117 tc_profile_name->Enable(false);
1118 tc_profile_name->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent &evt) {
1119 if (tc_profile_name->GetValue().IsEmpty()) {
1120 if (profile_name_prev.IsEmpty()) { tc_profile_name->SetValue(default_profile_name); }
1121 else { tc_profile_name->SetValue(profile_name_prev); }
1122 } else {
1123 profile_name_prev = tc_profile_name->GetValue();
1124 }
1125 evt.Skip();
1126 });
1127
1128 cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) {
1129 tc_profile_name->Enable(custom_wanted());
1130 wizard_p()->on_custom_setup(custom_wanted());
1131
1132 });
1133
1134 append(cb_custom);
1135 append(label);
1136 append(tc_profile_name);
1137 }
1138
PageUpdate(ConfigWizard * parent)1139 PageUpdate::PageUpdate(ConfigWizard *parent)
1140 : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates"))
1141 , version_check(true)
1142 , preset_update(true)
1143 {
1144 const AppConfig *app_config = wxGetApp().app_config;
1145 auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
1146 boldfont.SetWeight(wxFONTWEIGHT_BOLD);
1147
1148 auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates"));
1149 box_slic3r->SetValue(app_config->get("version_check") == "1");
1150 append(box_slic3r);
1151 append_text(wxString::Format(_L(
1152 "If enabled, %s checks for new application versions online. When a new version becomes available, "
1153 "a notification is displayed at the next application startup (never during program usage). "
1154 "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME));
1155
1156 append_spacer(VERTICAL_SPACING);
1157
1158 auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically"));
1159 box_presets->SetValue(app_config->get("preset_update") == "1");
1160 append(box_presets);
1161 append_text(wxString::Format(_L(
1162 "If enabled, %s downloads updates of built-in system presets in the background."
1163 "These updates are downloaded into a separate temporary location."
1164 "When a new preset version becomes available it is offered at application startup."), SLIC3R_APP_NAME));
1165 const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings.");
1166 auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold);
1167 label_bold->SetFont(boldfont);
1168 label_bold->Wrap(WRAP_WIDTH);
1169 append(label_bold);
1170 append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied."));
1171
1172 box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); });
1173 box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
1174 }
1175
PageReloadFromDisk(ConfigWizard * parent)1176 PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent)
1177 : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk"))
1178 , full_pathnames(false)
1179 {
1180 auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files"));
1181 box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1");
1182 append(box_pathnames);
1183 append_text(_L(
1184 "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n"
1185 "If not enabled, the Reload from disk command will ask to select each file using an open file dialog."
1186 ));
1187
1188 box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); });
1189 }
1190
1191 #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
1192 #ifdef _WIN32
PageFilesAssociation(ConfigWizard * parent)1193 PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent)
1194 : ConfigWizardPage(parent, _L("Files association"), _L("Files association"))
1195 {
1196 cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer"));
1197 cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer"));
1198 // cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer"));
1199
1200 append(cb_3mf);
1201 append(cb_stl);
1202 // append(cb_gcode);
1203 }
1204 #endif // _WIN32
1205 #endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
1206
PageMode(ConfigWizard * parent)1207 PageMode::PageMode(ConfigWizard *parent)
1208 : ConfigWizardPage(parent, _L("View mode"), _L("View mode"))
1209 {
1210 append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n"
1211 "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. "
1212 "The other two offer progressively more sophisticated fine-tuning, "
1213 "they are suitable for advanced and expert users, respectively."));
1214
1215 radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode"));
1216 radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode"));
1217 radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode"));
1218
1219 append(radio_simple);
1220 append(radio_advanced);
1221 append(radio_expert);
1222
1223 append_text("\n" + _L("The size of the object can be specified in inches"));
1224 check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches"));
1225 append(check_inch);
1226 }
1227
on_activate()1228 void PageMode::on_activate()
1229 {
1230 std::string mode { "simple" };
1231 wxGetApp().app_config->get("", "view_mode", mode);
1232
1233 if (mode == "advanced") { radio_advanced->SetValue(true); }
1234 else if (mode == "expert") { radio_expert->SetValue(true); }
1235 else { radio_simple->SetValue(true); }
1236
1237 check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1");
1238 }
1239
serialize_mode(AppConfig * app_config) const1240 void PageMode::serialize_mode(AppConfig *app_config) const
1241 {
1242 std::string mode = "";
1243
1244 if (radio_simple->GetValue()) { mode = "simple"; }
1245 if (radio_advanced->GetValue()) { mode = "advanced"; }
1246 if (radio_expert->GetValue()) { mode = "expert"; }
1247
1248 // If "Mode" page wasn't selected (no one radiobutton is checked),
1249 // we shouldn't to update a view_mode value in app_config
1250 if (mode.empty())
1251 return;
1252
1253 app_config->set("view_mode", mode);
1254 app_config->set("use_inches", check_inch->GetValue() ? "1" : "0");
1255 }
1256
PageVendors(ConfigWizard * parent)1257 PageVendors::PageVendors(ConfigWizard *parent)
1258 : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors"))
1259 {
1260 const AppConfig &appconfig = this->wizard_p()->appconfig_new;
1261
1262 append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":");
1263
1264 auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
1265 boldfont.SetWeight(wxFONTWEIGHT_BOLD);
1266
1267 for (const auto &pair : wizard_p()->bundles) {
1268 const VendorProfile *vendor = pair.second.vendor_profile;
1269 if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
1270
1271 auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name);
1272 cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) {
1273 wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked());
1274 });
1275
1276 const auto &vendors = appconfig.vendors();
1277 const bool enabled = vendors.find(pair.first) != vendors.end();
1278 if (enabled) {
1279 cbox->SetValue(true);
1280
1281 auto pages = wizard_p()->pages_3rdparty.find(vendor->id);
1282 wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created");
1283
1284 for (PagePrinters* page : { pages->second.first, pages->second.second })
1285 if (page) page->install = true;
1286 }
1287
1288 append(cbox);
1289 }
1290 }
1291
PageFirmware(ConfigWizard * parent)1292 PageFirmware::PageFirmware(ConfigWizard *parent)
1293 : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1)
1294 , gcode_opt(*print_config_def.get("gcode_flavor"))
1295 , gcode_picker(nullptr)
1296 {
1297 append_text(_L("Choose the type of firmware used by your printer."));
1298 append_text(_(gcode_opt.tooltip));
1299
1300 wxArrayString choices;
1301 choices.Alloc(gcode_opt.enum_labels.size());
1302 for (const auto &label : gcode_opt.enum_labels) {
1303 choices.Add(label);
1304 }
1305
1306 gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
1307 const auto &enum_values = gcode_opt.enum_values;
1308 auto needle = enum_values.cend();
1309 if (gcode_opt.default_value) {
1310 needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize());
1311 }
1312 if (needle != enum_values.cend()) {
1313 gcode_picker->SetSelection(needle - enum_values.cbegin());
1314 } else {
1315 gcode_picker->SetSelection(0);
1316 }
1317
1318 append(gcode_picker);
1319 }
1320
apply_custom_config(DynamicPrintConfig & config)1321 void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
1322 {
1323 auto sel = gcode_picker->GetSelection();
1324 if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) {
1325 auto *opt = new ConfigOptionEnum<GCodeFlavor>(static_cast<GCodeFlavor>(sel));
1326 config.set_key_value("gcode_flavor", opt);
1327 }
1328 }
1329
PageBedShape(ConfigWizard * parent)1330 PageBedShape::PageBedShape(ConfigWizard *parent)
1331 : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1)
1332 , shape_panel(new BedShapePanel(this))
1333 {
1334 append_text(_L("Set the shape of your printer's bed."));
1335
1336 shape_panel->build_panel(*wizard_p()->custom_config->option<ConfigOptionPoints>("bed_shape"),
1337 *wizard_p()->custom_config->option<ConfigOptionString>("bed_custom_texture"),
1338 *wizard_p()->custom_config->option<ConfigOptionString>("bed_custom_model"));
1339
1340 append(shape_panel);
1341 }
1342
apply_custom_config(DynamicPrintConfig & config)1343 void PageBedShape::apply_custom_config(DynamicPrintConfig &config)
1344 {
1345 const std::vector<Vec2d>& points = shape_panel->get_shape();
1346 const std::string& custom_texture = shape_panel->get_custom_texture();
1347 const std::string& custom_model = shape_panel->get_custom_model();
1348 config.set_key_value("bed_shape", new ConfigOptionPoints(points));
1349 config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture));
1350 config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model));
1351 }
1352
PageDiameters(ConfigWizard * parent)1353 PageDiameters::PageDiameters(ConfigWizard *parent)
1354 : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1)
1355 , spin_nozzle(new wxSpinCtrlDouble(this, wxID_ANY))
1356 , spin_filam(new wxSpinCtrlDouble(this, wxID_ANY))
1357 {
1358 spin_nozzle->SetDigits(2);
1359 spin_nozzle->SetIncrement(0.1);
1360 auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value<ConfigOptionFloats>();
1361 spin_nozzle->SetValue(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5);
1362
1363 spin_filam->SetDigits(2);
1364 spin_filam->SetIncrement(0.25);
1365 auto *default_filam = print_config_def.get("filament_diameter")->get_default_value<ConfigOptionFloats>();
1366 spin_filam->SetValue(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0);
1367
1368 append_text(_L("Enter the diameter of your printer's hot end nozzle."));
1369
1370 auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5);
1371 auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:"));
1372 auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm"));
1373 sizer_nozzle->AddGrowableCol(0, 1);
1374 sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
1375 sizer_nozzle->Add(spin_nozzle);
1376 sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
1377 append(sizer_nozzle);
1378
1379 append_spacer(VERTICAL_SPACING);
1380
1381 append_text(_L("Enter the diameter of your filament."));
1382 append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average."));
1383
1384 auto *sizer_filam = new wxFlexGridSizer(3, 5, 5);
1385 auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:"));
1386 auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm"));
1387 sizer_filam->AddGrowableCol(0, 1);
1388 sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL);
1389 sizer_filam->Add(spin_filam);
1390 sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL);
1391 append(sizer_filam);
1392 }
1393
apply_custom_config(DynamicPrintConfig & config)1394 void PageDiameters::apply_custom_config(DynamicPrintConfig &config)
1395 {
1396 auto *opt_nozzle = new ConfigOptionFloats(1, spin_nozzle->GetValue());
1397 config.set_key_value("nozzle_diameter", opt_nozzle);
1398 auto *opt_filam = new ConfigOptionFloats(1, spin_filam->GetValue());
1399 config.set_key_value("filament_diameter", opt_filam);
1400
1401 auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) {
1402 char buf[64];
1403 sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4);
1404 config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false));
1405 };
1406
1407 set_extrusion_width("support_material_extrusion_width", 0.35);
1408 set_extrusion_width("top_infill_extrusion_width", 0.40);
1409 set_extrusion_width("first_layer_extrusion_width", 0.42);
1410
1411 set_extrusion_width("extrusion_width", 0.45);
1412 set_extrusion_width("perimeter_extrusion_width", 0.45);
1413 set_extrusion_width("external_perimeter_extrusion_width", 0.45);
1414 set_extrusion_width("infill_extrusion_width", 0.45);
1415 set_extrusion_width("solid_infill_extrusion_width", 0.45);
1416 }
1417
PageTemperatures(ConfigWizard * parent)1418 PageTemperatures::PageTemperatures(ConfigWizard *parent)
1419 : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1)
1420 , spin_extr(new wxSpinCtrlDouble(this, wxID_ANY))
1421 , spin_bed(new wxSpinCtrlDouble(this, wxID_ANY))
1422 {
1423 spin_extr->SetIncrement(5.0);
1424 const auto &def_extr = *print_config_def.get("temperature");
1425 spin_extr->SetRange(def_extr.min, def_extr.max);
1426 auto *default_extr = def_extr.get_default_value<ConfigOptionInts>();
1427 spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200);
1428
1429 spin_bed->SetIncrement(5.0);
1430 const auto &def_bed = *print_config_def.get("bed_temperature");
1431 spin_bed->SetRange(def_bed.min, def_bed.max);
1432 auto *default_bed = def_bed.get_default_value<ConfigOptionInts>();
1433 spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0);
1434
1435 append_text(_L("Enter the temperature needed for extruding your filament."));
1436 append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS."));
1437
1438 auto *sizer_extr = new wxFlexGridSizer(3, 5, 5);
1439 auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:"));
1440 auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C"));
1441 sizer_extr->AddGrowableCol(0, 1);
1442 sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL);
1443 sizer_extr->Add(spin_extr);
1444 sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL);
1445 append(sizer_extr);
1446
1447 append_spacer(VERTICAL_SPACING);
1448
1449 append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed."));
1450 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."));
1451
1452 auto *sizer_bed = new wxFlexGridSizer(3, 5, 5);
1453 auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:"));
1454 auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C"));
1455 sizer_bed->AddGrowableCol(0, 1);
1456 sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL);
1457 sizer_bed->Add(spin_bed);
1458 sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL);
1459 append(sizer_bed);
1460 }
1461
apply_custom_config(DynamicPrintConfig & config)1462 void PageTemperatures::apply_custom_config(DynamicPrintConfig &config)
1463 {
1464 auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue());
1465 config.set_key_value("temperature", opt_extr);
1466 auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue());
1467 config.set_key_value("first_layer_temperature", opt_extr1st);
1468 auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue());
1469 config.set_key_value("bed_temperature", opt_bed);
1470 auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue());
1471 config.set_key_value("first_layer_bed_temperature", opt_bed1st);
1472 }
1473
1474
1475 // Index
1476
ConfigWizardIndex(wxWindow * parent)1477 ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent)
1478 : wxPanel(parent)
1479 , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192))
1480 , bullet_black(ScalableBitmap(parent, "bullet_black.png"))
1481 , bullet_blue(ScalableBitmap(parent, "bullet_blue.png"))
1482 , bullet_white(ScalableBitmap(parent, "bullet_white.png"))
1483 , item_active(NO_ITEM)
1484 , item_hover(NO_ITEM)
1485 , last_page((size_t)-1)
1486 {
1487 SetMinSize(bg.bmp().GetSize());
1488
1489 const wxSize size = GetTextExtent("m");
1490 em_w = size.x;
1491 em_h = size.y;
1492
1493 // Add logo bitmap.
1494 // This could be done in on_paint() along with the index labels, but I've found it tricky
1495 // to get the bitmap rendered well on all platforms with transparent background.
1496 // In some cases it didn't work at all. And so wxStaticBitmap is used here instead,
1497 // because it has all the platform quirks figured out.
1498 auto *sizer = new wxBoxSizer(wxVERTICAL);
1499 logo = new wxStaticBitmap(this, wxID_ANY, bg.bmp());
1500 sizer->AddStretchSpacer();
1501 sizer->Add(logo);
1502 SetSizer(sizer);
1503
1504 Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this);
1505 Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this);
1506
1507 Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) {
1508 if (item_hover != -1) {
1509 item_hover = -1;
1510 Refresh();
1511 }
1512 evt.Skip();
1513 });
1514
1515 Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) {
1516 if (item_hover >= 0) { go_to(item_hover); }
1517 });
1518 }
1519
1520 wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
1521
add_page(ConfigWizardPage * page)1522 void ConfigWizardIndex::add_page(ConfigWizardPage *page)
1523 {
1524 last_page = items.size();
1525 items.emplace_back(Item { page->shortname, page->indent, page });
1526 Refresh();
1527 }
1528
add_label(wxString label,unsigned indent)1529 void ConfigWizardIndex::add_label(wxString label, unsigned indent)
1530 {
1531 items.emplace_back(Item { std::move(label), indent, nullptr });
1532 Refresh();
1533 }
1534
active_page() const1535 ConfigWizardPage* ConfigWizardIndex::active_page() const
1536 {
1537 if (item_active >= items.size()) { return nullptr; }
1538
1539 return items[item_active].page;
1540 }
1541
go_prev()1542 void ConfigWizardIndex::go_prev()
1543 {
1544 // Search for a preceiding item that is a page (not a label, ie. page != nullptr)
1545
1546 if (item_active == NO_ITEM) { return; }
1547
1548 for (size_t i = item_active; i > 0; i--) {
1549 if (items[i - 1].page != nullptr) {
1550 go_to(i - 1);
1551 return;
1552 }
1553 }
1554 }
1555
go_next()1556 void ConfigWizardIndex::go_next()
1557 {
1558 // Search for a next item that is a page (not a label, ie. page != nullptr)
1559
1560 if (item_active == NO_ITEM) { return; }
1561
1562 for (size_t i = item_active + 1; i < items.size(); i++) {
1563 if (items[i].page != nullptr) {
1564 go_to(i);
1565 return;
1566 }
1567 }
1568 }
1569
1570 // This one actually performs the go-to op
go_to(size_t i)1571 void ConfigWizardIndex::go_to(size_t i)
1572 {
1573 if (i != item_active
1574 && i < items.size()
1575 && items[i].page != nullptr) {
1576 auto *new_active = items[i].page;
1577 auto *former_active = active_page();
1578 if (former_active != nullptr) {
1579 former_active->Hide();
1580 }
1581
1582 item_active = i;
1583 new_active->Show();
1584
1585 wxCommandEvent evt(EVT_INDEX_PAGE, GetId());
1586 AddPendingEvent(evt);
1587
1588 Refresh();
1589
1590 new_active->on_activate();
1591 }
1592 }
1593
go_to(const ConfigWizardPage * page)1594 void ConfigWizardIndex::go_to(const ConfigWizardPage *page)
1595 {
1596 if (page == nullptr) { return; }
1597
1598 for (size_t i = 0; i < items.size(); i++) {
1599 if (items[i].page == page) {
1600 go_to(i);
1601 return;
1602 }
1603 }
1604 }
1605
clear()1606 void ConfigWizardIndex::clear()
1607 {
1608 auto *former_active = active_page();
1609 if (former_active != nullptr) { former_active->Hide(); }
1610
1611 items.clear();
1612 item_active = NO_ITEM;
1613 }
1614
on_paint(wxPaintEvent & evt)1615 void ConfigWizardIndex::on_paint(wxPaintEvent & evt)
1616 {
1617 const auto size = GetClientSize();
1618 if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; }
1619
1620 wxPaintDC dc(this);
1621
1622 const auto bullet_w = bullet_black.bmp().GetSize().GetWidth();
1623 const auto bullet_h = bullet_black.bmp().GetSize().GetHeight();
1624 const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0;
1625 const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0;
1626 const int yinc = item_height();
1627
1628 int index_width = 0;
1629
1630 unsigned y = 0;
1631 for (size_t i = 0; i < items.size(); i++) {
1632 const Item& item = items[i];
1633 unsigned x = em_w/2 + item.indent * em_w;
1634
1635 if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) {
1636 dc.DrawBitmap(bullet_blue.bmp(), x, y + yoff_icon, false);
1637 }
1638 else if (i < item_active) { dc.DrawBitmap(bullet_black.bmp(), x, y + yoff_icon, false); }
1639 else if (i > item_active) { dc.DrawBitmap(bullet_white.bmp(), x, y + yoff_icon, false); }
1640
1641 x += + bullet_w + em_w/2;
1642 const auto text_size = dc.GetTextExtent(item.label);
1643 dc.DrawText(item.label, x, y + yoff_text);
1644
1645 y += yinc;
1646 index_width = std::max(index_width, (int)x + text_size.x);
1647 }
1648
1649 if (GetMinSize().x < index_width) {
1650 CallAfter([this, index_width]() {
1651 SetMinSize(wxSize(index_width, GetMinSize().y));
1652 Refresh();
1653 });
1654 }
1655 }
1656
on_mouse_move(wxMouseEvent & evt)1657 void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt)
1658 {
1659 const wxClientDC dc(this);
1660 const wxPoint pos = evt.GetLogicalPosition(dc);
1661
1662 const ssize_t item_hover_new = pos.y / item_height();
1663
1664 if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) {
1665 item_hover = item_hover_new;
1666 Refresh();
1667 }
1668
1669 evt.Skip();
1670 }
1671
msw_rescale()1672 void ConfigWizardIndex::msw_rescale()
1673 {
1674 const wxSize size = GetTextExtent("m");
1675 em_w = size.x;
1676 em_h = size.y;
1677
1678 bg.msw_rescale();
1679 SetMinSize(bg.bmp().GetSize());
1680 logo->SetBitmap(bg.bmp());
1681
1682 bullet_black.msw_rescale();
1683 bullet_blue.msw_rescale();
1684 bullet_white.msw_rescale();
1685 Refresh();
1686 }
1687
1688
1689 // Materials
1690
1691 const std::string Materials::UNKNOWN = "(Unknown)";
1692
push(const Preset * preset)1693 void Materials::push(const Preset *preset)
1694 {
1695 presets.emplace_back(preset);
1696 types.insert(technology & T_FFF
1697 ? Materials::get_filament_type(preset)
1698 : Materials::get_material_type(preset));
1699 }
1700
add_printer(const Preset * preset)1701 void Materials::add_printer(const Preset* preset)
1702 {
1703 printers.insert(preset);
1704 }
1705
clear()1706 void Materials::clear()
1707 {
1708 presets.clear();
1709 types.clear();
1710 printers.clear();
1711 compatibility_counter.clear();
1712 }
1713
appconfig_section() const1714 const std::string& Materials::appconfig_section() const
1715 {
1716 return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
1717 }
1718
get_type(const Preset * preset) const1719 const std::string& Materials::get_type(const Preset *preset) const
1720 {
1721 return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset);
1722 }
1723
get_vendor(const Preset * preset) const1724 const std::string& Materials::get_vendor(const Preset *preset) const
1725 {
1726 return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset);
1727 }
1728
get_filament_type(const Preset * preset)1729 const std::string& Materials::get_filament_type(const Preset *preset)
1730 {
1731 const auto *opt = preset->config.opt<ConfigOptionStrings>("filament_type");
1732 if (opt != nullptr && opt->values.size() > 0) {
1733 return opt->values[0];
1734 } else {
1735 return UNKNOWN;
1736 }
1737 }
1738
get_filament_vendor(const Preset * preset)1739 const std::string& Materials::get_filament_vendor(const Preset *preset)
1740 {
1741 const auto *opt = preset->config.opt<ConfigOptionString>("filament_vendor");
1742 return opt != nullptr ? opt->value : UNKNOWN;
1743 }
1744
get_material_type(const Preset * preset)1745 const std::string& Materials::get_material_type(const Preset *preset)
1746 {
1747 const auto *opt = preset->config.opt<ConfigOptionString>("material_type");
1748 if (opt != nullptr) {
1749 return opt->value;
1750 } else {
1751 return UNKNOWN;
1752 }
1753 }
1754
get_material_vendor(const Preset * preset)1755 const std::string& Materials::get_material_vendor(const Preset *preset)
1756 {
1757 const auto *opt = preset->config.opt<ConfigOptionString>("material_vendor");
1758 return opt != nullptr ? opt->value : UNKNOWN;
1759 }
1760
1761 // priv
1762
1763 static const std::unordered_map<std::string, std::pair<std::string, std::string>> legacy_preset_map {{
1764 { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") },
1765 { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") },
1766 { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
1767 { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") },
1768 { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
1769 { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") },
1770 { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") },
1771 { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") },
1772 }};
1773
load_pages()1774 void ConfigWizard::priv::load_pages()
1775 {
1776 wxWindowUpdateLocker freeze_guard(q);
1777 (void)freeze_guard;
1778
1779 const ConfigWizardPage *former_active = index->active_page();
1780
1781 index->clear();
1782
1783 index->add_page(page_welcome);
1784
1785 // Printers
1786 index->add_page(page_fff);
1787 index->add_page(page_msla);
1788 index->add_page(page_vendors);
1789 for (const auto &pages : pages_3rdparty) {
1790 for ( PagePrinters* page : { pages.second.first, pages.second.second })
1791 if (page && page->install)
1792 index->add_page(page);
1793 }
1794
1795 index->add_page(page_custom);
1796 if (page_custom->custom_wanted()) {
1797 index->add_page(page_firmware);
1798 index->add_page(page_bed);
1799 index->add_page(page_diams);
1800 index->add_page(page_temps);
1801 }
1802
1803 // Filaments & Materials
1804 if (any_fff_selected) { index->add_page(page_filaments); }
1805 if (any_sla_selected) { index->add_page(page_sla_materials); }
1806
1807 // there should to be selected at least one printer
1808 btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected);
1809
1810 index->add_page(page_update);
1811 index->add_page(page_reload_from_disk);
1812 #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
1813 #ifdef _WIN32
1814 index->add_page(page_files_association);
1815 #endif // _WIN32
1816 #endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
1817 index->add_page(page_mode);
1818
1819 index->go_to(former_active); // Will restore the active item/page if possible
1820
1821 q->Layout();
1822 }
1823
init_dialog_size()1824 void ConfigWizard::priv::init_dialog_size()
1825 {
1826 // Clamp the Wizard size based on screen dimensions
1827
1828 const auto idx = wxDisplay::GetFromWindow(q);
1829 wxDisplay display(idx != wxNOT_FOUND ? idx : 0u);
1830
1831 const auto disp_rect = display.GetClientArea();
1832 wxRect window_rect(
1833 disp_rect.x + disp_rect.width / 20,
1834 disp_rect.y + disp_rect.height / 20,
1835 9*disp_rect.width / 10,
1836 9*disp_rect.height / 10);
1837
1838 const int width_hint = index->GetSize().GetWidth() + page_fff->get_width() + 30 * em(); // XXX: magic constant, I found no better solution
1839 if (width_hint < window_rect.width) {
1840 window_rect.x += (window_rect.width - width_hint) / 2;
1841 window_rect.width = width_hint;
1842 }
1843
1844 q->SetSize(window_rect);
1845 }
1846
load_vendors()1847 void ConfigWizard::priv::load_vendors()
1848 {
1849 bundles = BundleMap::load();
1850
1851 // Load up the set of vendors / models / variants the user has had enabled up till now
1852 AppConfig *app_config = wxGetApp().app_config;
1853 if (! app_config->legacy_datadir()) {
1854 appconfig_new.set_vendors(*app_config);
1855 } else {
1856 // In case of legacy datadir, try to guess the preference based on the printer preset files that are present
1857 const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer";
1858 for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir))
1859 if (Slic3r::is_ini_file(dir_entry)) {
1860 auto needle = legacy_preset_map.find(dir_entry.path().filename().string());
1861 if (needle == legacy_preset_map.end()) { continue; }
1862
1863 const auto &model = needle->second.first;
1864 const auto &variant = needle->second.second;
1865 appconfig_new.set_variant("PrusaResearch", model, variant, true);
1866 }
1867 }
1868
1869 // Initialize the is_visible flag in printer Presets
1870 for (auto &pair : bundles) {
1871 pair.second.preset_bundle->load_installed_printers(appconfig_new);
1872 }
1873
1874 // Copy installed filaments and SLA material names from app_config to appconfig_new
1875 // while resolving current names of profiles, which were renamed in the meantime.
1876 for (PrinterTechnology technology : { ptFFF, ptSLA }) {
1877 const std::string §ion_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
1878 std::map<std::string, std::string> section_new;
1879 if (app_config->has_section(section_name)) {
1880 const std::map<std::string, std::string> §ion_old = app_config->get_section(section_name);
1881 for (const std::pair<std::string, std::string> &material_name_and_installed : section_old)
1882 if (material_name_and_installed.second == "1") {
1883 // Material is installed. Resolve it in bundles.
1884 size_t num_found = 0;
1885 const std::string &material_name = material_name_and_installed.first;
1886 for (auto &bundle : bundles) {
1887 const PresetCollection &materials = bundle.second.preset_bundle->materials(technology);
1888 const Preset *preset = materials.find_preset(material_name);
1889 if (preset == nullptr) {
1890 // Not found. Maybe the material preset is there, bu it was was renamed?
1891 const std::string *new_name = materials.get_preset_name_renamed(material_name);
1892 if (new_name != nullptr)
1893 preset = materials.find_preset(*new_name);
1894 }
1895 if (preset != nullptr) {
1896 // Materal preset was found, mark it as installed.
1897 section_new[preset->name] = "1";
1898 ++ num_found;
1899 }
1900 }
1901 if (num_found == 0)
1902 BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name;
1903 else if (num_found > 1)
1904 BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found;
1905 }
1906 }
1907 appconfig_new.set_section(section_name, section_new);
1908 };
1909 }
1910
add_page(ConfigWizardPage * page)1911 void ConfigWizard::priv::add_page(ConfigWizardPage *page)
1912 {
1913 const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0;
1914 hscroll_sizer->Add(page, proportion, wxEXPAND);
1915 all_pages.push_back(page);
1916 }
1917
enable_next(bool enable)1918 void ConfigWizard::priv::enable_next(bool enable)
1919 {
1920 btn_next->Enable(enable);
1921 btn_finish->Enable(enable);
1922 }
1923
set_start_page(ConfigWizard::StartPage start_page)1924 void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page)
1925 {
1926 switch (start_page) {
1927 case ConfigWizard::SP_PRINTERS:
1928 index->go_to(page_fff);
1929 btn_next->SetFocus();
1930 break;
1931 case ConfigWizard::SP_FILAMENTS:
1932 index->go_to(page_filaments);
1933 btn_finish->SetFocus();
1934 break;
1935 case ConfigWizard::SP_MATERIALS:
1936 index->go_to(page_sla_materials);
1937 btn_finish->SetFocus();
1938 break;
1939 default:
1940 index->go_to(page_welcome);
1941 btn_next->SetFocus();
1942 break;
1943 }
1944 }
1945
create_3rdparty_pages()1946 void ConfigWizard::priv::create_3rdparty_pages()
1947 {
1948 for (const auto &pair : bundles) {
1949 const VendorProfile *vendor = pair.second.vendor_profile;
1950 if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
1951
1952 bool is_fff_technology = false;
1953 bool is_sla_technology = false;
1954
1955 for (auto& model: vendor->models)
1956 {
1957 if (!is_fff_technology && model.technology == ptFFF)
1958 is_fff_technology = true;
1959 if (!is_sla_technology && model.technology == ptSLA)
1960 is_sla_technology = true;
1961 }
1962
1963 PagePrinters* pageFFF = nullptr;
1964 PagePrinters* pageSLA = nullptr;
1965
1966 if (is_fff_technology) {
1967 pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF);
1968 add_page(pageFFF);
1969 }
1970
1971 if (is_sla_technology) {
1972 pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA);
1973 add_page(pageSLA);
1974 }
1975
1976 pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}});
1977 }
1978 }
1979
set_run_reason(RunReason run_reason)1980 void ConfigWizard::priv::set_run_reason(RunReason run_reason)
1981 {
1982 this->run_reason = run_reason;
1983 for (auto &page : all_pages) {
1984 page->set_run_reason(run_reason);
1985 }
1986 }
1987
update_materials(Technology technology)1988 void ConfigWizard::priv::update_materials(Technology technology)
1989 {
1990 if (any_fff_selected && (technology & T_FFF)) {
1991 filaments.clear();
1992 aliases_fff.clear();
1993 // Iterate filaments in all bundles
1994 for (const auto &pair : bundles) {
1995 for (const auto &filament : pair.second.preset_bundle->filaments) {
1996 // Check if filament is already added
1997 if (filaments.containts(&filament))
1998 continue;
1999 // Iterate printers in all bundles
2000 for (const auto &printer : pair.second.preset_bundle->printers) {
2001 if (!printer.is_visible || printer.printer_technology() != ptFFF)
2002 continue;
2003 // Filter out inapplicable printers
2004 if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) {
2005 if (!filaments.containts(&filament)) {
2006 filaments.push(&filament);
2007 if (!filament.alias.empty())
2008 aliases_fff[filament.alias].insert(filament.name);
2009 }
2010 filaments.add_printer(&printer);
2011 }
2012 }
2013
2014 }
2015 }
2016 // count compatible printers
2017 for (const auto& preset : filaments.presets) {
2018
2019 const auto filter = [preset](const std::pair<std::string, size_t> element) {
2020 return preset->alias == element.first;
2021 };
2022 if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) {
2023 continue;
2024 }
2025 std::vector<size_t> idx_with_same_alias;
2026 for (size_t i = 0; i < filaments.presets.size(); ++i) {
2027 if (preset->alias == filaments.presets[i]->alias)
2028 idx_with_same_alias.push_back(i);
2029 }
2030 size_t counter = 0;
2031 for (const auto& printer : filaments.printers) {
2032 if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF)
2033 continue;
2034 bool compatible = false;
2035 // Test otrher materials with same alias
2036 for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
2037 const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]);
2038 const Preset& prntr = *printer;
2039 if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
2040 compatible = true;
2041 break;
2042 }
2043 }
2044 if (compatible)
2045 counter++;
2046 }
2047 filaments.compatibility_counter.emplace_back(preset->alias, counter);
2048 }
2049 }
2050
2051 if (any_sla_selected && (technology & T_SLA)) {
2052 sla_materials.clear();
2053 aliases_sla.clear();
2054
2055 // Iterate SLA materials in all bundles
2056 for (const auto &pair : bundles) {
2057 for (const auto &material : pair.second.preset_bundle->sla_materials) {
2058 // Check if material is already added
2059 if (sla_materials.containts(&material))
2060 continue;
2061 // Iterate printers in all bundles
2062 // For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
2063 for (const auto& printer : pair.second.preset_bundle->printers) {
2064 if(!printer.is_visible || printer.printer_technology() != ptSLA)
2065 continue;
2066 // Filter out inapplicable printers
2067 if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
2068 // Check if material is already added
2069 if(!sla_materials.containts(&material)) {
2070 sla_materials.push(&material);
2071 if (!material.alias.empty())
2072 aliases_sla[material.alias].insert(material.name);
2073 }
2074 sla_materials.add_printer(&printer);
2075 }
2076 }
2077 }
2078 }
2079 // count compatible printers
2080 for (const auto& preset : sla_materials.presets) {
2081
2082 const auto filter = [preset](const std::pair<std::string, size_t> element) {
2083 return preset->alias == element.first;
2084 };
2085 if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) {
2086 continue;
2087 }
2088 std::vector<size_t> idx_with_same_alias;
2089 for (size_t i = 0; i < sla_materials.presets.size(); ++i) {
2090 if(preset->alias == sla_materials.presets[i]->alias)
2091 idx_with_same_alias.push_back(i);
2092 }
2093 size_t counter = 0;
2094 for (const auto& printer : sla_materials.printers) {
2095 if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA)
2096 continue;
2097 bool compatible = false;
2098 // Test otrher materials with same alias
2099 for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
2100 const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]);
2101 const Preset& prntr = *printer;
2102 if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
2103 compatible = true;
2104 break;
2105 }
2106 }
2107 if (compatible)
2108 counter++;
2109 }
2110 sla_materials.compatibility_counter.emplace_back(preset->alias, counter);
2111 }
2112 }
2113 }
2114
on_custom_setup(const bool custom_wanted)2115 void ConfigWizard::priv::on_custom_setup(const bool custom_wanted)
2116 {
2117 custom_printer_selected = custom_wanted;
2118 load_pages();
2119 }
2120
on_printer_pick(PagePrinters * page,const PrinterPickerEvent & evt)2121 void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt)
2122 {
2123 if (check_sla_selected() != any_sla_selected ||
2124 check_fff_selected() != any_fff_selected) {
2125 any_fff_selected = check_fff_selected();
2126 any_sla_selected = check_sla_selected();
2127
2128 load_pages();
2129 }
2130
2131 // Update the is_visible flag on relevant printer profiles
2132 for (auto &pair : bundles) {
2133 if (pair.first != evt.vendor_id) { continue; }
2134
2135 for (auto &preset : pair.second.preset_bundle->printers) {
2136 if (preset.config.opt_string("printer_model") == evt.model_id
2137 && preset.config.opt_string("printer_variant") == evt.variant_name) {
2138 preset.is_visible = evt.enable;
2139 }
2140 }
2141
2142 // When a printer model is picked, but there is no material installed compatible with this printer model,
2143 // install default materials for selected printer model silently.
2144 check_and_install_missing_materials(page->technology, evt.model_id);
2145 }
2146
2147 if (page->technology & T_FFF) {
2148 page_filaments->clear();
2149 } else if (page->technology & T_SLA) {
2150 page_sla_materials->clear();
2151 }
2152 }
2153
select_default_materials_for_printer_model(const VendorProfile::PrinterModel & printer_model,Technology technology)2154 void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology)
2155 {
2156 PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
2157 for (const std::string& material : printer_model.default_materials)
2158 appconfig_new.set(page_materials->materials->appconfig_section(), material, "1");
2159 }
2160
select_default_materials_for_printer_models(Technology technology,const std::set<const VendorProfile::PrinterModel * > & printer_models)2161 void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set<const VendorProfile::PrinterModel*> &printer_models)
2162 {
2163 PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
2164 const std::string &appconfig_section = page_materials->materials->appconfig_section();
2165
2166 auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models](PagePrinters *page_printers, Technology technology)
2167 {
2168 const std::string vendor_id = page_printers->get_vendor_id();
2169 for (auto& pair : bundles)
2170 if (pair.first == vendor_id)
2171 for (const VendorProfile::PrinterModel *printer_model : printer_models)
2172 for (const std::string &material : printer_model->default_materials)
2173 appconfig_new.set(appconfig_section, material, "1");
2174 };
2175
2176 PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla;
2177 select_default_materials_for_printer_page(page_printers, technology);
2178
2179 for (const auto& printer : pages_3rdparty)
2180 {
2181 page_printers = technology & T_FFF ? printer.second.first : printer.second.second;
2182 if (page_printers)
2183 select_default_materials_for_printer_page(page_printers, technology);
2184 }
2185
2186 update_materials(technology);
2187 ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets();
2188 }
2189
on_3rdparty_install(const VendorProfile * vendor,bool install)2190 void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install)
2191 {
2192 auto it = pages_3rdparty.find(vendor->id);
2193 wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile");
2194
2195 for (PagePrinters* page : { it->second.first, it->second.second })
2196 if (page) {
2197 if (page->install && !install)
2198 page->select_all(false);
2199 page->install = install;
2200 // if some 3rd vendor is selected, select first printer for them
2201 if (install)
2202 page->printer_pickers[0]->select_one(0, true);
2203 page->Layout();
2204 }
2205
2206 load_pages();
2207 }
2208
on_bnt_finish()2209 bool ConfigWizard::priv::on_bnt_finish()
2210 {
2211 /* When Filaments or Sla Materials pages are activated,
2212 * materials for this pages are automaticaly updated and presets are reloaded.
2213 *
2214 * But, if _Finish_ button was clicked without activation of those pages
2215 * (for example, just some printers were added/deleted),
2216 * than last changes wouldn't be updated for filaments/materials.
2217 * SO, do that before close of Wizard
2218 */
2219 update_materials(T_ANY);
2220 if (any_fff_selected)
2221 page_filaments->reload_presets();
2222 if (any_sla_selected)
2223 page_sla_materials->reload_presets();
2224
2225 // theres no need to check that filament is selected if we have only custom printer
2226 if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true;
2227 // check, that there is selected at least one filament/material
2228 return check_and_install_missing_materials(T_ANY);
2229 }
2230
2231 // This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed
2232 // for each Printer preset of each Printer Model installed.
2233 //
2234 // 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.
2235 // Otherwise the user is quieried whether to install the missing default materials or not.
2236 //
2237 // Return true if the tested Printer Models already had materials installed.
2238 // Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these
2239 // respective Printer Models or not.
check_and_install_missing_materials(Technology technology,const std::string & only_for_model_id)2240 bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id)
2241 {
2242 // Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle,
2243 // which is compatible with it.
2244 const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string §ion)
2245 {
2246 const std::map<std::string, std::string> &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map<std::string, std::string>();
2247 std::set<const VendorProfile::PrinterModel*> printer_models_without_material;
2248 for (const auto &pair : bundles) {
2249 const PresetCollection &materials = pair.second.preset_bundle->materials(technology);
2250 for (const auto &printer : pair.second.preset_bundle->printers) {
2251 if (printer.is_visible && printer.printer_technology() == technology) {
2252 const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer);
2253 assert(printer_model != nullptr);
2254 if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) &&
2255 printer_models_without_material.find(printer_model) == printer_models_without_material.end()) {
2256 bool has_material = false;
2257 for (const std::pair<std::string, std::string> &preset : appconfig_presets) {
2258 if (preset.second == "1") {
2259 const Preset *material = materials.find_preset(preset.first, false);
2260 if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
2261 has_material = true;
2262 break;
2263 }
2264 }
2265 }
2266 if (! has_material)
2267 printer_models_without_material.insert(printer_model);
2268 }
2269 }
2270 }
2271 }
2272 assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id);
2273 return printer_models_without_material;
2274 };
2275
2276 const auto ask_and_select_default_materials = [this](const wxString &message, const std::set<const VendorProfile::PrinterModel*> &printer_models, Technology technology)
2277 {
2278 wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO);
2279 if (msg.ShowModal() == wxID_YES)
2280 select_default_materials_for_printer_models(technology, printer_models);
2281 };
2282
2283 const auto printer_model_list = [](const std::set<const VendorProfile::PrinterModel*> &printer_models) -> wxString {
2284 wxString out;
2285 for (const VendorProfile::PrinterModel *printer_model : printer_models) {
2286 wxString name = from_u8(printer_model->name);
2287 out += "\t\t";
2288 out += name;
2289 out += "\n";
2290 }
2291 return out;
2292 };
2293
2294 if (any_fff_selected && (technology & T_FFF)) {
2295 std::set<const VendorProfile::PrinterModel*> printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS);
2296 if (! printer_models_without_material.empty()) {
2297 if (only_for_model_id.empty())
2298 ask_and_select_default_materials(
2299 _L("The following FFF printer models have no filament selected:") +
2300 "\n\n\t" +
2301 printer_model_list(printer_models_without_material) +
2302 "\n\n\t" +
2303 _L("Do you want to select default filaments for these FFF printer models?"),
2304 printer_models_without_material,
2305 T_FFF);
2306 else
2307 select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF);
2308 return false;
2309 }
2310 }
2311
2312 if (any_sla_selected && (technology & T_SLA)) {
2313 std::set<const VendorProfile::PrinterModel*> printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS);
2314 if (! printer_models_without_material.empty()) {
2315 if (only_for_model_id.empty())
2316 ask_and_select_default_materials(
2317 _L("The following SLA printer models have no materials selected:") +
2318 "\n\n\t" +
2319 printer_model_list(printer_models_without_material) +
2320 "\n\n\t" +
2321 _L("Do you want to select default SLA materials for these printer models?"),
2322 printer_models_without_material,
2323 T_SLA);
2324 else
2325 select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA);
2326 return false;
2327 }
2328 }
2329
2330 return true;
2331 }
2332
apply_config(AppConfig * app_config,PresetBundle * preset_bundle,const PresetUpdater * updater)2333 bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
2334 {
2335 const auto enabled_vendors = appconfig_new.vendors();
2336
2337 // Install bundles from resources if needed:
2338 std::vector<std::string> install_bundles;
2339 for (const auto &pair : bundles) {
2340 if (! pair.second.is_in_resources) { continue; }
2341
2342 if (pair.second.is_prusa_bundle) {
2343 // Always install Prusa bundle, because it has a lot of filaments/materials
2344 // likely to be referenced by other profiles.
2345 install_bundles.emplace_back(pair.first);
2346 continue;
2347 }
2348
2349 const auto vendor = enabled_vendors.find(pair.first);
2350 if (vendor == enabled_vendors.end()) { continue; }
2351
2352 size_t size_sum = 0;
2353 for (const auto &model : vendor->second) { size_sum += model.second.size(); }
2354
2355 if (size_sum > 0) {
2356 // This vendor needs to be installed
2357 install_bundles.emplace_back(pair.first);
2358 }
2359 }
2360
2361 // Decide whether to create snapshot based on run_reason and the reset profile checkbox
2362 bool snapshot = true;
2363 Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE;
2364 switch (run_reason) {
2365 case ConfigWizard::RR_DATA_EMPTY:
2366 snapshot = false;
2367 break;
2368 case ConfigWizard::RR_DATA_LEGACY:
2369 snapshot = true;
2370 break;
2371 case ConfigWizard::RR_DATA_INCOMPAT:
2372 // In this case snapshot has already been taken by
2373 // PresetUpdater with the appropriate reason
2374 snapshot = false;
2375 break;
2376 case ConfigWizard::RR_USER:
2377 snapshot = page_welcome->reset_user_profile();
2378 snapshot_reason = Snapshot::SNAPSHOT_USER;
2379 break;
2380 }
2381
2382 if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Continue with applying configuration changes?")))
2383 return false;
2384
2385 if (install_bundles.size() > 0) {
2386 // Install bundles from resources.
2387 // Don't create snapshot - we've already done that above if applicable.
2388 if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
2389 return false;
2390 } else {
2391 BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
2392 }
2393
2394 if (page_welcome->reset_user_profile()) {
2395 BOOST_LOG_TRIVIAL(info) << "Resetting user profiles...";
2396 preset_bundle->reset(true);
2397 }
2398
2399 app_config->set_vendors(appconfig_new);
2400 if (appconfig_new.has_section(AppConfig::SECTION_FILAMENTS)) {
2401 app_config->set_section(AppConfig::SECTION_FILAMENTS, appconfig_new.get_section(AppConfig::SECTION_FILAMENTS));
2402 }
2403 if (appconfig_new.has_section(AppConfig::SECTION_MATERIALS)) {
2404 app_config->set_section(AppConfig::SECTION_MATERIALS, appconfig_new.get_section(AppConfig::SECTION_MATERIALS));
2405 }
2406 app_config->set("version_check", page_update->version_check ? "1" : "0");
2407 app_config->set("preset_update", page_update->preset_update ? "1" : "0");
2408 app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
2409
2410 #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
2411 #ifdef _WIN32
2412 app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0");
2413 app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0");
2414 // app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0");
2415
2416 if (wxGetApp().is_editor()) {
2417 if (page_files_association->associate_3mf())
2418 wxGetApp().associate_3mf_files();
2419 if (page_files_association->associate_stl())
2420 wxGetApp().associate_stl_files();
2421 }
2422 // else {
2423 // if (page_files_association->associate_gcode())
2424 // wxGetApp().associate_gcode_files();
2425 // }
2426
2427 #endif // _WIN32
2428 #endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
2429
2430 page_mode->serialize_mode(app_config);
2431
2432 std::string preferred_model;
2433
2434 // Figure out the default pre-selected printer based on the selections in the pickers.
2435 // The default is the first selected printer model (one with at least 1 variant selected).
2436 // The default is only applied by load_presets() if the user doesn't have a (visible) printer
2437 // selected already.
2438 // Prusa printers are considered first, then 3rd party.
2439 const auto config_prusa = enabled_vendors.find("PrusaResearch");
2440 if (config_prusa != enabled_vendors.end()) {
2441 for (const auto &model : bundles.prusa_bundle().vendor_profile->models) {
2442 const auto model_it = config_prusa->second.find(model.id);
2443 if (model_it != config_prusa->second.end() && model_it->second.size() > 0) {
2444 preferred_model = model.id;
2445 break;
2446 }
2447 }
2448 }
2449 if (preferred_model.empty()) {
2450 for (const auto &bundle : bundles) {
2451 if (bundle.second.is_prusa_bundle) { continue; }
2452
2453 const auto config = enabled_vendors.find(bundle.first);
2454 if (config == enabled_vendors.end()) { continue; }
2455 for (const auto &model : bundle.second.vendor_profile->models) {
2456 const auto model_it = config->second.find(model.id);
2457 if (model_it != config->second.end() && model_it->second.size() > 0) {
2458 preferred_model = model.id;
2459 break;
2460 }
2461 }
2462 }
2463 }
2464
2465 // Reloading the configs after some modifications were done to PrusaSlicer.ini.
2466 // Just perform the substitutions silently, as the substitutions were already presented to the user on application start-up
2467 // and the Wizard shall not create any new values that would require substitution.
2468 // Throw on substitutions in system profiles, as the system profiles provided over the air should be compatible with this PrusaSlicer version.
2469 preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, preferred_model);
2470
2471 if (page_custom->custom_wanted()) {
2472 page_firmware->apply_custom_config(*custom_config);
2473 page_bed->apply_custom_config(*custom_config);
2474 page_diams->apply_custom_config(*custom_config);
2475 page_temps->apply_custom_config(*custom_config);
2476
2477 const std::string profile_name = page_custom->profile_name();
2478 preset_bundle->load_config_from_wizard(profile_name, *custom_config);
2479 }
2480
2481 // Update the selections from the compatibilty.
2482 preset_bundle->export_selections(*app_config);
2483
2484 return true;
2485 }
2486
update_presets_in_config(const std::string & section,const std::string & alias_key,bool add)2487 void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add)
2488 {
2489 const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla;
2490
2491 auto update = [this, add](const std::string& s, const std::string& key) {
2492 assert(! s.empty());
2493 if (add)
2494 appconfig_new.set(s, key, "1");
2495 else
2496 appconfig_new.erase(s, key);
2497 };
2498
2499 // add or delete presets had a same alias
2500 auto it = aliases.find(alias_key);
2501 if (it != aliases.end())
2502 for (const std::string& name : it->second)
2503 update(section, name);
2504 }
2505
check_fff_selected()2506 bool ConfigWizard::priv::check_fff_selected()
2507 {
2508 bool ret = page_fff->any_selected();
2509 for (const auto& printer: pages_3rdparty)
2510 if (printer.second.first) // FFF page
2511 ret |= printer.second.first->any_selected();
2512 return ret;
2513 }
2514
check_sla_selected()2515 bool ConfigWizard::priv::check_sla_selected()
2516 {
2517 bool ret = page_msla->any_selected();
2518 for (const auto& printer: pages_3rdparty)
2519 if (printer.second.second) // SLA page
2520 ret |= printer.second.second->any_selected();
2521 return ret;
2522 }
2523
2524
2525 // Public
2526
ConfigWizard(wxWindow * parent)2527 ConfigWizard::ConfigWizard(wxWindow *parent)
2528 : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
2529 , p(new priv(this))
2530 {
2531 this->SetFont(wxGetApp().normal_font());
2532
2533 p->load_vendors();
2534 p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({
2535 "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature",
2536 }));
2537
2538 p->index = new ConfigWizardIndex(this);
2539
2540 auto *vsizer = new wxBoxSizer(wxVERTICAL);
2541 auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
2542 auto *hline = new wxStaticLine(this);
2543 p->btnsizer = new wxBoxSizer(wxHORIZONTAL);
2544
2545 // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling.
2546 // Later, we compare that to the size of the current screen and set minimum width based on that (see below).
2547 p->hscroll = new wxScrolledWindow(this);
2548 p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL);
2549 p->hscroll->SetSizer(p->hscroll_sizer);
2550
2551 topsizer->Add(p->index, 0, wxEXPAND);
2552 topsizer->AddSpacer(INDEX_MARGIN);
2553 topsizer->Add(p->hscroll, 1, wxEXPAND);
2554
2555 p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers"));
2556 p->btnsizer->Add(p->btn_sel_all);
2557
2558 p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back"));
2559 p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >"));
2560 p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish"));
2561 p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel")); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac
2562 p->btnsizer->AddStretchSpacer();
2563 p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING);
2564 p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING);
2565 p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING);
2566 p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING);
2567
2568 const auto prusa_it = p->bundles.find("PrusaResearch");
2569 wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found");
2570 const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile;
2571
2572 p->add_page(p->page_welcome = new PageWelcome(this));
2573
2574 p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF);
2575 p->add_page(p->page_fff);
2576
2577 p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA);
2578 p->add_page(p->page_msla);
2579
2580 // Pages for 3rd party vendors
2581 p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors
2582 p->add_page(p->page_vendors = new PageVendors(this));
2583 p->add_page(p->page_custom = new PageCustom(this));
2584 p->custom_printer_selected = p->page_custom->custom_wanted();
2585
2586 p->any_sla_selected = p->check_sla_selected();
2587 p->any_fff_selected = p->check_fff_selected();
2588
2589 p->update_materials(T_ANY);
2590
2591 p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
2592 _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") ));
2593 p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
2594 _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") ));
2595
2596
2597 p->add_page(p->page_update = new PageUpdate(this));
2598 p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this));
2599 #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
2600 #ifdef _WIN32
2601 p->add_page(p->page_files_association = new PageFilesAssociation(this));
2602 #endif // _WIN32
2603 #endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
2604 p->add_page(p->page_mode = new PageMode(this));
2605 p->add_page(p->page_firmware = new PageFirmware(this));
2606 p->add_page(p->page_bed = new PageBedShape(this));
2607 p->add_page(p->page_diams = new PageDiameters(this));
2608 p->add_page(p->page_temps = new PageTemperatures(this));
2609
2610 p->load_pages();
2611 p->index->go_to(size_t{0});
2612
2613 vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
2614 vsizer->Add(hline, 0, wxEXPAND);
2615 vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN);
2616
2617 SetSizer(vsizer);
2618 SetSizerAndFit(vsizer);
2619
2620 // We can now enable scrolling on hscroll
2621 p->hscroll->SetScrollRate(30, 30);
2622
2623 on_window_geometry(this, [this]() {
2624 p->init_dialog_size();
2625 });
2626
2627 p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); });
2628
2629 p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
2630 {
2631 // check, that there is selected at least one filament/material
2632 ConfigWizardPage* active_page = this->p->index->active_page();
2633 if (// Leaving the filaments or SLA materials page and
2634 (active_page == p->page_filaments || active_page == p->page_sla_materials) &&
2635 // some Printer models had no filament or SLA material selected.
2636 ! p->check_and_install_missing_materials(dynamic_cast<PageMaterials*>(active_page)->materials->technology))
2637 // In that case don't leave the page and the function above queried the user whether to install default materials.
2638 return;
2639 this->p->index->go_next();
2640 });
2641
2642 p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
2643 {
2644 if (p->on_bnt_finish())
2645 this->EndModal(wxID_OK);
2646 });
2647
2648 p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) {
2649 p->any_sla_selected = true;
2650 p->load_pages();
2651 p->page_fff->select_all(true, false);
2652 p->page_msla->select_all(true, false);
2653 p->index->go_to(p->page_mode);
2654 });
2655
2656 p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) {
2657 const bool is_last = p->index->active_is_last();
2658 p->btn_next->Show(! is_last);
2659 if (is_last)
2660 p->btn_finish->SetFocus();
2661
2662 Layout();
2663 });
2664
2665 if (wxLinux_gtk3)
2666 this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) {
2667 ConfigWizardPage* active_page = p->index->active_page();
2668 if (!active_page)
2669 return;
2670 for (auto page : p->all_pages)
2671 if (page != active_page)
2672 page->Hide();
2673 // update best size for the dialog after hiding of the non-active pages
2674 vsizer->SetSizeHints(this);
2675 // set initial dialog size
2676 p->init_dialog_size();
2677 });
2678 }
2679
~ConfigWizard()2680 ConfigWizard::~ConfigWizard() {}
2681
run(RunReason reason,StartPage start_page)2682 bool ConfigWizard::run(RunReason reason, StartPage start_page)
2683 {
2684 BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page;
2685
2686 GUI_App &app = wxGetApp();
2687
2688 p->set_run_reason(reason);
2689 p->set_start_page(start_page);
2690
2691 if (ShowModal() == wxID_OK) {
2692 if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater))
2693 return false;
2694 app.app_config->set_legacy_datadir(false);
2695 app.update_mode();
2696 app.obj_manipul()->update_ui_from_settings();
2697 BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied";
2698 return true;
2699 } else {
2700 BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled";
2701 return false;
2702 }
2703 }
2704
name(const bool from_menu)2705 const wxString& ConfigWizard::name(const bool from_menu/* = false*/)
2706 {
2707 // A different naming convention is used for the Wizard on Windows & GTK vs. OSX.
2708 // Note: Don't call _() macro here.
2709 // This function just return the current name according to the OS.
2710 // Translation is implemented inside GUI_App::add_config_menu()
2711 #if __APPLE__
2712 static const wxString config_wizard_name = L("Configuration Assistant");
2713 static const wxString config_wizard_name_menu = L("Configuration &Assistant");
2714 #else
2715 static const wxString config_wizard_name = L("Configuration Wizard");
2716 static const wxString config_wizard_name_menu = L("Configuration &Wizard");
2717 #endif
2718 return from_menu ? config_wizard_name_menu : config_wizard_name;
2719 }
2720
on_dpi_changed(const wxRect & suggested_rect)2721 void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect)
2722 {
2723 p->index->msw_rescale();
2724
2725 const int em = em_unit();
2726
2727 msw_buttons_rescale(this, em, { wxID_APPLY,
2728 wxID_CANCEL,
2729 p->btn_sel_all->GetId(),
2730 p->btn_next->GetId(),
2731 p->btn_prev->GetId() });
2732
2733 for (auto printer_picker: p->page_fff->printer_pickers)
2734 msw_buttons_rescale(this, em, printer_picker->get_button_indexes());
2735
2736 p->init_dialog_size();
2737
2738 Refresh();
2739 }
2740
2741 }
2742 }
2743