1 #include "Plater.hpp"
2 
3 #include <cstddef>
4 #include <algorithm>
5 #include <numeric>
6 #include <vector>
7 #include <string>
8 #include <regex>
9 #include <future>
10 #include <boost/algorithm/string.hpp>
11 #include <boost/optional.hpp>
12 #include <boost/filesystem/path.hpp>
13 #include <boost/filesystem/operations.hpp>
14 #include <boost/log/trivial.hpp>
15 #include <boost/nowide/convert.hpp>
16 
17 #include <wx/sizer.h>
18 #include <wx/stattext.h>
19 #include <wx/button.h>
20 #include <wx/bmpcbox.h>
21 #include <wx/statbox.h>
22 #include <wx/statbmp.h>
23 #include <wx/filedlg.h>
24 #include <wx/dnd.h>
25 #include <wx/progdlg.h>
26 #include <wx/wupdlock.h>
27 #include <wx/numdlg.h>
28 #include <wx/debug.h>
29 #include <wx/busyinfo.h>
30 
31 #include "libslic3r/libslic3r.h"
32 #include "libslic3r/Format/STL.hpp"
33 #include "libslic3r/Format/AMF.hpp"
34 #include "libslic3r/Format/3mf.hpp"
35 #include "libslic3r/GCode/ThumbnailData.hpp"
36 #include "libslic3r/Model.hpp"
37 #include "libslic3r/SLA/Hollowing.hpp"
38 #include "libslic3r/SLA/SupportPoint.hpp"
39 #include "libslic3r/SLA/ReprojectPointsOnMesh.hpp"
40 #include "libslic3r/Polygon.hpp"
41 #include "libslic3r/Print.hpp"
42 #include "libslic3r/PrintConfig.hpp"
43 #include "libslic3r/SLAPrint.hpp"
44 #include "libslic3r/Utils.hpp"
45 #include "libslic3r/PresetBundle.hpp"
46 
47 #include "GUI.hpp"
48 #include "GUI_App.hpp"
49 #include "GUI_ObjectList.hpp"
50 #include "GUI_ObjectManipulation.hpp"
51 #include "GUI_ObjectLayers.hpp"
52 #include "GUI_Utils.hpp"
53 #include "wxExtensions.hpp"
54 #include "MainFrame.hpp"
55 #include "format.hpp"
56 #include "3DScene.hpp"
57 #include "GLCanvas3D.hpp"
58 #include "Selection.hpp"
59 #include "GLToolbar.hpp"
60 #include "GUI_Preview.hpp"
61 #include "3DBed.hpp"
62 #include "Camera.hpp"
63 #include "Mouse3DController.hpp"
64 #include "Tab.hpp"
65 #include "Jobs/ArrangeJob.hpp"
66 #include "Jobs/FillBedJob.hpp"
67 #include "Jobs/RotoptimizeJob.hpp"
68 #include "Jobs/SLAImportJob.hpp"
69 #include "BackgroundSlicingProcess.hpp"
70 #include "ProgressStatusBar.hpp"
71 #include "PrintHostDialogs.hpp"
72 #include "ConfigWizard.hpp"
73 #include "../Utils/ASCIIFolding.hpp"
74 #include "../Utils/PrintHost.hpp"
75 #include "../Utils/FixModelByWin10.hpp"
76 #include "../Utils/UndoRedo.hpp"
77 #include "../Utils/PresetUpdater.hpp"
78 #include "../Utils/Process.hpp"
79 #include "RemovableDriveManager.hpp"
80 #include "InstanceCheck.hpp"
81 #include "NotificationManager.hpp"
82 #include "PresetComboBoxes.hpp"
83 
84 #ifdef __APPLE__
85 #include "Gizmos/GLGizmosManager.hpp"
86 #endif // __APPLE__
87 
88 #include <wx/glcanvas.h>    // Needs to be last because reasons :-/
89 #include "WipeTowerDialog.hpp"
90 #include "libslic3r/CustomGCode.hpp"
91 #include "libslic3r/Platform.hpp"
92 
93 using boost::optional;
94 namespace fs = boost::filesystem;
95 using Slic3r::_3DScene;
96 using Slic3r::Preset;
97 using Slic3r::PrintHostJob;
98 using Slic3r::GUI::format_wxstr;
99 
100 static const std::pair<unsigned int, unsigned int> THUMBNAIL_SIZE_3MF = { 256, 256 };
101 
102 namespace Slic3r {
103 namespace GUI {
104 
105 wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS,     SimpleEvent);
106 wxDEFINE_EVENT(EVT_SLICING_UPDATE,                  SlicingStatusEvent);
107 wxDEFINE_EVENT(EVT_SLICING_COMPLETED,               wxCommandEvent);
108 wxDEFINE_EVENT(EVT_PROCESS_COMPLETED,               SlicingProcessCompletedEvent);
109 wxDEFINE_EVENT(EVT_EXPORT_BEGAN,                    wxCommandEvent);
110 
111 // Sidebar widgets
112 
113 // struct InfoBox : public wxStaticBox
114 // {
115 //     InfoBox(wxWindow *parent, const wxString &label) :
116 //         wxStaticBox(parent, wxID_ANY, label)
117 //     {
118 //         SetFont(GUI::small_font().Bold());
119 //     }
120 // };
121 
122 class ObjectInfo : public wxStaticBoxSizer
123 {
124 public:
125     ObjectInfo(wxWindow *parent);
126 
127     wxStaticBitmap *manifold_warning_icon;
128     wxStaticText *info_size;
129     wxStaticText *info_volume;
130     wxStaticText *info_facets;
131     wxStaticText *info_materials;
132     wxStaticText *info_manifold;
133 
134     wxStaticText *label_volume;
135     wxStaticText *label_materials;
136     std::vector<wxStaticText *> sla_hidden_items;
137 
138     bool        showing_manifold_warning_icon;
139     void        show_sizer(bool show);
140     void        msw_rescale();
141 };
142 
ObjectInfo(wxWindow * parent)143 ObjectInfo::ObjectInfo(wxWindow *parent) :
144     wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _L("Info")), wxVERTICAL)
145 {
146     GetStaticBox()->SetFont(wxGetApp().bold_font());
147 
148     auto *grid_sizer = new wxFlexGridSizer(4, 5, 15);
149     grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
150 //     grid_sizer->AddGrowableCol(1, 1);
151 //     grid_sizer->AddGrowableCol(3, 1);
152 
153     auto init_info_label = [parent, grid_sizer](wxStaticText **info_label, wxString text_label) {
154         auto *text = new wxStaticText(parent, wxID_ANY, text_label+":");
155         text->SetFont(wxGetApp().small_font());
156         *info_label = new wxStaticText(parent, wxID_ANY, "");
157         (*info_label)->SetFont(wxGetApp().small_font());
158         grid_sizer->Add(text, 0);
159         grid_sizer->Add(*info_label, 0);
160         return text;
161     };
162 
163     init_info_label(&info_size, _L("Size"));
164     label_volume = init_info_label(&info_volume, _L("Volume"));
165     init_info_label(&info_facets, _L("Facets"));
166     label_materials = init_info_label(&info_materials, _L("Materials"));
167     Add(grid_sizer, 0, wxEXPAND);
168 
169     auto *info_manifold_text = new wxStaticText(parent, wxID_ANY, _L("Manifold") + ":");
170     info_manifold_text->SetFont(wxGetApp().small_font());
171     info_manifold = new wxStaticText(parent, wxID_ANY, "");
172     info_manifold->SetFont(wxGetApp().small_font());
173     manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap("exclamation"));
174     auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL);
175     sizer_manifold->Add(info_manifold_text, 0);
176     sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2);
177     sizer_manifold->Add(info_manifold, 0, wxLEFT, 2);
178     Add(sizer_manifold, 0, wxEXPAND | wxTOP, 4);
179 
180     sla_hidden_items = { label_volume, info_volume, label_materials, info_materials };
181 }
182 
show_sizer(bool show)183 void ObjectInfo::show_sizer(bool show)
184 {
185     Show(show);
186     if (show)
187         manifold_warning_icon->Show(showing_manifold_warning_icon && show);
188 }
189 
msw_rescale()190 void ObjectInfo::msw_rescale()
191 {
192     manifold_warning_icon->SetBitmap(create_scaled_bitmap("exclamation"));
193 }
194 
195 enum SlicedInfoIdx
196 {
197     siFilament_m,
198     siFilament_mm3,
199     siFilament_g,
200     siMateril_unit,
201     siCost,
202     siEstimatedTime,
203     siWTNumbetOfToolchanges,
204 
205     siCount
206 };
207 
208 class SlicedInfo : public wxStaticBoxSizer
209 {
210 public:
211     SlicedInfo(wxWindow *parent);
212     void SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label="");
213 
214 private:
215     std::vector<std::pair<wxStaticText*, wxStaticText*>> info_vec;
216 };
217 
SlicedInfo(wxWindow * parent)218 SlicedInfo::SlicedInfo(wxWindow *parent) :
219     wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _L("Sliced Info")), wxVERTICAL)
220 {
221     GetStaticBox()->SetFont(wxGetApp().bold_font());
222 
223     auto *grid_sizer = new wxFlexGridSizer(2, 5, 15);
224     grid_sizer->SetFlexibleDirection(wxVERTICAL);
225 
226     info_vec.reserve(siCount);
227 
228     auto init_info_label = [this, parent, grid_sizer](wxString text_label) {
229         auto *text = new wxStaticText(parent, wxID_ANY, text_label);
230         text->SetFont(wxGetApp().small_font());
231         auto info_label = new wxStaticText(parent, wxID_ANY, "N/A");
232         info_label->SetFont(wxGetApp().small_font());
233         grid_sizer->Add(text, 0);
234         grid_sizer->Add(info_label, 0);
235         info_vec.push_back(std::pair<wxStaticText*, wxStaticText*>(text, info_label));
236     };
237 
238     init_info_label(_L("Used Filament (m)"));
239     init_info_label(_L("Used Filament (mm³)"));
240     init_info_label(_L("Used Filament (g)"));
241     init_info_label(_L("Used Material (unit)"));
242     init_info_label(_L("Cost (money)"));
243     init_info_label(_L("Estimated printing time"));
244     init_info_label(_L("Number of tool changes"));
245 
246     Add(grid_sizer, 0, wxEXPAND);
247     this->Show(false);
248 }
249 
SetTextAndShow(SlicedInfoIdx idx,const wxString & text,const wxString & new_label)250 void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label/*=""*/)
251 {
252     const bool show = text != "N/A";
253     if (show)
254         info_vec[idx].second->SetLabelText(text);
255     if (!new_label.IsEmpty())
256         info_vec[idx].first->SetLabelText(new_label);
257     info_vec[idx].first->Show(show);
258     info_vec[idx].second->Show(show);
259 }
260 
261 // Frequently changed parameters
262 
263 class FreqChangedParams : public OG_Settings
264 {
265     double		    m_brim_width = 0.0;
266     wxButton*       m_wiping_dialog_button{ nullptr };
267     wxSizer*        m_sizer {nullptr};
268 
269     std::shared_ptr<ConfigOptionsGroup> m_og_sla;
270     std::vector<ScalableButton*>        m_empty_buttons;
271 public:
272     FreqChangedParams(wxWindow* parent);
~FreqChangedParams()273     ~FreqChangedParams() {}
274 
get_wiping_dialog_button()275     wxButton*       get_wiping_dialog_button() { return m_wiping_dialog_button; }
276     wxSizer*        get_sizer() override;
277     ConfigOptionsGroup* get_og(const bool is_fff);
278     void            Show(const bool is_fff);
279 
280     void            msw_rescale();
281 };
282 
msw_rescale()283 void FreqChangedParams::msw_rescale()
284 {
285     m_og->msw_rescale();
286     m_og_sla->msw_rescale();
287 
288     for (auto btn: m_empty_buttons)
289         btn->msw_rescale();
290 }
291 
FreqChangedParams(wxWindow * parent)292 FreqChangedParams::FreqChangedParams(wxWindow* parent) :
293     OG_Settings(parent, false)
294 {
295     DynamicPrintConfig*	config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
296 
297     // Frequently changed parameters for FFF_technology
298     m_og->set_config(config);
299     m_og->hide_labels();
300 
301     m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) {
302         Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT);
303         if (!tab_print) return;
304 
305         if (opt_key == "fill_density") {
306             tab_print->update_dirty();
307             tab_print->reload_config();
308             tab_print->update();
309         }
310         else
311         {
312             DynamicPrintConfig new_conf = *config;
313             if (opt_key == "brim") {
314                 double new_val;
315                 double brim_width = config->opt_float("brim_width");
316                 if (boost::any_cast<bool>(value) == true)
317                 {
318                     new_val = m_brim_width == 0.0 ? 5 :
319                         m_brim_width < 0.0 ? m_brim_width * (-1) :
320                         m_brim_width;
321                 }
322                 else {
323                     m_brim_width = brim_width * (-1);
324                     new_val = 0;
325                 }
326                 new_conf.set_key_value("brim_width", new ConfigOptionFloat(new_val));
327             }
328             else {
329                 assert(opt_key == "support");
330                 const wxString& selection = boost::any_cast<wxString>(value);
331                 PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
332 
333                 auto support_material = selection == _("None") ? false : true;
334                 new_conf.set_key_value("support_material", new ConfigOptionBool(support_material));
335 
336                 if (selection == _("Everywhere")) {
337                     new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false));
338                     if (printer_technology == ptFFF)
339                         new_conf.set_key_value("support_material_auto", new ConfigOptionBool(true));
340                 } else if (selection == _("Support on build plate only")) {
341                     new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true));
342                     if (printer_technology == ptFFF)
343                         new_conf.set_key_value("support_material_auto", new ConfigOptionBool(true));
344                 } else if (selection == _("For support enforcers only")) {
345                     assert(printer_technology == ptFFF);
346                     new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false));
347                     new_conf.set_key_value("support_material_auto", new ConfigOptionBool(false));
348                 }
349             }
350             tab_print->load_config(new_conf);
351         }
352     };
353 
354 
355     Line line = Line { "", "" };
356 
357     ConfigOptionDef support_def;
358     support_def.label = L("Supports");
359     support_def.type = coStrings;
360     support_def.gui_type = "select_open";
361     support_def.tooltip = L("Select what kind of support do you need");
362     support_def.enum_labels.push_back(L("None"));
363     support_def.enum_labels.push_back(L("Support on build plate only"));
364     support_def.enum_labels.push_back(L("For support enforcers only"));
365     support_def.enum_labels.push_back(L("Everywhere"));
366     support_def.set_default_value(new ConfigOptionStrings{ "None" });
367     Option option = Option(support_def, "support");
368     option.opt.full_width = true;
369     line.append_option(option);
370 
371     /* Not a best solution, but
372      * Temporary workaround for right border alignment
373      */
374     auto empty_widget = [this] (wxWindow* parent) {
375         auto sizer = new wxBoxSizer(wxHORIZONTAL);
376         auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString,
377             wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
378         sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, int(0.3 * wxGetApp().em_unit()));
379         m_empty_buttons.push_back(btn);
380         return sizer;
381     };
382     line.append_widget(empty_widget);
383 
384     m_og->append_line(line);
385 
386 
387     line = Line { "", "" };
388 
389     option = m_og->get_option("fill_density");
390     option.opt.label = L("Infill");
391     option.opt.width = 8;
392     option.opt.sidetext = "   ";
393     line.append_option(option);
394 
395     m_brim_width = config->opt_float("brim_width");
396     ConfigOptionDef def;
397     def.label = L("Brim");
398     def.type = coBool;
399     def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer.");
400     def.gui_type = "";
401     def.set_default_value(new ConfigOptionBool{ m_brim_width > 0.0 ? true : false });
402     option = Option(def, "brim");
403     option.opt.sidetext = "";
404     line.append_option(option);
405 
406     auto wiping_dialog_btn = [this](wxWindow* parent) {
407         m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _L("Purging volumes") + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
408         m_wiping_dialog_button->SetFont(wxGetApp().normal_font());
409         auto sizer = new wxBoxSizer(wxHORIZONTAL);
410         sizer->Add(m_wiping_dialog_button, 0, wxALIGN_CENTER_VERTICAL);
411         m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e)
412         {
413             auto &project_config = wxGetApp().preset_bundle->project_config;
414             const std::vector<double> &init_matrix = (project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values;
415             const std::vector<double> &init_extruders = (project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values;
416 
417             const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config();
418 
419             WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(init_extruders), extruder_colours);
420 
421             if (dlg.ShowModal() == wxID_OK) {
422                 std::vector<float> matrix = dlg.get_matrix();
423                 std::vector<float> extruders = dlg.get_extruders();
424                 (project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values = std::vector<double>(matrix.begin(), matrix.end());
425                 (project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values = std::vector<double>(extruders.begin(), extruders.end());
426                 wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent));
427             }
428         }));
429 
430         auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString,
431                                       wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
432         sizer->Add(btn , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT,
433             int(0.3 * wxGetApp().em_unit()));
434         m_empty_buttons.push_back(btn);
435 
436         return sizer;
437     };
438     line.append_widget(wiping_dialog_btn);
439     m_og->append_line(line);
440 
441     m_og->activate();
442 
443     Choice* choice = dynamic_cast<Choice*>(m_og->get_field("support"));
444     choice->suppress_scroll();
445 
446     // Frequently changed parameters for SLA_technology
447     m_og_sla = std::make_shared<ConfigOptionsGroup>(parent, "");
448     m_og_sla->hide_labels();
449     DynamicPrintConfig*	config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
450     m_og_sla->set_config(config_sla);
451 
452     m_og_sla->m_on_change = [config_sla](t_config_option_key opt_key, boost::any value) {
453         Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT);
454         if (!tab) return;
455 
456         DynamicPrintConfig new_conf = *config_sla;
457         if (opt_key == "pad") {
458             const wxString& selection = boost::any_cast<wxString>(value);
459 
460             const bool pad_enable = selection == _("None") ? false : true;
461             new_conf.set_key_value("pad_enable", new ConfigOptionBool(pad_enable));
462 
463             if (selection == _("Below object"))
464                 new_conf.set_key_value("pad_around_object", new ConfigOptionBool(false));
465             else if (selection == _("Around object"))
466                 new_conf.set_key_value("pad_around_object", new ConfigOptionBool(true));
467         }
468         else
469         {
470             assert(opt_key == "support");
471             const wxString& selection = boost::any_cast<wxString>(value);
472 
473             const bool supports_enable = selection == _("None") ? false : true;
474             new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable));
475 
476             if (selection == _("Everywhere"))
477                 new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(false));
478             else if (selection == _("Support on build plate only"))
479                 new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(true));
480         }
481 
482         tab->load_config(new_conf);
483         tab->update_dirty();
484     };
485 
486     line = Line{ "", "" };
487 
488     ConfigOptionDef support_def_sla = support_def;
489     support_def_sla.set_default_value(new ConfigOptionStrings{ "None" });
490     assert(support_def_sla.enum_labels[2] == L("For support enforcers only"));
491     support_def_sla.enum_labels.erase(support_def_sla.enum_labels.begin() + 2);
492     option = Option(support_def_sla, "support");
493     option.opt.full_width = true;
494     line.append_option(option);
495     line.append_widget(empty_widget);
496     m_og_sla->append_line(line);
497 
498     line = Line{ "", "" };
499 
500     ConfigOptionDef pad_def;
501     pad_def.label = L("Pad");
502     pad_def.type = coStrings;
503     pad_def.gui_type = "select_open";
504     pad_def.tooltip = L("Select what kind of pad do you need");
505     pad_def.enum_labels.push_back(L("None"));
506     pad_def.enum_labels.push_back(L("Below object"));
507     pad_def.enum_labels.push_back(L("Around object"));
508     pad_def.set_default_value(new ConfigOptionStrings{ "Below object" });
509     option = Option(pad_def, "pad");
510     option.opt.full_width = true;
511     line.append_option(option);
512     line.append_widget(empty_widget);
513 
514     m_og_sla->append_line(line);
515 
516     m_og_sla->activate();
517     choice = dynamic_cast<Choice*>(m_og_sla->get_field("support"));
518     choice->suppress_scroll();
519     choice = dynamic_cast<Choice*>(m_og_sla->get_field("pad"));
520     choice->suppress_scroll();
521 
522     m_sizer = new wxBoxSizer(wxVERTICAL);
523     m_sizer->Add(m_og->sizer, 0, wxEXPAND);
524     m_sizer->Add(m_og_sla->sizer, 0, wxEXPAND);
525 }
526 
527 
get_sizer()528 wxSizer* FreqChangedParams::get_sizer()
529 {
530     return m_sizer;
531 }
532 
Show(const bool is_fff)533 void FreqChangedParams::Show(const bool is_fff)
534 {
535     const bool is_wdb_shown = m_wiping_dialog_button->IsShown();
536     m_og->Show(is_fff);
537     m_og_sla->Show(!is_fff);
538 
539     // correct showing of the FreqChangedParams sizer when m_wiping_dialog_button is hidden
540     if (is_fff && !is_wdb_shown)
541         m_wiping_dialog_button->Hide();
542 }
543 
get_og(const bool is_fff)544 ConfigOptionsGroup* FreqChangedParams::get_og(const bool is_fff)
545 {
546     return is_fff ? m_og.get() : m_og_sla.get();
547 }
548 
549 // Sidebar / private
550 
551 enum class ActionButtonType : int {
552     abReslice,
553     abExport,
554     abSendGCode
555 };
556 
557 struct Sidebar::priv
558 {
559     Plater *plater;
560 
561     wxScrolledWindow *scrolled;
562     wxPanel* presets_panel; // Used for MSW better layouts
563 
564     ModeSizer  *mode_sizer;
565     wxFlexGridSizer *sizer_presets;
566     PlaterPresetComboBox *combo_print;
567     std::vector<PlaterPresetComboBox*> combos_filament;
568     wxBoxSizer *sizer_filaments;
569     PlaterPresetComboBox *combo_sla_print;
570     PlaterPresetComboBox *combo_sla_material;
571     PlaterPresetComboBox *combo_printer;
572 
573     wxBoxSizer *sizer_params;
574     FreqChangedParams   *frequently_changed_parameters{ nullptr };
575     ObjectList          *object_list{ nullptr };
576     ObjectManipulation  *object_manipulation{ nullptr };
577     ObjectSettings      *object_settings{ nullptr };
578     ObjectLayers        *object_layers{ nullptr };
579     ObjectInfo *object_info;
580     SlicedInfo *sliced_info;
581 
582     wxButton *btn_export_gcode;
583     wxButton *btn_reslice;
584     ScalableButton *btn_send_gcode;
585     //ScalableButton *btn_eject_device;
586 	ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected)
587 
588     bool                is_collapsed {false};
589     Search::OptionsSearcher     searcher;
590 
privSlic3r::GUI::Sidebar::priv591     priv(Plater *plater) : plater(plater) {}
592     ~priv();
593 
594     void show_preset_comboboxes();
595 };
596 
~priv()597 Sidebar::priv::~priv()
598 {
599     if (object_manipulation != nullptr)
600         delete object_manipulation;
601 
602     if (object_settings != nullptr)
603         delete object_settings;
604 
605     if (frequently_changed_parameters != nullptr)
606         delete frequently_changed_parameters;
607 
608     if (object_layers != nullptr)
609         delete object_layers;
610 }
611 
show_preset_comboboxes()612 void Sidebar::priv::show_preset_comboboxes()
613 {
614     const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA;
615 
616     for (size_t i = 0; i < 4; ++i)
617         sizer_presets->Show(i, !showSLA);
618 
619     for (size_t i = 4; i < 8; ++i) {
620         if (sizer_presets->IsShown(i) != showSLA)
621             sizer_presets->Show(i, showSLA);
622     }
623 
624     frequently_changed_parameters->Show(!showSLA);
625 
626     scrolled->GetParent()->Layout();
627     scrolled->Refresh();
628 }
629 
630 
631 // Sidebar / public
632 
Sidebar(Plater * parent)633 Sidebar::Sidebar(Plater *parent)
634     : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(42 * wxGetApp().em_unit(), -1)), p(new priv(parent))
635 {
636     p->scrolled = new wxScrolledWindow(this);
637     p->scrolled->SetScrollbars(0, 100, 1, 2);
638 
639     SetFont(wxGetApp().normal_font());
640 #ifndef __APPLE__
641     SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
642 #endif
643 
644     // Sizer in the scrolled area
645     auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL);
646     p->scrolled->SetSizer(scrolled_sizer);
647 
648     // Sizer with buttons for mode changing
649     p->mode_sizer = new ModeSizer(p->scrolled);
650 
651     // The preset chooser
652     p->sizer_presets = new wxFlexGridSizer(10, 1, 1, 2);
653     p->sizer_presets->AddGrowableCol(0, 1);
654     p->sizer_presets->SetFlexibleDirection(wxBOTH);
655 
656     bool is_msw = false;
657 #ifdef __WINDOWS__
658     p->scrolled->SetDoubleBuffered(true);
659 
660     p->presets_panel = new wxPanel(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
661     p->presets_panel->SetSizer(p->sizer_presets);
662 
663     is_msw = true;
664 #else
665     p->presets_panel = p->scrolled;
666 #endif //__WINDOWS__
667 
668     p->sizer_filaments = new wxBoxSizer(wxVERTICAL);
669 
670     auto init_combo = [this](PlaterPresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) {
671         auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label + " :");
672         text->SetFont(wxGetApp().small_font());
673         *combo = new PlaterPresetComboBox(p->presets_panel, preset_type);
674 
675         auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL);
676         combo_and_btn_sizer->Add(*combo, 1, wxEXPAND);
677         if ((*combo)->edit_btn)
678             combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT,
679                                     int(0.3*wxGetApp().em_unit()));
680 
681         auto *sizer_presets = this->p->sizer_presets;
682         auto *sizer_filaments = this->p->sizer_filaments;
683         sizer_presets->Add(text, 0, wxALIGN_LEFT | wxEXPAND | wxRIGHT, 4);
684         if (! filament) {
685             sizer_presets->Add(combo_and_btn_sizer, 0, wxEXPAND | wxBOTTOM, 1);
686         } else {
687             sizer_filaments->Add(combo_and_btn_sizer, 0, wxEXPAND | wxBOTTOM, 1);
688             (*combo)->set_extruder_idx(0);
689             sizer_presets->Add(sizer_filaments, 1, wxEXPAND);
690         }
691     };
692 
693     p->combos_filament.push_back(nullptr);
694     init_combo(&p->combo_print,         _L("Print settings"),     Preset::TYPE_PRINT,         false);
695     init_combo(&p->combos_filament[0],  _L("Filament"),           Preset::TYPE_FILAMENT,      true);
696     init_combo(&p->combo_sla_print,     _L("SLA print settings"), Preset::TYPE_SLA_PRINT,     false);
697     init_combo(&p->combo_sla_material,  _L("SLA material"),       Preset::TYPE_SLA_MATERIAL,  false);
698     init_combo(&p->combo_printer,       _L("Printer"),            Preset::TYPE_PRINTER,       false);
699 
700     const int margin_5  = int(0.5*wxGetApp().em_unit());// 5;
701 
702     p->sizer_params = new wxBoxSizer(wxVERTICAL);
703 
704     // Frequently changed parameters
705     p->frequently_changed_parameters = new FreqChangedParams(p->scrolled);
706     p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, wxOSX ? 1 : margin_5);
707 
708     // Object List
709     p->object_list = new ObjectList(p->scrolled);
710     p->sizer_params->Add(p->object_list->get_sizer(), 1, wxEXPAND);
711 
712     // Object Manipulations
713     p->object_manipulation = new ObjectManipulation(p->scrolled);
714     p->object_manipulation->Hide();
715     p->sizer_params->Add(p->object_manipulation->get_sizer(), 0, wxEXPAND | wxTOP, margin_5);
716 
717     // Frequently Object Settings
718     p->object_settings = new ObjectSettings(p->scrolled);
719     p->object_settings->Hide();
720     p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5);
721 
722     // Object Layers
723     p->object_layers = new ObjectLayers(p->scrolled);
724     p->object_layers->Hide();
725     p->sizer_params->Add(p->object_layers->get_sizer(), 0, wxEXPAND | wxTOP, margin_5);
726 
727     // Info boxes
728     p->object_info = new ObjectInfo(p->scrolled);
729     p->sliced_info = new SlicedInfo(p->scrolled);
730 
731     // Sizer in the scrolled area
732     scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/);
733     is_msw ?
734         scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) :
735         scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5);
736     scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND | wxLEFT, margin_5);
737     scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5);
738     scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5);
739 
740     // Buttons underneath the scrolled area
741 
742     // rescalable bitmap buttons "Send to printer" and "Remove device"
743 
744     auto init_scalable_btn = [this](ScalableButton** btn, const std::string& icon_name, wxString tooltip = wxEmptyString)
745     {
746 #ifdef __APPLE__
747         int bmp_px_cnt = 16;
748 #else
749         int bmp_px_cnt = 32;
750 #endif //__APPLE__
751         ScalableBitmap bmp = ScalableBitmap(this, icon_name, bmp_px_cnt);
752         *btn = new ScalableButton(this, wxID_ANY, bmp, "", wxBU_EXACTFIT);
753         (*btn)->SetToolTip(tooltip);
754         (*btn)->Hide();
755     };
756 
757     init_scalable_btn(&p->btn_send_gcode   , "export_gcode", _L("Send to printer") + " " +GUI::shortkey_ctrl_prefix() + "Shift+G");
758 //    init_scalable_btn(&p->btn_eject_device, "eject_sd"       , _L("Remove device ") + GUI::shortkey_ctrl_prefix() + "T");
759 	init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + " " + GUI::shortkey_ctrl_prefix() + "U");
760 
761     // regular buttons "Slice now" and "Export G-code"
762 
763 //    const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4;
764     const int scaled_height = p->btn_export_gcode_removable->GetBitmapHeight() + 4;
765     auto init_btn = [this](wxButton **btn, wxString label, const int button_height) {
766         *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition,
767                             wxSize(-1, button_height), wxBU_EXACTFIT);
768         (*btn)->SetFont(wxGetApp().bold_font());
769     };
770 
771     init_btn(&p->btn_export_gcode, _L("Export G-code") + dots , scaled_height);
772     init_btn(&p->btn_reslice     , _L("Slice now")            , scaled_height);
773 
774     enable_buttons(false);
775 
776     auto *btns_sizer = new wxBoxSizer(wxVERTICAL);
777 
778     auto* complect_btns_sizer = new wxBoxSizer(wxHORIZONTAL);
779     complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND);
780     complect_btns_sizer->Add(p->btn_send_gcode);
781 	complect_btns_sizer->Add(p->btn_export_gcode_removable);
782 //    complect_btns_sizer->Add(p->btn_eject_device);
783 
784 
785     btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5);
786     btns_sizer->Add(complect_btns_sizer, 0, wxEXPAND | wxTOP, margin_5);
787 
788     auto *sizer = new wxBoxSizer(wxVERTICAL);
789     sizer->Add(p->scrolled, 1, wxEXPAND);
790     sizer->Add(btns_sizer, 0, wxEXPAND | wxLEFT, margin_5);
791     SetSizer(sizer);
792 
793     // Events
794     p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(false); });
795     p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&)
796     {
797         const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT);
798         if (export_gcode_after_slicing)
799             p->plater->export_gcode(true);
800         else
801             p->plater->reslice();
802         p->plater->select_view_3D("Preview");
803     });
804     p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); });
805 //    p->btn_eject_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); });
806 	p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); });
807 }
808 
~Sidebar()809 Sidebar::~Sidebar() {}
810 
init_filament_combo(PlaterPresetComboBox ** combo,const int extr_idx)811 void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int extr_idx) {
812     *combo = new PlaterPresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT);
813 //         # copy icons from first choice
814 //         $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
815 
816     (*combo)->set_extruder_idx(extr_idx);
817 
818     auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL);
819     combo_and_btn_sizer->Add(*combo, 1, wxEXPAND);
820     combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT,
821                             int(0.3*wxGetApp().em_unit()));
822 
823     auto /***/sizer_filaments = this->p->sizer_filaments;
824     sizer_filaments->Add(combo_and_btn_sizer, 1, wxEXPAND | wxBOTTOM, 1);
825 }
826 
remove_unused_filament_combos(const size_t current_extruder_count)827 void Sidebar::remove_unused_filament_combos(const size_t current_extruder_count)
828 {
829     if (current_extruder_count >= p->combos_filament.size())
830         return;
831     auto sizer_filaments = this->p->sizer_filaments;
832     while (p->combos_filament.size() > current_extruder_count) {
833         const int last = p->combos_filament.size() - 1;
834         sizer_filaments->Remove(last);
835         (*p->combos_filament[last]).Destroy();
836         p->combos_filament.pop_back();
837     }
838 }
839 
update_all_preset_comboboxes()840 void Sidebar::update_all_preset_comboboxes()
841 {
842     PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
843     const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology();
844 
845     // Update the print choosers to only contain the compatible presets, update the dirty flags.
846     if (print_tech == ptFFF)
847         p->combo_print->update();
848     else {
849         p->combo_sla_print->update();
850         p->combo_sla_material->update();
851     }
852     // Update the printer choosers, update the dirty flags.
853     p->combo_printer->update();
854     // Update the filament choosers to only contain the compatible presets, update the color preview,
855     // update the dirty flags.
856     if (print_tech == ptFFF) {
857         for (PlaterPresetComboBox* cb : p->combos_filament)
858             cb->update();
859     }
860 }
861 
update_presets(Preset::Type preset_type)862 void Sidebar::update_presets(Preset::Type preset_type)
863 {
864     PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
865     const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology();
866 
867     switch (preset_type) {
868     case Preset::TYPE_FILAMENT:
869     {
870         const size_t extruder_cnt = print_tech != ptFFF ? 1 :
871                                 dynamic_cast<ConfigOptionFloats*>(preset_bundle.printers.get_edited_preset().config.option("nozzle_diameter"))->values.size();
872         const size_t filament_cnt = p->combos_filament.size() > extruder_cnt ? extruder_cnt : p->combos_filament.size();
873 
874         if (filament_cnt == 1) {
875             // Single filament printer, synchronize the filament presets.
876             const std::string &name = preset_bundle.filaments.get_selected_preset_name();
877             preset_bundle.set_filament_preset(0, name);
878         }
879 
880         for (size_t i = 0; i < filament_cnt; i++)
881             p->combos_filament[i]->update();
882 
883         break;
884     }
885 
886     case Preset::TYPE_PRINT:
887         p->combo_print->update();
888         break;
889 
890     case Preset::TYPE_SLA_PRINT:
891         p->combo_sla_print->update();
892         break;
893 
894     case Preset::TYPE_SLA_MATERIAL:
895         p->combo_sla_material->update();
896         break;
897 
898     case Preset::TYPE_PRINTER:
899     {
900         update_all_preset_comboboxes();
901         p->show_preset_comboboxes();
902         break;
903     }
904 
905     default: break;
906     }
907 
908     // Synchronize config.ini with the current selections.
909     wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
910 }
911 
update_mode_sizer() const912 void Sidebar::update_mode_sizer() const
913 {
914     p->mode_sizer->SetMode(m_mode);
915 }
916 
change_top_border_for_mode_sizer(bool increase_border)917 void Sidebar::change_top_border_for_mode_sizer(bool increase_border)
918 {
919     p->mode_sizer->set_items_flag(increase_border ? wxTOP : 0);
920     p->mode_sizer->set_items_border(increase_border ? int(0.5 * wxGetApp().em_unit()) : 0);
921 }
922 
update_reslice_btn_tooltip() const923 void Sidebar::update_reslice_btn_tooltip() const
924 {
925     wxString tooltip = wxString("Slice") + " [" + GUI::shortkey_ctrl_prefix() + "R]";
926     if (m_mode != comSimple)
927         tooltip += wxString("\n") + _L("Hold Shift to Slice & Export G-code");
928     p->btn_reslice->SetToolTip(tooltip);
929 }
930 
msw_rescale()931 void Sidebar::msw_rescale()
932 {
933     SetMinSize(wxSize(40 * wxGetApp().em_unit(), -1));
934 
935     p->mode_sizer->msw_rescale();
936 
937     for (PlaterPresetComboBox* combo : std::vector<PlaterPresetComboBox*> { p->combo_print,
938                                                                 p->combo_sla_print,
939                                                                 p->combo_sla_material,
940                                                                 p->combo_printer } )
941         combo->msw_rescale();
942     for (PlaterPresetComboBox* combo : p->combos_filament)
943         combo->msw_rescale();
944 
945     p->frequently_changed_parameters->msw_rescale();
946     p->object_list->msw_rescale();
947     p->object_manipulation->msw_rescale();
948     p->object_settings->msw_rescale();
949     p->object_layers->msw_rescale();
950 
951     p->object_info->msw_rescale();
952 
953     p->btn_send_gcode->msw_rescale();
954 //    p->btn_eject_device->msw_rescale();
955 	p->btn_export_gcode_removable->msw_rescale();
956     const int scaled_height = p->btn_export_gcode_removable->GetBitmap().GetHeight() + 4;
957     p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height));
958     p->btn_reslice     ->SetMinSize(wxSize(-1, scaled_height));
959 
960     p->scrolled->Layout();
961 }
962 
sys_color_changed()963 void Sidebar::sys_color_changed()
964 {
965     for (PlaterPresetComboBox* combo : std::vector<PlaterPresetComboBox*>{  p->combo_print,
966                                                                 p->combo_sla_print,
967                                                                 p->combo_sla_material,
968                                                                 p->combo_printer })
969         combo->msw_rescale();
970     for (PlaterPresetComboBox* combo : p->combos_filament)
971         combo->msw_rescale();
972 
973     p->object_list->sys_color_changed();
974     p->object_manipulation->sys_color_changed();
975     p->object_layers->sys_color_changed();
976 
977     // btn...->msw_rescale() updates icon on button, so use it
978     p->btn_send_gcode->msw_rescale();
979 //    p->btn_eject_device->msw_rescale();
980     p->btn_export_gcode_removable->msw_rescale();
981 
982     p->scrolled->Layout();
983 }
984 
search()985 void Sidebar::search()
986 {
987     p->searcher.search();
988 }
989 
jump_to_option(size_t selected)990 void Sidebar::jump_to_option(size_t selected)
991 {
992     const Search::Option& opt = p->searcher.get_option(selected);
993     wxGetApp().get_tab(opt.type)->activate_option(boost::nowide::narrow(opt.opt_key), boost::nowide::narrow(opt.category));
994 
995     // Switch to the Settings NotePad
996 //    wxGetApp().mainframe->select_tab();
997 }
998 
obj_manipul()999 ObjectManipulation* Sidebar::obj_manipul()
1000 {
1001     return p->object_manipulation;
1002 }
1003 
obj_list()1004 ObjectList* Sidebar::obj_list()
1005 {
1006     return p->object_list;
1007 }
1008 
obj_settings()1009 ObjectSettings* Sidebar::obj_settings()
1010 {
1011     return p->object_settings;
1012 }
1013 
obj_layers()1014 ObjectLayers* Sidebar::obj_layers()
1015 {
1016     return p->object_layers;
1017 }
1018 
scrolled_panel()1019 wxScrolledWindow* Sidebar::scrolled_panel()
1020 {
1021     return p->scrolled;
1022 }
1023 
presets_panel()1024 wxPanel* Sidebar::presets_panel()
1025 {
1026     return p->presets_panel;
1027 }
1028 
og_freq_chng_params(const bool is_fff)1029 ConfigOptionsGroup* Sidebar::og_freq_chng_params(const bool is_fff)
1030 {
1031     return p->frequently_changed_parameters->get_og(is_fff);
1032 }
1033 
get_wiping_dialog_button()1034 wxButton* Sidebar::get_wiping_dialog_button()
1035 {
1036     return p->frequently_changed_parameters->get_wiping_dialog_button();
1037 }
1038 
update_objects_list_extruder_column(size_t extruders_count)1039 void Sidebar::update_objects_list_extruder_column(size_t extruders_count)
1040 {
1041     p->object_list->update_objects_list_extruder_column(extruders_count);
1042 }
1043 
show_info_sizer()1044 void Sidebar::show_info_sizer()
1045 {
1046     if (!p->plater->is_single_full_object_selection() ||
1047         m_mode < comExpert ||
1048         p->plater->model().objects.empty()) {
1049         p->object_info->Show(false);
1050         return;
1051     }
1052 
1053     int obj_idx = p->plater->get_selected_object_idx();
1054 
1055     const ModelObject* model_object = p->plater->model().objects[obj_idx];
1056     // hack to avoid crash when deleting the last object on the bed
1057     if (model_object->volumes.empty())
1058     {
1059         p->object_info->Show(false);
1060         return;
1061     }
1062 
1063     bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
1064     double koef = imperial_units ? ObjectManipulation::mm_to_in : 1.0f;
1065 
1066     auto size = model_object->bounding_box().size();
1067     p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0)*koef, size(1)*koef, size(2)*koef));
1068     p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast<int>(model_object->materials_count())));
1069 
1070     const auto& stats = model_object->get_object_stl_stats();//model_object->volumes.front()->mesh.stl.stats;
1071     p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume*pow(koef,3)));
1072     p->object_info->info_facets->SetLabel(wxString::Format(_L("%d (%d shells)"), static_cast<int>(model_object->facets_count()), stats.number_of_parts));
1073 
1074     int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
1075         stats.facets_added + stats.facets_reversed + stats.backwards_edges;
1076     if (errors > 0) {
1077         wxString tooltip = wxString::Format(_L("Auto-repaired (%d errors)"), errors);
1078         p->object_info->info_manifold->SetLabel(tooltip);
1079 
1080         tooltip += ":\n" + wxString::Format(_L("%d degenerate facets, %d edges fixed, %d facets removed, "
1081                                         "%d facets added, %d facets reversed, %d backwards edges"),
1082                                         stats.degenerate_facets, stats.edges_fixed, stats.facets_removed,
1083                                         stats.facets_added, stats.facets_reversed, stats.backwards_edges);
1084 
1085         p->object_info->showing_manifold_warning_icon = true;
1086         p->object_info->info_manifold->SetToolTip(tooltip);
1087         p->object_info->manifold_warning_icon->SetToolTip(tooltip);
1088     }
1089     else {
1090         p->object_info->info_manifold->SetLabel(_L("Yes"));
1091         p->object_info->showing_manifold_warning_icon = false;
1092         p->object_info->info_manifold->SetToolTip("");
1093         p->object_info->manifold_warning_icon->SetToolTip("");
1094     }
1095 
1096     p->object_info->show_sizer(true);
1097 
1098     if (p->plater->printer_technology() == ptSLA) {
1099         for (auto item: p->object_info->sla_hidden_items)
1100             item->Show(false);
1101     }
1102 }
1103 
update_sliced_info_sizer()1104 void Sidebar::update_sliced_info_sizer()
1105 {
1106     if (p->sliced_info->IsShown(size_t(0)))
1107     {
1108         if (p->plater->printer_technology() == ptSLA)
1109         {
1110             const SLAPrintStatistics& ps = p->plater->sla_print().print_statistics();
1111             wxString new_label = _L("Used Material (ml)") + ":";
1112             const bool is_supports = ps.support_used_material > 0.0;
1113             if (is_supports)
1114                 new_label += format_wxstr("\n    - %s\n    - %s", _L("object(s)"), _L("supports and pad"));
1115 
1116             wxString info_text = is_supports ?
1117                 wxString::Format("%.2f \n%.2f \n%.2f", (ps.objects_used_material + ps.support_used_material) / 1000,
1118                                                        ps.objects_used_material / 1000,
1119                                                        ps.support_used_material / 1000) :
1120                 wxString::Format("%.2f", (ps.objects_used_material + ps.support_used_material) / 1000);
1121             p->sliced_info->SetTextAndShow(siMateril_unit, info_text, new_label);
1122 
1123             wxString str_total_cost = "N/A";
1124 
1125             DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_SLA_MATERIAL)->get_config();
1126             if (cfg->option("bottle_cost")->getFloat() > 0.0 &&
1127                 cfg->option("bottle_volume")->getFloat() > 0.0)
1128             {
1129                 double material_cost = cfg->option("bottle_cost")->getFloat() /
1130                                        cfg->option("bottle_volume")->getFloat();
1131                 str_total_cost = wxString::Format("%.3f", material_cost*(ps.objects_used_material + ps.support_used_material) / 1000);
1132             }
1133             p->sliced_info->SetTextAndShow(siCost, str_total_cost, "Cost");
1134 
1135             wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : get_time_dhms(float(ps.estimated_print_time));
1136             p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _L("Estimated printing time") + ":");
1137 
1138             // Hide non-SLA sliced info parameters
1139             p->sliced_info->SetTextAndShow(siFilament_m, "N/A");
1140             p->sliced_info->SetTextAndShow(siFilament_mm3, "N/A");
1141             p->sliced_info->SetTextAndShow(siFilament_g, "N/A");
1142             p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, "N/A");
1143         }
1144         else
1145         {
1146             const PrintStatistics& ps = p->plater->fff_print().print_statistics();
1147             const bool is_wipe_tower = ps.total_wipe_tower_filament > 0;
1148 
1149             bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
1150             double koef = imperial_units ? ObjectManipulation::in_to_mm : 1000.0;
1151 
1152             wxString new_label = imperial_units ? _L("Used Filament (in)") : _L("Used Filament (m)");
1153             if (is_wipe_tower)
1154                 new_label += format_wxstr(":\n    - %1%\n    - %2%", _L("objects"), _L("wipe tower"));
1155 
1156             wxString info_text = is_wipe_tower ?
1157                                 wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / /*1000*/koef,
1158                                                 (ps.total_used_filament - ps.total_wipe_tower_filament) / /*1000*/koef,
1159                                                 ps.total_wipe_tower_filament / /*1000*/koef) :
1160                                 wxString::Format("%.2f", ps.total_used_filament / /*1000*/koef);
1161             p->sliced_info->SetTextAndShow(siFilament_m,    info_text,      new_label);
1162 
1163             koef = imperial_units ? pow(ObjectManipulation::mm_to_in, 3) : 1.0f;
1164             new_label = imperial_units ? _L("Used Filament (in³)") : _L("Used Filament (mm³)");
1165             info_text = wxString::Format("%.2f", imperial_units ? ps.total_extruded_volume * koef : ps.total_extruded_volume);
1166             p->sliced_info->SetTextAndShow(siFilament_mm3,  info_text,      new_label);
1167 
1168             if (ps.total_weight == 0.0)
1169                 p->sliced_info->SetTextAndShow(siFilament_g, "N/A");
1170             else {
1171                 new_label = _L("Used Filament (g)");
1172                 info_text = wxString::Format("%.2f", ps.total_weight);
1173 
1174                 const std::vector<std::string>& filament_presets = wxGetApp().preset_bundle->filament_presets;
1175                 const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
1176 
1177                 if (ps.filament_stats.size() > 1)
1178                     new_label += ":";
1179 
1180                 for (auto filament : ps.filament_stats) {
1181                     const Preset* filament_preset = filaments.find_preset(filament_presets[filament.first], false);
1182                     if (filament_preset) {
1183                         double filament_weight;
1184                         if (ps.filament_stats.size() == 1)
1185                             filament_weight = ps.total_weight;
1186                         else {
1187                             double filament_density = filament_preset->config.opt_float("filament_density", 0);
1188                             filament_weight = filament.second * filament_density * 2.4052f * 0.001; // assumes 1.75mm filament diameter;
1189 
1190                             new_label += "\n    - " + format_wxstr(_L("Filament at extruder %1%"), filament.first + 1);
1191                             info_text += wxString::Format("\n%.2f", filament_weight);
1192                         }
1193 
1194                         double spool_weight = filament_preset->config.opt_float("filament_spool_weight", 0);
1195                         if (spool_weight != 0.0) {
1196                             new_label += "\n      " + _L("(including spool)");
1197                             info_text += wxString::Format(" (%.2f)\n", filament_weight + spool_weight);
1198                         }
1199                     }
1200                 }
1201 
1202                 p->sliced_info->SetTextAndShow(siFilament_g, info_text, new_label);
1203             }
1204 
1205             new_label = _L("Cost");
1206             if (is_wipe_tower)
1207                 new_label += format_wxstr(":\n    - %1%\n    - %2%", _L("objects"), _L("wipe tower"));
1208 
1209             info_text = ps.total_cost == 0.0 ? "N/A" :
1210                         is_wipe_tower ?
1211                         wxString::Format("%.2f \n%.2f \n%.2f", ps.total_cost,
1212                                             (ps.total_cost - ps.total_wipe_tower_cost),
1213                                             ps.total_wipe_tower_cost) :
1214                         wxString::Format("%.2f", ps.total_cost);
1215             p->sliced_info->SetTextAndShow(siCost, info_text,      new_label);
1216 
1217             if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A")
1218                 p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A");
1219             else {
1220                 info_text = "";
1221                 new_label = _L("Estimated printing time") + ":";
1222                 if (ps.estimated_normal_print_time != "N/A") {
1223                     new_label += format_wxstr("\n   - %1%", _L("normal mode"));
1224                     info_text += format_wxstr("\n%1%", short_time(ps.estimated_normal_print_time));
1225 
1226                     // uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate
1227                     //if (p->plater->is_sidebar_collapsed())
1228                     p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed());
1229                     p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time);
1230 
1231                 }
1232                 if (ps.estimated_silent_print_time != "N/A") {
1233                     new_label += format_wxstr("\n   - %1%", _L("stealth mode"));
1234                     info_text += format_wxstr("\n%1%", short_time(ps.estimated_silent_print_time));
1235                 }
1236                 p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label);
1237             }
1238 
1239             // if there is a wipe tower, insert number of toolchanges info into the array:
1240             p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", ps.total_toolchanges) : "N/A");
1241 
1242             // Hide non-FFF sliced info parameters
1243             p->sliced_info->SetTextAndShow(siMateril_unit, "N/A");
1244         }
1245     }
1246 
1247     Layout();
1248 }
1249 
show_sliced_info_sizer(const bool show)1250 void Sidebar::show_sliced_info_sizer(const bool show)
1251 {
1252     wxWindowUpdateLocker freeze_guard(this);
1253 
1254     p->sliced_info->Show(show);
1255     if (show)
1256         update_sliced_info_sizer();
1257 
1258     Layout();
1259     p->scrolled->Refresh();
1260 }
1261 
enable_buttons(bool enable)1262 void Sidebar::enable_buttons(bool enable)
1263 {
1264     p->btn_reslice->Enable(enable);
1265     p->btn_export_gcode->Enable(enable);
1266     p->btn_send_gcode->Enable(enable);
1267 //    p->btn_eject_device->Enable(enable);
1268 	p->btn_export_gcode_removable->Enable(enable);
1269 }
1270 
show_reslice(bool show) const1271 bool Sidebar::show_reslice(bool show)          const { return p->btn_reslice->Show(show); }
show_export(bool show) const1272 bool Sidebar::show_export(bool show)           const { return p->btn_export_gcode->Show(show); }
show_send(bool show) const1273 bool Sidebar::show_send(bool show)             const { return p->btn_send_gcode->Show(show); }
show_export_removable(bool show) const1274 bool Sidebar::show_export_removable(bool show) const { return p->btn_export_gcode_removable->Show(show); }
1275 //bool Sidebar::show_eject(bool show)            const { return p->btn_eject_device->Show(show); }
1276 //bool Sidebar::get_eject_shown()                const { return p->btn_eject_device->IsShown(); }
1277 
is_multifilament()1278 bool Sidebar::is_multifilament()
1279 {
1280     return p->combos_filament.size() > 1;
1281 }
1282 
get_search_inputs(ConfigOptionMode mode)1283 static std::vector<Search::InputInfo> get_search_inputs(ConfigOptionMode mode)
1284 {
1285     std::vector<Search::InputInfo> ret {};
1286 
1287     auto& tabs_list = wxGetApp().tabs_list;
1288     auto print_tech = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology();
1289     for (auto tab : tabs_list)
1290         if (tab->supports_printer_technology(print_tech))
1291             ret.emplace_back(Search::InputInfo {tab->get_config(), tab->type(), mode});
1292 
1293     return ret;
1294 }
1295 
update_searcher()1296 void Sidebar::update_searcher()
1297 {
1298     p->searcher.init(get_search_inputs(m_mode));
1299 }
1300 
update_mode()1301 void Sidebar::update_mode()
1302 {
1303     m_mode = wxGetApp().get_mode();
1304 
1305     update_reslice_btn_tooltip();
1306     update_mode_sizer();
1307     update_searcher();
1308 
1309     wxWindowUpdateLocker noUpdates(this);
1310 
1311     p->object_list->get_sizer()->Show(m_mode > comSimple);
1312 
1313     p->object_list->unselect_objects();
1314     p->object_list->update_selections();
1315     p->object_list->update_object_menu();
1316 
1317     Layout();
1318 }
1319 
is_collapsed()1320 bool Sidebar::is_collapsed() { return p->is_collapsed; }
1321 
collapse(bool collapse)1322 void Sidebar::collapse(bool collapse)
1323 {
1324     p->is_collapsed = collapse;
1325 
1326     this->Show(!collapse);
1327     p->plater->Layout();
1328 
1329     // save collapsing state to the AppConfig
1330     if (wxGetApp().is_editor())
1331         wxGetApp().app_config->set("collapsed_sidebar", collapse ? "1" : "0");
1332 }
1333 
1334 
update_ui_from_settings()1335 void Sidebar::update_ui_from_settings()
1336 {
1337     p->object_manipulation->update_ui_from_settings();
1338     show_info_sizer();
1339     update_sliced_info_sizer();
1340     // update Cut gizmo, if it's open
1341     p->plater->canvas3D()->update_gizmos_on_off_state();
1342     p->plater->canvas3D()->request_extra_frame();
1343 }
1344 
combos_filament()1345 std::vector<PlaterPresetComboBox*>& Sidebar::combos_filament()
1346 {
1347     return p->combos_filament;
1348 }
1349 
get_searcher()1350 Search::OptionsSearcher& Sidebar::get_searcher()
1351 {
1352     return p->searcher;
1353 }
1354 
get_search_line()1355 std::string& Sidebar::get_search_line()
1356 {
1357     return p->searcher.search_string();
1358 }
1359 
1360 // Plater::DropTarget
1361 
1362 class PlaterDropTarget : public wxFileDropTarget
1363 {
1364 public:
PlaterDropTarget(Plater * plater)1365     PlaterDropTarget(Plater* plater) : m_plater(plater) { this->SetDefaultAction(wxDragCopy); }
1366 
1367     virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames);
1368 
1369 private:
1370     Plater* m_plater;
1371 
1372 #if !ENABLE_DRAG_AND_DROP_FIX
1373     static const std::regex pattern_drop;
1374     static const std::regex pattern_gcode_drop;
1375 #endif // !ENABLE_DRAG_AND_DROP_FIX
1376 };
1377 
1378 #if !ENABLE_DRAG_AND_DROP_FIX
1379 const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", std::regex::icase);
1380 const std::regex PlaterDropTarget::pattern_gcode_drop(".*[.](gcode|g)", std::regex::icase);
1381 
1382 enum class LoadType : unsigned char
1383 {
1384     Unknown,
1385     OpenProject,
1386     LoadGeometry,
1387     LoadConfig
1388 };
1389 
1390 class ProjectDropDialog : public DPIDialog
1391 {
1392     wxRadioBox* m_action{ nullptr };
1393 public:
1394     ProjectDropDialog(const std::string& filename);
1395 
get_action() const1396     int get_action() const { return m_action->GetSelection() + 1; }
1397 
1398 protected:
1399     void on_dpi_changed(const wxRect& suggested_rect) override;
1400 };
1401 
ProjectDropDialog(const std::string & filename)1402 ProjectDropDialog::ProjectDropDialog(const std::string& filename)
1403     : DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY,
1404         from_u8((boost::format(_utf8(L("%s - Drop project file"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition,
1405         wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
1406 {
1407     SetFont(wxGetApp().normal_font());
1408 
1409     wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
1410 
1411     const wxString choices[] = { _L("Open as project"),
1412                                  _L("Import geometry only"),
1413                                  _L("Import config only") };
1414 
1415     main_sizer->Add(new wxStaticText(this, wxID_ANY,
1416         _L("Select an action to apply to the file") + ": " + from_u8(filename)), 0, wxEXPAND | wxALL, 10);
1417     m_action = new wxRadioBox(this, wxID_ANY, _L("Action"), wxDefaultPosition, wxDefaultSize,
1418         WXSIZEOF(choices), choices, 0, wxRA_SPECIFY_ROWS);
1419     int action = std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
1420         static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)) - 1;
1421     m_action->SetSelection(action);
1422     main_sizer->Add(m_action, 1, wxEXPAND | wxRIGHT | wxLEFT, 10);
1423 
1424     wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL);
1425     wxCheckBox* check = new wxCheckBox(this, wxID_ANY, _L("Don't show again"));
1426     check->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& evt) {
1427         wxGetApp().app_config->set("show_drop_project_dialog", evt.IsChecked() ? "0" : "1");
1428         });
1429 
1430     bottom_sizer->Add(check, 0, wxEXPAND | wxRIGHT, 5);
1431     bottom_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxLEFT, 5);
1432     main_sizer->Add(bottom_sizer, 0, wxEXPAND | wxALL, 10);
1433 
1434     SetSizer(main_sizer);
1435     main_sizer->SetSizeHints(this);
1436 }
1437 
on_dpi_changed(const wxRect & suggested_rect)1438 void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect)
1439 {
1440     const int em = em_unit();
1441     SetMinSize(wxSize(65 * em, 30 * em));
1442     Fit();
1443     Refresh();
1444 }
1445 #endif // !ENABLE_DRAG_AND_DROP_FIX
1446 
OnDropFiles(wxCoord x,wxCoord y,const wxArrayString & filenames)1447 bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
1448 {
1449 #if !ENABLE_DRAG_AND_DROP_FIX
1450     std::vector<fs::path> paths;
1451 #endif // !ENABLE_DRAG_AND_DROP_FIX
1452 
1453 #ifdef WIN32
1454     // hides the system icon
1455     this->MSWUpdateDragImageOnLeave();
1456 #endif // WIN32
1457 
1458 #if ENABLE_DRAG_AND_DROP_FIX
1459     return (m_plater != nullptr) ? m_plater->load_files(filenames) : false;
1460 #else
1461     // gcode viewer section
1462     if (wxGetApp().is_gcode_viewer()) {
1463         for (const auto& filename : filenames) {
1464             fs::path path(into_path(filename));
1465             if (std::regex_match(path.string(), pattern_gcode_drop))
1466                 paths.push_back(std::move(path));
1467         }
1468 
1469         if (paths.size() > 1) {
1470             wxMessageDialog(static_cast<wxWindow*>(m_plater), _L("You can open only one .gcode file at a time."),
1471                 wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal();
1472             return false;
1473         }
1474         else if (paths.size() == 1) {
1475             m_plater->load_gcode(from_path(paths.front()));
1476             return true;
1477         }
1478         return false;
1479     }
1480 
1481     // editor section
1482     for (const auto &filename : filenames) {
1483         fs::path path(into_path(filename));
1484         if (std::regex_match(path.string(), pattern_drop))
1485             paths.push_back(std::move(path));
1486         else if (std::regex_match(path.string(), pattern_gcode_drop))
1487             start_new_gcodeviewer(&filename);
1488         else
1489             return false;
1490     }
1491     if (paths.empty())
1492         // Likely all paths processed were gcodes, for which a G-code viewer instance has hopefully been started.
1493         return false;
1494 
1495     // searches for project files
1496     for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) {
1497         std::string filename = (*it).filename().string();
1498         if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) {
1499             LoadType load_type = LoadType::Unknown;
1500             if (!m_plater->model().objects.empty()) {
1501                 if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") {
1502                     ProjectDropDialog dlg(filename);
1503                     if (dlg.ShowModal() == wxID_OK) {
1504                         int choice = dlg.get_action();
1505                         load_type = static_cast<LoadType>(choice);
1506                         wxGetApp().app_config->set("drop_project_action", std::to_string(choice));
1507                     }
1508                 }
1509                 else
1510                     load_type = static_cast<LoadType>(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
1511                         static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)));
1512             }
1513             else
1514                 load_type = LoadType::OpenProject;
1515 
1516             if (load_type == LoadType::Unknown)
1517                 return false;
1518 
1519             switch (load_type) {
1520             case LoadType::OpenProject: {
1521                 m_plater->load_project(from_path(*it));
1522                 break;
1523             }
1524             case LoadType::LoadGeometry: {
1525                 Plater::TakeSnapshot snapshot(m_plater, _L("Import Object"));
1526                 std::vector<fs::path> in_paths;
1527                 in_paths.emplace_back(*it);
1528                 m_plater->load_files(in_paths, true, false);
1529                 break;
1530             }
1531             case LoadType::LoadConfig: {
1532                 std::vector<fs::path> in_paths;
1533                 in_paths.emplace_back(*it);
1534                 m_plater->load_files(in_paths, false, true);
1535                 break;
1536             }
1537             }
1538 
1539             return true;
1540         }
1541     }
1542 
1543     // other files
1544     wxString snapshot_label;
1545     assert(!paths.empty());
1546     if (paths.size() == 1) {
1547         snapshot_label = _L("Load File");
1548         snapshot_label += ": ";
1549         snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
1550     }
1551     else {
1552         snapshot_label = _L("Load Files");
1553         snapshot_label += ": ";
1554         snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
1555         for (size_t i = 1; i < paths.size(); ++i) {
1556             snapshot_label += ", ";
1557             snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str());
1558         }
1559     }
1560     Plater::TakeSnapshot snapshot(m_plater, snapshot_label);
1561     m_plater->load_files(paths);
1562 
1563     return true;
1564 #endif // ENABLE_DRAG_AND_DROP_FIX
1565 }
1566 
1567 // State to manage showing after export notifications and device ejecting
1568 enum ExportingStatus{
1569     NOT_EXPORTING,
1570     EXPORTING_TO_REMOVABLE,
1571     EXPORTING_TO_LOCAL
1572 };
1573 
1574 // Plater / private
1575 struct Plater::priv
1576 {
1577     // PIMPL back pointer ("Q-Pointer")
1578     Plater *q;
1579     MainFrame *main_frame;
1580 
1581     // Object popup menu
1582     MenuWithSeparators object_menu;
1583     // Part popup menu
1584     MenuWithSeparators part_menu;
1585     // SLA-Object popup menu
1586     MenuWithSeparators sla_object_menu;
1587     // Default popup menu (when nothing is selected on 3DScene)
1588     MenuWithSeparators default_menu;
1589 
1590     // Removed/Prepended Items according to the view mode
1591     std::vector<wxMenuItem*> items_increase;
1592     std::vector<wxMenuItem*> items_decrease;
1593     std::vector<wxMenuItem*> items_set_number_of_copies;
1594     enum MenuIdentifier {
1595         miObjectFFF=0,
1596         miObjectSLA
1597     };
1598 
1599     // Data
1600     Slic3r::DynamicPrintConfig *config;        // FIXME: leak?
1601     Slic3r::Print               fff_print;
1602     Slic3r::SLAPrint            sla_print;
1603     Slic3r::Model               model;
1604     PrinterTechnology           printer_technology = ptFFF;
1605     Slic3r::GCodeProcessor::Result gcode_result;
1606 
1607     // GUI elements
1608     wxSizer* panel_sizer{ nullptr };
1609     wxPanel* current_panel{ nullptr };
1610     std::vector<wxPanel*> panels;
1611     Sidebar *sidebar;
1612     Bed3D bed;
1613     Camera camera;
1614 #if ENABLE_ENVIRONMENT_MAP
1615     GLTexture environment_texture;
1616 #endif // ENABLE_ENVIRONMENT_MAP
1617     Mouse3DController mouse3d_controller;
1618     View3D* view3D;
1619     GLToolbar view_toolbar;
1620     GLToolbar collapse_toolbar;
1621     Preview *preview;
1622     NotificationManager* notification_manager { nullptr };
1623 
1624     BackgroundSlicingProcess    background_process;
1625     bool suppressed_backround_processing_update { false };
1626 
1627     // Jobs defined inside the group class will be managed so that only one can
1628     // run at a time. Also, the background process will be stopped if a job is
1629     // started. It is up the the plater to ensure that the background slicing
1630     // can't be restarted while a ui job is still running.
1631     class Jobs: public ExclusiveJobGroup
1632     {
1633         priv *m;
1634         size_t m_arrange_id, m_fill_bed_id, m_rotoptimize_id, m_sla_import_id;
1635 
before_start()1636         void before_start() override { m->background_process.stop(); }
1637 
1638     public:
Jobs(priv * _m)1639         Jobs(priv *_m) : m(_m)
1640         {
1641             m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q));
1642             m_fill_bed_id = add_job(std::make_unique<FillBedJob>(m->statusbar(), m->q));
1643             m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q));
1644             m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->statusbar(), m->q));
1645         }
1646 
arrange()1647         void arrange()
1648         {
1649             m->take_snapshot(_(L("Arrange")));
1650             start(m_arrange_id);
1651         }
1652 
fill_bed()1653         void fill_bed()
1654         {
1655             m->take_snapshot(_(L("Fill bed")));
1656             start(m_fill_bed_id);
1657         }
1658 
optimize_rotation()1659         void optimize_rotation()
1660         {
1661             m->take_snapshot(_(L("Optimize Rotation")));
1662             start(m_rotoptimize_id);
1663         }
1664 
import_sla_arch()1665         void import_sla_arch()
1666         {
1667             m->take_snapshot(_(L("Import SLA archive")));
1668             start(m_sla_import_id);
1669         }
1670 
1671     } m_ui_jobs;
1672 
1673     bool                        delayed_scene_refresh;
1674     std::string                 delayed_error_message;
1675 
1676     wxTimer                     background_process_timer;
1677 
1678     std::string                 label_btn_export;
1679     std::string                 label_btn_send;
1680 
1681 #if ENABLE_RENDER_STATISTICS
1682     bool                        show_render_statistic_dialog{ false };
1683 #endif // ENABLE_RENDER_STATISTICS
1684 
1685     static const std::regex pattern_bundle;
1686     static const std::regex pattern_3mf;
1687     static const std::regex pattern_zip_amf;
1688     static const std::regex pattern_any_amf;
1689     static const std::regex pattern_prusa;
1690 
1691     priv(Plater *q, MainFrame *main_frame);
1692     ~priv();
1693 
1694     enum class UpdateParams {
1695         FORCE_FULL_SCREEN_REFRESH          = 1,
1696         FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
1697         POSTPONE_VALIDATION_ERROR_MESSAGE  = 4,
1698     };
1699     void update(unsigned int flags = 0);
1700     void select_view(const std::string& direction);
1701     void select_view_3D(const std::string& name);
1702     void select_next_view_3D();
1703 
is_preview_shownSlic3r::GUI::Plater::priv1704     bool is_preview_shown() const { return current_panel == preview; }
is_preview_loadedSlic3r::GUI::Plater::priv1705     bool is_preview_loaded() const { return preview->is_loaded(); }
is_view3D_shownSlic3r::GUI::Plater::priv1706     bool is_view3D_shown() const { return current_panel == view3D; }
1707 
are_view3D_labels_shownSlic3r::GUI::Plater::priv1708     bool are_view3D_labels_shown() const { return (current_panel == view3D) && view3D->get_canvas3d()->are_labels_shown(); }
show_view3D_labelsSlic3r::GUI::Plater::priv1709     void show_view3D_labels(bool show) { if (current_panel == view3D) view3D->get_canvas3d()->show_labels(show); }
1710 
is_sidebar_collapsedSlic3r::GUI::Plater::priv1711     bool is_sidebar_collapsed() const   { return sidebar->is_collapsed(); }
1712     void collapse_sidebar(bool collapse);
1713 
is_view3D_layers_editing_enabledSlic3r::GUI::Plater::priv1714     bool is_view3D_layers_editing_enabled() const { return (current_panel == view3D) && view3D->get_canvas3d()->is_layers_editing_enabled(); }
1715 
1716     void set_current_canvas_as_dirty();
1717     GLCanvas3D* get_current_canvas3D();
1718     void unbind_canvas_event_handlers();
1719     void reset_canvas_volumes();
1720 
1721     bool init_view_toolbar();
1722     bool init_collapse_toolbar();
1723 
1724     void update_preview_bottom_toolbar();
1725     void update_preview_moves_slider();
1726     void enable_preview_moves_slider(bool enable);
1727 
1728     void reset_gcode_toolpaths();
1729 
1730     void reset_all_gizmos();
1731     void update_ui_from_settings(bool apply_free_camera_correction = true);
1732     void update_main_toolbar_tooltips();
1733     std::shared_ptr<ProgressStatusBar> statusbar();
1734     std::string get_config(const std::string &key) const;
1735     BoundingBoxf bed_shape_bb() const;
1736     BoundingBox scaled_bed_shape_bb() const;
1737 
1738     std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool used_inches = false);
1739     std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
1740     wxString get_export_file(GUI::FileType file_type);
1741 
1742     const Selection& get_selection() const;
1743     Selection& get_selection();
1744     int get_selected_object_idx() const;
1745     int get_selected_volume_idx() const;
1746     void selection_changed();
1747     void object_list_changed();
1748 
1749     void select_all();
1750     void deselect_all();
1751     void remove(size_t obj_idx);
1752     void delete_object_from_model(size_t obj_idx);
1753     void reset();
1754     void mirror(Axis axis);
1755     void split_object();
1756     void split_volume();
1757     void scale_selection_to_fit_print_volume();
1758 
1759     // Return the active Undo/Redo stack. It may be either the main stack or the Gimzo stack.
undo_redo_stackSlic3r::GUI::Plater::priv1760     Slic3r::UndoRedo::Stack& undo_redo_stack() { assert(m_undo_redo_stack_active != nullptr); return *m_undo_redo_stack_active; }
undo_redo_stack_mainSlic3r::GUI::Plater::priv1761     Slic3r::UndoRedo::Stack& undo_redo_stack_main() { return m_undo_redo_stack_main; }
1762     void enter_gizmos_stack();
1763     void leave_gizmos_stack();
1764 
1765     void take_snapshot(const std::string& snapshot_name);
take_snapshotSlic3r::GUI::Plater::priv1766     void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); }
1767     int  get_active_snapshot_index();
1768 
1769     void undo();
1770     void redo();
1771     void undo_redo_to(size_t time_to_load);
1772 
suppress_snapshotsSlic3r::GUI::Plater::priv1773     void suppress_snapshots()   { this->m_prevent_snapshots++; }
allow_snapshotsSlic3r::GUI::Plater::priv1774     void allow_snapshots()      { this->m_prevent_snapshots--; }
1775 
background_processing_enabledSlic3r::GUI::Plater::priv1776     bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; }
1777     void update_print_volume_state();
1778     void schedule_background_process();
1779     // Update background processing thread from the current config and Model.
1780     enum UpdateBackgroundProcessReturnState {
1781         // update_background_process() reports, that the Print / SLAPrint was updated in a way,
1782         // that the background process was invalidated and it needs to be re-run.
1783         UPDATE_BACKGROUND_PROCESS_RESTART = 1,
1784         // update_background_process() reports, that the Print / SLAPrint was updated in a way,
1785         // that a scene needs to be refreshed (you should call _3DScene::reload_scene(canvas3Dwidget, false))
1786         UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE = 2,
1787         // update_background_process() reports, that the Print / SLAPrint is invalid, and the error message
1788         // was sent to the status line.
1789         UPDATE_BACKGROUND_PROCESS_INVALID = 4,
1790         // Restart even if the background processing is disabled.
1791         UPDATE_BACKGROUND_PROCESS_FORCE_RESTART = 8,
1792         // Restart for G-code (or SLA zip) export or upload.
1793         UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT = 16,
1794     };
1795     // returns bit mask of UpdateBackgroundProcessReturnState
1796     unsigned int update_background_process(bool force_validation = false, bool postpone_error_messages = false);
1797     // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
1798     bool restart_background_process(unsigned int state);
1799     // returns bit mask of UpdateBackgroundProcessReturnState
1800     unsigned int update_restart_background_process(bool force_scene_update, bool force_preview_update);
show_delayed_error_messageSlic3r::GUI::Plater::priv1801 	void show_delayed_error_message() {
1802 		if (!this->delayed_error_message.empty()) {
1803 			std::string msg = std::move(this->delayed_error_message);
1804 			this->delayed_error_message.clear();
1805 			GUI::show_error(this->q, msg);
1806 		}
1807 	}
1808     void export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job);
1809     void reload_from_disk();
1810     void reload_all_from_disk();
1811     void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
1812 
1813     void set_current_panel(wxPanel* panel);
1814 
1815     void on_select_preset(wxCommandEvent&);
1816     void on_slicing_update(SlicingStatusEvent&);
1817     void on_slicing_completed(wxCommandEvent&);
1818     void on_process_completed(SlicingProcessCompletedEvent&);
1819 	void on_export_began(wxCommandEvent&);
1820     void on_layer_editing_toggled(bool enable);
1821 	void on_slicing_began();
1822 
1823 	void clear_warnings();
1824 	void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid);
1825     // Update notification manager with the current state of warnings produced by the background process (slicing).
1826 	void actualize_slicing_warnings(const PrintBase &print);
1827 	// Displays dialog window with list of warnings.
1828 	// Returns true if user clicks OK.
1829 	// Returns true if current_warnings vector is empty without showning the dialog
1830 	bool warnings_dialog();
1831 
1832     void on_action_add(SimpleEvent&);
1833     void on_action_split_objects(SimpleEvent&);
1834     void on_action_split_volumes(SimpleEvent&);
1835     void on_action_layersediting(SimpleEvent&);
1836 
1837     void on_object_select(SimpleEvent&);
1838     void on_right_click(RBtnEvent&);
1839     void on_wipetower_moved(Vec3dEvent&);
1840     void on_wipetower_rotated(Vec3dEvent&);
1841     void on_update_geometry(Vec3dsEvent<2>&);
1842     void on_3dcanvas_mouse_dragging_finished(SimpleEvent&);
1843 
1844     void update_object_menu();
1845     void show_action_buttons(const bool is_ready_to_slice) const;
1846 
1847     // Set the bed shape to a single closed 2D polygon(array of two element arrays),
1848     // triangulate the bed and store the triangles into m_bed.m_triangles,
1849     // fills the m_bed.m_grid_lines and sets m_bed.m_origin.
1850     // Sets m_bed.m_polygon to limit the object placement.
1851     void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false);
1852 
1853     bool can_delete() const;
1854     bool can_delete_all() const;
1855     bool can_increase_instances() const;
1856     bool can_decrease_instances() const;
1857     bool can_split_to_objects() const;
1858     bool can_split_to_volumes() const;
1859     bool can_arrange() const;
1860     bool can_layers_editing() const;
1861     bool can_fix_through_netfabb() const;
1862     bool can_set_instance_to_object() const;
1863     bool can_mirror() const;
1864     bool can_reload_from_disk() const;
1865 
1866     void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
1867     void generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
1868 
1869     void msw_rescale_object_menu();
1870 
1871     void bring_instance_forward() const;
1872 
1873     // returns the path to project file with the given extension (none if extension == wxEmptyString)
1874     // extension should contain the leading dot, i.e.: ".3mf"
1875     wxString get_project_filename(const wxString& extension = wxEmptyString) const;
1876     void set_project_filename(const wxString& filename);
1877 
1878     // Caching last value of show_action_buttons parameter for show_action_buttons(), so that a callback which does not know this state will not override it.
1879     mutable bool    			ready_to_slice = { false };
1880     // Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes.
1881     ExportingStatus             exporting_status { NOT_EXPORTING };
1882     std::string                 last_output_path;
1883     std::string                 last_output_dir_path;
inside_snapshot_captureSlic3r::GUI::Plater::priv1884     bool                        inside_snapshot_capture() { return m_prevent_snapshots != 0; }
1885 	bool                        process_completed_with_error { false };
1886 private:
1887     bool init_object_menu();
1888     bool init_common_menu(wxMenu* menu, const bool is_part = false);
1889     bool complit_init_object_menu();
1890     bool complit_init_sla_object_menu();
1891     bool complit_init_part_menu();
1892 
1893     bool can_split() const;
1894     bool layers_height_allowed() const;
1895 
1896     void update_fff_scene();
1897     void update_sla_scene();
1898     void undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator it_snapshot);
1899     void update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool temp_snapshot_was_taken = false);
1900 
1901     // path to project file stored with no extension
1902     wxString 					m_project_filename;
1903     Slic3r::UndoRedo::Stack 	m_undo_redo_stack_main;
1904     Slic3r::UndoRedo::Stack 	m_undo_redo_stack_gizmos;
1905     Slic3r::UndoRedo::Stack    *m_undo_redo_stack_active = &m_undo_redo_stack_main;
1906     int                         m_prevent_snapshots = 0;     /* Used for avoid of excess "snapshoting".
1907                                                               * Like for "delete selected" or "set numbers of copies"
1908                                                               * we should call tack_snapshot just ones
1909                                                               * instead of calls for each action separately
1910                                                               * */
1911     std::string 				m_last_fff_printer_profile_name;
1912     std::string 				m_last_sla_printer_profile_name;
1913 
1914 	// vector of all warnings generated by last slicing
1915 	std::vector<std::pair<Slic3r::PrintStateBase::Warning, size_t>> current_warnings;
1916 	bool show_warning_dialog { false };
1917 
1918 };
1919 
1920 const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
1921 const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase);
1922 const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase);
1923 const std::regex Plater::priv::pattern_any_amf(".*[.](amf|amf[.]xml|zip[.]amf)", std::regex::icase);
1924 const std::regex Plater::priv::pattern_prusa(".*prusa", std::regex::icase);
1925 
priv(Plater * q,MainFrame * main_frame)1926 Plater::priv::priv(Plater *q, MainFrame *main_frame)
1927     : q(q)
1928     , main_frame(main_frame)
1929     , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({
1930         "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance",
1931         "brim_width", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material",
1932         "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle",
1933         "extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology",
1934         // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor.
1935         "layer_height", "first_layer_height", "min_layer_height", "max_layer_height",
1936         "brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers",
1937         "support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers"
1938         }))
1939     , sidebar(new Sidebar(q))
1940     , m_ui_jobs(this)
1941     , delayed_scene_refresh(false)
1942     , view_toolbar(GLToolbar::Radio, "View")
1943     , collapse_toolbar(GLToolbar::Normal, "Collapse")
1944     , m_project_filename(wxEmptyString)
1945 {
1946     this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font());
1947 
1948     background_process.set_fff_print(&fff_print);
1949     background_process.set_sla_print(&sla_print);
1950     background_process.set_gcode_result(&gcode_result);
1951     background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
__anon2efd02221002(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) 1952         {
1953             std::packaged_task<void(ThumbnailsList&, const Vec2ds&, bool, bool, bool, bool)> task([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) {
1954                 generate_thumbnails(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background);
1955                 });
1956             std::future<void> result = task.get_future();
1957             wxTheApp->CallAfter([&]() { task(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background); });
1958             result.wait();
1959         });
1960     background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
1961     background_process.set_finished_event(EVT_PROCESS_COMPLETED);
1962 	background_process.set_export_began_event(EVT_EXPORT_BEGAN);
1963     // Default printer technology for default config.
1964     background_process.select_technology(this->printer_technology);
1965     // Register progress callback from the Print class to the Plater.
1966 
__anon2efd02221302(const Slic3r::PrintBase::SlicingStatus &status) 1967     auto statuscb = [this](const Slic3r::PrintBase::SlicingStatus &status) {
1968         wxQueueEvent(this->q, new Slic3r::SlicingStatusEvent(EVT_SLICING_UPDATE, 0, status));
1969     };
1970     fff_print.set_status_callback(statuscb);
1971     sla_print.set_status_callback(statuscb);
1972     this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this);
1973 
1974     view3D = new View3D(q, &model, config, &background_process);
__anon2efd02221402() 1975     preview = new Preview(q, &model, config, &background_process, &gcode_result, [this]() { schedule_background_process(); });
1976 
1977 #ifdef __APPLE__
1978     // set default view_toolbar icons size equal to GLGizmosManager::Default_Icons_Size
1979     view_toolbar.set_icons_size(GLGizmosManager::Default_Icons_Size);
1980 #endif // __APPLE__
1981 
1982     panels.push_back(view3D);
1983     panels.push_back(preview);
1984 
1985     this->background_process_timer.SetOwner(this->q, 0);
1986     this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt)
__anon2efd02221502(wxTimerEvent &evt) 1987     {
1988         if (!this->suppressed_backround_processing_update)
1989             this->update_restart_background_process(false, false);
1990     });
1991 
1992     update();
1993 
1994     auto *hsizer = new wxBoxSizer(wxHORIZONTAL);
1995     panel_sizer = new wxBoxSizer(wxHORIZONTAL);
1996     panel_sizer->Add(view3D, 1, wxEXPAND | wxALL, 0);
1997     panel_sizer->Add(preview, 1, wxEXPAND | wxALL, 0);
1998     hsizer->Add(panel_sizer, 1, wxEXPAND | wxALL, 0);
1999     hsizer->Add(sidebar, 0, wxEXPAND | wxLEFT | wxRIGHT, 0);
2000     q->SetSizer(hsizer);
2001 
2002     init_object_menu();
2003 
2004     // Events:
2005 
2006     if (wxGetApp().is_editor()) {
2007         // Preset change event
2008         sidebar->Bind(wxEVT_COMBOBOX, &priv::on_select_preset, this);
__anon2efd02221602(wxEvent&) 2009         sidebar->Bind(EVT_OBJ_LIST_OBJECT_SELECT, [this](wxEvent&) { priv::selection_changed(); });
__anon2efd02221702(SimpleEvent&) 2010         sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
2011     }
2012 
2013     wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas();
2014 
2015     if (wxGetApp().is_editor()) {
2016         // 3DScene events:
__anon2efd02221802(SimpleEvent&) 2017         view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
2018         view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this);
2019         view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this);
__anon2efd02221902(SimpleEvent&) 2020         view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); });
__anon2efd02221a02(SimpleEvent&) 2021         view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
__anon2efd02221b02(SimpleEvent&) 2022         view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
__anon2efd02221c02(SimpleEvent&) 2023         view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
2024         view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int>& evt)
__anon2efd02221d02(Event<int>& evt) 2025             { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
__anon2efd02221e02(SimpleEvent&) 2026         view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
__anon2efd02221f02(SimpleEvent&) 2027         view3D_canvas->Bind(EVT_GLCANVAS_FORCE_UPDATE, [this](SimpleEvent&) { update(); });
2028         view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this);
2029         view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this);
__anon2efd02222002(SimpleEvent&) 2030         view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); });
__anon2efd02222102(SimpleEvent&) 2031         view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); });
__anon2efd02222202(Event<bool>& evt) 2032         view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event<bool>& evt) { this->sidebar->enable_buttons(evt.data); });
2033         view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this);
2034         view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this);
__anon2efd02222302(SimpleEvent&) 2035         view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
__anon2efd02222402(SimpleEvent&) 2036         view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); });
__anon2efd02222502(SimpleEvent&) 2037         view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); });
__anon2efd02222602(SimpleEvent&) 2038         view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); });
__anon2efd02222702(SimpleEvent&) 2039         view3D_canvas->Bind(EVT_GLCANVAS_COLLAPSE_SIDEBAR, [this](SimpleEvent&) { this->q->collapse_sidebar(!this->q->is_sidebar_collapsed());  });
__anon2efd02222802(SimpleEvent&) 2040         view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); });
__anon2efd02222902(Event<float>& evt) 2041         view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event<float>& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); });
__anon2efd02222a02(HeightProfileSmoothEvent& evt) 2042         view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); });
__anon2efd02222b02(SimpleEvent&) 2043         view3D_canvas->Bind(EVT_GLCANVAS_RELOAD_FROM_DISK, [this](SimpleEvent&) { this->reload_all_from_disk(); });
2044 
2045         // 3DScene/Toolbar:
2046         view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
__anon2efd02222c02(SimpleEvent&) 2047         view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
__anon2efd02222d02(SimpleEvent&) 2048         view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
__anon2efd02222e02(SimpleEvent&) 2049         view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
__anon2efd02222f02(SimpleEvent&) 2050         view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); });
__anon2efd02223002(SimpleEvent&) 2051         view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
__anon2efd02223102(SimpleEvent&) 2052         view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); });
__anon2efd02223202(SimpleEvent&) 2053         view3D_canvas->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); });
2054         view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this);
2055         view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this);
2056         view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this);
2057     }
__anon2efd02223302(SimpleEvent&) 2058     view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); });
2059 
2060     // Preview events:
__anon2efd02223402(SimpleEvent&) 2061     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
__anon2efd02223502(SimpleEvent&) 2062     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); });
2063     if (wxGetApp().is_editor()) {
__anon2efd02223602(SimpleEvent&) 2064         preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
__anon2efd02223702(SimpleEvent&) 2065         preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_COLLAPSE_SIDEBAR, [this](SimpleEvent&) { this->q->collapse_sidebar(!this->q->is_sidebar_collapsed());  });
2066     }
__anon2efd02223802(wxKeyEvent& evt) 2067     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_JUMP_TO, [this](wxKeyEvent& evt) { preview->jump_layers_slider(evt); });
2068 #if ENABLE_ARROW_KEYS_WITH_SLIDERS
__anon2efd02223902(wxKeyEvent& evt) 2069     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_SLIDERS, [this](wxKeyEvent& evt) {
2070         preview->move_layers_slider(evt);
2071         preview->move_moves_slider(evt);
2072         });
2073 #else
__anon2efd02223a02(wxKeyEvent& evt) 2074     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, [this](wxKeyEvent& evt) { preview->move_layers_slider(evt); });
2075 #endif // ENABLE_ARROW_KEYS_WITH_SLIDERS
__anon2efd02223b02(wxKeyEvent& evt) 2076     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_layers_slider(evt); });
2077     if (wxGetApp().is_gcode_viewer())
__anon2efd02223c02(SimpleEvent&) 2078         preview->Bind(EVT_GLCANVAS_RELOAD_FROM_DISK, [this](SimpleEvent&) { this->q->reload_gcode_from_disk(); });
2079 
2080     if (wxGetApp().is_editor()) {
2081         q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
2082         q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
2083         q->Bind(EVT_EXPORT_BEGAN, &priv::on_export_began, this);
__anon2efd02223d02(SimpleEvent&) 2084         q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); });
__anon2efd02223e02(SimpleEvent&) 2085         q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); });
2086     }
2087 
2088     // Drop target:
2089     q->SetDropTarget(new PlaterDropTarget(q));   // if my understanding is right, wxWindow takes the owenership
2090     q->Layout();
2091 
2092     set_current_panel(wxGetApp().is_editor() ? static_cast<wxPanel*>(view3D) : static_cast<wxPanel*>(preview));
2093     if (wxGetApp().is_gcode_viewer())
2094         preview->hide_layers_slider();
2095 
2096     // updates camera type from .ini file
2097     camera.enable_update_config_on_type_change(true);
2098     camera.set_type(get_config("use_perspective_camera"));
2099 
2100     // Load the 3DConnexion device database.
2101     mouse3d_controller.load_config(*wxGetApp().app_config);
2102 	// Start the background thread to detect and connect to a HID device (Windows and Linux).
2103 	// Connect to a 3DConnextion driver (OSX).
2104     mouse3d_controller.init();
2105 #ifdef _WIN32
2106     // Register an USB HID (Human Interface Device) attach event. evt contains Win32 path to the USB device containing VID, PID and other info.
2107     // This event wakes up the Mouse3DController's background thread to enumerate HID devices, if the VID of the callback event
2108     // is one of the 3D Mouse vendors (3DConnexion or Logitech).
__anon2efd02223f02(HIDDeviceAttachedEvent &evt) 2109     this->q->Bind(EVT_HID_DEVICE_ATTACHED, [this](HIDDeviceAttachedEvent &evt) {
2110     	mouse3d_controller.device_attached(evt.data);
2111         });
2112 #if ENABLE_CTRL_M_ON_WINDOWS
__anon2efd02224002(HIDDeviceAttachedEvent& evt) 2113     this->q->Bind(EVT_HID_DEVICE_DETACHED, [this](HIDDeviceAttachedEvent& evt) {
2114         mouse3d_controller.device_detached(evt.data);
2115         });
2116 #endif // ENABLE_CTRL_M_ON_WINDOWS
2117 #endif /* _WIN32 */
2118 
2119 	notification_manager = new NotificationManager(this->q);
2120     if (wxGetApp().is_editor()) {
__anon2efd02224102(EjectDriveNotificationClickedEvent&) 2121         this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); });
__anon2efd02224202(ExportGcodeNotificationClickedEvent&) 2122         this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); });
__anon2efd02224302(PresetUpdateAvailableClickedEvent&) 2123         this->q->Bind(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, [this](PresetUpdateAvailableClickedEvent&) {  wxGetApp().get_preset_updater()->on_update_notification_confirm(); });
__anon2efd02224402(RemovableDriveEjectEvent &evt) 2124 	    this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) {
2125 		    if (evt.data.second) {
2126 			    this->show_action_buttons(this->ready_to_slice);
2127                 notification_manager->close_notification_of_type(NotificationType::ExportFinished);
2128                 notification_manager->push_notification(NotificationType::CustomNotification,
2129                                                         NotificationManager::NotificationLevel::RegularNotification,
2130                                                         format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path)
2131                     );
2132             } else {
2133                 notification_manager->push_notification(NotificationType::CustomNotification,
2134                                                         NotificationManager::NotificationLevel::ErrorNotification,
2135                                                         format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path)
2136                     );
2137             }
2138 	    });
__anon2efd02224502(RemovableDrivesChangedEvent &) 2139         this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) {
2140 		    this->show_action_buttons(this->ready_to_slice);
2141 		    // Close notification ExportingFinished but only if last export was to removable
2142 		    notification_manager->device_ejected();
2143 	    });
2144         // Start the background thread and register this window as a target for update events.
2145         wxGetApp().removable_drive_manager()->init(this->q);
2146 #ifdef _WIN32
2147         // Trigger enumeration of removable media on Win32 notification.
__anon2efd02224602(VolumeAttachedEvent &evt) 2148         this->q->Bind(EVT_VOLUME_ATTACHED, [this](VolumeAttachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); });
__anon2efd02224702(VolumeDetachedEvent &evt) 2149         this->q->Bind(EVT_VOLUME_DETACHED, [this](VolumeDetachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); });
2150 #endif /* _WIN32 */
2151     }
2152 
2153     // Initialize the Undo / Redo stack with a first snapshot.
2154     this->take_snapshot(_L("New Project"));
2155 
2156 #if ENABLE_DRAG_AND_DROP_FIX
__anon2efd02224802(LoadFromOtherInstanceEvent& evt) 2157     this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent& evt) {
2158         BOOST_LOG_TRIVIAL(trace) << "Received load from other instance event.";
2159         wxArrayString input_files;
2160         for (size_t i = 0; i < evt.data.size(); ++i) {
2161             input_files.push_back(from_u8(evt.data[i].string()));
2162         }
2163         wxGetApp().mainframe->Raise();
2164         this->q->load_files(input_files);
2165     });
2166 #else
__anon2efd02224902(LoadFromOtherInstanceEvent &evt) 2167     this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent &evt) {
2168 		BOOST_LOG_TRIVIAL(trace) << "Received load from other instance event.";
2169         this->load_files(evt.data, true, true);
2170     });
2171 #endif // ENABLE_DRAG_AND_DROP_FIX
__anon2efd02224a02(InstanceGoToFrontEvent &) 2172     this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) {
2173         bring_instance_forward();
2174     });
2175 	wxGetApp().other_instance_message_handler()->init(this->q);
2176 
2177     // collapse sidebar according to saved value
2178     if (wxGetApp().is_editor()) {
2179         bool is_collapsed = wxGetApp().app_config->get("collapsed_sidebar") == "1";
2180         sidebar->collapse(is_collapsed);
2181     }
2182 }
2183 
~priv()2184 Plater::priv::~priv()
2185 {
2186     if (config != nullptr)
2187         delete config;
2188 }
2189 
update(unsigned int flags)2190 void Plater::priv::update(unsigned int flags)
2191 {
2192     // the following line, when enabled, causes flickering on NVIDIA graphics cards
2193 //    wxWindowUpdateLocker freeze_guard(q);
2194     if (get_config("autocenter") == "1") {
2195         // auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
2196         // const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values);
2197         // const BoundingBox bed_shape_bb = bed_shape.bounding_box();
2198         const Vec2d& bed_center = bed_shape_bb().center();
2199         model.center_instances_around_point(bed_center);
2200     }
2201 
2202     unsigned int update_status = 0;
2203     if (this->printer_technology == ptSLA || (flags & (unsigned int)UpdateParams::FORCE_BACKGROUND_PROCESSING_UPDATE))
2204         // Update the SLAPrint from the current Model, so that the reload_scene()
2205         // pulls the correct data.
2206         update_status = this->update_background_process(false, flags & (unsigned int)UpdateParams::POSTPONE_VALIDATION_ERROR_MESSAGE);
2207     this->view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH);
2208     this->preview->reload_print();
2209     if (this->printer_technology == ptSLA)
2210         this->restart_background_process(update_status);
2211     else
2212         this->schedule_background_process();
2213 }
2214 
select_view(const std::string & direction)2215 void Plater::priv::select_view(const std::string& direction)
2216 {
2217     if (current_panel == view3D)
2218         view3D->select_view(direction);
2219     else if (current_panel == preview)
2220         preview->select_view(direction);
2221 }
2222 
select_view_3D(const std::string & name)2223 void Plater::priv::select_view_3D(const std::string& name)
2224 {
2225     if (name == "3D")
2226         set_current_panel(view3D);
2227     else if (name == "Preview")
2228         set_current_panel(preview);
2229 
2230     wxGetApp().update_ui_from_settings(false);
2231 }
2232 
select_next_view_3D()2233 void Plater::priv::select_next_view_3D()
2234 {
2235     if (current_panel == view3D)
2236         set_current_panel(preview);
2237     else if (current_panel == preview)
2238         set_current_panel(view3D);
2239 }
2240 
collapse_sidebar(bool collapse)2241 void Plater::priv::collapse_sidebar(bool collapse)
2242 {
2243     sidebar->collapse(collapse);
2244 
2245     // Now update the tooltip in the toolbar.
2246     std::string new_tooltip = collapse
2247                               ? _utf8(L("Expand sidebar"))
2248                               : _utf8(L("Collapse sidebar"));
2249     new_tooltip += " [Shift+Tab]";
2250     int id = collapse_toolbar.get_item_id("collapse_sidebar");
2251     collapse_toolbar.set_tooltip(id, new_tooltip);
2252 }
2253 
2254 
reset_all_gizmos()2255 void Plater::priv::reset_all_gizmos()
2256 {
2257     view3D->get_canvas3d()->reset_all_gizmos();
2258 }
2259 
2260 // Called after the Preferences dialog is closed and the program settings are saved.
2261 // Update the UI based on the current preferences.
update_ui_from_settings(bool apply_free_camera_correction)2262 void Plater::priv::update_ui_from_settings(bool apply_free_camera_correction)
2263 {
2264     camera.set_type(wxGetApp().app_config->get("use_perspective_camera"));
2265     if (apply_free_camera_correction && wxGetApp().app_config->get("use_free_camera") != "1")
2266         camera.recover_from_free_camera();
2267 
2268     view3D->get_canvas3d()->update_ui_from_settings();
2269     preview->get_canvas3d()->update_ui_from_settings();
2270 
2271     sidebar->update_ui_from_settings();
2272 }
2273 
2274 // Called after the print technology was changed.
2275 // Update the tooltips for "Switch to Settings" button in maintoolbar
update_main_toolbar_tooltips()2276 void Plater::priv::update_main_toolbar_tooltips()
2277 {
2278     view3D->get_canvas3d()->update_tooltip_for_settings_item_in_main_toolbar();
2279 }
2280 
statusbar()2281 std::shared_ptr<ProgressStatusBar> Plater::priv::statusbar()
2282 {
2283     return main_frame->m_statusbar;
2284 }
2285 
get_config(const std::string & key) const2286 std::string Plater::priv::get_config(const std::string &key) const
2287 {
2288     return wxGetApp().app_config->get(key);
2289 }
2290 
bed_shape_bb() const2291 BoundingBoxf Plater::priv::bed_shape_bb() const
2292 {
2293     BoundingBox bb = scaled_bed_shape_bb();
2294     return BoundingBoxf(unscale(bb.min), unscale(bb.max));
2295 }
2296 
scaled_bed_shape_bb() const2297 BoundingBox Plater::priv::scaled_bed_shape_bb() const
2298 {
2299     const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
2300     const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values);
2301     return bed_shape.bounding_box();
2302 }
2303 
load_files(const std::vector<fs::path> & input_files,bool load_model,bool load_config,bool imperial_units)2304 std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool imperial_units/* = false*/)
2305 {
2306     if (input_files.empty()) { return std::vector<size_t>(); }
2307 
2308     auto *nozzle_dmrs = config->opt<ConfigOptionFloats>("nozzle_diameter");
2309 
2310     bool one_by_one = input_files.size() == 1 || printer_technology == ptSLA || nozzle_dmrs->values.size() <= 1;
2311     if (! one_by_one) {
2312         for (const auto &path : input_files) {
2313             if (std::regex_match(path.string(), pattern_bundle)) {
2314                 one_by_one = true;
2315                 break;
2316             }
2317         }
2318     }
2319 
2320     const auto loading = _L("Loading") + dots;
2321     wxProgressDialog dlg(loading, "", 100, q, wxPD_AUTO_HIDE);
2322     dlg.Pulse();
2323 
2324     auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model();
2325     std::vector<size_t> obj_idxs;
2326 
2327     for (size_t i = 0; i < input_files.size(); ++i) {
2328         const auto &path = input_files[i];
2329         const auto filename = path.filename();
2330         const auto dlg_info = _L("Loading file") + ": " + from_path(filename);
2331         dlg.Update(static_cast<int>(100.0f * static_cast<float>(i) / static_cast<float>(input_files.size())), dlg_info);
2332 
2333         const bool type_3mf = std::regex_match(path.string(), pattern_3mf);
2334         const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf);
2335         const bool type_any_amf = !type_3mf && std::regex_match(path.string(), pattern_any_amf);
2336         const bool type_prusa = std::regex_match(path.string(), pattern_prusa);
2337 
2338         Slic3r::Model model;
2339         bool is_project_file = type_prusa;
2340         try {
2341             if (type_3mf || type_zip_amf) {
2342                 DynamicPrintConfig config;
2343                 {
2344                     DynamicPrintConfig config_loaded;
2345                     ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable };
2346                     model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, &config_substitutions, only_if(load_config, Model::LoadAttribute::CheckVersion));
2347                     if (load_config && !config_loaded.empty()) {
2348                         // Based on the printer technology field found in the loaded config, select the base for the config,
2349                         PrinterTechnology printer_technology = Preset::printer_technology(config_loaded);
2350 
2351                         // We can't to load SLA project if there is at least one multi-part object on the bed
2352                         if (printer_technology == ptSLA)
2353                         {
2354                             const ModelObjectPtrs& objects = q->model().objects;
2355                             for (auto object : objects)
2356                                 if (object->volumes.size() > 1)
2357                                 {
2358                                     Slic3r::GUI::show_info(nullptr,
2359                                         _L("You cannot load SLA project with a multi-part object on the bed") + "\n\n" +
2360                                         _L("Please check your object list before preset changing."),
2361                                         _L("Attention!"));
2362                                     return obj_idxs;
2363                                 }
2364                         }
2365 
2366                         config.apply(printer_technology == ptFFF ?
2367                             static_cast<const ConfigBase&>(FullPrintConfig::defaults()) :
2368                             static_cast<const ConfigBase&>(SLAFullPrintConfig::defaults()));
2369                         // and place the loaded config over the base.
2370                         config += std::move(config_loaded);
2371                     }
2372                     if (! config_substitutions.empty())
2373                         show_substitutions_info(config_substitutions.substitutions, filename.string());
2374 
2375                     this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
2376                 }
2377 
2378                 if (load_config)
2379                 {
2380                     if (!config.empty()) {
2381                         Preset::normalize(config);
2382                         wxGetApp().preset_bundle->load_config_model(filename.string(), std::move(config));
2383                         if (printer_technology == ptFFF)
2384                             CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, &wxGetApp().preset_bundle->project_config);
2385                         // For exporting from the amf/3mf we shouldn't check printer_presets for the containing information about "Print Host upload"
2386                         wxGetApp().load_current_presets(false);
2387                         is_project_file = true;
2388                     }
2389                     wxGetApp().app_config->update_config_dir(path.parent_path().string());
2390                 }
2391             }
2392             else {
2393                 model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion));
2394                 for (auto obj : model.objects)
2395                     if (obj->name.empty())
2396                         obj->name = fs::path(obj->input_file).filename().string();
2397             }
2398         } catch (const ConfigurationError &e) {
2399             std::string message = GUI::format(_L("Failed loading file \"%1%\" due to an invalid configuration."), filename.string()) + "\n\n" + e.what();
2400             GUI::show_error(q, message);
2401             continue;
2402         } catch (const std::exception &e) {
2403             GUI::show_error(q, e.what());
2404             continue;
2405         }
2406 
2407         if (load_model)
2408         {
2409             // The model should now be initialized
2410 
2411             auto convert_from_imperial_units = [](Model& model, bool only_small_volumes) {
2412                 model.convert_from_imperial_units(only_small_volumes);
2413 //                wxGetApp().app_config->set("use_inches", "1");
2414                 wxGetApp().sidebar().update_ui_from_settings();
2415             };
2416 
2417             if (!is_project_file) {
2418                 if (imperial_units)
2419                     // Convert even if the object is big.
2420                     convert_from_imperial_units(model, false);
2421                 else if (model.looks_like_imperial_units()) {
2422                     wxMessageDialog msg_dlg(q, format_wxstr(_L(
2423                         "Some object(s) in file %s looks like saved in inches.\n"
2424                         "Should I consider them as a saved in inches and convert them?"), from_path(filename)) + "\n",
2425                         _L("The object appears to be saved in inches"), wxICON_WARNING | wxYES | wxNO);
2426                     if (msg_dlg.ShowModal() == wxID_YES)
2427                         //FIXME up-scale only the small parts?
2428                         convert_from_imperial_units(model, true);
2429                 }
2430 
2431                 if (model.looks_like_multipart_object()) {
2432                     wxMessageDialog msg_dlg(q, _L(
2433                         "This file contains several objects positioned at multiple heights.\n"
2434                         "Instead of considering them as multiple objects, should I consider\n"
2435                         "this file as a single object having multiple parts?") + "\n",
2436                         _L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO);
2437                     if (msg_dlg.ShowModal() == wxID_YES) {
2438                         model.convert_multipart_object(nozzle_dmrs->values.size());
2439                     }
2440                 }
2441             }
2442             else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) {
2443                 wxMessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n",
2444                     _L("Detected advanced data"), wxICON_WARNING | wxYES | wxNO);
2445                 if (msg_dlg.ShowModal() == wxID_YES)
2446                 {
2447                     Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
2448                     view3D->set_as_dirty();
2449                 }
2450                 else
2451                     return obj_idxs;
2452             }
2453 
2454             for (ModelObject* model_object : model.objects) {
2455                 if (!type_3mf && !type_zip_amf)
2456                     model_object->center_around_origin(false);
2457                 model_object->ensure_on_bed();
2458             }
2459 
2460             // check multi-part object adding for the SLA-printing
2461             if (printer_technology == ptSLA)
2462             {
2463                 for (auto obj : model.objects)
2464                     if ( obj->volumes.size()>1 ) {
2465                         Slic3r::GUI::show_error(nullptr,
2466                             format_wxstr(_L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part"),
2467                                         from_path(filename)));
2468                         return obj_idxs;
2469                     }
2470             }
2471 
2472             if (one_by_one) {
2473                 auto loaded_idxs = load_model_objects(model.objects);
2474                 obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
2475             } else {
2476                 // This must be an .stl or .obj file, which may contain a maximum of one volume.
2477                 for (const ModelObject* model_object : model.objects) {
2478                     new_model->add_object(*model_object);
2479                 }
2480             }
2481         }
2482     }
2483 
2484     if (new_model != nullptr && new_model->objects.size() > 1) {
2485         wxMessageDialog msg_dlg(q, _L(
2486                 "Multiple objects were loaded for a multi-material printer.\n"
2487                 "Instead of considering them as multiple objects, should I consider\n"
2488                 "these files to represent a single object having multiple parts?") + "\n",
2489                 _L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO);
2490         if (msg_dlg.ShowModal() == wxID_YES) {
2491             new_model->convert_multipart_object(nozzle_dmrs->values.size());
2492         }
2493 
2494         auto loaded_idxs = load_model_objects(new_model->objects);
2495         obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
2496     }
2497 
2498     if (load_model)
2499     {
2500         wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().string());
2501         // XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames...
2502         statusbar()->set_status_text(_L("Loaded"));
2503     }
2504 
2505     // automatic selection of added objects
2506     if (!obj_idxs.empty() && (view3D != nullptr))
2507     {
2508         // update printable state for new volumes on canvas3D
2509         wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs);
2510 
2511         Selection& selection = view3D->get_canvas3d()->get_selection();
2512         selection.clear();
2513         for (size_t idx : obj_idxs)
2514         {
2515             selection.add_object((unsigned int)idx, false);
2516         }
2517 
2518         if (view3D->get_canvas3d()->get_gizmos_manager().is_enabled())
2519             // this is required because the selected object changed and the flatten on face an sla support gizmos need to be updated accordingly
2520             view3D->get_canvas3d()->update_gizmos_on_off_state();
2521     }
2522 
2523     return obj_idxs;
2524 }
2525 
2526 // #define AUTOPLACEMENT_ON_LOAD
2527 
load_model_objects(const ModelObjectPtrs & model_objects)2528 std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects)
2529 {
2530     const BoundingBoxf bed_shape = bed_shape_bb();
2531     const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast<double>(), 1.0) - 2.0 * Vec3d::Ones();
2532 
2533 #ifndef AUTOPLACEMENT_ON_LOAD
2534     // bool need_arrange = false;
2535 #endif /* AUTOPLACEMENT_ON_LOAD */
2536     bool scaled_down = false;
2537     std::vector<size_t> obj_idxs;
2538     unsigned int obj_count = model.objects.size();
2539 
2540 #ifdef AUTOPLACEMENT_ON_LOAD
2541     ModelInstancePtrs new_instances;
2542 #endif /* AUTOPLACEMENT_ON_LOAD */
2543     for (ModelObject *model_object : model_objects) {
2544         auto *object = model.add_object(*model_object);
2545         std::string object_name = object->name.empty() ? fs::path(object->input_file).filename().string() : object->name;
2546         obj_idxs.push_back(obj_count++);
2547 
2548         if (model_object->instances.empty()) {
2549 #ifdef AUTOPLACEMENT_ON_LOAD
2550             object->center_around_origin();
2551             new_instances.emplace_back(object->add_instance());
2552 #else /* AUTOPLACEMENT_ON_LOAD */
2553             // if object has no defined position(s) we need to rearrange everything after loading
2554             // need_arrange = true;
2555              // add a default instance and center object around origin
2556             object->center_around_origin();  // also aligns object to Z = 0
2557             ModelInstance* instance = object->add_instance();
2558             instance->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -object->origin_translation(2)));
2559 #endif /* AUTOPLACEMENT_ON_LOAD */
2560         }
2561 
2562         const Vec3d size = object->bounding_box().size();
2563         const Vec3d ratio = size.cwiseQuotient(bed_size);
2564         const double max_ratio = std::max(ratio(0), ratio(1));
2565         if (max_ratio > 10000) {
2566             // the size of the object is too big -> this could lead to overflow when moving to clipper coordinates,
2567             // so scale down the mesh
2568             double inv = 1. / max_ratio;
2569             object->scale_mesh_after_creation(Vec3d(inv, inv, inv));
2570             object->origin_translation = Vec3d::Zero();
2571             object->center_around_origin();
2572             scaled_down = true;
2573         } else if (max_ratio > 5) {
2574             const Vec3d inverse = 1.0 / max_ratio * Vec3d::Ones();
2575             for (ModelInstance *instance : object->instances) {
2576                 instance->set_scaling_factor(inverse);
2577             }
2578             scaled_down = true;
2579         }
2580 
2581         object->ensure_on_bed();
2582     }
2583 
2584 #ifdef AUTOPLACEMENT_ON_LOAD
2585     // FIXME distance should be a config value /////////////////////////////////
2586     auto min_obj_distance = static_cast<coord_t>(6/SCALING_FACTOR);
2587     const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
2588     assert(bed_shape_opt);
2589     auto& bedpoints = bed_shape_opt->values;
2590     Polyline bed; bed.points.reserve(bedpoints.size());
2591     for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
2592 
2593     std::pair<bool, GLCanvas3D::WipeTowerInfo> wti = view3D->get_canvas3d()->get_wipe_tower_info();
2594 
2595     arr::find_new_position(model, new_instances, min_obj_distance, bed, wti);
2596 
2597     // it remains to move the wipe tower:
2598     view3D->get_canvas3d()->arrange_wipe_tower(wti);
2599 
2600 #endif /* AUTOPLACEMENT_ON_LOAD */
2601 
2602     if (scaled_down) {
2603         GUI::show_info(q,
2604             _L("Your object appears to be too large, so it was automatically scaled down to fit your print bed."),
2605             _L("Object too large?"));
2606     }
2607 
2608     for (const size_t idx : obj_idxs) {
2609         wxGetApp().obj_list()->add_object_to_list(idx);
2610     }
2611 
2612     update();
2613     object_list_changed();
2614 
2615     this->schedule_background_process();
2616 
2617     return obj_idxs;
2618 }
2619 
get_export_file(GUI::FileType file_type)2620 wxString Plater::priv::get_export_file(GUI::FileType file_type)
2621 {
2622     wxString wildcard;
2623     switch (file_type) {
2624         case FT_STL:
2625         case FT_AMF:
2626         case FT_3MF:
2627         case FT_GCODE:
2628         case FT_OBJ:
2629             wildcard = file_wildcards(file_type);
2630         break;
2631         default:
2632             wildcard = file_wildcards(FT_MODEL);
2633         break;
2634     }
2635 
2636     // Update printbility state of each of the ModelInstances.
2637     this->update_print_volume_state();
2638 
2639     const Selection& selection = get_selection();
2640     int obj_idx = selection.get_object_idx();
2641 
2642     fs::path output_file;
2643     if (file_type == FT_3MF)
2644         // for 3mf take the path from the project filename, if any
2645         output_file = into_path(get_project_filename(".3mf"));
2646 
2647     if (output_file.empty())
2648     {
2649         // first try to get the file name from the current selection
2650         if ((0 <= obj_idx) && (obj_idx < (int)this->model.objects.size()))
2651             output_file = this->model.objects[obj_idx]->get_export_filename();
2652 
2653         if (output_file.empty())
2654             // Find the file name of the first printable object.
2655             output_file = this->model.propose_export_file_name_and_path();
2656 
2657         if (output_file.empty() && !model.objects.empty())
2658             // Find the file name of the first object.
2659             output_file = this->model.objects[0]->get_export_filename();
2660     }
2661 
2662     wxString dlg_title;
2663     switch (file_type) {
2664         case FT_STL:
2665         {
2666             output_file.replace_extension("stl");
2667             dlg_title = _L("Export STL file:");
2668             break;
2669         }
2670         case FT_AMF:
2671         {
2672             // XXX: Problem on OS X with double extension?
2673             output_file.replace_extension("zip.amf");
2674             dlg_title = _L("Export AMF file:");
2675             break;
2676         }
2677         case FT_3MF:
2678         {
2679             output_file.replace_extension("3mf");
2680             dlg_title = _L("Save file as:");
2681             break;
2682         }
2683         case FT_OBJ:
2684         {
2685             output_file.replace_extension("obj");
2686             dlg_title = _L("Export OBJ file:");
2687             break;
2688         }
2689         default: break;
2690     }
2691 
2692     wxFileDialog dlg(q, dlg_title,
2693         from_path(output_file.parent_path()), from_path(output_file.filename()),
2694         wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
2695 
2696     if (dlg.ShowModal() != wxID_OK)
2697         return wxEmptyString;
2698 
2699     wxString out_path = dlg.GetPath();
2700     fs::path path(into_path(out_path));
2701     wxGetApp().app_config->update_last_output_dir(path.parent_path().string());
2702 
2703     return out_path;
2704 }
2705 
get_selection() const2706 const Selection& Plater::priv::get_selection() const
2707 {
2708     return view3D->get_canvas3d()->get_selection();
2709 }
2710 
get_selection()2711 Selection& Plater::priv::get_selection()
2712 {
2713     return view3D->get_canvas3d()->get_selection();
2714 }
2715 
get_selected_object_idx() const2716 int Plater::priv::get_selected_object_idx() const
2717 {
2718     int idx = get_selection().get_object_idx();
2719     return ((0 <= idx) && (idx < 1000)) ? idx : -1;
2720 }
2721 
get_selected_volume_idx() const2722 int Plater::priv::get_selected_volume_idx() const
2723 {
2724     auto& selection = get_selection();
2725     int idx = selection.get_object_idx();
2726     if ((0 > idx) || (idx > 1000))
2727         return-1;
2728     const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
2729     if (model.objects[idx]->volumes.size() > 1)
2730         return v->volume_idx();
2731     return -1;
2732 }
2733 
selection_changed()2734 void Plater::priv::selection_changed()
2735 {
2736     // if the selection is not valid to allow for layer editing, we need to turn off the tool if it is running
2737     bool enable_layer_editing = layers_height_allowed();
2738     if (!enable_layer_editing && view3D->is_layers_editing_enabled()) {
2739         SimpleEvent evt(EVT_GLTOOLBAR_LAYERSEDITING);
2740         on_action_layersediting(evt);
2741     }
2742 
2743     // forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears)
2744     view3D->render();
2745 }
2746 
object_list_changed()2747 void Plater::priv::object_list_changed()
2748 {
2749     const bool export_in_progress = this->background_process.is_export_scheduled(); // || ! send_gcode_file.empty());
2750     // XXX: is this right?
2751     const bool model_fits = view3D->check_volumes_outside_state() == ModelInstancePVS_Inside;
2752 
2753     sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits);
2754 }
2755 
select_all()2756 void Plater::priv::select_all()
2757 {
2758     view3D->select_all();
2759     this->sidebar->obj_list()->update_selections();
2760 }
2761 
deselect_all()2762 void Plater::priv::deselect_all()
2763 {
2764     view3D->deselect_all();
2765 }
2766 
remove(size_t obj_idx)2767 void Plater::priv::remove(size_t obj_idx)
2768 {
2769     if (view3D->is_layers_editing_enabled())
2770         view3D->enable_layers_editing(false);
2771 
2772     model.delete_object(obj_idx);
2773     update();
2774     // Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
2775     sidebar->obj_list()->delete_object_from_list(obj_idx);
2776     object_list_changed();
2777 }
2778 
2779 
delete_object_from_model(size_t obj_idx)2780 void Plater::priv::delete_object_from_model(size_t obj_idx)
2781 {
2782     wxString snapshot_label = _L("Delete Object");
2783     if (! model.objects[obj_idx]->name.empty())
2784         snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str());
2785     Plater::TakeSnapshot snapshot(q, snapshot_label);
2786     model.delete_object(obj_idx);
2787     update();
2788     object_list_changed();
2789 }
2790 
reset()2791 void Plater::priv::reset()
2792 {
2793     Plater::TakeSnapshot snapshot(q, _L("Reset Project"));
2794 
2795 	clear_warnings();
2796 
2797     set_project_filename(wxEmptyString);
2798 
2799     if (view3D->is_layers_editing_enabled())
2800         view3D->enable_layers_editing(false);
2801 
2802     reset_gcode_toolpaths();
2803     gcode_result.reset();
2804 
2805     // Stop and reset the Print content.
2806     this->background_process.reset();
2807     model.clear_objects();
2808     update();
2809     // Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
2810     sidebar->obj_list()->delete_all_objects_from_list();
2811     object_list_changed();
2812 
2813     // The hiding of the slicing results, if shown, is not taken care by the background process, so we do it here
2814     this->sidebar->show_sliced_info_sizer(false);
2815 
2816     model.custom_gcode_per_print_z.gcodes.clear();
2817 }
2818 
mirror(Axis axis)2819 void Plater::priv::mirror(Axis axis)
2820 {
2821     view3D->mirror_selection(axis);
2822 }
2823 
find_new_position(const ModelInstancePtrs & instances,coord_t min_d)2824 void Plater::find_new_position(const ModelInstancePtrs &instances,
2825                                      coord_t min_d)
2826 {
2827     arrangement::ArrangePolygons movable, fixed;
2828 
2829     for (const ModelObject *mo : p->model.objects)
2830         for (const ModelInstance *inst : mo->instances) {
2831             auto it = std::find(instances.begin(), instances.end(), inst);
2832             auto arrpoly = inst->get_arrange_polygon();
2833 
2834             if (it == instances.end())
2835                 fixed.emplace_back(std::move(arrpoly));
2836             else
2837                 movable.emplace_back(std::move(arrpoly));
2838         }
2839 
2840     if (auto wt = get_wipe_tower_arrangepoly(*this))
2841         fixed.emplace_back(*wt);
2842 
2843     arrangement::arrange(movable, fixed, get_bed_shape(*config()),
2844                          arrangement::ArrangeParams{min_d});
2845 
2846     for (size_t i = 0; i < instances.size(); ++i)
2847         if (movable[i].bed_idx == 0)
2848             instances[i]->apply_arrange_result(movable[i].translation.cast<double>(),
2849                                                movable[i].rotation);
2850 }
2851 
split_object()2852 void Plater::priv::split_object()
2853 {
2854     int obj_idx = get_selected_object_idx();
2855     if (obj_idx == -1)
2856         return;
2857 
2858     // we clone model object because split_object() adds the split volumes
2859     // into the same model object, thus causing duplicates when we call load_model_objects()
2860     Model new_model = model;
2861     ModelObject* current_model_object = new_model.objects[obj_idx];
2862 
2863     if (current_model_object->volumes.size() > 1)
2864     {
2865         Slic3r::GUI::warning_catcher(q, _L("The selected object can't be split because it contains more than one volume/material."));
2866         return;
2867     }
2868 
2869     wxBusyCursor wait;
2870     ModelObjectPtrs new_objects;
2871     current_model_object->split(&new_objects);
2872     if (new_objects.size() == 1)
2873         Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one part."));
2874     else
2875     {
2876         Plater::TakeSnapshot snapshot(q, _L("Split to Objects"));
2877 
2878         unsigned int counter = 1;
2879         for (ModelObject* m : new_objects)
2880             m->name = current_model_object->name + "_" + std::to_string(counter++);
2881 
2882         remove(obj_idx);
2883 
2884         // load all model objects at once, otherwise the plate would be rearranged after each one
2885         // causing original positions not to be kept
2886         std::vector<size_t> idxs = load_model_objects(new_objects);
2887 
2888         // select newly added objects
2889         for (size_t idx : idxs)
2890         {
2891             get_selection().add_object((unsigned int)idx, false);
2892         }
2893     }
2894 }
2895 
split_volume()2896 void Plater::priv::split_volume()
2897 {
2898     wxGetApp().obj_list()->split();
2899 }
2900 
scale_selection_to_fit_print_volume()2901 void Plater::priv::scale_selection_to_fit_print_volume()
2902 {
2903     this->view3D->get_canvas3d()->get_selection().scale_to_fit_print_volume(*config);
2904 }
2905 
schedule_background_process()2906 void Plater::priv::schedule_background_process()
2907 {
2908     delayed_error_message.clear();
2909     // Trigger the timer event after 0.5s
2910     this->background_process_timer.Start(500, wxTIMER_ONE_SHOT);
2911     // Notify the Canvas3D that something has changed, so it may invalidate some of the layer editing stuff.
2912     this->view3D->get_canvas3d()->set_config(this->config);
2913 }
2914 
update_print_volume_state()2915 void Plater::priv::update_print_volume_state()
2916 {
2917     BoundingBox     bed_box_2D = get_extents(Polygon::new_scale(this->config->opt<ConfigOptionPoints>("bed_shape")->values));
2918     BoundingBoxf3   print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(this->config->opt_float("max_print_height"))));
2919     // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced.
2920     print_volume.min(2) = -1e10;
2921     this->q->model().update_print_volume_state(print_volume);
2922 }
2923 
2924 // Update background processing thread from the current config and Model.
2925 // Returns a bitmask of UpdateBackgroundProcessReturnState.
update_background_process(bool force_validation,bool postpone_error_messages)2926 unsigned int Plater::priv::update_background_process(bool force_validation, bool postpone_error_messages)
2927 {
2928     // bitmap of enum UpdateBackgroundProcessReturnState
2929     unsigned int return_state = 0;
2930 
2931     // If the update_background_process() was not called by the timer, kill the timer,
2932     // so the update_restart_background_process() will not be called again in vain.
2933     this->background_process_timer.Stop();
2934     // Update the "out of print bed" state of ModelInstances.
2935     this->update_print_volume_state();
2936     // Apply new config to the possibly running background task.
2937     bool               was_running = this->background_process.running();
2938     Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config());
2939 
2940     // Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
2941     if (view3D->is_layers_editing_enabled())
2942         view3D->get_wxglcanvas()->Refresh();
2943 
2944     if (invalidated == Print::APPLY_STATUS_INVALIDATED) {
2945         // Some previously calculated data on the Print was invalidated.
2946         // Hide the slicing results, as the current slicing status is no more valid.
2947         this->sidebar->show_sliced_info_sizer(false);
2948         // Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared.
2949         // Otherwise they will be just refreshed.
2950         if (this->preview != nullptr) {
2951             // If the preview is not visible, the following line just invalidates the preview,
2952             // but the G-code paths or SLA preview are calculated first once the preview is made visible.
2953             reset_gcode_toolpaths();
2954             this->preview->reload_print();
2955         }
2956         // In FDM mode, we need to reload the 3D scene because of the wipe tower preview box.
2957         // In SLA mode, we need to reload the 3D scene every time to show the support structures.
2958         if (this->printer_technology == ptSLA || (this->printer_technology == ptFFF && this->config->opt_bool("wipe_tower")))
2959             return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE;
2960     }
2961 
2962     if ((invalidated != Print::APPLY_STATUS_UNCHANGED || force_validation) && ! this->background_process.empty()) {
2963 		// The delayed error message is no more valid.
2964 		this->delayed_error_message.clear();
2965 		// The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors.
2966         std::string err = this->background_process.validate();
2967         if (err.empty()) {
2968 			notification_manager->set_all_slicing_errors_gray(true);
2969             if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled())
2970                 return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
2971         } else {
2972 			// The print is not valid.
2973 			// Show error as notification.
2974             notification_manager->push_slicing_error_notification(err);
2975             return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
2976         }
2977     } else if (! this->delayed_error_message.empty()) {
2978     	// Reusing the old state.
2979         return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
2980     }
2981 
2982 	//actualizate warnings
2983 	if (invalidated != Print::APPLY_STATUS_UNCHANGED) {
2984 		actualize_slicing_warnings(*this->background_process.current_print());
2985 		show_warning_dialog = false;
2986 		process_completed_with_error = false;
2987 	}
2988 
2989     if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() &&
2990         (return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) {
2991         // The background processing was killed and it will not be restarted.
2992         wxCommandEvent evt(EVT_PROCESS_COMPLETED);
2993         evt.SetInt(-1);
2994         // Post the "canceled" callback message, so that it will be processed after any possible pending status bar update messages.
2995         wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
2996     }
2997 
2998     if ((return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
2999     {
3000         // Validation of the background data failed.
3001         const wxString invalid_str = _L("Invalid data");
3002         for (auto btn : {ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport})
3003             sidebar->set_btn_label(btn, invalid_str);
3004         process_completed_with_error = true;
3005     }
3006     else
3007     {
3008         // Background data is valid.
3009         if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ||
3010             (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 )
3011             this->statusbar()->set_status_text(_L("Ready to slice"));
3012 
3013         sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export));
3014         sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send));
3015 
3016         const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ?
3017                                       _L("Slicing") + dots : _L("Slice now");
3018         sidebar->set_btn_label(ActionButtonType::abReslice, slice_string);
3019 
3020         if (background_process.finished())
3021             show_action_buttons(false);
3022         else if (!background_process.empty() &&
3023                  !background_process.running()) /* Do not update buttons if background process is running
3024                                                  * This condition is important for SLA mode especially,
3025                                                  * when this function is called several times during calculations
3026                                                  * */
3027             show_action_buttons(true);
3028     }
3029 
3030     return return_state;
3031 }
3032 
3033 // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
restart_background_process(unsigned int state)3034 bool Plater::priv::restart_background_process(unsigned int state)
3035 {
3036     if (m_ui_jobs.is_any_running()) {
3037         // Avoid a race condition
3038         return false;
3039     }
3040 
3041     if ( ! this->background_process.empty() &&
3042          (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 &&
3043          ( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) ||
3044            (state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 ||
3045            (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) {
3046         // The print is valid and it can be started.
3047         if (this->background_process.start()) {
3048             this->statusbar()->set_cancel_callback([this]() {
3049                 this->statusbar()->set_status_text(_L("Cancelling"));
3050                 this->background_process.stop();
3051             });
3052 			if (!show_warning_dialog)
3053 				on_slicing_began();
3054             return true;
3055         }
3056     }
3057     return false;
3058 }
3059 
export_gcode(fs::path output_path,bool output_path_on_removable_media,PrintHostJob upload_job)3060 void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job)
3061 {
3062     wxCHECK_RET(!(output_path.empty() && upload_job.empty()), "export_gcode: output_path and upload_job empty");
3063 
3064     if (model.objects.empty())
3065         return;
3066 
3067     if (background_process.is_export_scheduled()) {
3068         GUI::show_error(q, _L("Another export job is currently running."));
3069         return;
3070     }
3071 
3072     // bitmask of UpdateBackgroundProcessReturnState
3073     unsigned int state = update_background_process(true);
3074     if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
3075         view3D->reload_scene(false);
3076 
3077     if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
3078         return;
3079 
3080     show_warning_dialog = true;
3081     if (! output_path.empty()) {
3082         background_process.schedule_export(output_path.string(), output_path_on_removable_media);
3083     } else {
3084         background_process.schedule_upload(std::move(upload_job));
3085     }
3086 
3087     // If the SLA processing of just a single object's supports is running, restart slicing for the whole object.
3088     this->background_process.set_task(PrintBase::TaskParams());
3089     this->restart_background_process(priv::UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT);
3090 }
3091 
update_restart_background_process(bool force_update_scene,bool force_update_preview)3092 unsigned int Plater::priv::update_restart_background_process(bool force_update_scene, bool force_update_preview)
3093 {
3094     // bitmask of UpdateBackgroundProcessReturnState
3095     unsigned int state = this->update_background_process(false);
3096     if (force_update_scene || (state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0)
3097         view3D->reload_scene(false);
3098 
3099     if (force_update_preview)
3100         this->preview->reload_print();
3101     this->restart_background_process(state);
3102     return state;
3103 }
3104 
update_fff_scene()3105 void Plater::priv::update_fff_scene()
3106 {
3107     if (this->preview != nullptr)
3108         this->preview->reload_print();
3109     // In case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth:
3110     view3D->reload_scene(true);
3111 
3112 }
3113 
update_sla_scene()3114 void Plater::priv::update_sla_scene()
3115 {
3116     // Update the SLAPrint from the current Model, so that the reload_scene()
3117     // pulls the correct data.
3118     delayed_scene_refresh = false;
3119     this->update_restart_background_process(true, true);
3120 }
3121 
reload_from_disk()3122 void Plater::priv::reload_from_disk()
3123 {
3124     Plater::TakeSnapshot snapshot(q, _L("Reload from disk"));
3125 
3126     const Selection& selection = get_selection();
3127 
3128     if (selection.is_wipe_tower())
3129         return;
3130 
3131     // struct to hold selected ModelVolumes by their indices
3132     struct SelectedVolume
3133     {
3134         int object_idx;
3135         int volume_idx;
3136 
3137         // operators needed by std::algorithms
3138         bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); }
3139         bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); }
3140     };
3141     std::vector<SelectedVolume> selected_volumes;
3142 
3143     // collects selected ModelVolumes
3144     const std::set<unsigned int>& selected_volumes_idxs = selection.get_volume_idxs();
3145     for (unsigned int idx : selected_volumes_idxs)
3146     {
3147         const GLVolume* v = selection.get_volume(idx);
3148         int v_idx = v->volume_idx();
3149         if (v_idx >= 0)
3150         {
3151             int o_idx = v->object_idx();
3152             if ((0 <= o_idx) && (o_idx < (int)model.objects.size()))
3153                 selected_volumes.push_back({ o_idx, v_idx });
3154         }
3155     }
3156     std::sort(selected_volumes.begin(), selected_volumes.end());
3157     selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end());
3158 
3159     // collects paths of files to load
3160     std::vector<fs::path> input_paths;
3161     std::vector<fs::path> missing_input_paths;
3162     for (const SelectedVolume& v : selected_volumes)
3163     {
3164         const ModelObject* object = model.objects[v.object_idx];
3165         const ModelVolume* volume = object->volumes[v.volume_idx];
3166 
3167         if (!volume->source.input_file.empty())
3168         {
3169             if (fs::exists(volume->source.input_file))
3170                 input_paths.push_back(volume->source.input_file);
3171             else
3172                 missing_input_paths.push_back(volume->source.input_file);
3173         }
3174         else if (!object->input_file.empty() && volume->is_model_part() && !volume->name.empty())
3175             missing_input_paths.push_back(volume->name);
3176     }
3177 
3178     std::sort(missing_input_paths.begin(), missing_input_paths.end());
3179     missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end());
3180 
3181     while (!missing_input_paths.empty())
3182     {
3183         // ask user to select the missing file
3184         fs::path search = missing_input_paths.back();
3185         wxString title = _L("Please select the file to reload");
3186 #if defined(__APPLE__)
3187         title += " (" + from_u8(search.filename().string()) + ")";
3188 #endif // __APPLE__
3189         title += ":";
3190         wxFileDialog dialog(q, title, "", from_u8(search.filename().string()), file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
3191         if (dialog.ShowModal() != wxID_OK)
3192             return;
3193 
3194         std::string sel_filename_path = dialog.GetPath().ToUTF8().data();
3195         std::string sel_filename = fs::path(sel_filename_path).filename().string();
3196         if (boost::algorithm::iequals(search.filename().string(), sel_filename))
3197         {
3198             input_paths.push_back(sel_filename_path);
3199             missing_input_paths.pop_back();
3200 
3201             fs::path sel_path = fs::path(sel_filename_path).remove_filename().string();
3202 
3203             std::vector<fs::path>::iterator it = missing_input_paths.begin();
3204             while (it != missing_input_paths.end())
3205             {
3206                 // try to use the path of the selected file with all remaining missing files
3207                 fs::path repathed_filename = sel_path;
3208                 repathed_filename /= it->filename();
3209                 if (fs::exists(repathed_filename))
3210                 {
3211                     input_paths.push_back(repathed_filename.string());
3212                     it = missing_input_paths.erase(it);
3213                 }
3214                 else
3215                     ++it;
3216             }
3217         }
3218         else
3219         {
3220             wxString message = _L("It is not allowed to change the file to reload") + " (" + from_u8(search.filename().string()) + ").\n" + _L("Do you want to retry") + " ?";
3221             wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION);
3222             if (dlg.ShowModal() != wxID_YES)
3223                 return;
3224         }
3225     }
3226 
3227     std::sort(input_paths.begin(), input_paths.end());
3228     input_paths.erase(std::unique(input_paths.begin(), input_paths.end()), input_paths.end());
3229 
3230     std::vector<wxString> fail_list;
3231 
3232     // load one file at a time
3233     for (size_t i = 0; i < input_paths.size(); ++i)
3234     {
3235         const auto& path = input_paths[i].string();
3236 
3237         wxBusyCursor wait;
3238         wxBusyInfo info(_L("Reload from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas());
3239 
3240         Model new_model;
3241         try
3242         {
3243             new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances);
3244             for (ModelObject* model_object : new_model.objects)
3245             {
3246                 model_object->center_around_origin();
3247                 model_object->ensure_on_bed();
3248             }
3249         }
3250         catch (std::exception&)
3251         {
3252             // error while loading
3253             return;
3254         }
3255 
3256         // update the selected volumes whose source is the current file
3257         for (const SelectedVolume& sel_v : selected_volumes)
3258         {
3259             ModelObject* old_model_object = model.objects[sel_v.object_idx];
3260             ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx];
3261 
3262             bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string());
3263             bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string());
3264             if (has_source || has_name)
3265             {
3266                 int new_volume_idx = -1;
3267                 int new_object_idx = -1;
3268                 if (has_source)
3269                 {
3270                     // take idxs from source
3271                     new_volume_idx = old_volume->source.volume_idx;
3272                     new_object_idx = old_volume->source.object_idx;
3273                 }
3274                 else
3275                 {
3276                     // take idxs from the 1st matching volume
3277                     for (size_t o = 0; o < new_model.objects.size(); ++o)
3278                     {
3279                         ModelObject* obj = new_model.objects[o];
3280                         bool found = false;
3281                         for (size_t v = 0; v < obj->volumes.size(); ++v)
3282                         {
3283                             if (obj->volumes[v]->name == old_volume->name)
3284                             {
3285                                 new_volume_idx = (int)v;
3286                                 new_object_idx = (int)o;
3287                                 found = true;
3288                                 break;
3289                             }
3290                         }
3291                         if (found)
3292                             break;
3293                     }
3294                 }
3295 
3296                 if ((new_object_idx < 0) && ((int)new_model.objects.size() <= new_object_idx))
3297                 {
3298                     fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name));
3299                     continue;
3300                 }
3301                 ModelObject* new_model_object = new_model.objects[new_object_idx];
3302                 if ((new_volume_idx < 0) && ((int)new_model.objects.size() <= new_volume_idx))
3303                 {
3304                     fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name));
3305                     continue;
3306                 }
3307                 if (new_volume_idx < (int)new_model_object->volumes.size())
3308                 {
3309                     old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]);
3310                     ModelVolume* new_volume = old_model_object->volumes.back();
3311                     new_volume->set_new_unique_id();
3312                     new_volume->config.apply(old_volume->config);
3313                     new_volume->set_type(old_volume->type());
3314                     new_volume->set_material_id(old_volume->material_id());
3315                     new_volume->set_transformation(old_volume->get_transformation() * old_volume->source.transform);
3316                     new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
3317                     if (old_volume->source.is_converted_from_inches)
3318                         new_volume->convert_from_imperial_units();
3319                     std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back());
3320                     old_model_object->delete_volume(old_model_object->volumes.size() - 1);
3321                     old_model_object->ensure_on_bed();
3322 
3323                     sla::reproject_points_and_holes(old_model_object);
3324                 }
3325             }
3326         }
3327     }
3328 
3329     if (!fail_list.empty())
3330     {
3331         wxString message = _L("Unable to reload:") + "\n";
3332         for (const wxString& s : fail_list)
3333         {
3334             message += s + "\n";
3335         }
3336         wxMessageDialog dlg(q, message, _L("Error during reload"), wxOK | wxOK_DEFAULT | wxICON_WARNING);
3337         dlg.ShowModal();
3338     }
3339 
3340     // update 3D scene
3341     update();
3342 
3343     // new GLVolumes have been created at this point, so update their printable state
3344     for (size_t i = 0; i < model.objects.size(); ++i)
3345     {
3346         view3D->get_canvas3d()->update_instance_printable_state_for_object(i);
3347     }
3348 }
3349 
reload_all_from_disk()3350 void Plater::priv::reload_all_from_disk()
3351 {
3352     if (model.objects.empty())
3353         return;
3354 
3355     Plater::TakeSnapshot snapshot(q, _L("Reload all from disk"));
3356     Plater::SuppressSnapshots suppress(q);
3357 
3358     Selection& selection = get_selection();
3359     Selection::IndicesList curr_idxs = selection.get_volume_idxs();
3360     // reload from disk uses selection
3361     select_all();
3362     reload_from_disk();
3363     // restore previous selection
3364     selection.clear();
3365     for (unsigned int idx : curr_idxs)
3366     {
3367         selection.add(idx, false);
3368     }
3369 }
3370 
fix_through_netfabb(const int obj_idx,const int vol_idx)3371 void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/)
3372 {
3373     if (obj_idx < 0)
3374         return;
3375 
3376     // Do not fix anything when a gizmo is open. There might be issues with updates
3377     // and what is worse, the snapshot time would refer to the internal stack.
3378     if (q->canvas3D()->get_gizmos_manager().get_current_type() != GLGizmosManager::Undefined) {
3379         notification_manager->push_notification(
3380                     NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
3381                     NotificationManager::NotificationLevel::RegularNotification,
3382                     _u8L("ERROR: Please close all manipulators available from "
3383                          "the left toolbar before fixing the mesh."));
3384         return;
3385     }
3386 
3387     // size_t snapshot_time = undo_redo_stack().active_snapshot_time();
3388     Plater::TakeSnapshot snapshot(q, _L("Fix through NetFabb"));
3389 
3390     ModelObject* mo = model.objects[obj_idx];
3391 
3392     // If there are custom supports/seams, remove them. Fixed mesh
3393     // may be different and they would make no sense.
3394     bool paint_removed = false;
3395     for (ModelVolume* mv : mo->volumes) {
3396         paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty();
3397         mv->supported_facets.clear();
3398         mv->seam_facets.clear();
3399     }
3400     if (paint_removed) {
3401         // snapshot_time is captured by copy so the lambda knows where to undo/redo to.
3402         notification_manager->push_notification(
3403                     NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
3404                     NotificationManager::NotificationLevel::RegularNotification,
3405                     _u8L("Custom supports and seams were removed after repairing the mesh."));
3406 //                    _u8L("Undo the repair"),
3407 //                    [this, snapshot_time](wxEvtHandler*){
3408 //                        // Make sure the snapshot is still available and that
3409 //                        // we are in the main stack and not in a gizmo-stack.
3410 //                        if (undo_redo_stack().has_undo_snapshot(snapshot_time)
3411 //                         && q->canvas3D()->get_gizmos_manager().get_current() == nullptr)
3412 //                            undo_redo_to(snapshot_time);
3413 //                        else
3414 //                            notification_manager->push_notification(
3415 //                                NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
3416 //                                NotificationManager::NotificationLevel::RegularNotification,
3417 //                                _u8L("Cannot undo to before the mesh repair!"));
3418 //                        return true;
3419 //                    });
3420     }
3421 
3422     fix_model_by_win10_sdk_gui(*mo, vol_idx);
3423     sla::reproject_points_and_holes(mo);
3424     this->update();
3425     this->object_list_changed();
3426     this->schedule_background_process();
3427 }
3428 
set_current_panel(wxPanel * panel)3429 void Plater::priv::set_current_panel(wxPanel* panel)
3430 {
3431     if (std::find(panels.begin(), panels.end(), panel) == panels.end())
3432         return;
3433 
3434 #ifdef __WXMAC__
3435     bool force_render = (current_panel != nullptr);
3436 #endif // __WXMAC__
3437 
3438     if (current_panel == panel)
3439         return;
3440 
3441     wxPanel* old_panel = current_panel;
3442     current_panel = panel;
3443     // to reduce flickering when changing view, first set as visible the new current panel
3444     for (wxPanel* p : panels) {
3445         if (p == current_panel) {
3446 #ifdef __WXMAC__
3447             // On Mac we need also to force a render to avoid flickering when changing view
3448             if (force_render) {
3449                 if (p == view3D)
3450                     dynamic_cast<View3D*>(p)->get_canvas3d()->render();
3451                 else if (p == preview)
3452                     dynamic_cast<Preview*>(p)->get_canvas3d()->render();
3453             }
3454 #endif // __WXMAC__
3455             p->Show();
3456         }
3457     }
3458     // then set to invisible the other
3459     for (wxPanel* p : panels) {
3460         if (p != current_panel)
3461             p->Hide();
3462     }
3463 
3464     panel_sizer->Layout();
3465 
3466     if (current_panel == view3D) {
3467         if (old_panel == preview)
3468             preview->get_canvas3d()->unbind_event_handlers();
3469 
3470         view3D->get_canvas3d()->bind_event_handlers();
3471 
3472         if (view3D->is_reload_delayed()) {
3473             // Delayed loading of the 3D scene.
3474             if (this->printer_technology == ptSLA) {
3475                 // Update the SLAPrint from the current Model, so that the reload_scene()
3476                 // pulls the correct data.
3477                 this->update_restart_background_process(true, false);
3478             } else
3479                 view3D->reload_scene(true);
3480         }
3481 
3482         // sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably)
3483         view3D->set_as_dirty();
3484         view_toolbar.select_item("3D");
3485         if(notification_manager != nullptr)
3486             notification_manager->set_in_preview(false);
3487     }
3488     else if (current_panel == preview) {
3489         if (old_panel == view3D)
3490             view3D->get_canvas3d()->unbind_event_handlers();
3491 
3492         preview->get_canvas3d()->bind_event_handlers();
3493 
3494         // see: Plater::priv::object_list_changed()
3495         // FIXME: it may be better to have a single function making this check and let it be called wherever needed
3496         bool export_in_progress = this->background_process.is_export_scheduled();
3497         bool model_fits = view3D->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside;
3498         if (!model.objects.empty() && !export_in_progress && model_fits)
3499             this->q->reslice();
3500         // keeps current gcode preview, if any
3501         preview->reload_print(true);
3502 
3503         preview->set_as_dirty();
3504         view_toolbar.select_item("Preview");
3505         if (notification_manager != nullptr)
3506             notification_manager->set_in_preview(true);
3507     }
3508 
3509     current_panel->SetFocusFromKbd();
3510 }
3511 
on_select_preset(wxCommandEvent & evt)3512 void Plater::priv::on_select_preset(wxCommandEvent &evt)
3513 {
3514     auto preset_type = static_cast<Preset::Type>(evt.GetInt());
3515     auto *combo = static_cast<PlaterPresetComboBox*>(evt.GetEventObject());
3516 
3517     // see https://github.com/prusa3d/PrusaSlicer/issues/3889
3518     // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender"),
3519     // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive.
3520     // So, use GetSelection() from event parameter
3521     // But in this function we couldn't use evt.GetSelection(), because m_commandInt is used for preset_type
3522     // Thus, get selection in this way:
3523     int selection = combo->FindString(evt.GetString(), true);
3524 
3525     auto idx = combo->get_extruder_idx();
3526 
3527     //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox,
3528     //! but the OSX version derived from wxOwnerDrawnCombo.
3529     //! So, to get selected string we do
3530     //!     combo->GetString(combo->GetSelection())
3531     //! instead of
3532     //!     combo->GetStringSelection().ToUTF8().data());
3533 
3534     std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
3535         Preset::remove_suffix_modified(combo->GetString(selection).ToUTF8().data()));
3536 
3537     if (preset_type == Preset::TYPE_FILAMENT) {
3538         wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
3539     }
3540 
3541     bool select_preset = !combo->selection_is_changed_according_to_physical_printers();
3542     // TODO: ?
3543     if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) {
3544         // Only update the plater UI for the 2nd and other filaments.
3545         combo->update();
3546     }
3547     else if (select_preset) {
3548         if (preset_type == Preset::TYPE_PRINTER) {
3549             PhysicalPrinterCollection& physical_printers = wxGetApp().preset_bundle->physical_printers;
3550             if(combo->is_selected_physical_printer())
3551                 preset_name = physical_printers.get_selected_printer_preset_name();
3552             else
3553                 physical_printers.unselect_printer();
3554         }
3555         wxWindowUpdateLocker noUpdates(sidebar->presets_panel());
3556         wxGetApp().get_tab(preset_type)->select_preset(preset_name);
3557     }
3558 
3559     // update plater with new config
3560     q->on_config_change(wxGetApp().preset_bundle->full_config());
3561     if (preset_type == Preset::TYPE_PRINTER) {
3562     /* Settings list can be changed after printer preset changing, so
3563      * update all settings items for all item had it.
3564      * Furthermore, Layers editing is implemented only for FFF printers
3565      * and for SLA presets they should be deleted
3566      */
3567         wxGetApp().obj_list()->update_object_list_by_printer_technology();
3568     }
3569 
3570 #ifdef __WXMSW__
3571     // From the Win 2004 preset combobox lose a focus after change the preset selection
3572     // and that is why the up/down arrow doesn't work properly
3573     // (see https://github.com/prusa3d/PrusaSlicer/issues/5531 ).
3574     // So, set the focus to the combobox explicitly
3575     combo->SetFocus();
3576 #endif
3577 }
3578 
on_slicing_update(SlicingStatusEvent & evt)3579 void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
3580 {
3581     if (evt.status.percent >= -1) {
3582         if (m_ui_jobs.is_any_running()) {
3583             // Avoid a race condition
3584             return;
3585         }
3586 
3587         this->statusbar()->set_progress(evt.status.percent);
3588         this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…"));
3589         //notification_manager->set_progress_bar_percentage("Slicing progress", (float)evt.status.percent / 100.0f);
3590     }
3591     if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE | PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) {
3592         switch (this->printer_technology) {
3593         case ptFFF:
3594             this->update_fff_scene();
3595             break;
3596         case ptSLA:
3597             // If RELOAD_SLA_SUPPORT_POINTS, then the SLA gizmo is updated (reload_scene calls update_gizmos_data)
3598             if (view3D->is_dragging())
3599                 delayed_scene_refresh = true;
3600             else
3601                 this->update_sla_scene();
3602             break;
3603         default: break;
3604         }
3605     } else if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) {
3606         // Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways.
3607         this->preview->reload_print();
3608     }
3609 
3610     if (evt.status.flags & (PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS | PrintBase::SlicingStatus::UPDATE_PRINT_OBJECT_STEP_WARNINGS)) {
3611         // Update notification center with warnings of object_id and its warning_step.
3612         ObjectID object_id = evt.status.warning_object_id;
3613         int warning_step = evt.status.warning_step;
3614         PrintStateBase::StateWithWarnings state;
3615         if (evt.status.flags & PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS) {
3616             state = this->printer_technology == ptFFF ?
3617                 this->fff_print.step_state_with_warnings(static_cast<PrintStep>(warning_step)) :
3618                 this->sla_print.step_state_with_warnings(static_cast<SLAPrintStep>(warning_step));
3619         } else if (this->printer_technology == ptFFF) {
3620             const PrintObject *print_object = this->fff_print.get_object(object_id);
3621             if (print_object)
3622                 state = print_object->step_state_with_warnings(static_cast<PrintObjectStep>(warning_step));
3623         } else {
3624             const SLAPrintObject *print_object = this->sla_print.get_object(object_id);
3625             if (print_object)
3626                 state = print_object->step_state_with_warnings(static_cast<SLAPrintObjectStep>(warning_step));
3627         }
3628         // Now process state.warnings.
3629 		for (auto const& warning : state.warnings) {
3630 			if (warning.current) {
3631                 notification_manager->push_slicing_warning_notification(warning.message, false, object_id, warning_step);
3632                 add_warning(warning, object_id.id);
3633 			}
3634 		}
3635     }
3636 }
3637 
on_slicing_completed(wxCommandEvent & evt)3638 void Plater::priv::on_slicing_completed(wxCommandEvent & evt)
3639 {
3640     notification_manager->push_slicing_complete_notification(evt.GetInt(), is_sidebar_collapsed());
3641     switch (this->printer_technology) {
3642     case ptFFF:
3643         this->update_fff_scene();
3644         break;
3645     case ptSLA:
3646         if (view3D->is_dragging())
3647             delayed_scene_refresh = true;
3648         else
3649             this->update_sla_scene();
3650         break;
3651     default: break;
3652     }
3653 
3654 }
on_export_began(wxCommandEvent & evt)3655 void Plater::priv::on_export_began(wxCommandEvent& evt)
3656 {
3657 	if (show_warning_dialog)
3658 		warnings_dialog();
3659 }
on_slicing_began()3660 void Plater::priv::on_slicing_began()
3661 {
3662 	clear_warnings();
3663 	notification_manager->close_notification_of_type(NotificationType::SlicingComplete);
3664 }
add_warning(const Slic3r::PrintStateBase::Warning & warning,size_t oid)3665 void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid)
3666 {
3667 	for (auto const& it : current_warnings) {
3668 		if (warning.message_id == it.first.message_id) {
3669 			if (warning.message_id != 0 || (warning.message_id == 0 && warning.message == it.first.message))
3670 				return;
3671 		}
3672 	}
3673 	current_warnings.emplace_back(std::pair<Slic3r::PrintStateBase::Warning, size_t>(warning, oid));
3674 }
actualize_slicing_warnings(const PrintBase & print)3675 void Plater::priv::actualize_slicing_warnings(const PrintBase &print)
3676 {
3677     std::vector<ObjectID> ids = print.print_object_ids();
3678     if (ids.empty()) {
3679         clear_warnings();
3680         return;
3681     }
3682     ids.emplace_back(print.id());
3683     std::sort(ids.begin(), ids.end());
3684 	notification_manager->remove_slicing_warnings_of_released_objects(ids);
3685     notification_manager->set_all_slicing_warnings_gray(true);
3686 }
clear_warnings()3687 void Plater::priv::clear_warnings()
3688 {
3689 	notification_manager->close_slicing_errors_and_warnings();
3690 	this->current_warnings.clear();
3691 }
warnings_dialog()3692 bool Plater::priv::warnings_dialog()
3693 {
3694 	if (current_warnings.empty())
3695 		return true;
3696 	std::string text = _u8L("There are active warnings concerning sliced models:") + "\n";
3697 	for (auto const& it : current_warnings) {
3698         size_t next_n = it.first.message.find_first_of('\n', 0);
3699 		text += "\n";
3700 		if (next_n != std::string::npos)
3701 			text += it.first.message.substr(0, next_n);
3702 		else
3703 			text += it.first.message;
3704 	}
3705 	//text += "\n\nDo you still wish to export?";
3706 	wxMessageDialog msg_wingow(this->q, text, wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK);
3707 	const auto res = msg_wingow.ShowModal();
3708 	return res == wxID_OK;
3709 
3710 }
on_process_completed(SlicingProcessCompletedEvent & evt)3711 void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
3712 {
3713     // Stop the background task, wait until the thread goes into the "Idle" state.
3714     // At this point of time the thread should be either finished or canceled,
3715     // so the following call just confirms, that the produced data were consumed.
3716     this->background_process.stop();
3717     this->statusbar()->reset_cancel_callback();
3718     this->statusbar()->stop_busy();
3719 
3720     // Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
3721     this->background_process.reset_export();
3722     // This bool stops showing export finished notification even when process_completed_with_error is false
3723     bool has_error = false;
3724     if (evt.error()) {
3725         std::pair<std::string, bool> message = evt.format_error_message();
3726         if (evt.critical_error()) {
3727             if (q->m_tracking_popup_menu)
3728                 // We don't want to pop-up a message box when tracking a pop-up menu.
3729                 // We postpone the error message instead.
3730                 q->m_tracking_popup_menu_error_message = message.first;
3731             else
3732                 show_error(q, message.first, message.second);
3733         } else
3734             notification_manager->push_slicing_error_notification(message.first);
3735         this->statusbar()->set_status_text(from_u8(message.first));
3736         if (evt.invalidate_plater())
3737         {
3738             const wxString invalid_str = _L("Invalid data");
3739             for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport })
3740                 sidebar->set_btn_label(btn, invalid_str);
3741             process_completed_with_error = true;
3742         }
3743         has_error = true;
3744     }
3745     if (evt.cancelled())
3746         this->statusbar()->set_status_text(_L("Cancelled"));
3747 
3748     this->sidebar->show_sliced_info_sizer(evt.success());
3749 
3750     // This updates the "Slice now", "Export G-code", "Arrange" buttons status.
3751     // Namely, it refreshes the "Out of print bed" property of all the ModelObjects, and it enables
3752     // the "Slice now" and "Export G-code" buttons based on their "out of bed" status.
3753     this->object_list_changed();
3754 
3755     // refresh preview
3756     switch (this->printer_technology) {
3757     case ptFFF:
3758         this->update_fff_scene();
3759         break;
3760     case ptSLA:
3761         if (view3D->is_dragging())
3762             delayed_scene_refresh = true;
3763         else
3764             this->update_sla_scene();
3765         break;
3766     default: break;
3767     }
3768 
3769     if (evt.cancelled()) {
3770         if (wxGetApp().get_mode() == comSimple)
3771             sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now");
3772         show_action_buttons(true);
3773     } else {
3774         if(wxGetApp().get_mode() == comSimple) {
3775             show_action_buttons(false);
3776         }
3777         // If writing to removable drive was scheduled, show notification with eject button
3778         if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) {
3779             show_action_buttons(false);
3780             notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path,
3781                 // Don't offer the "Eject" button on ChromeOS, the Linux side has no control over it.
3782                 platform_flavor() != PlatformFlavor::LinuxOnChromium);
3783             wxGetApp().removable_drive_manager()->set_exporting_finished(true);
3784         }else if (exporting_status == ExportingStatus::EXPORTING_TO_LOCAL && !has_error)
3785             notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, false);
3786     }
3787     exporting_status = ExportingStatus::NOT_EXPORTING;
3788 }
3789 
on_layer_editing_toggled(bool enable)3790 void Plater::priv::on_layer_editing_toggled(bool enable)
3791 {
3792     view3D->enable_layers_editing(enable);
3793     view3D->set_as_dirty();
3794 }
3795 
on_action_add(SimpleEvent &)3796 void Plater::priv::on_action_add(SimpleEvent&)
3797 {
3798     if (q != nullptr)
3799         q->add_model();
3800 }
3801 
on_action_split_objects(SimpleEvent &)3802 void Plater::priv::on_action_split_objects(SimpleEvent&)
3803 {
3804     split_object();
3805 }
3806 
on_action_split_volumes(SimpleEvent &)3807 void Plater::priv::on_action_split_volumes(SimpleEvent&)
3808 {
3809     split_volume();
3810 }
3811 
on_action_layersediting(SimpleEvent &)3812 void Plater::priv::on_action_layersediting(SimpleEvent&)
3813 {
3814     view3D->enable_layers_editing(!view3D->is_layers_editing_enabled());
3815     notification_manager->set_move_from_overlay(view3D->is_layers_editing_enabled());
3816 }
3817 
on_object_select(SimpleEvent & evt)3818 void Plater::priv::on_object_select(SimpleEvent& evt)
3819 {
3820     wxGetApp().obj_list()->update_selections();
3821     selection_changed();
3822 }
3823 
on_right_click(RBtnEvent & evt)3824 void Plater::priv::on_right_click(RBtnEvent& evt)
3825 {
3826     int obj_idx = get_selected_object_idx();
3827 
3828     wxMenu* menu = nullptr;
3829 
3830     if (obj_idx == -1) // no one or several object are selected
3831     {
3832         if (evt.data.second) // right button was clicked on empty space
3833             menu = &default_menu;
3834         else
3835         {
3836             sidebar->obj_list()->show_multi_selection_menu();
3837             return;
3838         }
3839     }
3840     else
3841     {
3842         // If in 3DScene is(are) selected volume(s), but right button was clicked on empty space
3843         if (evt.data.second)
3844             return;
3845 
3846         int menu_item_convert_unit_position = 11;
3847 
3848         if (printer_technology == ptSLA)
3849             menu = &sla_object_menu;
3850         else
3851         {
3852             // show "Object menu" for each one or several FullInstance instead of FullObject
3853             const bool is_some_full_instances = get_selection().is_single_full_instance() ||
3854                                                 get_selection().is_single_full_object() ||
3855                                                 get_selection().is_multiple_full_instance();
3856             menu = is_some_full_instances ? &object_menu : &part_menu;
3857             if (!is_some_full_instances)
3858                 menu_item_convert_unit_position = 2;
3859         }
3860 
3861         sidebar->obj_list()->append_menu_item_convert_unit(menu, menu_item_convert_unit_position);
3862         sidebar->obj_list()->append_menu_item_settings(menu);
3863 
3864         if (printer_technology != ptSLA)
3865             sidebar->obj_list()->append_menu_item_change_extruder(menu);
3866 
3867         if (menu != &part_menu)
3868         {
3869             /* Remove/Prepend "increase/decrease instances" menu items according to the view mode.
3870              * Suppress to show those items for a Simple mode
3871              */
3872             const MenuIdentifier id = printer_technology == ptSLA ? miObjectSLA : miObjectFFF;
3873             if (wxGetApp().get_mode() == comSimple) {
3874                 if (menu->FindItem(_L("Add instance")) != wxNOT_FOUND)
3875                 {
3876                     /* Detach an items from the menu, but don't delete them
3877                      * so that they can be added back later
3878                      * (after switching to the Advanced/Expert mode)
3879                      */
3880                     menu->Remove(items_increase[id]);
3881                     menu->Remove(items_decrease[id]);
3882                     menu->Remove(items_set_number_of_copies[id]);
3883                 }
3884             }
3885             else {
3886                 if (menu->FindItem(_L("Add instance")) == wxNOT_FOUND)
3887                 {
3888                     // Prepend items to the menu, if those aren't not there
3889                     menu->Prepend(items_set_number_of_copies[id]);
3890                     menu->Prepend(items_decrease[id]);
3891                     menu->Prepend(items_increase[id]);
3892                 }
3893             }
3894         }
3895     }
3896 
3897     if (q != nullptr && menu) {
3898 #ifdef __linux__
3899         // For some reason on Linux the menu isn't displayed if position is specified
3900         // (even though the position is sane).
3901         q->PopupMenu(menu);
3902 #else
3903         q->PopupMenu(menu, (int)evt.data.first.x(), (int)evt.data.first.y());
3904 #endif
3905     }
3906 }
3907 
on_wipetower_moved(Vec3dEvent & evt)3908 void Plater::priv::on_wipetower_moved(Vec3dEvent &evt)
3909 {
3910     DynamicPrintConfig cfg;
3911     cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0);
3912     cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = evt.data(1);
3913     wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
3914 }
3915 
on_wipetower_rotated(Vec3dEvent & evt)3916 void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt)
3917 {
3918     DynamicPrintConfig cfg;
3919     cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0);
3920     cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = evt.data(1);
3921     cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = Geometry::rad2deg(evt.data(2));
3922     wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
3923 }
3924 
on_update_geometry(Vec3dsEvent<2> &)3925 void Plater::priv::on_update_geometry(Vec3dsEvent<2>&)
3926 {
3927     // TODO
3928 }
3929 
3930 // Update the scene from the background processing,
3931 // if the update message was received during mouse manipulation.
on_3dcanvas_mouse_dragging_finished(SimpleEvent &)3932 void Plater::priv::on_3dcanvas_mouse_dragging_finished(SimpleEvent&)
3933 {
3934     if (this->delayed_scene_refresh) {
3935         this->delayed_scene_refresh = false;
3936         this->update_sla_scene();
3937     }
3938 }
3939 
init_object_menu()3940 bool Plater::priv::init_object_menu()
3941 {
3942     items_increase.reserve(2);
3943     items_decrease.reserve(2);
3944     items_set_number_of_copies.reserve(2);
3945 
3946     init_common_menu(&object_menu);
3947     complit_init_object_menu();
3948 
3949     init_common_menu(&sla_object_menu);
3950     complit_init_sla_object_menu();
3951 
3952     init_common_menu(&part_menu, true);
3953     complit_init_part_menu();
3954 
3955     sidebar->obj_list()->create_default_popupmenu(&default_menu);
3956 
3957     return true;
3958 }
3959 
generate_thumbnail(ThumbnailData & data,unsigned int w,unsigned int h,bool printable_only,bool parts_only,bool show_bed,bool transparent_background)3960 void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
3961 {
3962     view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background);
3963 }
3964 
generate_thumbnails(ThumbnailsList & thumbnails,const Vec2ds & sizes,bool printable_only,bool parts_only,bool show_bed,bool transparent_background)3965 void Plater::priv::generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
3966 {
3967     thumbnails.clear();
3968     for (const Vec2d& size : sizes)
3969     {
3970         thumbnails.push_back(ThumbnailData());
3971         Point isize(size); // round to ints
3972         generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), printable_only, parts_only, show_bed, transparent_background);
3973         if (!thumbnails.back().is_valid())
3974             thumbnails.pop_back();
3975     }
3976 }
3977 
msw_rescale_object_menu()3978 void Plater::priv::msw_rescale_object_menu()
3979 {
3980     for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu, &default_menu })
3981         msw_rescale_menu(dynamic_cast<wxMenu*>(menu));
3982 }
3983 
get_project_filename(const wxString & extension) const3984 wxString Plater::priv::get_project_filename(const wxString& extension) const
3985 {
3986     return m_project_filename.empty() ? "" : m_project_filename + extension;
3987 }
3988 
set_project_filename(const wxString & filename)3989 void Plater::priv::set_project_filename(const wxString& filename)
3990 {
3991     boost::filesystem::path full_path = into_path(filename);
3992     boost::filesystem::path ext = full_path.extension();
3993     if (boost::iequals(ext.string(), ".amf")) {
3994         // Remove the first extension.
3995         full_path.replace_extension("");
3996         // It may be ".zip.amf".
3997         if (boost::iequals(full_path.extension().string(), ".zip"))
3998             // Remove the 2nd extension.
3999             full_path.replace_extension("");
4000     } else {
4001         // Remove just one extension.
4002         full_path.replace_extension("");
4003     }
4004 
4005     m_project_filename = from_path(full_path);
4006     wxGetApp().mainframe->update_title();
4007 
4008     if (!filename.empty())
4009         wxGetApp().mainframe->add_to_recent_projects(filename);
4010 }
4011 
init_common_menu(wxMenu * menu,const bool is_part)4012 bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/)
4013 {
4014     if (is_part) {
4015         append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"),
4016             [this](wxCommandEvent&) { q->remove_selected();         }, "delete",            nullptr, [this]() { return can_delete(); }, q);
4017 
4018         append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected volumes from disk"),
4019             [this](wxCommandEvent&) { q->reload_from_disk(); }, "", menu, [this]() { return can_reload_from_disk(); }, q);
4020 
4021         sidebar->obj_list()->append_menu_item_export_stl(menu);
4022     }
4023     else {
4024         wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _L("Add instance") + "\t+", _L("Add one more instance of the selected object"),
4025             [this](wxCommandEvent&) { q->increase_instances();      }, "add_copies",        nullptr, [this]() { return can_increase_instances(); }, q);
4026         wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _L("Remove instance") + "\t-", _L("Remove one instance of the selected object"),
4027             [this](wxCommandEvent&) { q->decrease_instances();      }, "remove_copies",     nullptr, [this]() { return can_decrease_instances(); }, q);
4028         wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _L("Set number of instances") + dots, _L("Change the number of instances of the selected object"),
4029             [this](wxCommandEvent&) { q->set_number_of_copies();    }, "number_of_copies",  nullptr, [this]() { return can_increase_instances(); }, q);
4030         append_menu_item(menu, wxID_ANY, _L("Fill bed with instances") + dots, _L("Fill the remaining area of bed with instances of the selected object"),
4031             [this](wxCommandEvent&) { q->fill_bed_with_instances();    }, "",  nullptr, [this]() { return can_increase_instances(); }, q);
4032 
4033 
4034         items_increase.push_back(item_increase);
4035         items_decrease.push_back(item_decrease);
4036         items_set_number_of_copies.push_back(item_set_number_of_copies);
4037 
4038         // Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake.
4039         append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"),
4040             [this](wxCommandEvent&) { q->remove_selected(); }, "delete",            nullptr, [this]() { return can_delete(); }, q);
4041 
4042         menu->AppendSeparator();
4043         sidebar->obj_list()->append_menu_item_instance_to_object(menu, q);
4044         menu->AppendSeparator();
4045 
4046         wxMenuItem* menu_item_printable = sidebar->obj_list()->append_menu_item_printable(menu, q);
4047         menu->AppendSeparator();
4048 
4049         append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected object from disk"),
4050             [this](wxCommandEvent&) { reload_from_disk(); }, "", nullptr, [this]() { return can_reload_from_disk(); }, q);
4051 
4052         append_menu_item(menu, wxID_ANY, _L("Export as STL") + dots, _L("Export the selected object as STL file"),
4053             [this](wxCommandEvent&) { q->export_stl(false, true); }, "", nullptr,
4054             [this]() {
4055                 const Selection& selection = get_selection();
4056                 return selection.is_single_full_instance() || selection.is_single_full_object();
4057             }, q);
4058 
4059         menu->AppendSeparator();
4060 
4061         // "Scale to print volume" makes a sense just for whole object
4062         sidebar->obj_list()->append_menu_item_scale_selection_to_fit_print_volume(menu);
4063 
4064         q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) {
4065             const Selection& selection = get_selection();
4066             int instance_idx = selection.get_instance_idx();
4067             evt.Enable(selection.is_single_full_instance() || selection.is_single_full_object());
4068             if (instance_idx != -1)
4069             {
4070                 evt.Check(model.objects[selection.get_object_idx()]->instances[instance_idx]->printable);
4071                 view3D->set_as_dirty();
4072             }
4073             }, menu_item_printable->GetId());
4074     }
4075 
4076     sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu);
4077 
4078     wxMenu* mirror_menu = new wxMenu();
4079     if (mirror_menu == nullptr)
4080         return false;
4081 
4082     append_menu_item(mirror_menu, wxID_ANY, _L("Along X axis"), _L("Mirror the selected object along the X axis"),
4083         [this](wxCommandEvent&) { mirror(X); }, "mark_X", menu);
4084     append_menu_item(mirror_menu, wxID_ANY, _L("Along Y axis"), _L("Mirror the selected object along the Y axis"),
4085         [this](wxCommandEvent&) { mirror(Y); }, "mark_Y", menu);
4086     append_menu_item(mirror_menu, wxID_ANY, _L("Along Z axis"), _L("Mirror the selected object along the Z axis"),
4087         [this](wxCommandEvent&) { mirror(Z); }, "mark_Z", menu);
4088 
4089     append_submenu(menu, mirror_menu, wxID_ANY, _L("Mirror"), _L("Mirror the selected object"), "",
4090         [this]() { return can_mirror(); }, q);
4091 
4092     return true;
4093 }
4094 
complit_init_object_menu()4095 bool Plater::priv::complit_init_object_menu()
4096 {
4097     wxMenu* split_menu = new wxMenu();
4098     if (split_menu == nullptr)
4099         return false;
4100 
4101     append_menu_item(split_menu, wxID_ANY, _L("To objects"), _L("Split the selected object into individual objects"),
4102         [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL",  &object_menu, [this]() { return can_split(); }, q);
4103     append_menu_item(split_menu, wxID_ANY, _L("To parts"), _L("Split the selected object into individual sub-parts"),
4104         [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL",   &object_menu, [this]() { return can_split(); }, q);
4105 
4106     append_submenu(&object_menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "",
4107         [this]() { return can_split() && wxGetApp().get_mode() > comSimple; }, q);
4108     object_menu.AppendSeparator();
4109 
4110     // Layers Editing for object
4111     sidebar->obj_list()->append_menu_item_layers_editing(&object_menu, q);
4112     object_menu.AppendSeparator();
4113 
4114     // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume()
4115 
4116     return true;
4117 }
4118 
complit_init_sla_object_menu()4119 bool Plater::priv::complit_init_sla_object_menu()
4120 {
4121     append_menu_item(&sla_object_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual objects"),
4122         [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL", nullptr, [this]() { return can_split(); }, q);
4123 
4124     sla_object_menu.AppendSeparator();
4125 
4126     // Add the automatic rotation sub-menu
4127     append_menu_item(
4128         &sla_object_menu, wxID_ANY, _(L("Optimize orientation")),
4129         _(L("Optimize the rotation of the object for better print results.")),
4130         [this](wxCommandEvent &) {
4131             m_ui_jobs.optimize_rotation();
4132         });
4133 
4134     return true;
4135 }
4136 
complit_init_part_menu()4137 bool Plater::priv::complit_init_part_menu()
4138 {
4139     append_menu_item(&part_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual sub-parts"),
4140         [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", nullptr, [this]() { return can_split(); }, q);
4141 
4142     part_menu.AppendSeparator();
4143 
4144     auto obj_list = sidebar->obj_list();
4145     obj_list->append_menu_item_change_type(&part_menu, q);
4146 
4147     return true;
4148 }
4149 
set_current_canvas_as_dirty()4150 void Plater::priv::set_current_canvas_as_dirty()
4151 {
4152     if (current_panel == view3D)
4153         view3D->set_as_dirty();
4154     else if (current_panel == preview)
4155         preview->set_as_dirty();
4156 }
4157 
get_current_canvas3D()4158 GLCanvas3D* Plater::priv::get_current_canvas3D()
4159 {
4160     return (current_panel == view3D) ? view3D->get_canvas3d() : ((current_panel == preview) ? preview->get_canvas3d() : nullptr);
4161 }
4162 
unbind_canvas_event_handlers()4163 void Plater::priv::unbind_canvas_event_handlers()
4164 {
4165     if (view3D != nullptr)
4166         view3D->get_canvas3d()->unbind_event_handlers();
4167 
4168     if (preview != nullptr)
4169         preview->get_canvas3d()->unbind_event_handlers();
4170 }
4171 
reset_canvas_volumes()4172 void Plater::priv::reset_canvas_volumes()
4173 {
4174     if (view3D != nullptr)
4175         view3D->get_canvas3d()->reset_volumes();
4176 
4177     if (preview != nullptr)
4178         preview->get_canvas3d()->reset_volumes();
4179 }
4180 
init_view_toolbar()4181 bool Plater::priv::init_view_toolbar()
4182 {
4183     if (wxGetApp().is_gcode_viewer())
4184         return true;
4185 
4186     if (view_toolbar.get_items_count() > 0)
4187         // already initialized
4188         return true;
4189 
4190     BackgroundTexture::Metadata background_data;
4191     background_data.filename = "toolbar_background.png";
4192     background_data.left = 16;
4193     background_data.top = 16;
4194     background_data.right = 16;
4195     background_data.bottom = 16;
4196 
4197     if (!view_toolbar.init(background_data))
4198         return false;
4199 
4200     view_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left);
4201     view_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Bottom);
4202     view_toolbar.set_border(5.0f);
4203     view_toolbar.set_gap_size(1.0f);
4204 
4205     GLToolbarItem::Data item;
4206 
4207     item.name = "3D";
4208     item.icon_filename = "editor.svg";
4209     item.tooltip = _utf8(L("3D editor view")) + " [" + GUI::shortkey_ctrl_prefix() + "5]";
4210     item.sprite_id = 0;
4211     item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); };
4212     if (!view_toolbar.add_item(item))
4213         return false;
4214 
4215     item.name = "Preview";
4216     item.icon_filename = "preview.svg";
4217     item.tooltip = _utf8(L("Preview")) + " [" + GUI::shortkey_ctrl_prefix() + "6]";
4218     item.sprite_id = 1;
4219     item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); };
4220     if (!view_toolbar.add_item(item))
4221         return false;
4222 
4223     view_toolbar.select_item("3D");
4224     view_toolbar.set_enabled(true);
4225 
4226     return true;
4227 }
4228 
init_collapse_toolbar()4229 bool Plater::priv::init_collapse_toolbar()
4230 {
4231     if (wxGetApp().is_gcode_viewer())
4232         return true;
4233 
4234     if (collapse_toolbar.get_items_count() > 0)
4235         // already initialized
4236         return true;
4237 
4238     BackgroundTexture::Metadata background_data;
4239     background_data.filename = "toolbar_background.png";
4240     background_data.left = 16;
4241     background_data.top = 16;
4242     background_data.right = 16;
4243     background_data.bottom = 16;
4244 
4245     if (!collapse_toolbar.init(background_data))
4246         return false;
4247 
4248     collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
4249     collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right);
4250     collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top);
4251     collapse_toolbar.set_border(5.0f);
4252     collapse_toolbar.set_separator_size(5);
4253     collapse_toolbar.set_gap_size(2);
4254 
4255     GLToolbarItem::Data item;
4256 
4257     item.name = "collapse_sidebar";
4258     item.icon_filename = "collapse.svg";
4259     item.sprite_id = 0;
4260     item.left.action_callback = []() {
4261         wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed());
4262     };
4263 
4264     if (!collapse_toolbar.add_item(item))
4265         return false;
4266 
4267     // Now "collapse" sidebar to current state. This is done so the tooltip
4268     // is updated before the toolbar is first used.
4269     wxGetApp().plater()->collapse_sidebar(wxGetApp().plater()->is_sidebar_collapsed());
4270     return true;
4271 }
4272 
update_preview_bottom_toolbar()4273 void Plater::priv::update_preview_bottom_toolbar()
4274 {
4275     preview->update_bottom_toolbar();
4276 }
4277 
update_preview_moves_slider()4278 void Plater::priv::update_preview_moves_slider()
4279 {
4280     preview->update_moves_slider();
4281 }
4282 
enable_preview_moves_slider(bool enable)4283 void Plater::priv::enable_preview_moves_slider(bool enable)
4284 {
4285     preview->enable_moves_slider(enable);
4286 }
4287 
reset_gcode_toolpaths()4288 void Plater::priv::reset_gcode_toolpaths()
4289 {
4290     preview->get_canvas3d()->reset_gcode_toolpaths();
4291 }
4292 
can_set_instance_to_object() const4293 bool Plater::priv::can_set_instance_to_object() const
4294 {
4295     const int obj_idx = get_selected_object_idx();
4296     return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1);
4297 }
4298 
can_split() const4299 bool Plater::priv::can_split() const
4300 {
4301     return sidebar->obj_list()->is_splittable();
4302 }
4303 
layers_height_allowed() const4304 bool Plater::priv::layers_height_allowed() const
4305 {
4306     if (printer_technology != ptFFF)
4307         return false;
4308 
4309     int obj_idx = get_selected_object_idx();
4310     return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed();
4311 }
4312 
can_mirror() const4313 bool Plater::priv::can_mirror() const
4314 {
4315     return get_selection().is_from_single_instance();
4316 }
4317 
can_reload_from_disk() const4318 bool Plater::priv::can_reload_from_disk() const
4319 {
4320     // struct to hold selected ModelVolumes by their indices
4321     struct SelectedVolume
4322     {
4323         int object_idx;
4324         int volume_idx;
4325 
4326         // operators needed by std::algorithms
4327         bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); }
4328         bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); }
4329     };
4330     std::vector<SelectedVolume> selected_volumes;
4331 
4332     const Selection& selection = get_selection();
4333 
4334     // collects selected ModelVolumes
4335     const std::set<unsigned int>& selected_volumes_idxs = selection.get_volume_idxs();
4336     for (unsigned int idx : selected_volumes_idxs)
4337     {
4338         const GLVolume* v = selection.get_volume(idx);
4339         int v_idx = v->volume_idx();
4340         if (v_idx >= 0)
4341         {
4342             int o_idx = v->object_idx();
4343             if ((0 <= o_idx) && (o_idx < (int)model.objects.size()))
4344                 selected_volumes.push_back({ o_idx, v_idx });
4345         }
4346     }
4347     std::sort(selected_volumes.begin(), selected_volumes.end());
4348     selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end());
4349 
4350     // collects paths of files to load
4351     std::vector<fs::path> paths;
4352     for (const SelectedVolume& v : selected_volumes)
4353     {
4354         const ModelObject* object = model.objects[v.object_idx];
4355         const ModelVolume* volume = object->volumes[v.volume_idx];
4356         if (!volume->source.input_file.empty())
4357             paths.push_back(volume->source.input_file);
4358         else if (!object->input_file.empty() && !volume->name.empty())
4359             paths.push_back(volume->name);
4360     }
4361     std::sort(paths.begin(), paths.end());
4362     paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
4363 
4364     return !paths.empty();
4365 }
4366 
set_bed_shape(const Pointfs & shape,const std::string & custom_texture,const std::string & custom_model,bool force_as_custom)4367 void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom)
4368 {
4369     bool new_shape = bed.set_shape(shape, custom_texture, custom_model, force_as_custom);
4370     if (new_shape) {
4371         if (view3D) view3D->bed_shape_changed();
4372         if (preview) preview->bed_shape_changed();
4373     }
4374 }
4375 
can_delete() const4376 bool Plater::priv::can_delete() const
4377 {
4378     return !get_selection().is_empty() && !get_selection().is_wipe_tower() && !m_ui_jobs.is_any_running();
4379 }
4380 
can_delete_all() const4381 bool Plater::priv::can_delete_all() const
4382 {
4383     return !model.objects.empty();
4384 }
4385 
can_fix_through_netfabb() const4386 bool Plater::priv::can_fix_through_netfabb() const
4387 {
4388     int obj_idx = get_selected_object_idx();
4389     if (obj_idx < 0)
4390         return false;
4391 
4392     return model.objects[obj_idx]->get_mesh_errors_count() > 0;
4393 }
4394 
can_increase_instances() const4395 bool Plater::priv::can_increase_instances() const
4396 {
4397     if (m_ui_jobs.is_any_running()) {
4398         return false;
4399     }
4400 
4401     int obj_idx = get_selected_object_idx();
4402     return (0 <= obj_idx) && (obj_idx < (int)model.objects.size());
4403 }
4404 
can_decrease_instances() const4405 bool Plater::priv::can_decrease_instances() const
4406 {
4407     if (m_ui_jobs.is_any_running()) {
4408         return false;
4409     }
4410 
4411     int obj_idx = get_selected_object_idx();
4412     return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1);
4413 }
4414 
can_split_to_objects() const4415 bool Plater::priv::can_split_to_objects() const
4416 {
4417     return can_split();
4418 }
4419 
can_split_to_volumes() const4420 bool Plater::priv::can_split_to_volumes() const
4421 {
4422     return (printer_technology != ptSLA) && can_split();
4423 }
4424 
can_arrange() const4425 bool Plater::priv::can_arrange() const
4426 {
4427     return !model.objects.empty() && !m_ui_jobs.is_any_running();
4428 }
4429 
can_layers_editing() const4430 bool Plater::priv::can_layers_editing() const
4431 {
4432     return layers_height_allowed();
4433 }
4434 
update_object_menu()4435 void Plater::priv::update_object_menu()
4436 {
4437     sidebar->obj_list()->append_menu_items_add_volume(&object_menu);
4438 }
4439 
show_action_buttons(const bool ready_to_slice) const4440 void Plater::priv::show_action_buttons(const bool ready_to_slice) const
4441 {
4442 	// Cache this value, so that the callbacks from the RemovableDriveManager may repeat that value when calling show_action_buttons().
4443     this->ready_to_slice = ready_to_slice;
4444 
4445     wxWindowUpdateLocker noUpdater(sidebar);
4446 
4447     DynamicPrintConfig* selected_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config();
4448     const auto print_host_opt = selected_printer_config ? selected_printer_config->option<ConfigOptionString>("print_host") : nullptr;
4449     const bool send_gcode_shown = print_host_opt != nullptr && !print_host_opt->value.empty();
4450 
4451     // when a background processing is ON, export_btn and/or send_btn are showing
4452     if (wxGetApp().app_config->get("background_processing") == "1")
4453     {
4454 	    RemovableDriveManager::RemovableDrivesStatus removable_media_status = wxGetApp().removable_drive_manager()->status();
4455 		if (sidebar->show_reslice(false) |
4456 			sidebar->show_export(true) |
4457 			sidebar->show_send(send_gcode_shown) |
4458 			sidebar->show_export_removable(removable_media_status.has_removable_drives))
4459 //			sidebar->show_eject(removable_media_status.has_eject))
4460             sidebar->Layout();
4461     }
4462     else
4463     {
4464 	    RemovableDriveManager::RemovableDrivesStatus removable_media_status;
4465 	    if (! ready_to_slice)
4466 	    	removable_media_status = wxGetApp().removable_drive_manager()->status();
4467         if (sidebar->show_reslice(ready_to_slice) |
4468             sidebar->show_export(!ready_to_slice) |
4469             sidebar->show_send(send_gcode_shown && !ready_to_slice) |
4470 			sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives))
4471 //            sidebar->show_eject(!ready_to_slice && removable_media_status.has_eject))
4472             sidebar->Layout();
4473     }
4474 }
4475 
enter_gizmos_stack()4476 void Plater::priv::enter_gizmos_stack()
4477 {
4478     assert(m_undo_redo_stack_active == &m_undo_redo_stack_main);
4479     if (m_undo_redo_stack_active == &m_undo_redo_stack_main) {
4480         m_undo_redo_stack_active = &m_undo_redo_stack_gizmos;
4481         assert(m_undo_redo_stack_active->empty());
4482         // Take the initial snapshot of the gizmos.
4483         // Not localized on purpose, the text will never be shown to the user.
4484         this->take_snapshot(std::string("Gizmos-Initial"));
4485     }
4486 }
4487 
leave_gizmos_stack()4488 void Plater::priv::leave_gizmos_stack()
4489 {
4490     assert(m_undo_redo_stack_active == &m_undo_redo_stack_gizmos);
4491     if (m_undo_redo_stack_active == &m_undo_redo_stack_gizmos) {
4492         assert(! m_undo_redo_stack_active->empty());
4493         m_undo_redo_stack_active->clear();
4494         m_undo_redo_stack_active = &m_undo_redo_stack_main;
4495     }
4496 }
4497 
get_active_snapshot_index()4498 int Plater::priv::get_active_snapshot_index()
4499 {
4500     const size_t active_snapshot_time = this->undo_redo_stack().active_snapshot_time();
4501     const std::vector<UndoRedo::Snapshot>& ss_stack = this->undo_redo_stack().snapshots();
4502     const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time));
4503     return it - ss_stack.begin();
4504 }
4505 
take_snapshot(const std::string & snapshot_name)4506 void Plater::priv::take_snapshot(const std::string& snapshot_name)
4507 {
4508     if (this->m_prevent_snapshots > 0)
4509         return;
4510     assert(this->m_prevent_snapshots >= 0);
4511     UndoRedo::SnapshotData snapshot_data;
4512     snapshot_data.printer_technology = this->printer_technology;
4513     if (this->view3D->is_layers_editing_enabled())
4514         snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE;
4515     if (this->sidebar->obj_list()->is_selected(itSettings)) {
4516         snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR;
4517         snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx();
4518     }
4519     else if (this->sidebar->obj_list()->is_selected(itLayer)) {
4520         snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR;
4521         snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx();
4522     }
4523     else if (this->sidebar->obj_list()->is_selected(itLayerRoot))
4524         snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR;
4525 
4526     // If SLA gizmo is active, ask it if it wants to trigger support generation
4527     // on loading this snapshot.
4528     if (view3D->get_canvas3d()->get_gizmos_manager().wants_reslice_supports_on_undo())
4529         snapshot_data.flags |= UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS;
4530 
4531     //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config.
4532     // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
4533     if (this->printer_technology == ptFFF) {
4534         const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
4535         model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y"));
4536         model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle");
4537     }
4538     this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data);
4539     this->undo_redo_stack().release_least_recently_used();
4540     // Save the last active preset name of a particular printer technology.
4541     ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name();
4542     BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info();
4543 }
4544 
undo()4545 void Plater::priv::undo()
4546 {
4547     const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
4548     auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time()));
4549     if (-- it_current != snapshots.begin())
4550         this->undo_redo_to(it_current);
4551 }
4552 
redo()4553 void Plater::priv::redo()
4554 {
4555     const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
4556     auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time()));
4557     if (++ it_current != snapshots.end())
4558         this->undo_redo_to(it_current);
4559 }
4560 
undo_redo_to(size_t time_to_load)4561 void Plater::priv::undo_redo_to(size_t time_to_load)
4562 {
4563     const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
4564     auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(time_to_load));
4565     assert(it_current != snapshots.end());
4566     this->undo_redo_to(it_current);
4567 }
4568 
undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator it_snapshot)4569 void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator it_snapshot)
4570 {
4571     // Make sure that no updating function calls take_snapshot until we are done.
4572     SuppressSnapshots snapshot_supressor(q);
4573 
4574     bool 				temp_snapshot_was_taken 	= this->undo_redo_stack().temp_snapshot_active();
4575     PrinterTechnology 	new_printer_technology 		= it_snapshot->snapshot_data.printer_technology;
4576     bool 				printer_technology_changed 	= this->printer_technology != new_printer_technology;
4577     if (printer_technology_changed) {
4578         // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type.
4579         std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA";
4580         if (! wxGetApp().check_unsaved_changes(format_wxstr(_L(
4581             "%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt)))
4582             // Don't switch the profiles.
4583             return;
4584     }
4585     // Save the last active preset name of a particular printer technology.
4586     ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name();
4587     //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config.
4588     // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
4589     if (this->printer_technology == ptFFF) {
4590         const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
4591                 model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y"));
4592                 model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle");
4593     }
4594     const int layer_range_idx = it_snapshot->snapshot_data.layer_range_idx;
4595     // Flags made of Snapshot::Flags enum values.
4596     unsigned int new_flags = it_snapshot->snapshot_data.flags;
4597     UndoRedo::SnapshotData top_snapshot_data;
4598     top_snapshot_data.printer_technology = this->printer_technology;
4599     if (this->view3D->is_layers_editing_enabled())
4600         top_snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE;
4601     if (this->sidebar->obj_list()->is_selected(itSettings)) {
4602         top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR;
4603         top_snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx();
4604     }
4605     else if (this->sidebar->obj_list()->is_selected(itLayer)) {
4606         top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR;
4607         top_snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx();
4608     }
4609     else if (this->sidebar->obj_list()->is_selected(itLayerRoot))
4610         top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR;
4611     bool   		 new_variable_layer_editing_active = (new_flags & UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE) != 0;
4612     bool         new_selected_settings_on_sidebar  = (new_flags & UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR) != 0;
4613     bool         new_selected_layer_on_sidebar     = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR) != 0;
4614     bool         new_selected_layerroot_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR) != 0;
4615 
4616     if (this->view3D->get_canvas3d()->get_gizmos_manager().wants_reslice_supports_on_undo())
4617         top_snapshot_data.flags |= UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS;
4618 
4619     // Disable layer editing before the Undo / Redo jump.
4620     if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled())
4621         view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
4622 
4623     // Make a copy of the snapshot, undo/redo could invalidate the iterator
4624     const UndoRedo::Snapshot snapshot_copy = *it_snapshot;
4625     // Do the jump in time.
4626     if (it_snapshot->timestamp < this->undo_redo_stack().active_snapshot_time() ?
4627         this->undo_redo_stack().undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), top_snapshot_data, it_snapshot->timestamp) :
4628         this->undo_redo_stack().redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) {
4629         if (printer_technology_changed) {
4630             // Switch to the other printer technology. Switch to the last printer active for that particular technology.
4631             AppConfig *app_config = wxGetApp().app_config;
4632             app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name);
4633             //FIXME Why are we reloading the whole preset bundle here? Please document. This is fishy and it is unnecessarily expensive.
4634             // Anyways, don't report any config value substitutions, they have been already reported to the user at application start up.
4635             wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent);
4636 			// load_current_presets() calls Tab::load_current_preset() -> TabPrint::update() -> Object_list::update_and_show_object_settings_item(),
4637 			// but the Object list still keeps pointer to the old Model. Avoid a crash by removing selection first.
4638 			this->sidebar->obj_list()->unselect_objects();
4639             // Load the currently selected preset into the GUI, update the preset selection box.
4640             // This also switches the printer technology based on the printer technology of the active printer profile.
4641             wxGetApp().load_current_presets();
4642         }
4643         //FIXME updating the Print config from the Wipe tower config values at the ModelWipeTower.
4644         // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
4645         if (this->printer_technology == ptFFF) {
4646             const DynamicPrintConfig &current_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
4647             Vec2d 					  current_position(current_config.opt_float("wipe_tower_x"), current_config.opt_float("wipe_tower_y"));
4648             double 					  current_rotation = current_config.opt_float("wipe_tower_rotation_angle");
4649             if (current_position != model.wipe_tower.position || current_rotation != model.wipe_tower.rotation) {
4650                 DynamicPrintConfig new_config;
4651                 new_config.set_key_value("wipe_tower_x", new ConfigOptionFloat(model.wipe_tower.position.x()));
4652                 new_config.set_key_value("wipe_tower_y", new ConfigOptionFloat(model.wipe_tower.position.y()));
4653                 new_config.set_key_value("wipe_tower_rotation_angle", new ConfigOptionFloat(model.wipe_tower.rotation));
4654                 Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT);
4655                 tab_print->load_config(new_config);
4656                 tab_print->update_dirty();
4657             }
4658         }
4659         // set selection mode for ObjectList on sidebar
4660         this->sidebar->obj_list()->set_selection_mode(new_selected_settings_on_sidebar  ? ObjectList::SELECTION_MODE::smSettings :
4661                                                       new_selected_layer_on_sidebar     ? ObjectList::SELECTION_MODE::smLayer :
4662                                                       new_selected_layerroot_on_sidebar ? ObjectList::SELECTION_MODE::smLayerRoot :
4663                                                                                           ObjectList::SELECTION_MODE::smUndef);
4664         if (new_selected_settings_on_sidebar || new_selected_layer_on_sidebar)
4665             this->sidebar->obj_list()->set_selected_layers_range_idx(layer_range_idx);
4666 
4667         this->update_after_undo_redo(snapshot_copy, temp_snapshot_was_taken);
4668         // Enable layer editing after the Undo / Redo jump.
4669         if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active)
4670             view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
4671     }
4672 }
4673 
update_after_undo_redo(const UndoRedo::Snapshot & snapshot,bool)4674 void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */)
4675 {
4676     this->view3D->get_canvas3d()->get_selection().clear();
4677     // Update volumes from the deserializd model, always stop / update the background processing (for both the SLA and FFF technologies).
4678     this->update((unsigned int)UpdateParams::FORCE_BACKGROUND_PROCESSING_UPDATE | (unsigned int)UpdateParams::POSTPONE_VALIDATION_ERROR_MESSAGE);
4679     // Release old snapshots if the memory allocated is excessive. This may remove the top most snapshot if jumping to the very first snapshot.
4680     //if (temp_snapshot_was_taken)
4681     // Release the old snapshots always, as it may have happened, that some of the triangle meshes got deserialized from the snapshot, while some
4682     // triangle meshes may have gotten released from the scene or the background processing, therefore now being calculated into the Undo / Redo stack size.
4683         this->undo_redo_stack().release_least_recently_used();
4684     //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time)
4685     this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack().selection_deserialized().mode), this->undo_redo_stack().selection_deserialized().volumes_and_instances);
4686     this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo(snapshot);
4687 
4688     wxGetApp().obj_list()->update_after_undo_redo();
4689 
4690     if (wxGetApp().get_mode() == comSimple && model_has_advanced_features(this->model)) {
4691         // If the user jumped to a snapshot that require user interface with advanced features, switch to the advanced mode without asking.
4692         // There is a little risk of surprising the user, as he already must have had the advanced or expert mode active for such a snapshot to be taken.
4693         Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
4694         view3D->set_as_dirty();
4695     }
4696 
4697 	// this->update() above was called with POSTPONE_VALIDATION_ERROR_MESSAGE, so that if an error message was generated when updating the back end, it would not open immediately,
4698 	// but it would be saved to be show later. Let's do it now. We do not want to display the message box earlier, because on Windows & OSX the message box takes over the message
4699 	// queue pump, which in turn executes the rendering function before a full update after the Undo / Redo jump.
4700 	this->show_delayed_error_message();
4701 
4702     //FIXME what about the state of the manipulators?
4703     //FIXME what about the focus? Cursor in the side panel?
4704 
4705     BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info();
4706 }
4707 
bring_instance_forward() const4708 void Plater::priv::bring_instance_forward() const
4709 {
4710 #ifdef __APPLE__
4711     wxGetApp().other_instance_message_handler()->bring_instance_forward();
4712     return;
4713 #endif //__APPLE__
4714     if (main_frame == nullptr) {
4715         BOOST_LOG_TRIVIAL(debug) << "Couldnt bring instance forward - mainframe is null";
4716         return;
4717     }
4718     BOOST_LOG_TRIVIAL(debug) << "prusaslicer window going forward";
4719     //this code maximize app window on Fedora
4720     {
4721         main_frame->Iconize(false);
4722         if (main_frame->IsMaximized())
4723             main_frame->Maximize(true);
4724         else
4725             main_frame->Maximize(false);
4726     }
4727     //this code maximize window on Ubuntu
4728     {
4729         main_frame->Restore();
4730         wxGetApp().GetTopWindow()->SetFocus();  // focus on my window
4731         wxGetApp().GetTopWindow()->Raise();  // bring window to front
4732         wxGetApp().GetTopWindow()->Show(true); // show the window
4733     }
4734 }
4735 
set_btn_label(const ActionButtonType btn_type,const wxString & label) const4736 void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const
4737 {
4738     switch (btn_type)
4739     {
4740         case ActionButtonType::abReslice:   p->btn_reslice->SetLabelText(label);        break;
4741         case ActionButtonType::abExport:    p->btn_export_gcode->SetLabelText(label);   break;
4742         case ActionButtonType::abSendGCode: /*p->btn_send_gcode->SetLabelText(label);*/     break;
4743     }
4744 }
4745 
4746 // Plater / Public
4747 
Plater(wxWindow * parent,MainFrame * main_frame)4748 Plater::Plater(wxWindow *parent, MainFrame *main_frame)
4749     : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxGetApp().get_min_size())
4750     , p(new priv(this, main_frame))
4751 {
4752     // Initialization performed in the private c-tor
4753 }
4754 
~Plater()4755 Plater::~Plater()
4756 {
4757 }
4758 
sidebar()4759 Sidebar&        Plater::sidebar()           { return *p->sidebar; }
model()4760 Model&          Plater::model()             { return p->model; }
fff_print() const4761 const Print&    Plater::fff_print() const   { return p->fff_print; }
fff_print()4762 Print&          Plater::fff_print()         { return p->fff_print; }
sla_print() const4763 const SLAPrint& Plater::sla_print() const   { return p->sla_print; }
sla_print()4764 SLAPrint&       Plater::sla_print()         { return p->sla_print; }
4765 
new_project()4766 void Plater::new_project()
4767 {
4768     p->select_view_3D("3D");
4769     wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL));
4770 }
4771 
load_project()4772 void Plater::load_project()
4773 {
4774     // Ask user for a project file name.
4775     wxString input_file;
4776     wxGetApp().load_project(this, input_file);
4777     // And finally load the new project.
4778     load_project(input_file);
4779 }
4780 
load_project(const wxString & filename)4781 void Plater::load_project(const wxString& filename)
4782 {
4783     if (filename.empty())
4784         return;
4785 
4786     // Take the Undo / Redo snapshot.
4787     Plater::TakeSnapshot snapshot(this, _L("Load Project") + ": " + wxString::FromUTF8(into_path(filename).stem().string().c_str()));
4788 
4789     p->reset();
4790 
4791     std::vector<fs::path> input_paths;
4792     input_paths.push_back(into_path(filename));
4793 
4794     std::vector<size_t> res = load_files(input_paths);
4795 
4796     // if res is empty no data has been loaded
4797     if (!res.empty())
4798         p->set_project_filename(filename);
4799 }
4800 
add_model(bool imperial_units)4801 void Plater::add_model(bool imperial_units/* = false*/)
4802 {
4803     wxArrayString input_files;
4804     wxGetApp().import_model(this, input_files);
4805     if (input_files.empty())
4806         return;
4807 
4808     std::vector<fs::path> paths;
4809     for (const auto &file : input_files)
4810         paths.push_back(into_path(file));
4811 
4812     wxString snapshot_label;
4813     assert(! paths.empty());
4814     if (paths.size() == 1) {
4815         snapshot_label = _L("Import Object");
4816         snapshot_label += ": ";
4817         snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
4818     } else {
4819         snapshot_label = _L("Import Objects");
4820         snapshot_label += ": ";
4821         snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
4822         for (size_t i = 1; i < paths.size(); ++ i) {
4823             snapshot_label += ", ";
4824             snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str());
4825         }
4826     }
4827 
4828     Plater::TakeSnapshot snapshot(this, snapshot_label);
4829     load_files(paths, true, false, imperial_units);
4830 }
4831 
import_sl1_archive()4832 void Plater::import_sl1_archive()
4833 {
4834     p->m_ui_jobs.import_sla_arch();
4835 }
4836 
extract_config_from_project()4837 void Plater::extract_config_from_project()
4838 {
4839     wxString input_file;
4840     wxGetApp().load_project(this, input_file);
4841 
4842     if (input_file.empty())
4843         return;
4844 
4845     std::vector<fs::path> input_paths;
4846     input_paths.push_back(into_path(input_file));
4847     load_files(input_paths, false, true);
4848 }
4849 
load_gcode()4850 void Plater::load_gcode()
4851 {
4852     // Ask user for a gcode file name.
4853     wxString input_file;
4854     wxGetApp().load_gcode(this, input_file);
4855     // And finally load the gcode file.
4856     load_gcode(input_file);
4857 }
4858 
load_gcode(const wxString & filename)4859 void Plater::load_gcode(const wxString& filename)
4860 {
4861     if (! is_gcode_file(into_u8(filename)) || m_last_loaded_gcode == filename)
4862         return;
4863 
4864     m_last_loaded_gcode = filename;
4865 
4866     // cleanup view before to start loading/processing
4867     p->gcode_result.reset();
4868     reset_gcode_toolpaths();
4869     p->preview->reload_print(false);
4870     p->get_current_canvas3D()->render();
4871 
4872     wxBusyCursor wait;
4873 
4874     // process gcode
4875     GCodeProcessor processor;
4876     processor.enable_producers(true);
4877     processor.process_file(filename.ToUTF8().data(), false);
4878     p->gcode_result = std::move(processor.extract_result());
4879 
4880     // show results
4881     p->preview->reload_print(false);
4882     p->preview->get_canvas3d()->zoom_to_gcode();
4883 
4884     if (p->preview->get_canvas3d()->get_gcode_layers_zs().empty()) {
4885         wxMessageDialog(this, _L("The selected file") + ":\n" + filename + "\n" + _L("does not contain valid gcode."),
4886             wxString(GCODEVIEWER_APP_NAME) + " - " + _L("Error while loading .gcode file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal();
4887         set_project_filename(wxEmptyString);
4888     }
4889     else
4890         set_project_filename(filename);
4891 }
4892 
reload_gcode_from_disk()4893 void Plater::reload_gcode_from_disk()
4894 {
4895     wxString filename(m_last_loaded_gcode);
4896     m_last_loaded_gcode.clear();
4897     load_gcode(filename);
4898 }
4899 
refresh_print()4900 void Plater::refresh_print()
4901 {
4902     p->preview->refresh_print();
4903 }
4904 
load_files(const std::vector<fs::path> & input_files,bool load_model,bool load_config,bool imperial_units)4905 std::vector<size_t> Plater::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { return p->load_files(input_files, load_model, load_config, imperial_units); }
4906 
4907 // To be called when providing a list of files to the GUI slic3r on command line.
load_files(const std::vector<std::string> & input_files,bool load_model,bool load_config,bool imperial_units)4908 std::vector<size_t> Plater::load_files(const std::vector<std::string>& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/)
4909 {
4910     std::vector<fs::path> paths;
4911     paths.reserve(input_files.size());
4912     for (const std::string& path : input_files)
4913         paths.emplace_back(path);
4914     return p->load_files(paths, load_model, load_config, imperial_units);
4915 }
4916 
4917 #if ENABLE_DRAG_AND_DROP_FIX
4918 enum class LoadType : unsigned char
4919 {
4920     Unknown,
4921     OpenProject,
4922     LoadGeometry,
4923     LoadConfig
4924 };
4925 
4926 class ProjectDropDialog : public DPIDialog
4927 {
4928     wxRadioBox* m_action{ nullptr };
4929 public:
4930     ProjectDropDialog(const std::string& filename);
4931 
get_action() const4932     int get_action() const { return m_action->GetSelection() + 1; }
4933 
4934 protected:
4935     void on_dpi_changed(const wxRect& suggested_rect) override;
4936 };
4937 
ProjectDropDialog(const std::string & filename)4938 ProjectDropDialog::ProjectDropDialog(const std::string& filename)
4939     : DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY,
4940         from_u8((boost::format(_utf8(L("%s - Drop project file"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition,
4941         wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
4942 {
4943     SetFont(wxGetApp().normal_font());
4944 
4945     wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
4946 
4947     const wxString choices[] = { _L("Open as project"),
4948                                  _L("Import geometry only"),
4949                                  _L("Import config only") };
4950 
4951     main_sizer->Add(new wxStaticText(this, wxID_ANY,
4952         _L("Select an action to apply to the file") + ": " + from_u8(filename)), 0, wxEXPAND | wxALL, 10);
4953     m_action = new wxRadioBox(this, wxID_ANY, _L("Action"), wxDefaultPosition, wxDefaultSize,
4954         WXSIZEOF(choices), choices, 0, wxRA_SPECIFY_ROWS);
4955     int action = std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
4956         static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)) - 1;
4957     m_action->SetSelection(action);
4958     main_sizer->Add(m_action, 1, wxEXPAND | wxRIGHT | wxLEFT, 10);
4959 
4960     wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL);
4961     wxCheckBox* check = new wxCheckBox(this, wxID_ANY, _L("Don't show again"));
4962     check->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& evt) {
4963         wxGetApp().app_config->set("show_drop_project_dialog", evt.IsChecked() ? "0" : "1");
4964         });
4965 
4966     bottom_sizer->Add(check, 0, wxEXPAND | wxRIGHT, 5);
4967     bottom_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxLEFT, 5);
4968     main_sizer->Add(bottom_sizer, 0, wxEXPAND | wxALL, 10);
4969 
4970     SetSizer(main_sizer);
4971     main_sizer->SetSizeHints(this);
4972 }
4973 
on_dpi_changed(const wxRect & suggested_rect)4974 void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect)
4975 {
4976     const int em = em_unit();
4977     SetMinSize(wxSize(65 * em, 30 * em));
4978     Fit();
4979     Refresh();
4980 }
4981 
load_files(const wxArrayString & filenames)4982 bool Plater::load_files(const wxArrayString& filenames)
4983 {
4984     const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", std::regex::icase);
4985     const std::regex pattern_gcode_drop(".*[.](gcode|g)", std::regex::icase);
4986 
4987     std::vector<fs::path> paths;
4988 
4989     // gcode viewer section
4990     if (wxGetApp().is_gcode_viewer()) {
4991         for (const auto& filename : filenames) {
4992             fs::path path(into_path(filename));
4993             if (std::regex_match(path.string(), pattern_gcode_drop))
4994                 paths.push_back(std::move(path));
4995         }
4996 
4997         if (paths.size() > 1) {
4998             wxMessageDialog(static_cast<wxWindow*>(this), _L("You can open only one .gcode file at a time."),
4999                 wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal();
5000             return false;
5001         }
5002         else if (paths.size() == 1) {
5003             load_gcode(from_path(paths.front()));
5004             return true;
5005         }
5006         return false;
5007     }
5008 
5009     // editor section
5010     for (const auto& filename : filenames) {
5011         fs::path path(into_path(filename));
5012         if (std::regex_match(path.string(), pattern_drop))
5013             paths.push_back(std::move(path));
5014         else if (std::regex_match(path.string(), pattern_gcode_drop))
5015             start_new_gcodeviewer(&filename);
5016         else
5017             continue;
5018     }
5019     if (paths.empty())
5020         // Likely all paths processed were gcodes, for which a G-code viewer instance has hopefully been started.
5021         return false;
5022 
5023     // searches for project files
5024     for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) {
5025         std::string filename = (*it).filename().string();
5026         if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) {
5027             LoadType load_type = LoadType::Unknown;
5028             if (!model().objects.empty()) {
5029                 if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") {
5030                     ProjectDropDialog dlg(filename);
5031                     if (dlg.ShowModal() == wxID_OK) {
5032                         int choice = dlg.get_action();
5033                         load_type = static_cast<LoadType>(choice);
5034                         wxGetApp().app_config->set("drop_project_action", std::to_string(choice));
5035                     }
5036                 }
5037                 else
5038                     load_type = static_cast<LoadType>(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
5039                         static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)));
5040             }
5041             else
5042                 load_type = LoadType::OpenProject;
5043 
5044             if (load_type == LoadType::Unknown)
5045                 return false;
5046 
5047             switch (load_type) {
5048             case LoadType::OpenProject: {
5049                 load_project(from_path(*it));
5050                 break;
5051             }
5052             case LoadType::LoadGeometry: {
5053                 Plater::TakeSnapshot snapshot(this, _L("Import Object"));
5054                 std::vector<fs::path> in_paths;
5055                 in_paths.emplace_back(*it);
5056                 load_files(in_paths, true, false);
5057                 break;
5058             }
5059             case LoadType::LoadConfig: {
5060                 std::vector<fs::path> in_paths;
5061                 in_paths.emplace_back(*it);
5062                 load_files(in_paths, false, true);
5063                 break;
5064             }
5065             case LoadType::Unknown : {
5066                 assert(false);
5067                 break;
5068             }
5069             }
5070 
5071             return true;
5072         }
5073     }
5074 
5075     // other files
5076     wxString snapshot_label;
5077     assert(!paths.empty());
5078     if (paths.size() == 1) {
5079         snapshot_label = _L("Load File");
5080         snapshot_label += ": ";
5081         snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
5082     }
5083     else {
5084         snapshot_label = _L("Load Files");
5085         snapshot_label += ": ";
5086         snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
5087         for (size_t i = 1; i < paths.size(); ++i) {
5088             snapshot_label += ", ";
5089             snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str());
5090         }
5091     }
5092     Plater::TakeSnapshot snapshot(this, snapshot_label);
5093     load_files(paths);
5094 
5095     return true;
5096 }
5097 #endif // ENABLE_DRAG_AND_DROP_FIX
5098 
update()5099 void Plater::update() { p->update(); }
5100 
stop_jobs()5101 void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); }
5102 
update_ui_from_settings(bool apply_free_camera_correction)5103 void Plater::update_ui_from_settings(bool apply_free_camera_correction) { p->update_ui_from_settings(apply_free_camera_correction); }
5104 
select_view(const std::string & direction)5105 void Plater::select_view(const std::string& direction) { p->select_view(direction); }
5106 
select_view_3D(const std::string & name)5107 void Plater::select_view_3D(const std::string& name) { p->select_view_3D(name); }
5108 
is_preview_shown() const5109 bool Plater::is_preview_shown() const { return p->is_preview_shown(); }
is_preview_loaded() const5110 bool Plater::is_preview_loaded() const { return p->is_preview_loaded(); }
is_view3D_shown() const5111 bool Plater::is_view3D_shown() const { return p->is_view3D_shown(); }
5112 
are_view3D_labels_shown() const5113 bool Plater::are_view3D_labels_shown() const { return p->are_view3D_labels_shown(); }
show_view3D_labels(bool show)5114 void Plater::show_view3D_labels(bool show) { p->show_view3D_labels(show); }
5115 
is_sidebar_collapsed() const5116 bool Plater::is_sidebar_collapsed() const { return p->is_sidebar_collapsed(); }
collapse_sidebar(bool show)5117 void Plater::collapse_sidebar(bool show) { p->collapse_sidebar(show); }
5118 
is_view3D_layers_editing_enabled() const5119 bool Plater::is_view3D_layers_editing_enabled() const { return p->is_view3D_layers_editing_enabled(); }
5120 
select_all()5121 void Plater::select_all() { p->select_all(); }
deselect_all()5122 void Plater::deselect_all() { p->deselect_all(); }
5123 
remove(size_t obj_idx)5124 void Plater::remove(size_t obj_idx) { p->remove(obj_idx); }
reset()5125 void Plater::reset() { p->reset(); }
reset_with_confirm()5126 void Plater::reset_with_confirm()
5127 {
5128     if (wxMessageDialog(static_cast<wxWindow*>(this), _L("All objects will be removed, continue?"), wxString(SLIC3R_APP_NAME) + " - " + _L("Delete all"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES)
5129         reset();
5130 }
5131 
delete_object_from_model(size_t obj_idx)5132 void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_model(obj_idx); }
5133 
remove_selected()5134 void Plater::remove_selected()
5135 {
5136     Plater::TakeSnapshot snapshot(this, _L("Delete Selected Objects"));
5137     this->p->view3D->delete_selected();
5138 }
5139 
increase_instances(size_t num)5140 void Plater::increase_instances(size_t num)
5141 {
5142     if (! can_increase_instances()) { return; }
5143 
5144     Plater::TakeSnapshot snapshot(this, _L("Increase Instances"));
5145 
5146     int obj_idx = p->get_selected_object_idx();
5147 
5148     ModelObject* model_object = p->model.objects[obj_idx];
5149     ModelInstance* model_instance = model_object->instances.back();
5150 
5151     bool was_one_instance = model_object->instances.size()==1;
5152 
5153     double offset_base = canvas3D()->get_size_proportional_to_max_bed_size(0.05);
5154     double offset = offset_base;
5155     for (size_t i = 0; i < num; i++, offset += offset_base) {
5156         Vec3d offset_vec = model_instance->get_offset() + Vec3d(offset, offset, 0.0);
5157         model_object->add_instance(offset_vec, model_instance->get_scaling_factor(), model_instance->get_rotation(), model_instance->get_mirror());
5158 //        p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec));
5159     }
5160 
5161     if (p->get_config("autocenter") == "1")
5162         arrange();
5163 
5164     p->update();
5165 
5166     p->get_selection().add_instance(obj_idx, (int)model_object->instances.size() - 1);
5167 
5168     sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num);
5169 
5170     p->selection_changed();
5171     this->p->schedule_background_process();
5172 }
5173 
decrease_instances(size_t num)5174 void Plater::decrease_instances(size_t num)
5175 {
5176     if (! can_decrease_instances()) { return; }
5177 
5178     Plater::TakeSnapshot snapshot(this, _L("Decrease Instances"));
5179 
5180     int obj_idx = p->get_selected_object_idx();
5181 
5182     ModelObject* model_object = p->model.objects[obj_idx];
5183     if (model_object->instances.size() > num) {
5184         for (size_t i = 0; i < num; ++ i)
5185             model_object->delete_last_instance();
5186         p->update();
5187         // Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
5188         sidebar().obj_list()->decrease_object_instances(obj_idx, num);
5189     }
5190     else {
5191         remove(obj_idx);
5192     }
5193 
5194     if (!model_object->instances.empty())
5195         p->get_selection().add_instance(obj_idx, (int)model_object->instances.size() - 1);
5196 
5197     p->selection_changed();
5198     this->p->schedule_background_process();
5199 }
5200 
set_number_of_copies()5201 void Plater::set_number_of_copies(/*size_t num*/)
5202 {
5203     int obj_idx = p->get_selected_object_idx();
5204     if (obj_idx == -1)
5205         return;
5206 
5207     ModelObject* model_object = p->model.objects[obj_idx];
5208 
5209     const int num = wxGetNumberFromUser( " ", _L("Enter the number of copies:"),
5210                                     _L("Copies of the selected object"), model_object->instances.size(), 0, 1000, this );
5211     if (num < 0)
5212         return;
5213 
5214     Plater::TakeSnapshot snapshot(this, wxString::Format(_L("Set numbers of copies to %d"), num));
5215 
5216     int diff = num - (int)model_object->instances.size();
5217     if (diff > 0)
5218         increase_instances(diff);
5219     else if (diff < 0)
5220         decrease_instances(-diff);
5221 }
5222 
fill_bed_with_instances()5223 void Plater::fill_bed_with_instances()
5224 {
5225     p->m_ui_jobs.fill_bed();
5226 }
5227 
is_selection_empty() const5228 bool Plater::is_selection_empty() const
5229 {
5230     return p->get_selection().is_empty() || p->get_selection().is_wipe_tower();
5231 }
5232 
scale_selection_to_fit_print_volume()5233 void Plater::scale_selection_to_fit_print_volume()
5234 {
5235     p->scale_selection_to_fit_print_volume();
5236 }
5237 
convert_unit(bool from_imperial_unit)5238 void Plater::convert_unit(bool from_imperial_unit)
5239 {
5240     std::vector<int> obj_idxs, volume_idxs;
5241     wxGetApp().obj_list()->get_selection_indexes(obj_idxs, volume_idxs);
5242     if (obj_idxs.empty() && volume_idxs.empty())
5243         return;
5244 
5245     TakeSnapshot snapshot(this, from_imperial_unit ? _L("Convert from imperial units") : _L("Revert conversion from imperial units"));
5246     wxBusyCursor wait;
5247 
5248     ModelObjectPtrs objects;
5249     for (int obj_idx : obj_idxs) {
5250         ModelObject *object = p->model.objects[obj_idx];
5251         object->convert_units(objects, from_imperial_unit, volume_idxs);
5252         remove(obj_idx);
5253     }
5254     p->load_model_objects(objects);
5255 
5256     Selection& selection = p->view3D->get_canvas3d()->get_selection();
5257     size_t last_obj_idx = p->model.objects.size() - 1;
5258 
5259     if (volume_idxs.empty()) {
5260         for (size_t i = 0; i < objects.size(); ++i)
5261             selection.add_object((unsigned int)(last_obj_idx - i), i == 0);
5262     }
5263     else {
5264         for (int vol_idx : volume_idxs)
5265             selection.add_volume(last_obj_idx, vol_idx, 0, false);
5266     }
5267 }
5268 
cut(size_t obj_idx,size_t instance_idx,coordf_t z,bool keep_upper,bool keep_lower,bool rotate_lower)5269 void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower)
5270 {
5271     wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds");
5272     auto *object = p->model.objects[obj_idx];
5273 
5274     wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds");
5275 
5276     if (!keep_upper && !keep_lower) {
5277         return;
5278     }
5279 
5280     Plater::TakeSnapshot snapshot(this, _L("Cut by Plane"));
5281 
5282     wxBusyCursor wait;
5283     const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower);
5284 
5285     remove(obj_idx);
5286     p->load_model_objects(new_objects);
5287 
5288     Selection& selection = p->get_selection();
5289     size_t last_id = p->model.objects.size() - 1;
5290     for (size_t i = 0; i < new_objects.size(); ++i)
5291     {
5292         selection.add_object((unsigned int)(last_id - i), i == 0);
5293     }
5294 }
5295 
export_gcode(bool prefer_removable)5296 void Plater::export_gcode(bool prefer_removable)
5297 {
5298     if (p->model.objects.empty())
5299         return;
5300 
5301     if (p->process_completed_with_error)
5302         return;
5303 
5304     // If possible, remove accents from accented latin characters.
5305     // This function is useful for generating file names to be processed by legacy firmwares.
5306     fs::path default_output_file;
5307     try {
5308         // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template.
5309         // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed.
5310         unsigned int state = this->p->update_restart_background_process(false, false);
5311         if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)
5312             return;
5313         default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf")));
5314     } catch (const Slic3r::PlaceholderParserError &ex) {
5315         // Show the error with monospaced font.
5316         show_error(this, ex.what(), true);
5317         return;
5318     } catch (const std::exception &ex) {
5319         show_error(this, ex.what(), false);
5320         return;
5321     }
5322     default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
5323     AppConfig 				&appconfig 				 = *wxGetApp().app_config;
5324     RemovableDriveManager 	&removable_drive_manager = *wxGetApp().removable_drive_manager();
5325     // Get a last save path, either to removable media or to an internal media.
5326     std::string      		 start_dir 				 = appconfig.get_last_output_dir(default_output_file.parent_path().string(), prefer_removable);
5327 	if (prefer_removable) {
5328 		// Returns a path to a removable media if it exists, prefering start_dir. Update the internal removable drives database.
5329 		start_dir = removable_drive_manager.get_removable_drive_path(start_dir);
5330 		if (start_dir.empty())
5331 			// Direct user to the last internal media.
5332 			start_dir = appconfig.get_last_output_dir(default_output_file.parent_path().string(), false);
5333 	}
5334 
5335     fs::path output_path;
5336     {
5337     	std::string ext = default_output_file.extension().string();
5338         wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 / SL1S file as:"),
5339             start_dir,
5340             from_path(default_output_file.filename()),
5341             GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : boost::iequals(ext, ".sl1s") ? FT_SL1S : FT_SL1, ext),
5342             wxFD_SAVE | wxFD_OVERWRITE_PROMPT
5343         );
5344             if (dlg.ShowModal() == wxID_OK)
5345             output_path = into_path(dlg.GetPath());
5346     }
5347 
5348     if (! output_path.empty()) {
5349 		bool path_on_removable_media = removable_drive_manager.set_and_verify_last_save_path(output_path.string());
5350         p->notification_manager->new_export_began(path_on_removable_media);
5351         p->exporting_status = path_on_removable_media ? ExportingStatus::EXPORTING_TO_REMOVABLE : ExportingStatus::EXPORTING_TO_LOCAL;
5352         p->last_output_path = output_path.string();
5353         p->last_output_dir_path = output_path.parent_path().string();
5354         p->export_gcode(output_path, path_on_removable_media, PrintHostJob());
5355         // Storing a path to AppConfig either as path to removable media or a path to internal media.
5356         // is_path_on_removable_drive() is called with the "true" parameter to update its internal database as the user may have shuffled the external drives
5357         // while the dialog was open.
5358         appconfig.update_last_output_dir(output_path.parent_path().string(), path_on_removable_media);
5359 
5360 	}
5361 }
5362 
export_stl(bool extended,bool selection_only)5363 void Plater::export_stl(bool extended, bool selection_only)
5364 {
5365     if (p->model.objects.empty()) { return; }
5366 
5367     wxString path = p->get_export_file(FT_STL);
5368     if (path.empty()) { return; }
5369     const std::string path_u8 = into_u8(path);
5370 
5371     wxBusyCursor wait;
5372 
5373     const auto &selection = p->get_selection();
5374     const auto obj_idx = selection.get_object_idx();
5375     if (selection_only && (obj_idx == -1 || selection.is_wipe_tower()))
5376         return;
5377 
5378     TriangleMesh mesh;
5379     if (p->printer_technology == ptFFF) {
5380         if (selection_only) {
5381             const ModelObject* model_object = p->model.objects[obj_idx];
5382             if (selection.get_mode() == Selection::Instance)
5383             {
5384                 if (selection.is_single_full_object())
5385                     mesh = model_object->mesh();
5386                 else
5387                     mesh = model_object->full_raw_mesh();
5388             }
5389             else
5390             {
5391                 const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
5392                 mesh = model_object->volumes[volume->volume_idx()]->mesh();
5393                 mesh.transform(volume->get_volume_transformation().get_matrix());
5394                 mesh.translate(-model_object->origin_translation.cast<float>());
5395             }
5396         }
5397         else {
5398             mesh = p->model.mesh();
5399         }
5400     }
5401     else
5402     {
5403         // This is SLA mode, all objects have only one volume.
5404         // However, we must have a look at the backend to load
5405         // hollowed mesh and/or supports
5406 
5407         const PrintObjects& objects = p->sla_print.objects();
5408         for (const SLAPrintObject* object : objects)
5409         {
5410             const ModelObject* model_object = object->model_object();
5411             if (selection_only) {
5412                 if (model_object->id() != p->model.objects[obj_idx]->id())
5413                     continue;
5414             }
5415             Transform3d mesh_trafo_inv = object->trafo().inverse();
5416             bool is_left_handed = object->is_left_handed();
5417 
5418             TriangleMesh pad_mesh;
5419             bool has_pad_mesh = extended && object->has_mesh(slaposPad);
5420             if (has_pad_mesh)
5421             {
5422                 pad_mesh = object->get_mesh(slaposPad);
5423                 pad_mesh.transform(mesh_trafo_inv);
5424             }
5425 
5426             TriangleMesh supports_mesh;
5427             bool has_supports_mesh = extended && object->has_mesh(slaposSupportTree);
5428             if (has_supports_mesh)
5429             {
5430                 supports_mesh = object->get_mesh(slaposSupportTree);
5431                 supports_mesh.transform(mesh_trafo_inv);
5432             }
5433             const std::vector<SLAPrintObject::Instance>& obj_instances = object->instances();
5434             for (const SLAPrintObject::Instance& obj_instance : obj_instances)
5435             {
5436                 auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(),
5437                     [&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; });
5438                 assert(it != model_object->instances.end());
5439 
5440                 if (it != model_object->instances.end())
5441                 {
5442                     bool one_inst_only = selection_only && ! selection.is_single_full_object();
5443 
5444                     int instance_idx = it - model_object->instances.begin();
5445                     const Transform3d& inst_transform = one_inst_only
5446                             ? Transform3d::Identity()
5447                             : object->model_object()->instances[instance_idx]->get_transformation().get_matrix();
5448 
5449                     TriangleMesh inst_mesh;
5450 
5451                     if (has_pad_mesh)
5452                     {
5453                         TriangleMesh inst_pad_mesh = pad_mesh;
5454                         inst_pad_mesh.transform(inst_transform, is_left_handed);
5455                         inst_mesh.merge(inst_pad_mesh);
5456                     }
5457 
5458                     if (has_supports_mesh)
5459                     {
5460                         TriangleMesh inst_supports_mesh = supports_mesh;
5461                         inst_supports_mesh.transform(inst_transform, is_left_handed);
5462                         inst_mesh.merge(inst_supports_mesh);
5463                     }
5464 
5465                     TriangleMesh inst_object_mesh = object->get_mesh_to_print();
5466                     inst_object_mesh.transform(mesh_trafo_inv);
5467                     inst_object_mesh.transform(inst_transform, is_left_handed);
5468 
5469                     inst_mesh.merge(inst_object_mesh);
5470 
5471                     // ensure that the instance lays on the bed
5472                     inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min[2]);
5473 
5474                     // merge instance with global mesh
5475                     mesh.merge(inst_mesh);
5476 
5477                     if (one_inst_only)
5478                         break;
5479                 }
5480             }
5481         }
5482     }
5483 
5484     Slic3r::store_stl(path_u8.c_str(), &mesh, true);
5485     p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path));
5486 }
5487 
export_amf()5488 void Plater::export_amf()
5489 {
5490     if (p->model.objects.empty()) { return; }
5491 
5492     wxString path = p->get_export_file(FT_AMF);
5493     if (path.empty()) { return; }
5494     const std::string path_u8 = into_u8(path);
5495 
5496     wxBusyCursor wait;
5497     bool export_config = true;
5498     DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
5499     bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1";
5500     if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) {
5501         // Success
5502         p->statusbar()->set_status_text(format_wxstr(_L("AMF file exported to %s"), path));
5503     } else {
5504         // Failure
5505         p->statusbar()->set_status_text(format_wxstr(_L("Error exporting AMF file %s"), path));
5506     }
5507 }
5508 
export_3mf(const boost::filesystem::path & output_path)5509 void Plater::export_3mf(const boost::filesystem::path& output_path)
5510 {
5511     if (p->model.objects.empty()) { return; }
5512 
5513     wxString path;
5514     bool export_config = true;
5515     if (output_path.empty())
5516     {
5517         path = p->get_export_file(FT_3MF);
5518         if (path.empty()) { return; }
5519     }
5520     else
5521         path = from_path(output_path);
5522 
5523     if (!path.Lower().EndsWith(".3mf"))
5524         return;
5525 
5526     DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
5527     const std::string path_u8 = into_u8(path);
5528     wxBusyCursor wait;
5529     bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1";
5530     ThumbnailData thumbnail_data;
5531     p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true);
5532     if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) {
5533         // Success
5534         p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path));
5535         p->set_project_filename(path);
5536     }
5537     else {
5538         // Failure
5539         p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path));
5540     }
5541 }
5542 
reload_from_disk()5543 void Plater::reload_from_disk()
5544 {
5545     p->reload_from_disk();
5546 }
5547 
reload_all_from_disk()5548 void Plater::reload_all_from_disk()
5549 {
5550     p->reload_all_from_disk();
5551 }
5552 
has_toolpaths_to_export() const5553 bool Plater::has_toolpaths_to_export() const
5554 {
5555     return  p->preview->get_canvas3d()->has_toolpaths_to_export();
5556 }
5557 
export_toolpaths_to_obj() const5558 void Plater::export_toolpaths_to_obj() const
5559 {
5560     if ((printer_technology() != ptFFF) || !is_preview_loaded())
5561         return;
5562 
5563     wxString path = p->get_export_file(FT_OBJ);
5564     if (path.empty())
5565         return;
5566 
5567     wxBusyCursor wait;
5568     p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
5569 }
5570 
reslice()5571 void Plater::reslice()
5572 {
5573     // There is "invalid data" button instead "slice now"
5574     if (p->process_completed_with_error)
5575         return;
5576 
5577     // Stop arrange and (or) optimize rotation tasks.
5578     this->stop_jobs();
5579 
5580     if (printer_technology() == ptSLA) {
5581         for (auto& object : model().objects)
5582             if (object->sla_points_status == sla::PointsStatus::NoPoints)
5583                 object->sla_points_status = sla::PointsStatus::Generating;
5584     }
5585 
5586     //FIXME Don't reslice if export of G-code or sending to OctoPrint is running.
5587     // bitmask of UpdateBackgroundProcessReturnState
5588     unsigned int state = this->p->update_background_process(true);
5589     if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
5590         this->p->view3D->reload_scene(false);
5591     // If the SLA processing of just a single object's supports is running, restart slicing for the whole object.
5592     this->p->background_process.set_task(PrintBase::TaskParams());
5593     // Only restarts if the state is valid.
5594     this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART);
5595 
5596     if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
5597         return;
5598 
5599     bool clean_gcode_toolpaths = true;
5600     if (p->background_process.running())
5601     {
5602         if (wxGetApp().get_mode() == comSimple)
5603             p->sidebar->set_btn_label(ActionButtonType::abReslice, _L("Slicing") + dots);
5604         else
5605         {
5606             p->sidebar->set_btn_label(ActionButtonType::abReslice, _L("Slice now"));
5607             p->show_action_buttons(false);
5608         }
5609     }
5610     else if (!p->background_process.empty() && !p->background_process.idle())
5611         p->show_action_buttons(true);
5612     else
5613         clean_gcode_toolpaths = false;
5614 
5615     if (clean_gcode_toolpaths)
5616         reset_gcode_toolpaths();
5617 
5618 #if ENABLE_PREVIEW_TYPE_CHANGE
5619     p->preview->reload_print(!clean_gcode_toolpaths);
5620 #else
5621     // update type of preview
5622     p->preview->update_view_type(!clean_gcode_toolpaths);
5623 #endif // ENABLE_PREVIEW_TYPE_CHANGE
5624 }
5625 
reslice_SLA_supports(const ModelObject & object,bool postpone_error_messages)5626 void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages)
5627 {
5628     reslice_SLA_until_step(slaposPad, object, postpone_error_messages);
5629 }
5630 
reslice_SLA_hollowing(const ModelObject & object,bool postpone_error_messages)5631 void Plater::reslice_SLA_hollowing(const ModelObject &object, bool postpone_error_messages)
5632 {
5633     reslice_SLA_until_step(slaposDrillHoles, object, postpone_error_messages);
5634 }
5635 
reslice_SLA_until_step(SLAPrintObjectStep step,const ModelObject & object,bool postpone_error_messages)5636 void Plater::reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &object, bool postpone_error_messages)
5637 {
5638     //FIXME Don't reslice if export of G-code or sending to OctoPrint is running.
5639     // bitmask of UpdateBackgroundProcessReturnState
5640     unsigned int state = this->p->update_background_process(true, postpone_error_messages);
5641     if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
5642         this->p->view3D->reload_scene(false);
5643 
5644     if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID))
5645         // Nothing to do on empty input or invalid configuration.
5646         return;
5647 
5648     // Limit calculation to the single object only.
5649     PrintBase::TaskParams task;
5650     task.single_model_object = object.id();
5651     // If the background processing is not enabled, calculate supports just for the single instance.
5652     // Otherwise calculate everything, but start with the provided object.
5653     if (!this->p->background_processing_enabled()) {
5654         task.single_model_instance_only = true;
5655         task.to_object_step = step;
5656     }
5657     this->p->background_process.set_task(task);
5658     // and let the background processing start.
5659     this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART);
5660 }
5661 
send_gcode()5662 void Plater::send_gcode()
5663 {
5664     // if physical_printer is selected, send gcode for this printer
5665     DynamicPrintConfig* physical_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config();
5666     if (! physical_printer_config || p->model.objects.empty())
5667         return;
5668 
5669     PrintHostJob upload_job(physical_printer_config);
5670     if (upload_job.empty())
5671         return;
5672 
5673     // Obtain default output path
5674     fs::path default_output_file;
5675     try {
5676         // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template.
5677         // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed.
5678         unsigned int state = this->p->update_restart_background_process(false, false);
5679         if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)
5680             return;
5681         default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf")));
5682     } catch (const Slic3r::PlaceholderParserError& ex) {
5683         // Show the error with monospaced font.
5684         show_error(this, ex.what(), true);
5685         return;
5686     } catch (const std::exception& ex) {
5687         show_error(this, ex.what(), false);
5688         return;
5689     }
5690     default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
5691 
5692     // Repetier specific: Query the server for the list of file groups.
5693     wxArrayString groups;
5694     {
5695         wxBusyCursor wait;
5696         upload_job.printhost->get_groups(groups);
5697     }
5698 
5699     PrintHostSendDialog dlg(default_output_file, upload_job.printhost->can_start_print(), groups);
5700     if (dlg.ShowModal() == wxID_OK) {
5701         upload_job.upload_data.upload_path = dlg.filename();
5702         upload_job.upload_data.start_print = dlg.start_print();
5703         upload_job.upload_data.group       = dlg.group();
5704         p->export_gcode(fs::path(), false, std::move(upload_job));
5705     }
5706 }
5707 
5708 // Called when the Eject button is pressed.
eject_drive()5709 void Plater::eject_drive()
5710 {
5711     wxBusyCursor wait;
5712 	wxGetApp().removable_drive_manager()->eject_drive();
5713 }
5714 
take_snapshot(const std::string & snapshot_name)5715 void Plater::take_snapshot(const std::string &snapshot_name) { p->take_snapshot(snapshot_name); }
take_snapshot(const wxString & snapshot_name)5716 void Plater::take_snapshot(const wxString &snapshot_name) { p->take_snapshot(snapshot_name); }
suppress_snapshots()5717 void Plater::suppress_snapshots() { p->suppress_snapshots(); }
allow_snapshots()5718 void Plater::allow_snapshots() { p->allow_snapshots(); }
undo()5719 void Plater::undo() { p->undo(); }
redo()5720 void Plater::redo() { p->redo(); }
undo_to(int selection)5721 void Plater::undo_to(int selection)
5722 {
5723     if (selection == 0) {
5724         p->undo();
5725         return;
5726     }
5727 
5728     const int idx = p->get_active_snapshot_index() - selection - 1;
5729     p->undo_redo_to(p->undo_redo_stack().snapshots()[idx].timestamp);
5730 }
redo_to(int selection)5731 void Plater::redo_to(int selection)
5732 {
5733     if (selection == 0) {
5734         p->redo();
5735         return;
5736     }
5737 
5738     const int idx = p->get_active_snapshot_index() + selection + 1;
5739     p->undo_redo_to(p->undo_redo_stack().snapshots()[idx].timestamp);
5740 }
undo_redo_string_getter(const bool is_undo,int idx,const char ** out_text)5741 bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text)
5742 {
5743     const std::vector<UndoRedo::Snapshot>& ss_stack = p->undo_redo_stack().snapshots();
5744     const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -(++idx) : idx);
5745 
5746     if (0 < idx_in_ss_stack && (size_t)idx_in_ss_stack < ss_stack.size() - 1) {
5747         *out_text = ss_stack[idx_in_ss_stack].name.c_str();
5748         return true;
5749     }
5750 
5751     return false;
5752 }
5753 
undo_redo_topmost_string_getter(const bool is_undo,std::string & out_text)5754 void Plater::undo_redo_topmost_string_getter(const bool is_undo, std::string& out_text)
5755 {
5756     const std::vector<UndoRedo::Snapshot>& ss_stack = p->undo_redo_stack().snapshots();
5757     const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -1 : 0);
5758 
5759     if (0 < idx_in_ss_stack && (size_t)idx_in_ss_stack < ss_stack.size() - 1) {
5760         out_text = ss_stack[idx_in_ss_stack].name;
5761         return;
5762     }
5763 
5764     out_text = "";
5765 }
5766 
search_string_getter(int idx,const char ** label,const char ** tooltip)5767 bool Plater::search_string_getter(int idx, const char** label, const char** tooltip)
5768 {
5769     const Search::OptionsSearcher& search_list = p->sidebar->get_searcher();
5770 
5771     if (0 <= idx && (size_t)idx < search_list.size()) {
5772         search_list[idx].get_marked_label_and_tooltip(label, tooltip);
5773         return true;
5774     }
5775 
5776     return false;
5777 }
5778 
on_extruders_change(size_t num_extruders)5779 void Plater::on_extruders_change(size_t num_extruders)
5780 {
5781     auto& choices = sidebar().combos_filament();
5782 
5783     if (num_extruders == choices.size())
5784         return;
5785 
5786     wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/);
5787 
5788     size_t i = choices.size();
5789     while ( i < num_extruders )
5790     {
5791         PlaterPresetComboBox* choice/*{ nullptr }*/;
5792         sidebar().init_filament_combo(&choice, i);
5793         choices.push_back(choice);
5794 
5795         // initialize selection
5796         choice->update();
5797         ++i;
5798     }
5799 
5800     // remove unused choices if any
5801     sidebar().remove_unused_filament_combos(num_extruders);
5802 
5803     sidebar().Layout();
5804     sidebar().scrolled_panel()->Refresh();
5805 }
5806 
on_config_change(const DynamicPrintConfig & config)5807 void Plater::on_config_change(const DynamicPrintConfig &config)
5808 {
5809     bool update_scheduled = false;
5810     bool bed_shape_changed = false;
5811     for (auto opt_key : p->config->diff(config)) {
5812         if (opt_key == "filament_colour")
5813         {
5814             update_scheduled = true; // update should be scheduled (for update 3DScene) #2738
5815 
5816             /* There is a case, when we use filament_color instead of extruder_color (when extruder_color == "").
5817              * Thus plater config option "filament_colour" should be filled with filament_presets values.
5818              * Otherwise, on 3dScene will be used last edited filament color for all volumes with extruder_color == "".
5819              */
5820             const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
5821             if (filament_presets.size() > 1 &&
5822                 p->config->option<ConfigOptionStrings>(opt_key)->values.size() != config.option<ConfigOptionStrings>(opt_key)->values.size())
5823             {
5824                 const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
5825                 std::vector<std::string> filament_colors;
5826                 filament_colors.reserve(filament_presets.size());
5827 
5828                 for (const std::string& filament_preset : filament_presets)
5829                     filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
5830 
5831                 p->config->option<ConfigOptionStrings>(opt_key)->values = filament_colors;
5832                 p->sidebar->obj_list()->update_extruder_colors();
5833                 continue;
5834             }
5835         }
5836 
5837         p->config->set_key_value(opt_key, config.option(opt_key)->clone());
5838         if (opt_key == "printer_technology") {
5839             this->set_printer_technology(config.opt_enum<PrinterTechnology>(opt_key));
5840             // print technology is changed, so we should to update a search list
5841             p->sidebar->update_searcher();
5842             p->sidebar->show_sliced_info_sizer(false);
5843             p->reset_gcode_toolpaths();
5844         }
5845         else if (opt_key == "bed_shape" || opt_key == "bed_custom_texture" || opt_key == "bed_custom_model") {
5846             bed_shape_changed = true;
5847             update_scheduled = true;
5848         }
5849         else if (boost::starts_with(opt_key, "wipe_tower") ||
5850             // opt_key == "filament_minimal_purge_on_wipe_tower" // ? #ys_FIXME
5851             opt_key == "single_extruder_multi_material") {
5852             update_scheduled = true;
5853         }
5854         else if(opt_key == "variable_layer_height") {
5855             if (p->config->opt_bool("variable_layer_height") != true) {
5856                 p->view3D->enable_layers_editing(false);
5857                 p->view3D->set_as_dirty();
5858             }
5859         }
5860         else if(opt_key == "extruder_colour") {
5861             update_scheduled = true;
5862 #if !ENABLE_PREVIEW_TYPE_CHANGE
5863             p->preview->set_number_extruders(p->config->option<ConfigOptionStrings>(opt_key)->values.size());
5864 #endif // !ENABLE_PREVIEW_TYPE_CHANGE
5865             p->sidebar->obj_list()->update_extruder_colors();
5866         } else if(opt_key == "max_print_height") {
5867             update_scheduled = true;
5868         }
5869         else if (opt_key == "printer_model") {
5870             // update to force bed selection(for texturing)
5871             bed_shape_changed = true;
5872             update_scheduled = true;
5873         }
5874     }
5875 
5876     if (bed_shape_changed)
5877         set_bed_shape();
5878 
5879     if (update_scheduled)
5880         update();
5881 
5882     if (p->main_frame->is_loaded())
5883         this->p->schedule_background_process();
5884 }
5885 
set_bed_shape() const5886 void Plater::set_bed_shape() const
5887 {
5888     set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
5889         p->config->option<ConfigOptionString>("bed_custom_texture")->value,
5890         p->config->option<ConfigOptionString>("bed_custom_model")->value);
5891 }
5892 
set_bed_shape(const Pointfs & shape,const std::string & custom_texture,const std::string & custom_model,bool force_as_custom) const5893 void Plater::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) const
5894 {
5895     p->set_bed_shape(shape, custom_texture, custom_model, force_as_custom);
5896 }
5897 
force_filament_colors_update()5898 void Plater::force_filament_colors_update()
5899 {
5900     bool update_scheduled = false;
5901     DynamicPrintConfig* config = p->config;
5902     const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
5903     if (filament_presets.size() > 1 &&
5904         p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == filament_presets.size())
5905     {
5906         const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
5907         std::vector<std::string> filament_colors;
5908         filament_colors.reserve(filament_presets.size());
5909 
5910         for (const std::string& filament_preset : filament_presets)
5911             filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
5912 
5913         if (config->option<ConfigOptionStrings>("filament_colour")->values != filament_colors) {
5914             config->option<ConfigOptionStrings>("filament_colour")->values = filament_colors;
5915             update_scheduled = true;
5916         }
5917     }
5918 
5919     if (update_scheduled) {
5920         update();
5921         p->sidebar->obj_list()->update_extruder_colors();
5922     }
5923 
5924     if (p->main_frame->is_loaded())
5925         this->p->schedule_background_process();
5926 }
5927 
force_print_bed_update()5928 void Plater::force_print_bed_update()
5929 {
5930 	// Fill in the printer model key with something which cannot possibly be valid, so that Plater::on_config_change() will update the print bed
5931 	// once a new Printer profile config is loaded.
5932 	p->config->opt_string("printer_model", true) = "\x01\x00\x01";
5933 }
5934 
on_activate()5935 void Plater::on_activate()
5936 {
5937 #if defined(__linux__) || defined(_WIN32)
5938     // Activating the main frame, and no window has keyboard focus.
5939     // Set the keyboard focus to the visible Canvas3D.
5940     if (this->p->view3D->IsShown() && wxWindow::FindFocus() != this->p->view3D->get_wxglcanvas())
5941         CallAfter([this]() { this->p->view3D->get_wxglcanvas()->SetFocus(); });
5942     else if (this->p->preview->IsShown() && wxWindow::FindFocus() != this->p->view3D->get_wxglcanvas())
5943         CallAfter([this]() { this->p->preview->get_wxglcanvas()->SetFocus(); });
5944 #endif
5945 
5946 	this->p->show_delayed_error_message();
5947 }
5948 
5949 // Get vector of extruder colors considering filament color, if extruder color is undefined.
get_extruder_colors_from_plater_config(const GCodeProcessor::Result * const result) const5950 std::vector<std::string> Plater::get_extruder_colors_from_plater_config(const GCodeProcessor::Result* const result) const
5951 {
5952     if (wxGetApp().is_gcode_viewer() && result != nullptr)
5953         return result->extruder_colors;
5954     else {
5955         const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
5956         std::vector<std::string> extruder_colors;
5957         if (!config->has("extruder_colour")) // in case of a SLA print
5958             return extruder_colors;
5959 
5960         extruder_colors = (config->option<ConfigOptionStrings>("extruder_colour"))->values;
5961         if (!wxGetApp().plater())
5962             return extruder_colors;
5963 
5964         const std::vector<std::string>& filament_colours = (p->config->option<ConfigOptionStrings>("filament_colour"))->values;
5965         for (size_t i = 0; i < extruder_colors.size(); ++i)
5966             if (extruder_colors[i] == "" && i < filament_colours.size())
5967                 extruder_colors[i] = filament_colours[i];
5968 
5969         return extruder_colors;
5970     }
5971 }
5972 
5973 /* Get vector of colors used for rendering of a Preview scene in "Color print" mode
5974  * It consists of extruder colors and colors, saved in model.custom_gcode_per_print_z
5975  */
get_colors_for_color_print(const GCodeProcessor::Result * const result) const5976 std::vector<std::string> Plater::get_colors_for_color_print(const GCodeProcessor::Result* const result) const
5977 {
5978     std::vector<std::string> colors = get_extruder_colors_from_plater_config(result);
5979     colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.gcodes.size());
5980 
5981     for (const CustomGCode::Item& code : p->model.custom_gcode_per_print_z.gcodes)
5982         if (code.type == CustomGCode::ColorChange)
5983             colors.emplace_back(code.color);
5984 
5985     return colors;
5986 }
5987 
get_project_filename(const wxString & extension) const5988 wxString Plater::get_project_filename(const wxString& extension) const
5989 {
5990     return p->get_project_filename(extension);
5991 }
5992 
set_project_filename(const wxString & filename)5993 void Plater::set_project_filename(const wxString& filename)
5994 {
5995     return p->set_project_filename(filename);
5996 }
5997 
is_export_gcode_scheduled() const5998 bool Plater::is_export_gcode_scheduled() const
5999 {
6000     return p->background_process.is_export_scheduled();
6001 }
6002 
get_selection() const6003 const Selection &Plater::get_selection() const
6004 {
6005     return p->get_selection();
6006 }
6007 
get_selected_object_idx()6008 int Plater::get_selected_object_idx()
6009 {
6010     return p->get_selected_object_idx();
6011 }
6012 
is_single_full_object_selection() const6013 bool Plater::is_single_full_object_selection() const
6014 {
6015     return p->get_selection().is_single_full_object();
6016 }
6017 
canvas3D()6018 GLCanvas3D* Plater::canvas3D()
6019 {
6020     return p->view3D->get_canvas3d();
6021 }
6022 
canvas3D() const6023 const GLCanvas3D* Plater::canvas3D() const
6024 {
6025     return p->view3D->get_canvas3d();
6026 }
6027 
get_current_canvas3D()6028 GLCanvas3D* Plater::get_current_canvas3D()
6029 {
6030     return p->get_current_canvas3D();
6031 }
6032 
bed_shape_bb() const6033 BoundingBoxf Plater::bed_shape_bb() const
6034 {
6035     return p->bed_shape_bb();
6036 }
6037 
arrange()6038 void Plater::arrange()
6039 {
6040     p->m_ui_jobs.arrange();
6041 }
6042 
set_current_canvas_as_dirty()6043 void Plater::set_current_canvas_as_dirty()
6044 {
6045     p->set_current_canvas_as_dirty();
6046 }
6047 
unbind_canvas_event_handlers()6048 void Plater::unbind_canvas_event_handlers()
6049 {
6050     p->unbind_canvas_event_handlers();
6051 }
6052 
reset_canvas_volumes()6053 void Plater::reset_canvas_volumes()
6054 {
6055     p->reset_canvas_volumes();
6056 }
6057 
printer_technology() const6058 PrinterTechnology Plater::printer_technology() const
6059 {
6060     return p->printer_technology;
6061 }
6062 
config() const6063 const DynamicPrintConfig * Plater::config() const { return p->config; }
6064 
set_printer_technology(PrinterTechnology printer_technology)6065 bool Plater::set_printer_technology(PrinterTechnology printer_technology)
6066 {
6067     p->printer_technology = printer_technology;
6068     bool ret = p->background_process.select_technology(printer_technology);
6069     if (ret) {
6070         // Update the active presets.
6071     }
6072     //FIXME for SLA synchronize
6073     //p->background_process.apply(Model)!
6074 
6075     p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export");
6076     p->label_btn_send   = printer_technology == ptFFF ? L("Send G-code")   : L("Send to printer");
6077 
6078     if (wxGetApp().mainframe != nullptr)
6079         wxGetApp().mainframe->update_menubar();
6080 
6081     p->update_main_toolbar_tooltips();
6082 
6083     p->sidebar->get_searcher().set_printer_technology(printer_technology);
6084 
6085     return ret;
6086 }
6087 
changed_object(int obj_idx)6088 void Plater::changed_object(int obj_idx)
6089 {
6090     if (obj_idx < 0)
6091         return;
6092     // recenter and re - align to Z = 0
6093     auto model_object = p->model.objects[obj_idx];
6094     model_object->ensure_on_bed();
6095     if (this->p->printer_technology == ptSLA) {
6096         // Update the SLAPrint from the current Model, so that the reload_scene()
6097         // pulls the correct data, update the 3D scene.
6098         this->p->update_restart_background_process(true, false);
6099     }
6100     else
6101         p->view3D->reload_scene(false);
6102 
6103     // update print
6104     this->p->schedule_background_process();
6105 }
6106 
changed_objects(const std::vector<size_t> & object_idxs)6107 void Plater::changed_objects(const std::vector<size_t>& object_idxs)
6108 {
6109     if (object_idxs.empty())
6110         return;
6111 
6112     for (size_t obj_idx : object_idxs)
6113     {
6114         if (obj_idx < p->model.objects.size())
6115             // recenter and re - align to Z = 0
6116             p->model.objects[obj_idx]->ensure_on_bed();
6117     }
6118     if (this->p->printer_technology == ptSLA) {
6119         // Update the SLAPrint from the current Model, so that the reload_scene()
6120         // pulls the correct data, update the 3D scene.
6121         this->p->update_restart_background_process(true, false);
6122     }
6123     else
6124         p->view3D->reload_scene(false);
6125 
6126     // update print
6127     this->p->schedule_background_process();
6128 }
6129 
schedule_background_process(bool schedule)6130 void Plater::schedule_background_process(bool schedule/* = true*/)
6131 {
6132     if (schedule)
6133         this->p->schedule_background_process();
6134 
6135     this->p->suppressed_backround_processing_update = false;
6136 }
6137 
is_background_process_update_scheduled() const6138 bool Plater::is_background_process_update_scheduled() const
6139 {
6140     return this->p->background_process_timer.IsRunning();
6141 }
6142 
suppress_background_process(const bool stop_background_process)6143 void Plater::suppress_background_process(const bool stop_background_process)
6144 {
6145     if (stop_background_process)
6146         this->p->background_process_timer.Stop();
6147 
6148     this->p->suppressed_backround_processing_update = true;
6149 }
6150 
fix_through_netfabb(const int obj_idx,const int vol_idx)6151 void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); }
6152 
update_object_menu()6153 void Plater::update_object_menu() { p->update_object_menu(); }
show_action_buttons(const bool ready_to_slice) const6154 void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_action_buttons(ready_to_slice); }
6155 
copy_selection_to_clipboard()6156 void Plater::copy_selection_to_clipboard()
6157 {
6158     // At first try to copy selected values to the ObjectList's clipboard
6159     // to check if Settings or Layers are selected in the list
6160     // and then copy to 3DCanvas's clipboard if not
6161     if (can_copy_to_clipboard() && !p->sidebar->obj_list()->copy_to_clipboard())
6162         p->view3D->get_canvas3d()->get_selection().copy_to_clipboard();
6163 }
6164 
paste_from_clipboard()6165 void Plater::paste_from_clipboard()
6166 {
6167     if (!can_paste_from_clipboard())
6168         return;
6169 
6170     Plater::TakeSnapshot snapshot(this, _L("Paste From Clipboard"));
6171 
6172     // At first try to paste values from the ObjectList's clipboard
6173     // to check if Settings or Layers were copied
6174     // and then paste from the 3DCanvas's clipboard if not
6175     if (!p->sidebar->obj_list()->paste_from_clipboard())
6176         p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
6177 }
6178 
search(bool plater_is_active)6179 void Plater::search(bool plater_is_active)
6180 {
6181     if (plater_is_active) {
6182         // plater should be focused for correct navigation inside search window
6183         this->SetFocus();
6184 
6185         wxKeyEvent evt;
6186 #ifdef __APPLE__
6187         evt.m_keyCode = 'f';
6188 #else /* __APPLE__ */
6189         evt.m_keyCode = WXK_CONTROL_F;
6190 #endif /* __APPLE__ */
6191         evt.SetControlDown(true);
6192         canvas3D()->on_char(evt);
6193     }
6194     else
6195     {
6196         wxPoint pos = this->ClientToScreen(wxPoint(0, 0));
6197         pos.x += em_unit(this) * 40;
6198         pos.y += em_unit(this) * 4;
6199         p->sidebar->get_searcher().search_dialog->Popup(pos);
6200     }
6201 }
6202 
msw_rescale()6203 void Plater::msw_rescale()
6204 {
6205     p->preview->msw_rescale();
6206 
6207     p->view3D->get_canvas3d()->msw_rescale();
6208 
6209     p->sidebar->msw_rescale();
6210 
6211     p->msw_rescale_object_menu();
6212 
6213     Layout();
6214     GetParent()->Layout();
6215 }
6216 
sys_color_changed()6217 void Plater::sys_color_changed()
6218 {
6219     p->preview->sys_color_changed();
6220     p->sidebar->sys_color_changed();
6221 
6222     // msw_rescale_menu updates just icons, so use it
6223     p->msw_rescale_object_menu();
6224 
6225     Layout();
6226     GetParent()->Layout();
6227 }
6228 
init_view_toolbar()6229 bool Plater::init_view_toolbar()
6230 {
6231     return p->init_view_toolbar();
6232 }
6233 
enable_view_toolbar(bool enable)6234 void Plater::enable_view_toolbar(bool enable)
6235 {
6236     p->view_toolbar.set_enabled(enable);
6237 }
6238 
init_collapse_toolbar()6239 bool Plater::init_collapse_toolbar()
6240 {
6241     return p->init_collapse_toolbar();
6242 }
6243 
enable_collapse_toolbar(bool enable)6244 void Plater::enable_collapse_toolbar(bool enable)
6245 {
6246     p->collapse_toolbar.set_enabled(enable);
6247 }
6248 
get_camera() const6249 const Camera& Plater::get_camera() const
6250 {
6251     return p->camera;
6252 }
6253 
get_camera()6254 Camera& Plater::get_camera()
6255 {
6256     return p->camera;
6257 }
6258 
6259 #if ENABLE_ENVIRONMENT_MAP
init_environment_texture()6260 void Plater::init_environment_texture()
6261 {
6262     if (p->environment_texture.get_id() == 0)
6263         p->environment_texture.load_from_file(resources_dir() + "/icons/Pmetal_001.png", false, GLTexture::SingleThreaded, false);
6264 }
6265 
get_environment_texture_id() const6266 unsigned int Plater::get_environment_texture_id() const
6267 {
6268     return p->environment_texture.get_id();
6269 }
6270 #endif // ENABLE_ENVIRONMENT_MAP
6271 
get_bed() const6272 const Bed3D& Plater::get_bed() const
6273 {
6274     return p->bed;
6275 }
6276 
get_bed()6277 Bed3D& Plater::get_bed()
6278 {
6279     return p->bed;
6280 }
6281 
get_view_toolbar() const6282 const GLToolbar& Plater::get_view_toolbar() const
6283 {
6284     return p->view_toolbar;
6285 }
6286 
get_view_toolbar()6287 GLToolbar& Plater::get_view_toolbar()
6288 {
6289     return p->view_toolbar;
6290 }
6291 
get_collapse_toolbar() const6292 const GLToolbar& Plater::get_collapse_toolbar() const
6293 {
6294     return p->collapse_toolbar;
6295 }
6296 
get_collapse_toolbar()6297 GLToolbar& Plater::get_collapse_toolbar()
6298 {
6299     return p->collapse_toolbar;
6300 }
6301 
update_preview_bottom_toolbar()6302 void Plater::update_preview_bottom_toolbar()
6303 {
6304     p->update_preview_bottom_toolbar();
6305 }
6306 
update_preview_moves_slider()6307 void Plater::update_preview_moves_slider()
6308 {
6309     p->update_preview_moves_slider();
6310 }
6311 
enable_preview_moves_slider(bool enable)6312 void Plater::enable_preview_moves_slider(bool enable)
6313 {
6314     p->enable_preview_moves_slider(enable);
6315 }
6316 
reset_gcode_toolpaths()6317 void Plater::reset_gcode_toolpaths()
6318 {
6319     p->reset_gcode_toolpaths();
6320 }
6321 
get_mouse3d_controller() const6322 const Mouse3DController& Plater::get_mouse3d_controller() const
6323 {
6324     return p->mouse3d_controller;
6325 }
6326 
get_mouse3d_controller()6327 Mouse3DController& Plater::get_mouse3d_controller()
6328 {
6329     return p->mouse3d_controller;
6330 }
6331 
get_notification_manager() const6332 const NotificationManager* Plater::get_notification_manager() const
6333 {
6334 	return p->notification_manager;
6335 }
6336 
get_notification_manager()6337 NotificationManager* Plater::get_notification_manager()
6338 {
6339 	return p->notification_manager;
6340 }
6341 
can_delete() const6342 bool Plater::can_delete() const { return p->can_delete(); }
can_delete_all() const6343 bool Plater::can_delete_all() const { return p->can_delete_all(); }
can_increase_instances() const6344 bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
can_decrease_instances() const6345 bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); }
can_set_instance_to_object() const6346 bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); }
can_fix_through_netfabb() const6347 bool Plater::can_fix_through_netfabb() const { return p->can_fix_through_netfabb(); }
can_split_to_objects() const6348 bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); }
can_split_to_volumes() const6349 bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); }
can_arrange() const6350 bool Plater::can_arrange() const { return p->can_arrange(); }
can_layers_editing() const6351 bool Plater::can_layers_editing() const { return p->can_layers_editing(); }
can_paste_from_clipboard() const6352 bool Plater::can_paste_from_clipboard() const
6353 {
6354     const Selection& selection = p->view3D->get_canvas3d()->get_selection();
6355     const Selection::Clipboard& clipboard = selection.get_clipboard();
6356 
6357     if (clipboard.is_empty() && p->sidebar->obj_list()->clipboard_is_empty())
6358         return false;
6359 
6360     if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !clipboard.is_sla_compliant())
6361         return false;
6362 
6363     Selection::EMode mode = clipboard.get_mode();
6364     if ((mode == Selection::Volume) && !selection.is_from_single_instance())
6365         return false;
6366 
6367     if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance))
6368         return false;
6369 
6370     return true;
6371 }
6372 
can_copy_to_clipboard() const6373 bool Plater::can_copy_to_clipboard() const
6374 {
6375     if (is_selection_empty())
6376         return false;
6377 
6378     const Selection& selection = p->view3D->get_canvas3d()->get_selection();
6379     if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !selection.is_sla_compliant())
6380         return false;
6381 
6382     return true;
6383 }
6384 
can_undo() const6385 bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); }
can_redo() const6386 bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); }
can_reload_from_disk() const6387 bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
undo_redo_stack_main() const6388 const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
clear_undo_redo_stack_main()6389 void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); }
enter_gizmos_stack()6390 void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); }
leave_gizmos_stack()6391 void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }
inside_snapshot_capture()6392 bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); }
6393 
6394 #if ENABLE_RENDER_STATISTICS
toggle_render_statistic_dialog()6395 void Plater::toggle_render_statistic_dialog()
6396 {
6397     p->show_render_statistic_dialog = !p->show_render_statistic_dialog;
6398 }
6399 
is_render_statistic_dialog_visible() const6400 bool Plater::is_render_statistic_dialog_visible() const
6401 {
6402     return p->show_render_statistic_dialog;
6403 }
6404 #endif // ENABLE_RENDER_STATISTICS
6405 
6406 // Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu.
PopupMenu(wxMenu * menu,const wxPoint & pos)6407 bool Plater::PopupMenu(wxMenu *menu, const wxPoint& pos)
6408 {
6409 	// Don't want to wake up and trigger reslicing while tracking the pop-up menu.
6410 	SuppressBackgroundProcessingUpdate sbpu;
6411 	// When tracking a pop-up menu, postpone error messages from the slicing result.
6412 	m_tracking_popup_menu = true;
6413 	bool out = this->wxPanel::PopupMenu(menu, pos);
6414 	m_tracking_popup_menu = false;
6415 	if (! m_tracking_popup_menu_error_message.empty()) {
6416         // Don't know whether the CallAfter is necessary, but it should not hurt.
6417         // The menus likely sends out some commands, so we may be safer if the dialog is shown after the menu command is processed.
6418 		wxString message = std::move(m_tracking_popup_menu_error_message);
6419         wxTheApp->CallAfter([message, this]() { show_error(this, message); });
6420         m_tracking_popup_menu_error_message.clear();
6421     }
6422 	return out;
6423 }
bring_instance_forward()6424 void Plater::bring_instance_forward()
6425 {
6426     p->bring_instance_forward();
6427 }
6428 
SuppressBackgroundProcessingUpdate()6429 SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() :
6430     m_was_scheduled(wxGetApp().plater()->is_background_process_update_scheduled())
6431 {
6432     wxGetApp().plater()->suppress_background_process(m_was_scheduled);
6433 }
6434 
~SuppressBackgroundProcessingUpdate()6435 SuppressBackgroundProcessingUpdate::~SuppressBackgroundProcessingUpdate()
6436 {
6437     wxGetApp().plater()->schedule_background_process(m_was_scheduled);
6438 }
6439 
6440 }}    // namespace Slic3r::GUI
6441