diff options
Diffstat (limited to 'src/slic3r/GUI/Plater.cpp')
-rw-r--r-- | src/slic3r/GUI/Plater.cpp | 3027 |
1 files changed, 1839 insertions, 1188 deletions
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8388c1cfd..7824dcfdf 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -12,6 +12,7 @@ #include <boost/filesystem/path.hpp> #include <boost/filesystem/operations.hpp> #include <boost/log/trivial.hpp> +#include <boost/nowide/convert.hpp> #include <wx/sizer.h> #include <wx/stattext.h> @@ -23,7 +24,6 @@ #include <wx/dnd.h> #include <wx/progdlg.h> #include <wx/wupdlock.h> -#include <wx/colordlg.h> #include <wx/numdlg.h> #include <wx/debug.h> #include <wx/busyinfo.h> @@ -32,26 +32,17 @@ #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" -#include "libslic3r/GCode/PreviewData.hpp" -#if ENABLE_THUMBNAIL_GENERATOR #include "libslic3r/GCode/ThumbnailData.hpp" -#endif // ENABLE_THUMBNAIL_GENERATOR #include "libslic3r/Model.hpp" #include "libslic3r/SLA/Hollowing.hpp" -#include "libslic3r/SLA/Rotfinder.hpp" #include "libslic3r/SLA/SupportPoint.hpp" +#include "libslic3r/SLA/ReprojectPointsOnMesh.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Utils.hpp" - -//#include "libslic3r/ClipperUtils.hpp" - -// #include "libnest2d/optimizers/nlopt/genetic.hpp" -// #include "libnest2d/backends/clipper/geometries.hpp" -// #include "libnest2d/utils/rotcalipers.hpp" -#include "libslic3r/MinAreaBoundingBox.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GUI.hpp" #include "GUI_App.hpp" @@ -61,6 +52,7 @@ #include "GUI_Utils.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" +#include "format.hpp" #include "3DScene.hpp" #include "GLCanvas3D.hpp" #include "Selection.hpp" @@ -70,8 +62,10 @@ #include "Camera.hpp" #include "Mouse3DController.hpp" #include "Tab.hpp" -#include "Job.hpp" -#include "PresetBundle.hpp" +#include "Jobs/ArrangeJob.hpp" +#include "Jobs/FillBedJob.hpp" +#include "Jobs/RotoptimizeJob.hpp" +#include "Jobs/SLAImportJob.hpp" #include "BackgroundSlicingProcess.hpp" #include "ProgressStatusBar.hpp" #include "PrintHostDialogs.hpp" @@ -80,8 +74,16 @@ #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" -#include "../Utils/Thread.hpp" +#include "../Utils/PresetUpdater.hpp" +#include "../Utils/Process.hpp" #include "RemovableDriveManager.hpp" +#include "InstanceCheck.hpp" +#include "NotificationManager.hpp" +#include "PresetComboBoxes.hpp" + +#ifdef __APPLE__ +#include "Gizmos/GLGizmosManager.hpp" +#endif // __APPLE__ #include <wx/glcanvas.h> // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" @@ -92,10 +94,9 @@ namespace fs = boost::filesystem; using Slic3r::_3DScene; using Slic3r::Preset; using Slic3r::PrintHostJob; +using Slic3r::GUI::format_wxstr; -#if ENABLE_THUMBNAIL_GENERATOR static const std::pair<unsigned int, unsigned int> THUMBNAIL_SIZE_3MF = { 256, 256 }; -#endif // ENABLE_THUMBNAIL_GENERATOR namespace Slic3r { namespace GUI { @@ -103,7 +104,8 @@ namespace GUI { wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); -wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent); +wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, SlicingProcessCompletedEvent); +wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent); // Sidebar widgets @@ -138,7 +140,7 @@ public: }; ObjectInfo::ObjectInfo(wxWindow *parent) : - wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _(L("Info"))), wxVERTICAL) + wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _L("Info")), wxVERTICAL) { GetStaticBox()->SetFont(wxGetApp().bold_font()); @@ -157,13 +159,13 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : return text; }; - init_info_label(&info_size, _(L("Size"))); - label_volume = init_info_label(&info_volume, _(L("Volume"))); - init_info_label(&info_facets, _(L("Facets"))); - label_materials = init_info_label(&info_materials, _(L("Materials"))); + init_info_label(&info_size, _L("Size")); + label_volume = init_info_label(&info_volume, _L("Volume")); + init_info_label(&info_facets, _L("Facets")); + label_materials = init_info_label(&info_materials, _L("Materials")); Add(grid_sizer, 0, wxEXPAND); - auto *info_manifold_text = new wxStaticText(parent, wxID_ANY, _(L("Manifold")) + ":"); + auto *info_manifold_text = new wxStaticText(parent, wxID_ANY, _L("Manifold") + ":"); info_manifold_text->SetFont(wxGetApp().small_font()); info_manifold = new wxStaticText(parent, wxID_ANY, ""); info_manifold->SetFont(wxGetApp().small_font()); @@ -213,7 +215,7 @@ private: }; SlicedInfo::SlicedInfo(wxWindow *parent) : - wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _(L("Sliced Info"))), wxVERTICAL) + wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _L("Sliced Info")), wxVERTICAL) { GetStaticBox()->SetFont(wxGetApp().bold_font()); @@ -232,13 +234,13 @@ SlicedInfo::SlicedInfo(wxWindow *parent) : info_vec.push_back(std::pair<wxStaticText*, wxStaticText*>(text, info_label)); }; - init_info_label(_(L("Used Filament (m)"))); - init_info_label(_(L("Used Filament (mm³)"))); - init_info_label(_(L("Used Filament (g)"))); - init_info_label(_(L("Used Material (unit)"))); - init_info_label(_(L("Cost (money)"))); - init_info_label(_(L("Estimated printing time"))); - init_info_label(_(L("Number of tool changes"))); + init_info_label(_L("Used Filament (m)")); + init_info_label(_L("Used Filament (mm³)")); + init_info_label(_L("Used Filament (g)")); + init_info_label(_L("Used Material (unit)")); + init_info_label(_L("Cost (money)")); + init_info_label(_L("Estimated printing time")); + init_info_label(_L("Number of tool changes")); Add(grid_sizer, 0, wxEXPAND); this->Show(false); @@ -255,150 +257,6 @@ void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const w info_vec[idx].second->Show(show); } -PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : -PresetBitmapComboBox(parent, wxSize(15 * wxGetApp().em_unit(), -1)), - preset_type(preset_type), - last_selected(wxNOT_FOUND), - m_em_unit(wxGetApp().em_unit()) -{ - SetFont(wxGetApp().normal_font()); -#ifdef _WIN32 - // Workaround for ignoring CBN_EDITCHANGE events, which are processed after the content of the combo box changes, so that - // the index of the item inside CBN_EDITCHANGE may no more be valid. - EnableTextChangedEvents(false); -#endif /* _WIN32 */ - Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { - auto selected_item = this->GetSelection(); - - auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item)); - if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { - this->SetSelection(this->last_selected); - evt.StopPropagation(); - if (marker >= LABEL_ITEM_WIZARD_PRINTERS) { - ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME; - switch (marker) { - case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break; - case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break; - case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; - } - wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); - } - } else if ( this->last_selected != selected_item || - wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) { - this->last_selected = selected_item; - evt.SetInt(this->preset_type); - evt.Skip(); - } else { - evt.StopPropagation(); - } - }); - - if (preset_type == Slic3r::Preset::TYPE_FILAMENT) - { - Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) { - PresetBundle* preset_bundle = wxGetApp().preset_bundle; - const Preset* selected_preset = preset_bundle->filaments.find_preset(preset_bundle->filament_presets[extruder_idx]); - // Wide icons are shown if the currently selected preset is not compatible with the current printer, - // and red flag is drown in front of the selected preset. - bool wide_icons = selected_preset != nullptr && !selected_preset->is_compatible; - float scale = m_em_unit*0.1f; - - int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0; -#if defined(wxBITMAPCOMBOBOX_OWNERDRAWN_BASED) - shifl_Left += int(scale * 4 + 0.5f); // IMAGE_SPACING_RIGHT = 4 for wxBitmapComboBox -> Space left of image -#endif - int icon_right_pos = shifl_Left + int(scale * (24+4) + 0.5); - int mouse_pos = event.GetLogicalPosition(wxClientDC(this)).x; - if (mouse_pos < shifl_Left || mouse_pos > icon_right_pos ) { - // Let the combo box process the mouse click. - event.Skip(); - return; - } - - // Swallow the mouse click and open the color picker. - - // get current color - DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config(); - auto colors = static_cast<ConfigOptionStrings*>(cfg->option("extruder_colour")->clone()); - wxColour clr(colors->values[extruder_idx]); - if (!clr.IsOk()) - clr = wxColour(0,0,0); // Don't set alfa to transparence - - auto data = new wxColourData(); - data->SetChooseFull(1); - data->SetColour(clr); - - wxColourDialog dialog(this, data); - dialog.CenterOnParent(); - if (dialog.ShowModal() == wxID_OK) - { - colors->values[extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); - - DynamicPrintConfig cfg_new = *cfg; - cfg_new.set_key_value("extruder_colour", colors); - - wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new); - preset_bundle->update_plater_filament_ui(extruder_idx, this); - wxGetApp().plater()->on_config_change(cfg_new); - } - }); - } - - edit_btn = new ScalableButton(parent, wxID_ANY, "cog"); - edit_btn->SetToolTip(_(L("Click to edit preset"))); - - edit_btn->Bind(wxEVT_BUTTON, ([preset_type, this](wxCommandEvent) - { - Tab* tab = wxGetApp().get_tab(preset_type); - if (!tab) - return; - - int page_id = wxGetApp().tab_panel()->FindPage(tab); - if (page_id == wxNOT_FOUND) - return; - - wxGetApp().tab_panel()->ChangeSelection(page_id); - - /* In a case of a multi-material printing, for editing another Filament Preset - * it's needed to select this preset for the "Filament settings" Tab - */ - if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) - { - const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data(); - - // Call select_preset() only if there is new preset and not just modified - if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) ) - { - const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset); - tab->select_preset(/*selected_preset*/preset_name); - } - } - })); -} - -PresetComboBox::~PresetComboBox() -{ - if (edit_btn) - edit_btn->Destroy(); -} - - -void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) -{ - this->SetClientData(item, (void*)label_item_type); -} - -void PresetComboBox::check_selection() -{ - this->last_selected = GetSelection(); -} - -void PresetComboBox::msw_rescale() -{ - m_em_unit = wxGetApp().em_unit(); - edit_btn->msw_rescale(); -} - // Frequently changed parameters class FreqChangedParams : public OG_Settings @@ -444,11 +302,12 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : if (!tab_print) return; if (opt_key == "fill_density") { - value = m_og->get_config_value(*config, opt_key); - tab_print->set_value(opt_key, value); + tab_print->update_dirty(); + tab_print->reload_config(); tab_print->update(); } - else{ + else + { DynamicPrintConfig new_conf = *config; if (opt_key == "brim") { double new_val; @@ -489,8 +348,6 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : } tab_print->load_config(new_conf); } - - tab_print->update_dirty(); }; @@ -530,7 +387,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : option = m_og->get_option("fill_density"); option.opt.label = L("Infill"); - option.opt.width = 7/*6*/; + option.opt.width = 8; option.opt.sidetext = " "; line.append_option(option); @@ -546,7 +403,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : line.append_option(option); auto wiping_dialog_btn = [this](wxWindow* parent) { - m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _L("Purging volumes") + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); m_wiping_dialog_button->SetFont(wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_wiping_dialog_button, 0, wxALIGN_CENTER_VERTICAL); @@ -578,9 +435,12 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : return sizer; }; line.append_widget(wiping_dialog_btn); - m_og->append_line(line); + m_og->activate(); + + Choice* choice = dynamic_cast<Choice*>(m_og->get_field("support")); + choice->suppress_scroll(); // Frequently changed parameters for SLA_technology m_og_sla = std::make_shared<ConfigOptionsGroup>(parent, ""); @@ -588,7 +448,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : DynamicPrintConfig* config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; m_og_sla->set_config(config_sla); - m_og_sla->m_on_change = [config_sla, this](t_config_option_key opt_key, boost::any value) { + m_og_sla->m_on_change = [config_sla](t_config_option_key opt_key, boost::any value) { Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT); if (!tab) return; @@ -652,6 +512,12 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : m_og_sla->append_line(line); + m_og_sla->activate(); + choice = dynamic_cast<Choice*>(m_og_sla->get_field("support")); + choice->suppress_scroll(); + choice = dynamic_cast<Choice*>(m_og_sla->get_field("pad")); + choice->suppress_scroll(); + m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer->Add(m_og->sizer, 0, wxEXPAND); m_sizer->Add(m_og_sla->sizer, 0, wxEXPAND); @@ -696,12 +562,12 @@ struct Sidebar::priv ModeSizer *mode_sizer; wxFlexGridSizer *sizer_presets; - PresetComboBox *combo_print; - std::vector<PresetComboBox*> combos_filament; + PlaterPresetComboBox *combo_print; + std::vector<PlaterPresetComboBox*> combos_filament; wxBoxSizer *sizer_filaments; - PresetComboBox *combo_sla_print; - PresetComboBox *combo_sla_material; - PresetComboBox *combo_printer; + PlaterPresetComboBox *combo_sla_print; + PlaterPresetComboBox *combo_sla_material; + PlaterPresetComboBox *combo_printer; wxBoxSizer *sizer_params; FreqChangedParams *frequently_changed_parameters{ nullptr }; @@ -715,9 +581,12 @@ struct Sidebar::priv wxButton *btn_export_gcode; wxButton *btn_reslice; ScalableButton *btn_send_gcode; - ScalableButton *btn_remove_device; + //ScalableButton *btn_eject_device; ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected) + bool is_collapsed {false}; + Search::OptionsSearcher searcher; + priv(Plater *plater) : plater(plater) {} ~priv(); @@ -761,11 +630,15 @@ void Sidebar::priv::show_preset_comboboxes() // Sidebar / public Sidebar::Sidebar(Plater *parent) - : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(40 * wxGetApp().em_unit(), -1)), p(new priv(parent)) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(42 * wxGetApp().em_unit(), -1)), p(new priv(parent)) { p->scrolled = new wxScrolledWindow(this); p->scrolled->SetScrollbars(0, 100, 1, 2); + SetFont(wxGetApp().normal_font()); +#ifndef __APPLE__ + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif // Sizer in the scrolled area auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL); @@ -793,10 +666,10 @@ Sidebar::Sidebar(Plater *parent) p->sizer_filaments = new wxBoxSizer(wxVERTICAL); - auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { + auto init_combo = [this](PlaterPresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label + " :"); text->SetFont(wxGetApp().small_font()); - *combo = new PresetComboBox(p->presets_panel, preset_type); + *combo = new PlaterPresetComboBox(p->presets_panel, preset_type); auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL); combo_and_btn_sizer->Add(*combo, 1, wxEXPAND); @@ -817,11 +690,11 @@ Sidebar::Sidebar(Plater *parent) }; p->combos_filament.push_back(nullptr); - init_combo(&p->combo_print, _(L("Print settings")), Preset::TYPE_PRINT, false); - init_combo(&p->combos_filament[0], _(L("Filament")), Preset::TYPE_FILAMENT, true); - init_combo(&p->combo_sla_print, _(L("SLA print settings")), Preset::TYPE_SLA_PRINT, false); - init_combo(&p->combo_sla_material, _(L("SLA material")), Preset::TYPE_SLA_MATERIAL, false); - init_combo(&p->combo_printer, _(L("Printer")), Preset::TYPE_PRINTER, false); + init_combo(&p->combo_print, _L("Print settings"), Preset::TYPE_PRINT, false); + init_combo(&p->combos_filament[0], _L("Filament"), Preset::TYPE_FILAMENT, true); + init_combo(&p->combo_sla_print, _L("SLA print settings"), Preset::TYPE_SLA_PRINT, false); + init_combo(&p->combo_sla_material, _L("SLA material"), Preset::TYPE_SLA_MATERIAL, false); + init_combo(&p->combo_printer, _L("Printer"), Preset::TYPE_PRINTER, false); const int margin_5 = int(0.5*wxGetApp().em_unit());// 5; @@ -880,21 +753,22 @@ Sidebar::Sidebar(Plater *parent) (*btn)->Hide(); }; - init_scalable_btn(&p->btn_send_gcode , "export_gcode", _(L("Send to printer")) + "\tCtrl+Shift+G"); - init_scalable_btn(&p->btn_remove_device, "eject_sd" , _(L("Remove device")) + "\tCtrl+T"); - init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _(L("Export to SD card / Flash drive")) + "\tCtrl+U"); + init_scalable_btn(&p->btn_send_gcode , "export_gcode", _L("Send to printer") + " " +GUI::shortkey_ctrl_prefix() + "Shift+G"); +// init_scalable_btn(&p->btn_eject_device, "eject_sd" , _L("Remove device ") + GUI::shortkey_ctrl_prefix() + "T"); + init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + " " + GUI::shortkey_ctrl_prefix() + "U"); // regular buttons "Slice now" and "Export G-code" - const int scaled_height = p->btn_remove_device->GetBitmapHeight() + 4; +// const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4; + const int scaled_height = p->btn_export_gcode_removable->GetBitmapHeight() + 4; auto init_btn = [this](wxButton **btn, wxString label, const int button_height) { *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, wxSize(-1, button_height), wxBU_EXACTFIT); (*btn)->SetFont(wxGetApp().bold_font()); }; - init_btn(&p->btn_export_gcode, _(L("Export G-code")) + dots , scaled_height); - init_btn(&p->btn_reslice , _(L("Slice now")) , scaled_height); + init_btn(&p->btn_export_gcode, _L("Export G-code") + dots , scaled_height); + init_btn(&p->btn_reslice , _L("Slice now") , scaled_height); enable_buttons(false); @@ -904,7 +778,7 @@ Sidebar::Sidebar(Plater *parent) complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND); complect_btns_sizer->Add(p->btn_send_gcode); complect_btns_sizer->Add(p->btn_export_gcode_removable); - complect_btns_sizer->Add(p->btn_remove_device); +// complect_btns_sizer->Add(p->btn_eject_device); btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5); @@ -921,20 +795,20 @@ Sidebar::Sidebar(Plater *parent) { const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT); if (export_gcode_after_slicing) - p->plater->export_gcode(); + p->plater->export_gcode(true); else p->plater->reslice(); p->plater->select_view_3D("Preview"); }); p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); - p->btn_remove_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); }); +// p->btn_eject_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); }); p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); }); } Sidebar::~Sidebar() {} -void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) { - *combo = new PresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); +void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int extr_idx) { + *combo = new PlaterPresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); // # copy icons from first choice // $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; @@ -969,18 +843,18 @@ void Sidebar::update_all_preset_comboboxes() // Update the print choosers to only contain the compatible presets, update the dirty flags. if (print_tech == ptFFF) - preset_bundle.prints.update_plater_ui(p->combo_print); + p->combo_print->update(); else { - preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print); - preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material); + p->combo_sla_print->update(); + p->combo_sla_material->update(); } // Update the printer choosers, update the dirty flags. - preset_bundle.printers.update_plater_ui(p->combo_printer); + p->combo_printer->update(); // Update the filament choosers to only contain the compatible presets, update the color preview, // update the dirty flags. if (print_tech == ptFFF) { - for (size_t i = 0; i < p->combos_filament.size(); ++i) - preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]); + for (PlaterPresetComboBox* cb : p->combos_filament) + cb->update(); } } @@ -1002,23 +876,22 @@ void Sidebar::update_presets(Preset::Type preset_type) preset_bundle.set_filament_preset(0, name); } - for (size_t i = 0; i < filament_cnt; i++) { - preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]); - } + for (size_t i = 0; i < filament_cnt; i++) + p->combos_filament[i]->update(); break; } case Preset::TYPE_PRINT: - preset_bundle.prints.update_plater_ui(p->combo_print); + p->combo_print->update(); break; case Preset::TYPE_SLA_PRINT: - preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print); + p->combo_sla_print->update(); break; case Preset::TYPE_SLA_MATERIAL: - preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material); + p->combo_sla_material->update(); break; case Preset::TYPE_PRINTER: @@ -1040,11 +913,17 @@ void Sidebar::update_mode_sizer() const p->mode_sizer->SetMode(m_mode); } +void Sidebar::change_top_border_for_mode_sizer(bool increase_border) +{ + p->mode_sizer->set_items_flag(increase_border ? wxTOP : 0); + p->mode_sizer->set_items_border(increase_border ? int(0.5 * wxGetApp().em_unit()) : 0); +} + void Sidebar::update_reslice_btn_tooltip() const { wxString tooltip = wxString("Slice") + " [" + GUI::shortkey_ctrl_prefix() + "R]"; if (m_mode != comSimple) - tooltip += wxString("\n") + _(L("Hold Shift to Slice & Export G-code")); + tooltip += wxString("\n") + _L("Hold Shift to Slice & Export G-code"); p->btn_reslice->SetToolTip(tooltip); } @@ -1054,18 +933,14 @@ void Sidebar::msw_rescale() p->mode_sizer->msw_rescale(); - // Rescale preset comboboxes in respect to the current em_unit ... - for (PresetComboBox* combo : std::vector<PresetComboBox*> { p->combo_print, + for (PlaterPresetComboBox* combo : std::vector<PlaterPresetComboBox*> { p->combo_print, p->combo_sla_print, p->combo_sla_material, p->combo_printer } ) combo->msw_rescale(); - for (PresetComboBox* combo : p->combos_filament) + for (PlaterPresetComboBox* combo : p->combos_filament) combo->msw_rescale(); - // ... then refill them and set min size to correct layout of the sidebar - update_all_preset_comboboxes(); - p->frequently_changed_parameters->msw_rescale(); p->object_list->msw_rescale(); p->object_manipulation->msw_rescale(); @@ -1075,15 +950,51 @@ void Sidebar::msw_rescale() p->object_info->msw_rescale(); p->btn_send_gcode->msw_rescale(); - p->btn_remove_device->msw_rescale(); +// p->btn_eject_device->msw_rescale(); p->btn_export_gcode_removable->msw_rescale(); - const int scaled_height = p->btn_remove_device->GetBitmap().GetHeight() + 4; + const int scaled_height = p->btn_export_gcode_removable->GetBitmap().GetHeight() + 4; p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height)); p->btn_reslice ->SetMinSize(wxSize(-1, scaled_height)); p->scrolled->Layout(); } +void Sidebar::sys_color_changed() +{ + for (PlaterPresetComboBox* combo : std::vector<PlaterPresetComboBox*>{ p->combo_print, + p->combo_sla_print, + p->combo_sla_material, + p->combo_printer }) + combo->msw_rescale(); + for (PlaterPresetComboBox* combo : p->combos_filament) + combo->msw_rescale(); + + p->object_list->sys_color_changed(); + p->object_manipulation->sys_color_changed(); + p->object_layers->sys_color_changed(); + + // btn...->msw_rescale() updates icon on button, so use it + p->btn_send_gcode->msw_rescale(); +// p->btn_eject_device->msw_rescale(); + p->btn_export_gcode_removable->msw_rescale(); + + p->scrolled->Layout(); +} + +void Sidebar::search() +{ + p->searcher.search(); +} + +void Sidebar::jump_to_option(size_t selected) +{ + const Search::Option& opt = p->searcher.get_option(selected); + wxGetApp().get_tab(opt.type)->activate_option(boost::nowide::narrow(opt.opt_key), boost::nowide::narrow(opt.category)); + + // Switch to the Settings NotePad +// wxGetApp().mainframe->select_tab(); +} + ObjectManipulation* Sidebar::obj_manipul() { return p->object_manipulation; @@ -1148,22 +1059,25 @@ void Sidebar::show_info_sizer() return; } + bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + double koef = imperial_units ? ObjectManipulation::mm_to_in : 1.0f; + auto size = model_object->bounding_box().size(); - p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0), size(1), size(2))); + p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0)*koef, size(1)*koef, size(2)*koef)); p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast<int>(model_object->materials_count()))); const auto& stats = model_object->get_object_stl_stats();//model_object->volumes.front()->mesh.stl.stats; - p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume)); - p->object_info->info_facets->SetLabel(wxString::Format(_(L("%d (%d shells)")), static_cast<int>(model_object->facets_count()), stats.number_of_parts)); + p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume*pow(koef,3))); + p->object_info->info_facets->SetLabel(wxString::Format(_L("%d (%d shells)"), static_cast<int>(model_object->facets_count()), stats.number_of_parts)); int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + stats.facets_added + stats.facets_reversed + stats.backwards_edges; if (errors > 0) { - wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors)")), errors); + wxString tooltip = wxString::Format(_L("Auto-repaired (%d errors)"), errors); p->object_info->info_manifold->SetLabel(tooltip); - tooltip += ":\n" + wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, " - "%d facets added, %d facets reversed, %d backwards edges")), + tooltip += ":\n" + wxString::Format(_L("%d degenerate facets, %d edges fixed, %d facets removed, " + "%d facets added, %d facets reversed, %d backwards edges"), stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, stats.facets_added, stats.facets_reversed, stats.backwards_edges); @@ -1172,7 +1086,7 @@ void Sidebar::show_info_sizer() p->object_info->manifold_warning_icon->SetToolTip(tooltip); } else { - p->object_info->info_manifold->SetLabel(_(L("Yes"))); + p->object_info->info_manifold->SetLabel(_L("Yes")); p->object_info->showing_manifold_warning_icon = false; p->object_info->info_manifold->SetToolTip(""); p->object_info->manifold_warning_icon->SetToolTip(""); @@ -1193,10 +1107,10 @@ void Sidebar::update_sliced_info_sizer() if (p->plater->printer_technology() == ptSLA) { const SLAPrintStatistics& ps = p->plater->sla_print().print_statistics(); - wxString new_label = _(L("Used Material (ml)")) + ":"; + wxString new_label = _L("Used Material (ml)") + ":"; const bool is_supports = ps.support_used_material > 0.0; if (is_supports) - new_label += from_u8((boost::format("\n - %s\n - %s") % _utf8(L("object(s)")) % _utf8(L("supports and pad"))).str()); + new_label += format_wxstr("\n - %s\n - %s", _L("object(s)"), _L("supports and pad")); wxString info_text = is_supports ? wxString::Format("%.2f \n%.2f \n%.2f", (ps.objects_used_material + ps.support_used_material) / 1000, @@ -1218,7 +1132,7 @@ void Sidebar::update_sliced_info_sizer() p->sliced_info->SetTextAndShow(siCost, str_total_cost, "Cost"); wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : get_time_dhms(float(ps.estimated_print_time)); - p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _(L("Estimated printing time")) + ":"); + p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _L("Estimated printing time") + ":"); // Hide non-SLA sliced info parameters p->sliced_info->SetTextAndShow(siFilament_m, "N/A"); @@ -1231,23 +1145,65 @@ void Sidebar::update_sliced_info_sizer() const PrintStatistics& ps = p->plater->fff_print().print_statistics(); const bool is_wipe_tower = ps.total_wipe_tower_filament > 0; - wxString new_label = _(L("Used Filament (m)")); + bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + double koef = imperial_units ? ObjectManipulation::in_to_mm : 1000.0; + + wxString new_label = imperial_units ? _L("Used Filament (in)") : _L("Used Filament (m)"); if (is_wipe_tower) - new_label += from_u8((boost::format(":\n - %1%\n - %2%") % _utf8(L("objects")) % _utf8(L("wipe tower"))).str()); + new_label += format_wxstr(":\n - %1%\n - %2%", _L("objects"), _L("wipe tower")); wxString info_text = is_wipe_tower ? - wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / 1000, - (ps.total_used_filament - ps.total_wipe_tower_filament) / 1000, - ps.total_wipe_tower_filament / 1000) : - wxString::Format("%.2f", ps.total_used_filament / 1000); + wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / /*1000*/koef, + (ps.total_used_filament - ps.total_wipe_tower_filament) / /*1000*/koef, + ps.total_wipe_tower_filament / /*1000*/koef) : + wxString::Format("%.2f", ps.total_used_filament / /*1000*/koef); p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label); - p->sliced_info->SetTextAndShow(siFilament_mm3, wxString::Format("%.2f", ps.total_extruded_volume)); - p->sliced_info->SetTextAndShow(siFilament_g, ps.total_weight == 0.0 ? "N/A" : wxString::Format("%.2f", ps.total_weight)); + koef = imperial_units ? pow(ObjectManipulation::mm_to_in, 3) : 1.0f; + new_label = imperial_units ? _L("Used Filament (in³)") : _L("Used Filament (mm³)"); + info_text = wxString::Format("%.2f", imperial_units ? ps.total_extruded_volume * koef : ps.total_extruded_volume); + p->sliced_info->SetTextAndShow(siFilament_mm3, info_text, new_label); + + if (ps.total_weight == 0.0) + p->sliced_info->SetTextAndShow(siFilament_g, "N/A"); + else { + new_label = _L("Used Filament (g)"); + info_text = wxString::Format("%.2f", ps.total_weight); + + const std::vector<std::string>& filament_presets = wxGetApp().preset_bundle->filament_presets; + const PresetCollection& filaments = wxGetApp().preset_bundle->filaments; + + if (ps.filament_stats.size() > 1) + new_label += ":"; + + for (auto filament : ps.filament_stats) { + const Preset* filament_preset = filaments.find_preset(filament_presets[filament.first], false); + if (filament_preset) { + double filament_weight; + if (ps.filament_stats.size() == 1) + filament_weight = ps.total_weight; + else { + double filament_density = filament_preset->config.opt_float("filament_density", 0); + filament_weight = filament.second * filament_density * 2.4052f * 0.001; // assumes 1.75mm filament diameter; + + new_label += "\n - " + format_wxstr(_L("Filament at extruder %1%"), filament.first + 1); + info_text += wxString::Format("\n%.2f", filament_weight); + } + + double spool_weight = filament_preset->config.opt_float("filament_spool_weight", 0); + if (spool_weight != 0.0) { + new_label += "\n " + _L("(including spool)"); + info_text += wxString::Format(" (%.2f)\n", filament_weight + spool_weight); + } + } + } + + p->sliced_info->SetTextAndShow(siFilament_g, info_text, new_label); + } - new_label = _(L("Cost")); + new_label = _L("Cost"); if (is_wipe_tower) - new_label += from_u8((boost::format(":\n - %1%\n - %2%") % _utf8(L("objects")) % _utf8(L("wipe tower"))).str()); + new_label += format_wxstr(":\n - %1%\n - %2%", _L("objects"), _L("wipe tower")); info_text = ps.total_cost == 0.0 ? "N/A" : is_wipe_tower ? @@ -1260,44 +1216,23 @@ void Sidebar::update_sliced_info_sizer() if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); else { - new_label = _(L("Estimated printing time")) +":"; info_text = ""; - wxString str_color = _(L("Color")); - wxString str_pause = _(L("Pause")); - - auto fill_labels = [str_color, str_pause](const std::vector<std::pair<CustomGcodeType, std::string>>& times, - wxString& new_label, wxString& info_text) - { - int color_change_count = 0; - for (auto time : times) - if (time.first == cgtColorChange) - color_change_count++; - - for (int i = (int)times.size() - 1; i >= 0; --i) - { - if (i == 0 || times[i - 1].first == cgtPausePrint) - new_label += from_u8((boost::format("\n - %1%%2%") % (std::string(str_color.ToUTF8()) + " ") % color_change_count).str()); - else if (times[i - 1].first == cgtColorChange) - new_label += from_u8((boost::format("\n - %1%%2%") % (std::string(str_color.ToUTF8()) + " ") % color_change_count--).str()); - - if (i != (int)times.size() - 1 && times[i].first == cgtPausePrint) - new_label += from_u8((boost::format(" -> %1%") % std::string(str_pause.ToUTF8())).str()); + new_label = _L("Estimated printing time") + ":"; + if (ps.estimated_normal_print_time != "N/A") { + new_label += format_wxstr("\n - %1%", _L("normal mode")); + info_text += format_wxstr("\n%1%", short_time(ps.estimated_normal_print_time)); - info_text += from_u8((boost::format("\n%1%") % times[i].second).str()); - } - }; + // uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate + //if (p->plater->is_sidebar_collapsed()) + p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed()); + p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time); - if (ps.estimated_normal_print_time != "N/A") { - new_label += from_u8((boost::format("\n - %1%") % _utf8(L("normal mode"))).str()); - info_text += from_u8((boost::format("\n%1%") % ps.estimated_normal_print_time).str()); - fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text); } if (ps.estimated_silent_print_time != "N/A") { - new_label += from_u8((boost::format("\n - %1%") % _utf8(L("stealth mode"))).str()); - info_text += from_u8((boost::format("\n%1%") % ps.estimated_silent_print_time).str()); - fill_labels(ps.estimated_silent_custom_gcode_print_times, new_label, info_text); + new_label += format_wxstr("\n - %1%", _L("stealth mode")); + info_text += format_wxstr("\n%1%", short_time(ps.estimated_silent_print_time)); } - p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); + p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); } // if there is a wipe tower, insert number of toolchanges info into the array: @@ -1307,6 +1242,8 @@ void Sidebar::update_sliced_info_sizer() p->sliced_info->SetTextAndShow(siMateril_unit, "N/A"); } } + + Layout(); } void Sidebar::show_sliced_info_sizer(const bool show) @@ -1326,21 +1263,39 @@ void Sidebar::enable_buttons(bool enable) p->btn_reslice->Enable(enable); p->btn_export_gcode->Enable(enable); p->btn_send_gcode->Enable(enable); - p->btn_remove_device->Enable(enable); +// p->btn_eject_device->Enable(enable); p->btn_export_gcode_removable->Enable(enable); } -bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); } -bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); } -bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); } -bool Sidebar::show_disconnect(bool show) const { return p->btn_remove_device->Show(show); } -bool Sidebar::show_export_removable(bool show)const { return p->btn_export_gcode_removable->Show(show); } +bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); } +bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); } +bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); } +bool Sidebar::show_export_removable(bool show) const { return p->btn_export_gcode_removable->Show(show); } +//bool Sidebar::show_eject(bool show) const { return p->btn_eject_device->Show(show); } +//bool Sidebar::get_eject_shown() const { return p->btn_eject_device->IsShown(); } bool Sidebar::is_multifilament() { return p->combos_filament.size() > 1; } +static std::vector<Search::InputInfo> get_search_inputs(ConfigOptionMode mode) +{ + std::vector<Search::InputInfo> ret {}; + + auto& tabs_list = wxGetApp().tabs_list; + auto print_tech = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(); + for (auto tab : tabs_list) + if (tab->supports_printer_technology(print_tech)) + ret.emplace_back(Search::InputInfo {tab->get_config(), tab->type(), mode}); + + return ret; +} + +void Sidebar::update_searcher() +{ + p->searcher.init(get_search_inputs(m_mode)); +} void Sidebar::update_mode() { @@ -1348,6 +1303,7 @@ void Sidebar::update_mode() update_reslice_btn_tooltip(); update_mode_sizer(); + update_searcher(); wxWindowUpdateLocker noUpdates(this); @@ -1360,81 +1316,260 @@ void Sidebar::update_mode() Layout(); } -std::vector<PresetComboBox*>& Sidebar::combos_filament() +bool Sidebar::is_collapsed() { return p->is_collapsed; } + +void Sidebar::collapse(bool collapse) +{ + p->is_collapsed = collapse; + + this->Show(!collapse); + p->plater->Layout(); + + // save collapsing state to the AppConfig + if (wxGetApp().is_editor()) + wxGetApp().app_config->set("collapsed_sidebar", collapse ? "1" : "0"); +} + + +void Sidebar::update_ui_from_settings() +{ + p->object_manipulation->update_ui_from_settings(); + show_info_sizer(); + update_sliced_info_sizer(); + // update Cut gizmo, if it's open + p->plater->canvas3D()->update_gizmos_on_off_state(); + p->plater->canvas3D()->request_extra_frame(); +} + +std::vector<PlaterPresetComboBox*>& Sidebar::combos_filament() { return p->combos_filament; } +Search::OptionsSearcher& Sidebar::get_searcher() +{ + return p->searcher; +} + +std::string& Sidebar::get_search_line() +{ + return p->searcher.search_string(); +} + // Plater::DropTarget class PlaterDropTarget : public wxFileDropTarget { public: - PlaterDropTarget(Plater *plater) : plater(plater) { this->SetDefaultAction(wxDragCopy); } + PlaterDropTarget(Plater* plater) : m_plater(plater) { this->SetDefaultAction(wxDragCopy); } virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames); private: - Plater *plater; + Plater* m_plater; +#if !ENABLE_DRAG_AND_DROP_FIX static const std::regex pattern_drop; + static const std::regex pattern_gcode_drop; +#endif // !ENABLE_DRAG_AND_DROP_FIX }; +#if !ENABLE_DRAG_AND_DROP_FIX const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", std::regex::icase); +const std::regex PlaterDropTarget::pattern_gcode_drop(".*[.](gcode|g)", std::regex::icase); + +enum class LoadType : unsigned char +{ + Unknown, + OpenProject, + LoadGeometry, + LoadConfig +}; + +class ProjectDropDialog : public DPIDialog +{ + wxRadioBox* m_action{ nullptr }; +public: + ProjectDropDialog(const std::string& filename); + + int get_action() const { return m_action->GetSelection() + 1; } + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; +}; + +ProjectDropDialog::ProjectDropDialog(const std::string& filename) + : DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, + from_u8((boost::format(_utf8(L("%s - Drop project file"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition, + wxDefaultSize, wxDEFAULT_DIALOG_STYLE) +{ + SetFont(wxGetApp().normal_font()); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + + const wxString choices[] = { _L("Open as project"), + _L("Import geometry only"), + _L("Import config only") }; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, + _L("Select an action to apply to the file") + ": " + from_u8(filename)), 0, wxEXPAND | wxALL, 10); + m_action = new wxRadioBox(this, wxID_ANY, _L("Action"), wxDefaultPosition, wxDefaultSize, + WXSIZEOF(choices), choices, 0, wxRA_SPECIFY_ROWS); + int action = std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), + static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)) - 1; + m_action->SetSelection(action); + main_sizer->Add(m_action, 1, wxEXPAND | wxRIGHT | wxLEFT, 10); + + wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL); + wxCheckBox* check = new wxCheckBox(this, wxID_ANY, _L("Don't show again")); + check->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& evt) { + wxGetApp().app_config->set("show_drop_project_dialog", evt.IsChecked() ? "0" : "1"); + }); + + bottom_sizer->Add(check, 0, wxEXPAND | wxRIGHT, 5); + bottom_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxLEFT, 5); + main_sizer->Add(bottom_sizer, 0, wxEXPAND | wxALL, 10); + + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); +} + +void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int em = em_unit(); + SetMinSize(wxSize(65 * em, 30 * em)); + Fit(); + Refresh(); +} +#endif // !ENABLE_DRAG_AND_DROP_FIX bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) { +#if !ENABLE_DRAG_AND_DROP_FIX std::vector<fs::path> paths; +#endif // !ENABLE_DRAG_AND_DROP_FIX + +#ifdef WIN32 + // hides the system icon + this->MSWUpdateDragImageOnLeave(); +#endif // WIN32 + +#if ENABLE_DRAG_AND_DROP_FIX + return (m_plater != nullptr) ? m_plater->load_files(filenames) : false; +#else + // gcode viewer section + if (wxGetApp().is_gcode_viewer()) { + for (const auto& filename : filenames) { + fs::path path(into_path(filename)); + if (std::regex_match(path.string(), pattern_gcode_drop)) + paths.push_back(std::move(path)); + } + + if (paths.size() > 1) { + wxMessageDialog(static_cast<wxWindow*>(m_plater), _L("You can open only one .gcode file at a time."), + wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); + return false; + } + else if (paths.size() == 1) { + m_plater->load_gcode(from_path(paths.front())); + return true; + } + return false; + } + + // editor section for (const auto &filename : filenames) { fs::path path(into_path(filename)); - if (std::regex_match(path.string(), pattern_drop)) { + if (std::regex_match(path.string(), pattern_drop)) paths.push_back(std::move(path)); - } else { + else if (std::regex_match(path.string(), pattern_gcode_drop)) + start_new_gcodeviewer(&filename); + else return false; + } + if (paths.empty()) + // Likely all paths processed were gcodes, for which a G-code viewer instance has hopefully been started. + return false; + + // searches for project files + for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) { + std::string filename = (*it).filename().string(); + if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) { + LoadType load_type = LoadType::Unknown; + if (!m_plater->model().objects.empty()) { + if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") { + ProjectDropDialog dlg(filename); + if (dlg.ShowModal() == wxID_OK) { + int choice = dlg.get_action(); + load_type = static_cast<LoadType>(choice); + wxGetApp().app_config->set("drop_project_action", std::to_string(choice)); + } + } + else + load_type = static_cast<LoadType>(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), + static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig))); + } + else + load_type = LoadType::OpenProject; + + if (load_type == LoadType::Unknown) + return false; + + switch (load_type) { + case LoadType::OpenProject: { + m_plater->load_project(from_path(*it)); + break; + } + case LoadType::LoadGeometry: { + Plater::TakeSnapshot snapshot(m_plater, _L("Import Object")); + std::vector<fs::path> in_paths; + in_paths.emplace_back(*it); + m_plater->load_files(in_paths, true, false); + break; + } + case LoadType::LoadConfig: { + std::vector<fs::path> in_paths; + in_paths.emplace_back(*it); + m_plater->load_files(in_paths, false, true); + break; + } + } + + return true; } } + // other files wxString snapshot_label; - assert(! paths.empty()); + assert(!paths.empty()); if (paths.size() == 1) { - snapshot_label = _(L("Load File")); + snapshot_label = _L("Load File"); snapshot_label += ": "; snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); - } else { - snapshot_label = _(L("Load Files")); + } + else { + snapshot_label = _L("Load Files"); snapshot_label += ": "; snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); - for (size_t i = 1; i < paths.size(); ++ i) { + for (size_t i = 1; i < paths.size(); ++i) { snapshot_label += ", "; snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); } } - Plater::TakeSnapshot snapshot(plater, snapshot_label); - - // FIXME: when drag and drop is done on a .3mf or a .amf file we should clear the plater for consistence with the open project command - // (the following call to plater->load_files() will load the config data, if present) - - std::vector<size_t> res = plater->load_files(paths); - - // because right now the plater is not cleared, we set the project file (from the latest imported .3mf or .amf file) - // only if not set yet - // if res is empty no data has been loaded - if (!res.empty() && plater->get_project_filename().empty()) - { - for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) - { - std::string filename = (*it).filename().string(); - if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) - { - plater->set_project_filename(from_path(*it)); - break; - } - } - } + Plater::TakeSnapshot snapshot(m_plater, snapshot_label); + m_plater->load_files(paths); return true; +#endif // ENABLE_DRAG_AND_DROP_FIX } +// State to manage showing after export notifications and device ejecting +enum ExportingStatus{ + NOT_EXPORTING, + EXPORTING_TO_REMOVABLE, + EXPORTING_TO_LOCAL +}; + // Plater / private struct Plater::priv { @@ -1466,7 +1601,7 @@ struct Plater::priv Slic3r::SLAPrint sla_print; Slic3r::Model model; PrinterTechnology printer_technology = ptFFF; - Slic3r::GCodePreviewData gcode_preview_data; + Slic3r::GCodeProcessor::Result gcode_result; // GUI elements wxSizer* panel_sizer{ nullptr }; @@ -1475,338 +1610,64 @@ struct Plater::priv Sidebar *sidebar; Bed3D bed; Camera camera; +#if ENABLE_ENVIRONMENT_MAP + GLTexture environment_texture; +#endif // ENABLE_ENVIRONMENT_MAP Mouse3DController mouse3d_controller; View3D* view3D; GLToolbar view_toolbar; + GLToolbar collapse_toolbar; Preview *preview; + NotificationManager* notification_manager { nullptr }; BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; - // Cache the wti info - class WipeTower: public GLCanvas3D::WipeTowerInfo { - using ArrangePolygon = arrangement::ArrangePolygon; - friend priv; - public: - - void apply_arrange_result(const Vec2crd& tr, double rotation) - { - m_pos = unscaled(tr); m_rotation = rotation; - apply_wipe_tower(); - } - - ArrangePolygon get_arrange_polygon() const - { - Polygon p({ - {coord_t(0), coord_t(0)}, - {scaled(m_bb_size(X)), coord_t(0)}, - {scaled(m_bb_size)}, - {coord_t(0), scaled(m_bb_size(Y))}, - {coord_t(0), coord_t(0)}, - }); - - ArrangePolygon ret; - ret.poly.contour = std::move(p); - ret.translation = scaled(m_pos); - ret.rotation = m_rotation; - ret.priority++; - return ret; - } - } wipetower; - - WipeTower& updated_wipe_tower() { - auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); - wipetower.m_pos = wti.pos(); - wipetower.m_rotation = wti.rotation(); - wipetower.m_bb_size = wti.bb_size(); - return wipetower; - } - - // A class to handle UI jobs like arranging and optimizing rotation. - // These are not instant jobs, the user has to be informed about their - // state in the status progress indicator. On the other hand they are - // separated from the background slicing process. Ideally, these jobs should - // run when the background process is not running. - // - // TODO: A mechanism would be useful for blocking the plater interactions: - // objects would be frozen for the user. In case of arrange, an animation - // could be shown, or with the optimize orientations, partial results - // could be displayed. - class PlaterJob: public Job + // Jobs defined inside the group class will be managed so that only one can + // run at a time. Also, the background process will be stopped if a job is + // started. It is up the the plater to ensure that the background slicing + // can't be restarted while a ui job is still running. + class Jobs: public ExclusiveJobGroup { - priv *m_plater; - protected: - - priv & plater() { return *m_plater; } - const priv &plater() const { return *m_plater; } - - // Launched when the job is finished. It refreshes the 3Dscene by def. - void finalize() override - { - // Do a full refresh of scene tree, including regenerating - // all the GLVolumes. FIXME The update function shall just - // reload the modified matrices. - if (!Job::was_canceled()) - plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH)); - - Job::finalize(); - } - + priv *m; + size_t m_arrange_id, m_fill_bed_id, m_rotoptimize_id, m_sla_import_id; + + void before_start() override { m->background_process.stop(); } + public: - PlaterJob(priv *_plater) - : Job(_plater->statusbar()), m_plater(_plater) - {} - }; - - enum class Jobs : size_t { - Arrange, - Rotoptimize, - Hollow - }; - - class ArrangeJob : public PlaterJob - { - using ArrangePolygon = arrangement::ArrangePolygon; - using ArrangePolygons = arrangement::ArrangePolygons; - - // The gap between logical beds in the x axis expressed in ratio of - // the current bed width. - static const constexpr double LOGICAL_BED_GAP = 1. / 5.; - - ArrangePolygons m_selected, m_unselected, m_unprintable; - - // clear m_selected and m_unselected, reserve space for next usage - void clear_input() { - const Model &model = plater().model; - - size_t count = 0, cunprint = 0; // To know how much space to reserve - for (auto obj : model.objects) - for (auto mi : obj->instances) - mi->printable ? count++ : cunprint++; - - m_selected.clear(); - m_unselected.clear(); - m_unprintable.clear(); - m_selected.reserve(count + 1 /* for optional wti */); - m_unselected.reserve(count + 1 /* for optional wti */); - m_unprintable.reserve(cunprint /* for optional wti */); - } - - // Stride between logical beds - coord_t bed_stride() const { - double bedwidth = plater().bed_shape_bb().size().x(); - return scaled((1. + LOGICAL_BED_GAP) * bedwidth); - } - - // Set up arrange polygon for a ModelInstance and Wipe tower - template<class T> ArrangePolygon get_arrange_poly(T *obj) const { - ArrangePolygon ap = obj->get_arrange_polygon(); - ap.priority = 0; - ap.bed_idx = ap.translation.x() / bed_stride(); - ap.setter = [obj, this](const ArrangePolygon &p) { - if (p.is_arranged()) { - auto t = p.translation; - t.x() += p.bed_idx * bed_stride(); - obj->apply_arrange_result(t, p.rotation); - } - }; - return ap; - } - - // Prepare all objects on the bed regardless of the selection - void prepare_all() { - clear_input(); - - for (ModelObject *obj: plater().model.objects) - for (ModelInstance *mi : obj->instances) { - ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable; - cont.emplace_back(get_arrange_poly(mi)); - } - - auto& wti = plater().updated_wipe_tower(); - if (wti) m_selected.emplace_back(get_arrange_poly(&wti)); - } - - // Prepare the selected and unselected items separately. If nothing is - // selected, behaves as if everything would be selected. - void prepare_selected() { - clear_input(); - - Model &model = plater().model; - coord_t stride = bed_stride(); - - std::vector<const Selection::InstanceIdxsList *> - obj_sel(model.objects.size(), nullptr); - - for (auto &s : plater().get_selection().get_content()) - if (s.first < int(obj_sel.size())) - obj_sel[size_t(s.first)] = &s.second; - - // Go through the objects and check if inside the selection - for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { - const Selection::InstanceIdxsList * instlist = obj_sel[oidx]; - ModelObject *mo = model.objects[oidx]; - - std::vector<bool> inst_sel(mo->instances.size(), false); - - if (instlist) - for (auto inst_id : *instlist) - inst_sel[size_t(inst_id)] = true; - - for (size_t i = 0; i < inst_sel.size(); ++i) { - ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); - - ArrangePolygons &cont = mo->instances[i]->printable ? - (inst_sel[i] ? m_selected : - m_unselected) : - m_unprintable; - - cont.emplace_back(std::move(ap)); - } - } - - auto& wti = plater().updated_wipe_tower(); - if (wti) { - ArrangePolygon &&ap = get_arrange_poly(&wti); - - plater().get_selection().is_wipe_tower() ? - m_selected.emplace_back(std::move(ap)) : - m_unselected.emplace_back(std::move(ap)); - } - - // If the selection was empty arrange everything - if (m_selected.empty()) m_selected.swap(m_unselected); - - // The strides have to be removed from the fixed items. For the - // arrangeable (selected) items bed_idx is ignored and the - // translation is irrelevant. - for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; - } - - protected: - - void prepare() override + Jobs(priv *_m) : m(_m) { - wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); + m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q)); + m_fill_bed_id = add_job(std::make_unique<FillBedJob>(m->statusbar(), m->q)); + m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q)); + m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->statusbar(), m->q)); } - - public: - using PlaterJob::PlaterJob; - - int status_range() const override + + void arrange() { - return int(m_selected.size() + m_unprintable.size()); + m->take_snapshot(_(L("Arrange"))); + start(m_arrange_id); } - void process() override; - - void finalize() override { - // Ignore the arrange result if aborted. - if (was_canceled()) return; - - // Unprintable items go to the last virtual bed - int beds = 0; - - // Apply the arrange result to all selected objects - for (ArrangePolygon &ap : m_selected) { - beds = std::max(ap.bed_idx, beds); - ap.apply(); - } - - // Get the virtual beds from the unselected items - for (ArrangePolygon &ap : m_unselected) - beds = std::max(ap.bed_idx, beds); - - // Move the unprintable items to the last virtual bed. - for (ArrangePolygon &ap : m_unprintable) { - ap.bed_idx += beds + 1; - ap.apply(); - } - - plater().update(); + void fill_bed() + { + m->take_snapshot(_(L("Fill bed"))); + start(m_fill_bed_id); } - }; - - class RotoptimizeJob : public PlaterJob - { - public: - using PlaterJob::PlaterJob; - void process() override; - }; - - class HollowJob : public PlaterJob - { - public: - using PlaterJob::PlaterJob; - void prepare() override; - void process() override; - void finalize() override; - private: - GLGizmoHollow * get_gizmo(); - const GLGizmoHollow * get_gizmo() const; - std::unique_ptr<TriangleMesh> m_output_mesh; - std::unique_ptr<MeshRaycaster> m_output_raycaster; - const TriangleMesh* m_object_mesh = nullptr; - sla::HollowingConfig m_cfg; - }; - - // Jobs defined inside the group class will be managed so that only one can - // run at a time. Also, the background process will be stopped if a job is - // started. - class ExclusiveJobGroup { - - static const int ABORT_WAIT_MAX_MS = 10000; - - priv * m_plater; - - ArrangeJob arrange_job{m_plater}; - RotoptimizeJob rotoptimize_job{m_plater}; - HollowJob hollow_job{m_plater}; - - // To create a new job, just define a new subclass of Job, implement - // the process and the optional prepare() and finalize() methods - // Register the instance of the class in the m_jobs container - // if it cannot run concurrently with other jobs in this group - - std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job, - rotoptimize_job, - hollow_job}; - - public: - ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {} - - void start(Jobs jid) { - m_plater->background_process.stop(); - stop_all(); - m_jobs[size_t(jid)].get().start(); - } - - void cancel_all() { for (Job& j : m_jobs) j.cancel(); } - - void join_all(int wait_ms = 0) + void optimize_rotation() { - std::vector<bool> aborted(m_jobs.size(), false); - - for (size_t jid = 0; jid < m_jobs.size(); ++jid) - aborted[jid] = m_jobs[jid].get().join(wait_ms); - - if (!all_of(aborted)) - BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; + m->take_snapshot(_(L("Optimize Rotation"))); + start(m_rotoptimize_id); } - - void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } - - const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; } - - bool is_any_running() const + + void import_sla_arch() { - return std::any_of(m_jobs.begin(), - m_jobs.end(), - [](const Job &j) { return j.is_running(); }); + m->take_snapshot(_(L("Import SLA archive"))); + start(m_sla_import_id); } - - } m_ui_jobs{this}; + + } m_ui_jobs; bool delayed_scene_refresh; std::string delayed_error_message; @@ -1816,6 +1677,10 @@ struct Plater::priv std::string label_btn_export; std::string label_btn_send; +#if ENABLE_RENDER_STATISTICS + bool show_render_statistic_dialog{ false }; +#endif // ENABLE_RENDER_STATISTICS + static const std::regex pattern_bundle; static const std::regex pattern_3mf; static const std::regex pattern_zip_amf; @@ -1825,10 +1690,10 @@ struct Plater::priv priv(Plater *q, MainFrame *main_frame); ~priv(); - enum class UpdateParams { - FORCE_FULL_SCREEN_REFRESH = 1, - FORCE_BACKGROUND_PROCESSING_UPDATE = 2, - POSTPONE_VALIDATION_ERROR_MESSAGE = 4, + enum class UpdateParams { + FORCE_FULL_SCREEN_REFRESH = 1, + FORCE_BACKGROUND_PROCESSING_UPDATE = 2, + POSTPONE_VALIDATION_ERROR_MESSAGE = 4, }; void update(unsigned int flags = 0); void select_view(const std::string& direction); @@ -1842,21 +1707,34 @@ struct Plater::priv bool are_view3D_labels_shown() const { return (current_panel == view3D) && view3D->get_canvas3d()->are_labels_shown(); } void show_view3D_labels(bool show) { if (current_panel == view3D) view3D->get_canvas3d()->show_labels(show); } + bool is_sidebar_collapsed() const { return sidebar->is_collapsed(); } + void collapse_sidebar(bool collapse); + + bool is_view3D_layers_editing_enabled() const { return (current_panel == view3D) && view3D->get_canvas3d()->is_layers_editing_enabled(); } + void set_current_canvas_as_dirty(); GLCanvas3D* get_current_canvas3D(); + void unbind_canvas_event_handlers(); + void reset_canvas_volumes(); bool init_view_toolbar(); + bool init_collapse_toolbar(); + + void update_preview_bottom_toolbar(); + void update_preview_moves_slider(); + void enable_preview_moves_slider(bool enable); + + void reset_gcode_toolpaths(); void reset_all_gizmos(); - void update_ui_from_settings(); + void update_ui_from_settings(bool apply_free_camera_correction = true); + void update_main_toolbar_tooltips(); std::shared_ptr<ProgressStatusBar> statusbar(); std::string get_config(const std::string &key) const; BoundingBoxf bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const; - arrangement::BedShapeHint get_bed_shape_hint() const; - void find_new_position(const ModelInstancePtrs &instances, coord_t min_d); - std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config); + std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool used_inches = false); std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects); wxString get_export_file(GUI::FileType file_type); @@ -1873,9 +1751,6 @@ struct Plater::priv void delete_object_from_model(size_t obj_idx); void reset(); void mirror(Axis axis); - void arrange(); - void hollow(); - void sla_optimize_rotation(); void split_object(); void split_volume(); void scale_selection_to_fit_print_volume(); @@ -1939,8 +1814,19 @@ struct Plater::priv void on_select_preset(wxCommandEvent&); void on_slicing_update(SlicingStatusEvent&); void on_slicing_completed(wxCommandEvent&); - void on_process_completed(wxCommandEvent&); + void on_process_completed(SlicingProcessCompletedEvent&); + void on_export_began(wxCommandEvent&); void on_layer_editing_toggled(bool enable); + void on_slicing_began(); + + void clear_warnings(); + void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid); + // Update notification manager with the current state of warnings produced by the background process (slicing). + void actualize_slicing_warnings(const PrintBase &print); + // Displays dialog window with list of warnings. + // Returns true if user clicks OK. + // Returns true if current_warnings vector is empty without showning the dialog + bool warnings_dialog(); void on_action_add(SimpleEvent&); void on_action_split_objects(SimpleEvent&); @@ -1961,7 +1847,7 @@ struct Plater::priv // triangulate the bed and store the triangles into m_bed.m_triangles, // fills the m_bed.m_grid_lines and sets m_bed.m_origin. // Sets m_bed.m_polygon to limit the object placement. - void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model); + void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false); bool can_delete() const; bool can_delete_all() const; @@ -1976,13 +1862,13 @@ struct Plater::priv bool can_mirror() const; bool can_reload_from_disk() const; -#if ENABLE_THUMBNAIL_GENERATOR void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); void generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); -#endif // ENABLE_THUMBNAIL_GENERATOR void msw_rescale_object_menu(); + void bring_instance_forward() const; + // returns the path to project file with the given extension (none if extension == wxEmptyString) // extension should contain the leading dot, i.e.: ".3mf" wxString get_project_filename(const wxString& extension = wxEmptyString) const; @@ -1991,9 +1877,11 @@ struct Plater::priv // 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. mutable bool ready_to_slice = { false }; // 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. - bool writing_to_removable_device = { false }; + ExportingStatus exporting_status { NOT_EXPORTING }; + std::string last_output_path; + std::string last_output_dir_path; bool inside_snapshot_capture() { return m_prevent_snapshots != 0; } - + bool process_completed_with_error { false }; private: bool init_object_menu(); bool init_common_menu(wxMenu* menu, const bool is_part = false); @@ -2021,6 +1909,11 @@ private: * */ std::string m_last_fff_printer_profile_name; std::string m_last_sla_printer_profile_name; + + // vector of all warnings generated by last slicing + std::vector<std::pair<Slic3r::PrintStateBase::Warning, size_t>> current_warnings; + bool show_warning_dialog { false }; + }; const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); @@ -2034,8 +1927,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , main_frame(main_frame) , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", - "brim_width", "variable_layer_height", "serial_port", "serial_speed", "host_type", "print_host", - "printhost_apikey", "printhost_cafile", "nozzle_diameter", "single_extruder_multi_material", + "brim_width", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. @@ -2044,16 +1936,17 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers" })) , sidebar(new Sidebar(q)) + , m_ui_jobs(this) , delayed_scene_refresh(false) , view_toolbar(GLToolbar::Radio, "View") + , collapse_toolbar(GLToolbar::Normal, "Collapse") , m_project_filename(wxEmptyString) { this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); background_process.set_fff_print(&fff_print); background_process.set_sla_print(&sla_print); - background_process.set_gcode_preview_data(&gcode_preview_data); -#if ENABLE_THUMBNAIL_GENERATOR + background_process.set_gcode_result(&gcode_result); background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) { 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) { @@ -2063,9 +1956,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) wxTheApp->CallAfter([&]() { task(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background); }); result.wait(); }); -#endif // ENABLE_THUMBNAIL_GENERATOR background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED); + background_process.set_export_began_event(EVT_EXPORT_BEGAN); // Default printer technology for default config. background_process.select_technology(this->printer_technology); // Register progress callback from the Print class to the Plater. @@ -2077,8 +1970,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) sla_print.set_status_callback(statuscb); this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this); - view3D = new View3D(q, bed, camera, view_toolbar, &model, config, &background_process); - preview = new Preview(q, bed, camera, view_toolbar, &model, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); + view3D = new View3D(q, &model, config, &background_process); + preview = new Preview(q, &model, config, &background_process, &gcode_result, [this]() { schedule_background_process(); }); + +#ifdef __APPLE__ + // set default view_toolbar icons size equal to GLGizmosManager::Default_Icons_Size + view_toolbar.set_icons_size(GLGizmosManager::Default_Icons_Size); +#endif // __APPLE__ panels.push_back(view3D); panels.push_back(preview); @@ -2104,86 +2002,98 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // Events: - // Preset change event - sidebar->Bind(wxEVT_COMBOBOX, &priv::on_select_preset, this); - - sidebar->Bind(EVT_OBJ_LIST_OBJECT_SELECT, [this](wxEvent&) { priv::selection_changed(); }); - sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); + if (wxGetApp().is_editor()) { + // Preset change event + sidebar->Bind(wxEVT_COMBOBOX, &priv::on_select_preset, this); + sidebar->Bind(EVT_OBJ_LIST_OBJECT_SELECT, [this](wxEvent&) { priv::selection_changed(); }); + sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); + } wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas(); - // 3DScene events: - view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); - view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this); - view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); - view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); - view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); - view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); }); - view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); - view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int> &evt) - { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); - view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); - view3D_canvas->Bind(EVT_GLCANVAS_FORCE_UPDATE, [this](SimpleEvent&) { update(); }); - view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); - view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this); - view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); - view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); }); - view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event<bool> &evt) { this->sidebar->enable_buttons(evt.data); }); - view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this); - view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); - view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); - view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); }); - view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); }); - view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); }); - view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); }); - view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event<float>& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); }); - view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); }); - view3D_canvas->Bind(EVT_GLCANVAS_RELOAD_FROM_DISK, [this](SimpleEvent&) { this->reload_all_from_disk(); }); - - // 3DScene/Toolbar: - view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); - view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); - view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); }); - view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); }); - view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); }); - view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); }); - view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); }); - view3D_canvas->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); }); - view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this); - view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); - view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); - view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) - { - set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values, - config->option<ConfigOptionString>("bed_custom_texture")->value, - config->option<ConfigOptionString>("bed_custom_model")->value); - }); + + if (wxGetApp().is_editor()) { + // 3DScene events: + view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); + view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this); + view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); + view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); + view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); + view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); }); + view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); + view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int>& evt) + { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); + view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); + view3D_canvas->Bind(EVT_GLCANVAS_FORCE_UPDATE, [this](SimpleEvent&) { update(); }); + view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); + view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this); + view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); + view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); }); + view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event<bool>& evt) { this->sidebar->enable_buttons(evt.data); }); + view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this); + view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); + view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); + view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); }); + view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); }); + view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); }); + view3D_canvas->Bind(EVT_GLCANVAS_COLLAPSE_SIDEBAR, [this](SimpleEvent&) { this->q->collapse_sidebar(!this->q->is_sidebar_collapsed()); }); + view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); }); + view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event<float>& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); }); + view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); }); + view3D_canvas->Bind(EVT_GLCANVAS_RELOAD_FROM_DISK, [this](SimpleEvent&) { this->reload_all_from_disk(); }); + + // 3DScene/Toolbar: + view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); + view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this); + view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); + view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); + } + view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); }); // Preview events: preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) - { - set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values, - config->option<ConfigOptionString>("bed_custom_texture")->value, - config->option<ConfigOptionString>("bed_custom_model")->value); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); }); + if (wxGetApp().is_editor()) { + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_COLLAPSE_SIDEBAR, [this](SimpleEvent&) { this->q->collapse_sidebar(!this->q->is_sidebar_collapsed()); }); + } + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_JUMP_TO, [this](wxKeyEvent& evt) { preview->jump_layers_slider(evt); }); +#if ENABLE_ARROW_KEYS_WITH_SLIDERS + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_SLIDERS, [this](wxKeyEvent& evt) { + preview->move_layers_slider(evt); + preview->move_moves_slider(evt); }); - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); }); - - q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); - q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); - q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); }); - q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); }); +#else + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, [this](wxKeyEvent& evt) { preview->move_layers_slider(evt); }); +#endif // ENABLE_ARROW_KEYS_WITH_SLIDERS + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_layers_slider(evt); }); + if (wxGetApp().is_gcode_viewer()) + preview->Bind(EVT_GLCANVAS_RELOAD_FROM_DISK, [this](SimpleEvent&) { this->q->reload_gcode_from_disk(); }); + + if (wxGetApp().is_editor()) { + q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); + q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); + q->Bind(EVT_EXPORT_BEGAN, &priv::on_export_began, this); + q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); }); + q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); }); + } // Drop target: q->SetDropTarget(new PlaterDropTarget(q)); // if my understanding is right, wxWindow takes the owenership - - update_ui_from_settings(); q->Layout(); - set_current_panel(view3D); + set_current_panel(wxGetApp().is_editor() ? static_cast<wxPanel*>(view3D) : static_cast<wxPanel*>(preview)); + if (wxGetApp().is_gcode_viewer()) + preview->hide_layers_slider(); // updates camera type from .ini file + camera.enable_update_config_on_type_change(true); camera.set_type(get_config("use_perspective_camera")); // Load the 3DConnexion device database. @@ -2197,29 +2107,77 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // is one of the 3D Mouse vendors (3DConnexion or Logitech). this->q->Bind(EVT_HID_DEVICE_ATTACHED, [this](HIDDeviceAttachedEvent &evt) { mouse3d_controller.device_attached(evt.data); - }); + }); +#if ENABLE_CTRL_M_ON_WINDOWS + this->q->Bind(EVT_HID_DEVICE_DETACHED, [this](HIDDeviceAttachedEvent& evt) { + mouse3d_controller.device_detached(evt.data); + }); +#endif // ENABLE_CTRL_M_ON_WINDOWS #endif /* _WIN32 */ - this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this](RemovableDriveEjectEvent &evt) { - if (evt.data.second) { - this->show_action_buttons(this->ready_to_slice); - Slic3r::GUI::show_info(this->q, (boost::format(_utf8(L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."))) - % evt.data.first.name % evt.data.first.path).str()); - } else - Slic3r::GUI::show_info(this->q, (boost::format(_utf8(L("Ejecting of device %s(%s) has failed."))) - % evt.data.first.name % evt.data.first.path).str()); - }); - this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); }); - // Start the background thread and register this window as a target for update events. - wxGetApp().removable_drive_manager()->init(this->q); + notification_manager = new NotificationManager(this->q); + if (wxGetApp().is_editor()) { + this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); + this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); + this->q->Bind(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, [this](PresetUpdateAvailableClickedEvent&) { wxGetApp().get_preset_updater()->on_update_notification_confirm(); }); + this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) { + if (evt.data.second) { + this->show_action_buttons(this->ready_to_slice); + notification_manager->close_notification_of_type(NotificationType::ExportFinished); + notification_manager->push_notification(NotificationType::CustomNotification, + NotificationManager::NotificationLevel::RegularNotification, + format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path) + ); + } else { + notification_manager->push_notification(NotificationType::CustomNotification, + NotificationManager::NotificationLevel::ErrorNotification, + format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path) + ); + } + }); + this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) { + this->show_action_buttons(this->ready_to_slice); + // Close notification ExportingFinished but only if last export was to removable + notification_manager->device_ejected(); + }); + // Start the background thread and register this window as a target for update events. + wxGetApp().removable_drive_manager()->init(this->q); #ifdef _WIN32 - // Trigger enumeration of removable media on Win32 notification. - this->q->Bind(EVT_VOLUME_ATTACHED, [this](VolumeAttachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); - this->q->Bind(EVT_VOLUME_DETACHED, [this](VolumeDetachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); + // Trigger enumeration of removable media on Win32 notification. + this->q->Bind(EVT_VOLUME_ATTACHED, [this](VolumeAttachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); + this->q->Bind(EVT_VOLUME_DETACHED, [this](VolumeDetachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); #endif /* _WIN32 */ + } // Initialize the Undo / Redo stack with a first snapshot. - this->take_snapshot(_(L("New Project"))); + this->take_snapshot(_L("New Project")); + +#if ENABLE_DRAG_AND_DROP_FIX + this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent& evt) { + BOOST_LOG_TRIVIAL(trace) << "Received load from other instance event."; + wxArrayString input_files; + for (size_t i = 0; i < evt.data.size(); ++i) { + input_files.push_back(from_u8(evt.data[i].string())); + } + wxGetApp().mainframe->Raise(); + this->q->load_files(input_files); + }); +#else + this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent &evt) { + BOOST_LOG_TRIVIAL(trace) << "Received load from other instance event."; + this->load_files(evt.data, true, true); + }); +#endif // ENABLE_DRAG_AND_DROP_FIX + this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) { + bring_instance_forward(); + }); + wxGetApp().other_instance_message_handler()->init(this->q); + + // collapse sidebar according to saved value + if (wxGetApp().is_editor()) { + bool is_collapsed = wxGetApp().app_config->get("collapsed_sidebar") == "1"; + sidebar->collapse(is_collapsed); + } } Plater::priv::~priv() @@ -2267,6 +2225,8 @@ void Plater::priv::select_view_3D(const std::string& name) set_current_panel(view3D); else if (name == "Preview") set_current_panel(preview); + + wxGetApp().update_ui_from_settings(false); } void Plater::priv::select_next_view_3D() @@ -2277,6 +2237,20 @@ void Plater::priv::select_next_view_3D() set_current_panel(view3D); } +void Plater::priv::collapse_sidebar(bool collapse) +{ + sidebar->collapse(collapse); + + // Now update the tooltip in the toolbar. + std::string new_tooltip = collapse + ? _utf8(L("Expand sidebar")) + : _utf8(L("Collapse sidebar")); + new_tooltip += " [Shift+Tab]"; + int id = collapse_toolbar.get_item_id("collapse_sidebar"); + collapse_toolbar.set_tooltip(id, new_tooltip); +} + + void Plater::priv::reset_all_gizmos() { view3D->get_canvas3d()->reset_all_gizmos(); @@ -2284,14 +2258,23 @@ void Plater::priv::reset_all_gizmos() // Called after the Preferences dialog is closed and the program settings are saved. // Update the UI based on the current preferences. -void Plater::priv::update_ui_from_settings() +void Plater::priv::update_ui_from_settings(bool apply_free_camera_correction) { camera.set_type(wxGetApp().app_config->get("use_perspective_camera")); - if (wxGetApp().app_config->get("use_free_camera") != "1") + if (apply_free_camera_correction && wxGetApp().app_config->get("use_free_camera") != "1") camera.recover_from_free_camera(); view3D->get_canvas3d()->update_ui_from_settings(); preview->get_canvas3d()->update_ui_from_settings(); + + sidebar->update_ui_from_settings(); +} + +// Called after the print technology was changed. +// Update the tooltips for "Switch to Settings" button in maintoolbar +void Plater::priv::update_main_toolbar_tooltips() +{ + view3D->get_canvas3d()->update_tooltip_for_settings_item_in_main_toolbar(); } std::shared_ptr<ProgressStatusBar> Plater::priv::statusbar() @@ -2317,7 +2300,7 @@ BoundingBox Plater::priv::scaled_bed_shape_bb() const return bed_shape.bounding_box(); } -std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config) +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*/) { if (input_files.empty()) { return std::vector<size_t>(); } @@ -2333,18 +2316,18 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ } } - const auto loading = _(L("Loading")) + dots; - wxProgressDialog dlg(loading, loading); + const auto loading = _L("Loading") + dots; + wxProgressDialog dlg(loading, "", 100, q, wxPD_AUTO_HIDE); dlg.Pulse(); auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model(); std::vector<size_t> obj_idxs; - for (size_t i = 0; i < input_files.size(); i++) { + for (size_t i = 0; i < input_files.size(); ++i) { const auto &path = input_files[i]; const auto filename = path.filename(); - const auto dlg_info = from_u8((boost::format(_utf8(L("Processing input file %s"))) % from_path(filename)).str()) + "\n"; - dlg.Update(100 * i / input_files.size(), dlg_info); + const auto dlg_info = _L("Loading file") + ": " + from_path(filename); + dlg.Update(static_cast<int>(100.0f * static_cast<float>(i) / static_cast<float>(input_files.size())), dlg_info); const bool type_3mf = std::regex_match(path.string(), pattern_3mf); const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf); @@ -2371,9 +2354,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ if (object->volumes.size() > 1) { Slic3r::GUI::show_info(nullptr, - _(L("You cannot load SLA project with a multi-part object on the bed")) + "\n\n" + - _(L("Please check your object list before preset changing.")), - _(L("Attention!"))); + _L("You cannot load SLA project with a multi-part object on the bed") + "\n\n" + + _L("Please check your object list before preset changing."), + _L("Attention!")); return obj_idxs; } } @@ -2393,7 +2376,10 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ if (!config.empty()) { Preset::normalize(config); wxGetApp().preset_bundle->load_config_model(filename.string(), std::move(config)); - wxGetApp().load_current_presets(); + if (printer_technology == ptFFF) + CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, &wxGetApp().preset_bundle->project_config); + // For exporting from the amf/3mf we shouldn't check printer_presets for the containing information about "Print Host upload" + wxGetApp().load_current_presets(false); is_project_file = true; } wxGetApp().app_config->update_config_dir(path.parent_path().string()); @@ -2414,21 +2400,40 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ { // The model should now be initialized - if (! is_project_file) { + auto convert_from_imperial_units = [](Model& model, bool only_small_volumes) { + model.convert_from_imperial_units(only_small_volumes); +// wxGetApp().app_config->set("use_inches", "1"); + wxGetApp().sidebar().update_ui_from_settings(); + }; + + if (!is_project_file) { + if (imperial_units) + // Convert even if the object is big. + convert_from_imperial_units(model, false); + else if (model.looks_like_imperial_units()) { + wxMessageDialog msg_dlg(q, format_wxstr(_L( + "Some object(s) in file %s looks like saved in inches.\n" + "Should I consider them as a saved in inches and convert them?"), from_path(filename)) + "\n", + _L("The object appears to be saved in inches"), wxICON_WARNING | wxYES | wxNO); + if (msg_dlg.ShowModal() == wxID_YES) + //FIXME up-scale only the small parts? + convert_from_imperial_units(model, true); + } + if (model.looks_like_multipart_object()) { - wxMessageDialog msg_dlg(q, _(L( + wxMessageDialog msg_dlg(q, _L( "This file contains several objects positioned at multiple heights.\n" "Instead of considering them as multiple objects, should I consider\n" - "this file as a single object having multiple parts?")) + "\n", - _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO); + "this file as a single object having multiple parts?") + "\n", + _L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO); if (msg_dlg.ShowModal() == wxID_YES) { model.convert_multipart_object(nozzle_dmrs->values.size()); } } } else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) { - wxMessageDialog msg_dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?"))+"\n", - _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO); + wxMessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n", + _L("Detected advanced data"), wxICON_WARNING | wxYES | wxNO); if (msg_dlg.ShowModal() == wxID_YES) { Slic3r::GUI::wxGetApp().save_mode(comAdvanced); @@ -2450,8 +2455,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ for (auto obj : model.objects) if ( obj->volumes.size()>1 ) { Slic3r::GUI::show_error(nullptr, - from_u8((boost::format(_utf8(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part"))) - % from_path(filename)).str())); + format_wxstr(_L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part"), + from_path(filename))); return obj_idxs; } } @@ -2469,11 +2474,11 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ } if (new_model != nullptr && new_model->objects.size() > 1) { - wxMessageDialog msg_dlg(q, _(L( + wxMessageDialog msg_dlg(q, _L( "Multiple objects were loaded for a multi-material printer.\n" "Instead of considering them as multiple objects, should I consider\n" - "these files to represent a single object having multiple parts?")) + "\n", - _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO); + "these files to represent a single object having multiple parts?") + "\n", + _L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO); if (msg_dlg.ShowModal() == wxID_YES) { new_model->convert_multipart_object(nozzle_dmrs->values.size()); } @@ -2486,7 +2491,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ { wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().string()); // XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames... - statusbar()->set_status_text(_(L("Loaded"))); + statusbar()->set_status_text(_L("Loaded")); } // automatic selection of added objects @@ -2588,8 +2593,8 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode if (scaled_down) { GUI::show_info(q, - _(L("Your object appears to be too large, so it was automatically scaled down to fit your print bed.")), - _(L("Object too large?"))); + _L("Your object appears to be too large, so it was automatically scaled down to fit your print bed."), + _L("Object too large?")); } for (const size_t idx : obj_idxs) { @@ -2651,26 +2656,26 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) case FT_STL: { output_file.replace_extension("stl"); - dlg_title = _(L("Export STL file:")); + dlg_title = _L("Export STL file:"); break; } case FT_AMF: { // XXX: Problem on OS X with double extension? output_file.replace_extension("zip.amf"); - dlg_title = _(L("Export AMF file:")); + dlg_title = _L("Export AMF file:"); break; } case FT_3MF: { output_file.replace_extension("3mf"); - dlg_title = _(L("Save file as:")); + dlg_title = _L("Save file as:"); break; } case FT_OBJ: { output_file.replace_extension("obj"); - dlg_title = _(L("Export OBJ file:")); + dlg_title = _L("Export OBJ file:"); break; } default: break; @@ -2735,30 +2740,24 @@ void Plater::priv::object_list_changed() { const bool export_in_progress = this->background_process.is_export_scheduled(); // || ! send_gcode_file.empty()); // XXX: is this right? - const bool model_fits = view3D->check_volumes_outside_state() == ModelInstance::PVS_Inside; + const bool model_fits = view3D->check_volumes_outside_state() == ModelInstancePVS_Inside; sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits); } void Plater::priv::select_all() { -// this->take_snapshot(_(L("Select All"))); - view3D->select_all(); this->sidebar->obj_list()->update_selections(); } void Plater::priv::deselect_all() { -// this->take_snapshot(_(L("Deselect All"))); view3D->deselect_all(); } void Plater::priv::remove(size_t obj_idx) { - // Prevent toolpaths preview from rendering while we modify the Print object - preview->set_enabled(false); - if (view3D->is_layers_editing_enabled()) view3D->enable_layers_editing(false); @@ -2772,7 +2771,7 @@ void Plater::priv::remove(size_t obj_idx) void Plater::priv::delete_object_from_model(size_t obj_idx) { - wxString snapshot_label = _(L("Delete Object")); + wxString snapshot_label = _L("Delete Object"); if (! model.objects[obj_idx]->name.empty()) snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str()); Plater::TakeSnapshot snapshot(q, snapshot_label); @@ -2783,16 +2782,18 @@ void Plater::priv::delete_object_from_model(size_t obj_idx) void Plater::priv::reset() { - Plater::TakeSnapshot snapshot(q, _(L("Reset Project"))); + Plater::TakeSnapshot snapshot(q, _L("Reset Project")); - set_project_filename(wxEmptyString); + clear_warnings(); - // Prevent toolpaths preview from rendering while we modify the Print object - preview->set_enabled(false); + set_project_filename(wxEmptyString); if (view3D->is_layers_editing_enabled()) view3D->enable_layers_editing(false); + reset_gcode_toolpaths(); + gcode_result.reset(); + // Stop and reset the Print content. this->background_process.reset(); model.clear_objects(); @@ -2812,45 +2813,12 @@ void Plater::priv::mirror(Axis axis) view3D->mirror_selection(axis); } -void Plater::priv::arrange() -{ - this->take_snapshot(_(L("Arrange"))); - m_ui_jobs.start(Jobs::Arrange); -} - -void Plater::priv::hollow() -{ - this->take_snapshot(_(L("Hollow"))); - m_ui_jobs.start(Jobs::Hollow); -} - -// This method will find an optimal orientation for the currently selected item -// Very similar in nature to the arrange method above... -void Plater::priv::sla_optimize_rotation() { - this->take_snapshot(_(L("Optimize Rotation"))); - m_ui_jobs.start(Jobs::Rotoptimize); -} - -arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { - - const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape"); - assert(bed_shape_opt); - - if (!bed_shape_opt) return {}; - - auto &bedpoints = bed_shape_opt->values; - Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); - for (auto &v : bedpoints) bedpoly.append(scaled(v)); - - return arrangement::BedShapeHint(bedpoly); -} - -void Plater::priv::find_new_position(const ModelInstancePtrs &instances, +void Plater::find_new_position(const ModelInstancePtrs &instances, coord_t min_d) { arrangement::ArrangePolygons movable, fixed; - - for (const ModelObject *mo : model.objects) + + for (const ModelObject *mo : p->model.objects) for (const ModelInstance *inst : mo->instances) { auto it = std::find(instances.begin(), instances.end(), inst); auto arrpoly = inst->get_arrange_polygon(); @@ -2860,168 +2828,19 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances, else movable.emplace_back(std::move(arrpoly)); } - - if (updated_wipe_tower()) - fixed.emplace_back(wipetower.get_arrange_polygon()); - - arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint()); + + if (auto wt = get_wipe_tower_arrangepoly(*this)) + fixed.emplace_back(*wt); + + arrangement::arrange(movable, fixed, get_bed_shape(*config()), + arrangement::ArrangeParams{min_d}); for (size_t i = 0; i < instances.size(); ++i) if (movable[i].bed_idx == 0) - instances[i]->apply_arrange_result(movable[i].translation, + instances[i]->apply_arrange_result(movable[i].translation.cast<double>(), movable[i].rotation); } -void Plater::priv::ArrangeJob::process() { - static const auto arrangestr = _(L("Arranging")); - - // FIXME: I don't know how to obtain the minimum distance, it depends - // on printer technology. I guess the following should work but it crashes. - double dist = 6; // PrintConfig::min_object_distance(config); - if (plater().printer_technology == ptFFF) { - dist = PrintConfig::min_object_distance(plater().config); - } - - coord_t min_d = scaled(dist); - auto count = unsigned(m_selected.size() + m_unprintable.size()); - arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); - - auto stopfn = [this]() { return was_canceled(); }; - - try { - arrangement::arrange(m_selected, m_unselected, min_d, bedshape, - [this, count](unsigned st) { - st += m_unprintable.size(); - if (st > 0) update_status(int(count - st), arrangestr); - }, stopfn); - arrangement::arrange(m_unprintable, {}, min_d, bedshape, - [this, count](unsigned st) { - if (st > 0) update_status(int(count - st), arrangestr); - }, stopfn); - } catch (std::exception & /*e*/) { - GUI::show_error(plater().q, - _(L("Could not arrange model objects! " - "Some geometries may be invalid."))); - } - - // finalize just here. - update_status(int(count), - was_canceled() ? _(L("Arranging canceled.")) - : _(L("Arranging done."))); -} - -void Plater::priv::RotoptimizeJob::process() -{ - int obj_idx = plater().get_selected_object_idx(); - if (obj_idx < 0) { return; } - - ModelObject *o = plater().model.objects[size_t(obj_idx)]; - - auto r = sla::find_best_rotation( - *o, - .005f, - [this](unsigned s) { - if (s < 100) - update_status(int(s), - _(L("Searching for optimal orientation"))); - }, - [this]() { return was_canceled(); }); - - - double mindist = 6.0; // FIXME - - if (!was_canceled()) { - for(ModelInstance * oi : o->instances) { - oi->set_rotation({r[X], r[Y], r[Z]}); - - auto trmatrix = oi->get_transformation().get_matrix(); - Polygon trchull = o->convex_hull_2d(trmatrix); - - MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); - double r = rotbb.angle_to_X(); - - // The box should be landscape - if(rotbb.width() < rotbb.height()) r += PI / 2; - - Vec3d rt = oi->get_rotation(); rt(Z) += r; - - oi->set_rotation(rt); - } - - plater().find_new_position(o->instances, scaled(mindist)); - - // Correct the z offset of the object which was corrupted be - // the rotation - o->ensure_on_bed(); - } - - update_status(100, - was_canceled() ? _(L("Orientation search canceled.")) - : _(L("Orientation found."))); -} - -void Plater::priv::HollowJob::prepare() -{ - const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager(); - const GLGizmoHollow* gizmo_hollow = dynamic_cast<const GLGizmoHollow*>(gizmo_manager.get_current()); - assert(gizmo_hollow); - auto hlw_data = gizmo_hollow->get_hollowing_parameters(); - m_object_mesh = hlw_data.first; - m_cfg = hlw_data.second; - m_output_mesh.reset(); -} - -void Plater::priv::HollowJob::process() -{ - sla::JobController ctl; - ctl.stopcondition = [this]{ return was_canceled(); }; - ctl.statuscb = [this](unsigned st, const std::string &s) { - if (st < 100) update_status(int(st), s); - }; - - std::unique_ptr<TriangleMesh> omesh = - sla::generate_interior(*m_object_mesh, m_cfg, ctl); - - if (omesh && !omesh->empty()) { - m_output_mesh.reset(new TriangleMesh{*m_object_mesh}); - m_output_mesh->merge(*omesh); - m_output_mesh->require_shared_vertices(); - - update_status(90, _(L("Indexing hollowed object"))); - - m_output_raycaster.reset(new MeshRaycaster(*m_output_mesh)); - - update_status(100, was_canceled() ? _(L("Hollowing cancelled.")) : - _(L("Hollowing done."))); - } else { - update_status(100, _(L("Hollowing failed."))); - } -} - -void Plater::priv::HollowJob::finalize() -{ - if (auto gizmo = get_gizmo()) { - gizmo->update_mesh_raycaster(std::move(m_output_raycaster)); - gizmo->update_hollowed_mesh(std::move(m_output_mesh)); - } -} - -GLGizmoHollow *Plater::priv::HollowJob::get_gizmo() -{ - const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager(); - auto ret = dynamic_cast<GLGizmoHollow*>(gizmo_manager.get_current()); - assert(ret); - return ret; -} - -const GLGizmoHollow *Plater::priv::HollowJob::get_gizmo() const -{ - const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager(); - auto ret = dynamic_cast<const GLGizmoHollow*>(gizmo_manager.get_current()); - assert(ret); - return ret; -} - void Plater::priv::split_object() { int obj_idx = get_selected_object_idx(); @@ -3035,7 +2854,7 @@ void Plater::priv::split_object() if (current_model_object->volumes.size() > 1) { - Slic3r::GUI::warning_catcher(q, _(L("The selected object can't be split because it contains more than one volume/material."))); + Slic3r::GUI::warning_catcher(q, _L("The selected object can't be split because it contains more than one volume/material.")); return; } @@ -3043,10 +2862,10 @@ void Plater::priv::split_object() ModelObjectPtrs new_objects; current_model_object->split(&new_objects); if (new_objects.size() == 1) - Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part."))); + Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one part.")); else { - Plater::TakeSnapshot snapshot(q, _(L("Split to Objects"))); + Plater::TakeSnapshot snapshot(q, _L("Split to Objects")); unsigned int counter = 1; for (ModelObject* m : new_objects) @@ -3120,10 +2939,12 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool this->sidebar->show_sliced_info_sizer(false); // Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared. // Otherwise they will be just refreshed. - if (this->preview != nullptr) + if (this->preview != nullptr) { // If the preview is not visible, the following line just invalidates the preview, // but the G-code paths or SLA preview are calculated first once the preview is made visible. + reset_gcode_toolpaths(); this->preview->reload_print(); + } // In FDM mode, we need to reload the 3D scene because of the wipe tower preview box. // In SLA mode, we need to reload the 3D scene every time to show the support structures. if (this->printer_technology == ptSLA || (this->printer_technology == ptFFF && this->config->opt_bool("wipe_tower"))) @@ -3136,22 +2957,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors. std::string err = this->background_process.validate(); if (err.empty()) { + notification_manager->set_all_slicing_errors_gray(true); if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled()) return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; } else { - // The print is not valid. - // Only show the error message immediately, if the top level parent of this window is active. - auto p = dynamic_cast<wxWindow*>(this->q); - while (p->GetParent()) - p = p->GetParent(); - auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p); - if (! postpone_error_messages && top_level_wnd && top_level_wnd->IsActive()) { - // The error returned from the Print needs to be translated into the local language. - GUI::show_error(this->q, err); - } else { - // Show the error message once the main window gets activated. - this->delayed_error_message = err; - } + // The print is not valid. + // Show error as notification. + notification_manager->push_slicing_error_notification(err); return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; } } else if (! this->delayed_error_message.empty()) { @@ -3159,6 +2971,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; } + //actualizate warnings + if (invalidated != Print::APPLY_STATUS_UNCHANGED) { + actualize_slicing_warnings(*this->background_process.current_print()); + show_warning_dialog = false; + process_completed_with_error = false; + } + if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() && (return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) { // The background processing was killed and it will not be restarted. @@ -3171,22 +2990,23 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool if ((return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0) { // Validation of the background data failed. - const wxString invalid_str = _(L("Invalid data")); + const wxString invalid_str = _L("Invalid data"); for (auto btn : {ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport}) sidebar->set_btn_label(btn, invalid_str); + process_completed_with_error = true; } else { // Background data is valid. if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 || (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 ) - this->statusbar()->set_status_text(_(L("Ready to slice"))); + this->statusbar()->set_status_text(_L("Ready to slice")); sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export)); sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send)); const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ? - _(L("Slicing")) + dots : _(L("Slice now")); + _L("Slicing") + dots : _L("Slice now"); sidebar->set_btn_label(ActionButtonType::abReslice, slice_string); if (background_process.finished()) @@ -3218,9 +3038,11 @@ bool Plater::priv::restart_background_process(unsigned int state) // The print is valid and it can be started. if (this->background_process.start()) { this->statusbar()->set_cancel_callback([this]() { - this->statusbar()->set_status_text(_(L("Cancelling"))); + this->statusbar()->set_status_text(_L("Cancelling")); this->background_process.stop(); }); + if (!show_warning_dialog) + on_slicing_began(); return true; } } @@ -3235,7 +3057,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova return; if (background_process.is_export_scheduled()) { - GUI::show_error(q, _(L("Another export job is currently running."))); + GUI::show_error(q, _L("Another export job is currently running.")); return; } @@ -3247,6 +3069,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) return; + show_warning_dialog = true; if (! output_path.empty()) { background_process.schedule_export(output_path.string(), output_path_on_removable_media); } else { @@ -3290,7 +3113,7 @@ void Plater::priv::update_sla_scene() void Plater::priv::reload_from_disk() { - Plater::TakeSnapshot snapshot(q, _(L("Reload from disk"))); + Plater::TakeSnapshot snapshot(q, _L("Reload from disk")); const Selection& selection = get_selection(); @@ -3351,7 +3174,7 @@ void Plater::priv::reload_from_disk() { // ask user to select the missing file fs::path search = missing_input_paths.back(); - wxString title = _(L("Please select the file to reload")); + wxString title = _L("Please select the file to reload"); #if defined(__APPLE__) title += " (" + from_u8(search.filename().string()) + ")"; #endif // __APPLE__ @@ -3386,7 +3209,7 @@ void Plater::priv::reload_from_disk() } else { - 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")) + " ?"; + 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") + " ?"; wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() != wxID_YES) return; @@ -3404,7 +3227,7 @@ void Plater::priv::reload_from_disk() const auto& path = input_paths[i].string(); wxBusyCursor wait; - wxBusyInfo info(_(L("Reload from:")) + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas()); + wxBusyInfo info(_L("Reload from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas()); Model new_model; try @@ -3483,9 +3306,13 @@ void Plater::priv::reload_from_disk() new_volume->set_material_id(old_volume->material_id()); new_volume->set_transformation(old_volume->get_transformation() * old_volume->source.transform); new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); + if (old_volume->source.is_converted_from_inches) + new_volume->convert_from_imperial_units(); std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); old_model_object->ensure_on_bed(); + + sla::reproject_points_and_holes(old_model_object); } } } @@ -3493,12 +3320,12 @@ void Plater::priv::reload_from_disk() if (!fail_list.empty()) { - wxString message = _(L("Unable to reload:")) + "\n"; + wxString message = _L("Unable to reload:") + "\n"; for (const wxString& s : fail_list) { message += s + "\n"; } - wxMessageDialog dlg(q, message, _(L("Error during reload")), wxOK | wxOK_DEFAULT | wxICON_WARNING); + wxMessageDialog dlg(q, message, _L("Error during reload"), wxOK | wxOK_DEFAULT | wxICON_WARNING); dlg.ShowModal(); } @@ -3517,7 +3344,7 @@ void Plater::priv::reload_all_from_disk() if (model.objects.empty()) return; - Plater::TakeSnapshot snapshot(q, _(L("Reload all from disk"))); + Plater::TakeSnapshot snapshot(q, _L("Reload all from disk")); Plater::SuppressSnapshots suppress(q); Selection& selection = get_selection(); @@ -3538,9 +3365,54 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = if (obj_idx < 0) return; - Plater::TakeSnapshot snapshot(q, _(L("Fix Throught NetFabb"))); + // Do not fix anything when a gizmo is open. There might be issues with updates + // and what is worse, the snapshot time would refer to the internal stack. + if (q->canvas3D()->get_gizmos_manager().get_current_type() != GLGizmosManager::Undefined) { + notification_manager->push_notification( + NotificationType::CustomSupportsAndSeamRemovedAfterRepair, + NotificationManager::NotificationLevel::RegularNotification, + _u8L("ERROR: Please close all manipulators available from " + "the left toolbar before fixing the mesh.")); + return; + } + + // size_t snapshot_time = undo_redo_stack().active_snapshot_time(); + Plater::TakeSnapshot snapshot(q, _L("Fix through NetFabb")); + + ModelObject* mo = model.objects[obj_idx]; - fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx); + // If there are custom supports/seams, remove them. Fixed mesh + // may be different and they would make no sense. + bool paint_removed = false; + for (ModelVolume* mv : mo->volumes) { + paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty(); + mv->supported_facets.clear(); + mv->seam_facets.clear(); + } + if (paint_removed) { + // snapshot_time is captured by copy so the lambda knows where to undo/redo to. + notification_manager->push_notification( + NotificationType::CustomSupportsAndSeamRemovedAfterRepair, + NotificationManager::NotificationLevel::RegularNotification, + _u8L("Custom supports and seams were removed after repairing the mesh.")); +// _u8L("Undo the repair"), +// [this, snapshot_time](wxEvtHandler*){ +// // Make sure the snapshot is still available and that +// // we are in the main stack and not in a gizmo-stack. +// if (undo_redo_stack().has_undo_snapshot(snapshot_time) +// && q->canvas3D()->get_gizmos_manager().get_current() == nullptr) +// undo_redo_to(snapshot_time); +// else +// notification_manager->push_notification( +// NotificationType::CustomSupportsAndSeamRemovedAfterRepair, +// NotificationManager::NotificationLevel::RegularNotification, +// _u8L("Cannot undo to before the mesh repair!")); +// return true; +// }); + } + + fix_model_by_win10_sdk_gui(*mo, vol_idx); + sla::reproject_points_and_holes(mo); this->update(); this->object_list_changed(); this->schedule_background_process(); @@ -3558,16 +3430,14 @@ void Plater::priv::set_current_panel(wxPanel* panel) if (current_panel == panel) return; + wxPanel* old_panel = current_panel; current_panel = panel; // to reduce flickering when changing view, first set as visible the new current panel - for (wxPanel* p : panels) - { - if (p == current_panel) - { + for (wxPanel* p : panels) { + if (p == current_panel) { #ifdef __WXMAC__ // On Mac we need also to force a render to avoid flickering when changing view - if (force_render) - { + if (force_render) { if (p == view3D) dynamic_cast<View3D*>(p)->get_canvas3d()->render(); else if (p == preview) @@ -3578,21 +3448,22 @@ void Plater::priv::set_current_panel(wxPanel* panel) } } // then set to invisible the other - for (wxPanel* p : panels) - { + for (wxPanel* p : panels) { if (p != current_panel) p->Hide(); } panel_sizer->Layout(); - if (current_panel == view3D) - { - if (view3D->is_reload_delayed()) - { + if (current_panel == view3D) { + if (old_panel == preview) + preview->get_canvas3d()->unbind_event_handlers(); + + view3D->get_canvas3d()->bind_event_handlers(); + + if (view3D->is_reload_delayed()) { // Delayed loading of the 3D scene. - if (this->printer_technology == ptSLA) - { + if (this->printer_technology == ptSLA) { // Update the SLAPrint from the current Model, so that the reload_scene() // pulls the correct data. this->update_restart_background_process(true, false); @@ -3603,13 +3474,19 @@ void Plater::priv::set_current_panel(wxPanel* panel) // sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) view3D->set_as_dirty(); view_toolbar.select_item("3D"); + if(notification_manager != nullptr) + notification_manager->set_in_preview(false); } - else if (current_panel == preview) - { + else if (current_panel == preview) { + if (old_panel == view3D) + view3D->get_canvas3d()->unbind_event_handlers(); + + preview->get_canvas3d()->bind_event_handlers(); + // see: Plater::priv::object_list_changed() // FIXME: it may be better to have a single function making this check and let it be called wherever needed bool export_in_progress = this->background_process.is_export_scheduled(); - bool model_fits = view3D->check_volumes_outside_state() != ModelInstance::PVS_Partly_Outside; + bool model_fits = view3D->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside; if (!model.objects.empty() && !export_in_progress && model_fits) this->q->reslice(); // keeps current gcode preview, if any @@ -3617,6 +3494,8 @@ void Plater::priv::set_current_panel(wxPanel* panel) preview->set_as_dirty(); view_toolbar.select_item("Preview"); + if (notification_manager != nullptr) + notification_manager->set_in_preview(true); } current_panel->SetFocusFromKbd(); @@ -3625,7 +3504,15 @@ void Plater::priv::set_current_panel(wxPanel* panel) void Plater::priv::on_select_preset(wxCommandEvent &evt) { auto preset_type = static_cast<Preset::Type>(evt.GetInt()); - auto *combo = static_cast<PresetComboBox*>(evt.GetEventObject()); + auto *combo = static_cast<PlaterPresetComboBox*>(evt.GetEventObject()); + + // see https://github.com/prusa3d/PrusaSlicer/issues/3889 + // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender"), + // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. + // So, use GetSelection() from event parameter + // But in this function we couldn't use evt.GetSelection(), because m_commandInt is used for preset_type + // Thus, get selection in this way: + int selection = combo->FindString(evt.GetString(), true); auto idx = combo->get_extruder_idx(); @@ -3636,32 +3523,49 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) //! instead of //! combo->GetStringSelection().ToUTF8().data()); - const std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, - Preset::remove_suffix_modified(combo->GetString(combo->GetSelection()).ToUTF8().data())); + std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, + Preset::remove_suffix_modified(combo->GetString(selection).ToUTF8().data())); if (preset_type == Preset::TYPE_FILAMENT) { wxGetApp().preset_bundle->set_filament_preset(idx, preset_name); } + bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); // TODO: ? if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { // Only update the plater UI for the 2nd and other filaments. - wxGetApp().preset_bundle->update_plater_filament_ui(idx, combo); + combo->update(); } - else { + else if (select_preset) { + if (preset_type == Preset::TYPE_PRINTER) { + PhysicalPrinterCollection& physical_printers = wxGetApp().preset_bundle->physical_printers; + if(combo->is_selected_physical_printer()) + preset_name = physical_printers.get_selected_printer_preset_name(); + else + physical_printers.unselect_printer(); + } wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); wxGetApp().get_tab(preset_type)->select_preset(preset_name); } // update plater with new config - wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config()); + q->on_config_change(wxGetApp().preset_bundle->full_config()); + if (preset_type == Preset::TYPE_PRINTER) { /* Settings list can be changed after printer preset changing, so * update all settings items for all item had it. * Furthermore, Layers editing is implemented only for FFF printers * and for SLA presets they should be deleted */ - if (preset_type == Preset::TYPE_PRINTER) wxGetApp().obj_list()->update_object_list_by_printer_technology(); + } + +#ifdef __WXMSW__ + // From the Win 2004 preset combobox lose a focus after change the preset selection + // and that is why the up/down arrow doesn't work properly + // (see https://github.com/prusa3d/PrusaSlicer/issues/5531 ). + // So, set the focus to the combobox explicitly + combo->SetFocus(); +#endif } void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) @@ -3674,6 +3578,7 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) this->statusbar()->set_progress(evt.status.percent); this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…")); + //notification_manager->set_progress_bar_percentage("Slicing progress", (float)evt.status.percent / 100.0f); } if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE | PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { switch (this->printer_technology) { @@ -3693,10 +3598,38 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) // Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways. this->preview->reload_print(); } + + if (evt.status.flags & (PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS | PrintBase::SlicingStatus::UPDATE_PRINT_OBJECT_STEP_WARNINGS)) { + // Update notification center with warnings of object_id and its warning_step. + ObjectID object_id = evt.status.warning_object_id; + int warning_step = evt.status.warning_step; + PrintStateBase::StateWithWarnings state; + if (evt.status.flags & PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS) { + state = this->printer_technology == ptFFF ? + this->fff_print.step_state_with_warnings(static_cast<PrintStep>(warning_step)) : + this->sla_print.step_state_with_warnings(static_cast<SLAPrintStep>(warning_step)); + } else if (this->printer_technology == ptFFF) { + const PrintObject *print_object = this->fff_print.get_object(object_id); + if (print_object) + state = print_object->step_state_with_warnings(static_cast<PrintObjectStep>(warning_step)); + } else { + const SLAPrintObject *print_object = this->sla_print.get_object(object_id); + if (print_object) + state = print_object->step_state_with_warnings(static_cast<SLAPrintObjectStep>(warning_step)); + } + // Now process state.warnings. + for (auto const& warning : state.warnings) { + if (warning.current) { + notification_manager->push_slicing_warning_notification(warning.message, false, object_id, warning_step); + add_warning(warning, object_id.id); + } + } + } } -void Plater::priv::on_slicing_completed(wxCommandEvent &) +void Plater::priv::on_slicing_completed(wxCommandEvent & evt) { + notification_manager->push_slicing_complete_notification(evt.GetInt(), is_sidebar_collapsed()); switch (this->printer_technology) { case ptFFF: this->update_fff_scene(); @@ -3709,9 +3642,66 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &) break; default: break; } + +} +void Plater::priv::on_export_began(wxCommandEvent& evt) +{ + if (show_warning_dialog) + warnings_dialog(); +} +void Plater::priv::on_slicing_began() +{ + clear_warnings(); + notification_manager->close_notification_of_type(NotificationType::SlicingComplete); +} +void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) +{ + for (auto const& it : current_warnings) { + if (warning.message_id == it.first.message_id) { + if (warning.message_id != 0 || (warning.message_id == 0 && warning.message == it.first.message)) + return; + } + } + current_warnings.emplace_back(std::pair<Slic3r::PrintStateBase::Warning, size_t>(warning, oid)); } +void Plater::priv::actualize_slicing_warnings(const PrintBase &print) +{ + std::vector<ObjectID> ids = print.print_object_ids(); + if (ids.empty()) { + clear_warnings(); + return; + } + ids.emplace_back(print.id()); + std::sort(ids.begin(), ids.end()); + notification_manager->remove_slicing_warnings_of_released_objects(ids); + notification_manager->set_all_slicing_warnings_gray(true); +} +void Plater::priv::clear_warnings() +{ + notification_manager->close_slicing_errors_and_warnings(); + this->current_warnings.clear(); +} +bool Plater::priv::warnings_dialog() +{ + if (current_warnings.empty()) + return true; + std::string text = _u8L("There are active warnings concerning sliced models:") + "\n"; + bool empt = true; + for (auto const& it : current_warnings) { + int next_n = it.first.message.find_first_of('\n', 0); + text += "\n"; + if (next_n != std::string::npos) + text += it.first.message.substr(0, next_n); + else + text += it.first.message; + } + //text += "\n\nDo you still wish to export?"; + wxMessageDialog msg_wingow(this->q, text, wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK); + const auto res = msg_wingow.ShowModal(); + return res == wxID_OK; -void Plater::priv::on_process_completed(wxCommandEvent &evt) +} +void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) { // Stop the background task, wait until the thread goes into the "Idle" state. // At this point of time the thread should be either finished or canceled, @@ -3720,28 +3710,35 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) this->statusbar()->reset_cancel_callback(); this->statusbar()->stop_busy(); - const bool canceled = evt.GetInt() < 0; - const bool error = evt.GetInt() == 0; - const bool success = evt.GetInt() > 0; // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. this->background_process.reset_export(); - - if (error) { - wxString message = evt.GetString(); - if (message.IsEmpty()) - message = _(L("Export failed")); - if (q->m_tracking_popup_menu) - // We don't want to pop-up a message box when tracking a pop-up menu. - // We postpone the error message instead. - q->m_tracking_popup_menu_error_message = message; - else - show_error(q, message); - this->statusbar()->set_status_text(message); + // This bool stops showing export finished notification even when process_completed_with_error is false + bool has_error = false; + if (evt.error()) { + std::pair<std::string, bool> message = evt.format_error_message(); + if (evt.critical_error()) { + if (q->m_tracking_popup_menu) + // We don't want to pop-up a message box when tracking a pop-up menu. + // We postpone the error message instead. + q->m_tracking_popup_menu_error_message = message.first; + else + show_error(q, message.first, message.second); + } else + notification_manager->push_slicing_error_notification(message.first); + this->statusbar()->set_status_text(from_u8(message.first)); + if (evt.invalidate_plater()) + { + const wxString invalid_str = _L("Invalid data"); + for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport }) + sidebar->set_btn_label(btn, invalid_str); + process_completed_with_error = true; + } + has_error = true; } - if (canceled) - this->statusbar()->set_status_text(_(L("Cancelled"))); + if (evt.cancelled()) + this->statusbar()->set_status_text(_L("Cancelled")); - this->sidebar->show_sliced_info_sizer(success); + this->sidebar->show_sliced_info_sizer(evt.success()); // This updates the "Slice now", "Export G-code", "Arrange" buttons status. // Namely, it refreshes the "Out of print bed" property of all the ModelObjects, and it enables @@ -3762,15 +3759,23 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) default: break; } - - if (canceled) { + if (evt.cancelled()) { if (wxGetApp().get_mode() == comSimple) sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); show_action_buttons(true); + } else { + if(wxGetApp().get_mode() == comSimple) { + show_action_buttons(false); + } + // If writing to removable drive was scheduled, show notification with eject button + if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) { + show_action_buttons(false); + notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, true); + wxGetApp().removable_drive_manager()->set_exporting_finished(true); + }else if (exporting_status == ExportingStatus::EXPORTING_TO_LOCAL && !has_error) + notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, false); } - else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple) - show_action_buttons(false); - this->writing_to_removable_device = false; + exporting_status = ExportingStatus::NOT_EXPORTING; } void Plater::priv::on_layer_editing_toggled(bool enable) @@ -3798,12 +3803,11 @@ void Plater::priv::on_action_split_volumes(SimpleEvent&) void Plater::priv::on_action_layersediting(SimpleEvent&) { view3D->enable_layers_editing(!view3D->is_layers_editing_enabled()); + notification_manager->set_move_from_overlay(view3D->is_layers_editing_enabled()); } void Plater::priv::on_object_select(SimpleEvent& evt) { -// this->take_snapshot(_(L("Object Selection"))); - wxGetApp().obj_list()->update_selections(); selection_changed(); } @@ -3830,6 +3834,8 @@ void Plater::priv::on_right_click(RBtnEvent& evt) if (evt.data.second) return; + int menu_item_convert_unit_position = 11; + if (printer_technology == ptSLA) menu = &sla_object_menu; else @@ -3839,8 +3845,11 @@ void Plater::priv::on_right_click(RBtnEvent& evt) get_selection().is_single_full_object() || get_selection().is_multiple_full_instance(); menu = is_some_full_instances ? &object_menu : &part_menu; + if (!is_some_full_instances) + menu_item_convert_unit_position = 2; } + sidebar->obj_list()->append_menu_item_convert_unit(menu, menu_item_convert_unit_position); sidebar->obj_list()->append_menu_item_settings(menu); if (printer_technology != ptSLA) @@ -3853,7 +3862,7 @@ void Plater::priv::on_right_click(RBtnEvent& evt) */ const MenuIdentifier id = printer_technology == ptSLA ? miObjectSLA : miObjectFFF; if (wxGetApp().get_mode() == comSimple) { - if (menu->FindItem(_(L("Add instance"))) != wxNOT_FOUND) + if (menu->FindItem(_L("Add instance")) != wxNOT_FOUND) { /* Detach an items from the menu, but don't delete them * so that they can be added back later @@ -3865,7 +3874,7 @@ void Plater::priv::on_right_click(RBtnEvent& evt) } } else { - if (menu->FindItem(_(L("Add instance"))) == wxNOT_FOUND) + if (menu->FindItem(_L("Add instance")) == wxNOT_FOUND) { // Prepend items to the menu, if those aren't not there menu->Prepend(items_set_number_of_copies[id]); @@ -3939,7 +3948,6 @@ bool Plater::priv::init_object_menu() return true; } -#if ENABLE_THUMBNAIL_GENERATOR 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) { view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background); @@ -3957,7 +3965,6 @@ void Plater::priv::generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& thumbnails.pop_back(); } } -#endif // ENABLE_THUMBNAIL_GENERATOR void Plater::priv::msw_rescale_object_menu() { @@ -3996,21 +4003,23 @@ void Plater::priv::set_project_filename(const wxString& filename) bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/) { if (is_part) { - append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), + append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"), [this](wxCommandEvent&) { q->remove_selected(); }, "delete", nullptr, [this]() { return can_delete(); }, q); - append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")), + append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected volumes from disk"), [this](wxCommandEvent&) { q->reload_from_disk(); }, "", menu, [this]() { return can_reload_from_disk(); }, q); sidebar->obj_list()->append_menu_item_export_stl(menu); } else { - wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _(L("Add instance")) + "\t+", _(L("Add one more instance of the selected object")), + wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _L("Add instance") + "\t+", _L("Add one more instance of the selected object"), [this](wxCommandEvent&) { q->increase_instances(); }, "add_copies", nullptr, [this]() { return can_increase_instances(); }, q); - wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _(L("Remove instance")) + "\t-", _(L("Remove one instance of the selected object")), + wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _L("Remove instance") + "\t-", _L("Remove one instance of the selected object"), [this](wxCommandEvent&) { q->decrease_instances(); }, "remove_copies", nullptr, [this]() { return can_decrease_instances(); }, q); - 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")), + 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"), [this](wxCommandEvent&) { q->set_number_of_copies(); }, "number_of_copies", nullptr, [this]() { return can_increase_instances(); }, q); + 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"), + [this](wxCommandEvent&) { q->fill_bed_with_instances(); }, "", nullptr, [this]() { return can_increase_instances(); }, q); items_increase.push_back(item_increase); @@ -4018,7 +4027,7 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ items_set_number_of_copies.push_back(item_set_number_of_copies); // Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake. - append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), + append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"), [this](wxCommandEvent&) { q->remove_selected(); }, "delete", nullptr, [this]() { return can_delete(); }, q); menu->AppendSeparator(); @@ -4028,10 +4037,10 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ wxMenuItem* menu_item_printable = sidebar->obj_list()->append_menu_item_printable(menu, q); menu->AppendSeparator(); - append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected object from disk")), + append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected object from disk"), [this](wxCommandEvent&) { reload_from_disk(); }, "", nullptr, [this]() { return can_reload_from_disk(); }, q); - append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, _(L("Export the selected object as STL file")), + append_menu_item(menu, wxID_ANY, _L("Export as STL") + dots, _L("Export the selected object as STL file"), [this](wxCommandEvent&) { q->export_stl(false, true); }, "", nullptr, [this]() { const Selection& selection = get_selection(); @@ -4040,6 +4049,9 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ menu->AppendSeparator(); + // "Scale to print volume" makes a sense just for whole object + sidebar->obj_list()->append_menu_item_scale_selection_to_fit_print_volume(menu); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { const Selection& selection = get_selection(); int instance_idx = selection.get_instance_idx(); @@ -4054,20 +4066,18 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); - sidebar->obj_list()->append_menu_item_scale_selection_to_fit_print_volume(menu); - wxMenu* mirror_menu = new wxMenu(); if (mirror_menu == nullptr) return false; - append_menu_item(mirror_menu, wxID_ANY, _(L("Along X axis")), _(L("Mirror the selected object along the X axis")), + append_menu_item(mirror_menu, wxID_ANY, _L("Along X axis"), _L("Mirror the selected object along the X axis"), [this](wxCommandEvent&) { mirror(X); }, "mark_X", menu); - append_menu_item(mirror_menu, wxID_ANY, _(L("Along Y axis")), _(L("Mirror the selected object along the Y axis")), + append_menu_item(mirror_menu, wxID_ANY, _L("Along Y axis"), _L("Mirror the selected object along the Y axis"), [this](wxCommandEvent&) { mirror(Y); }, "mark_Y", menu); - append_menu_item(mirror_menu, wxID_ANY, _(L("Along Z axis")), _(L("Mirror the selected object along the Z axis")), + append_menu_item(mirror_menu, wxID_ANY, _L("Along Z axis"), _L("Mirror the selected object along the Z axis"), [this](wxCommandEvent&) { mirror(Z); }, "mark_Z", menu); - append_submenu(menu, mirror_menu, wxID_ANY, _(L("Mirror")), _(L("Mirror the selected object")), "", + append_submenu(menu, mirror_menu, wxID_ANY, _L("Mirror"), _L("Mirror the selected object"), "", [this]() { return can_mirror(); }, q); return true; @@ -4079,12 +4089,12 @@ bool Plater::priv::complit_init_object_menu() if (split_menu == nullptr) return false; - append_menu_item(split_menu, wxID_ANY, _(L("To objects")), _(L("Split the selected object into individual objects")), + append_menu_item(split_menu, wxID_ANY, _L("To objects"), _L("Split the selected object into individual objects"), [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL", &object_menu, [this]() { return can_split(); }, q); - append_menu_item(split_menu, wxID_ANY, _(L("To parts")), _(L("Split the selected object into individual sub-parts")), + append_menu_item(split_menu, wxID_ANY, _L("To parts"), _L("Split the selected object into individual sub-parts"), [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", &object_menu, [this]() { return can_split(); }, q); - append_submenu(&object_menu, split_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object")), "", + append_submenu(&object_menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "", [this]() { return can_split() && wxGetApp().get_mode() > comSimple; }, q); object_menu.AppendSeparator(); @@ -4099,21 +4109,25 @@ bool Plater::priv::complit_init_object_menu() bool Plater::priv::complit_init_sla_object_menu() { - append_menu_item(&sla_object_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object into individual objects")), + append_menu_item(&sla_object_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual objects"), [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL", nullptr, [this]() { return can_split(); }, q); sla_object_menu.AppendSeparator(); // Add the automatic rotation sub-menu - append_menu_item(&sla_object_menu, wxID_ANY, _(L("Optimize orientation")), _(L("Optimize the rotation of the object for better print results.")), - [this](wxCommandEvent&) { sla_optimize_rotation(); }); + append_menu_item( + &sla_object_menu, wxID_ANY, _(L("Optimize orientation")), + _(L("Optimize the rotation of the object for better print results.")), + [this](wxCommandEvent &) { + m_ui_jobs.optimize_rotation(); + }); return true; } bool Plater::priv::complit_init_part_menu() { - append_menu_item(&part_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object into individual sub-parts")), + append_menu_item(&part_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual sub-parts"), [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", nullptr, [this]() { return can_split(); }, q); part_menu.AppendSeparator(); @@ -4137,8 +4151,29 @@ GLCanvas3D* Plater::priv::get_current_canvas3D() return (current_panel == view3D) ? view3D->get_canvas3d() : ((current_panel == preview) ? preview->get_canvas3d() : nullptr); } +void Plater::priv::unbind_canvas_event_handlers() +{ + if (view3D != nullptr) + view3D->get_canvas3d()->unbind_event_handlers(); + + if (preview != nullptr) + preview->get_canvas3d()->unbind_event_handlers(); +} + +void Plater::priv::reset_canvas_volumes() +{ + if (view3D != nullptr) + view3D->get_canvas3d()->reset_volumes(); + + if (preview != nullptr) + preview->get_canvas3d()->reset_volumes(); +} + bool Plater::priv::init_view_toolbar() { + if (wxGetApp().is_gcode_viewer()) + return true; + if (view_toolbar.get_items_count() > 0) // already initialized return true; @@ -4182,6 +4217,70 @@ bool Plater::priv::init_view_toolbar() return true; } +bool Plater::priv::init_collapse_toolbar() +{ + if (wxGetApp().is_gcode_viewer()) + return true; + + if (collapse_toolbar.get_items_count() > 0) + // already initialized + return true; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!collapse_toolbar.init(background_data)) + return false; + + collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); + collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + collapse_toolbar.set_border(5.0f); + collapse_toolbar.set_separator_size(5); + collapse_toolbar.set_gap_size(2); + + GLToolbarItem::Data item; + + item.name = "collapse_sidebar"; + item.icon_filename = "collapse.svg"; + item.sprite_id = 0; + item.left.action_callback = []() { + wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed()); + }; + + if (!collapse_toolbar.add_item(item)) + return false; + + // Now "collapse" sidebar to current state. This is done so the tooltip + // is updated before the toolbar is first used. + wxGetApp().plater()->collapse_sidebar(wxGetApp().plater()->is_sidebar_collapsed()); + return true; +} + +void Plater::priv::update_preview_bottom_toolbar() +{ + preview->update_bottom_toolbar(); +} + +void Plater::priv::update_preview_moves_slider() +{ + preview->update_moves_slider(); +} + +void Plater::priv::enable_preview_moves_slider(bool enable) +{ + preview->enable_moves_slider(enable); +} + +void Plater::priv::reset_gcode_toolpaths() +{ + preview->get_canvas3d()->reset_gcode_toolpaths(); +} + bool Plater::priv::can_set_instance_to_object() const { const int obj_idx = get_selected_object_idx(); @@ -4256,11 +4355,10 @@ bool Plater::priv::can_reload_from_disk() const return !paths.empty(); } -void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) +void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { - bool new_shape = bed.set_shape(shape, custom_texture, custom_model); - if (new_shape) - { + bool new_shape = bed.set_shape(shape, custom_texture, custom_model, force_as_custom); + if (new_shape) { if (view3D) view3D->bed_shape_changed(); if (preview) preview->bed_shape_changed(); } @@ -4336,8 +4434,10 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const this->ready_to_slice = ready_to_slice; wxWindowUpdateLocker noUpdater(sidebar); - const auto prin_host_opt = config->option<ConfigOptionString>("print_host"); - const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty(); + + DynamicPrintConfig* selected_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); + const auto print_host_opt = selected_printer_config ? selected_printer_config->option<ConfigOptionString>("print_host") : nullptr; + const bool send_gcode_shown = print_host_opt != nullptr && !print_host_opt->value.empty(); // when a background processing is ON, export_btn and/or send_btn are showing if (wxGetApp().app_config->get("background_processing") == "1") @@ -4346,8 +4446,8 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const if (sidebar->show_reslice(false) | sidebar->show_export(true) | sidebar->show_send(send_gcode_shown) | - sidebar->show_export_removable(removable_media_status.has_removable_drives) | - sidebar->show_disconnect(removable_media_status.has_eject)) + sidebar->show_export_removable(removable_media_status.has_removable_drives)) +// sidebar->show_eject(removable_media_status.has_eject)) sidebar->Layout(); } else @@ -4358,8 +4458,8 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const if (sidebar->show_reslice(ready_to_slice) | sidebar->show_export(!ready_to_slice) | sidebar->show_send(send_gcode_shown && !ready_to_slice) | - sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives) | - sidebar->show_disconnect(!ready_to_slice && removable_media_status.has_eject)) + sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives)) +// sidebar->show_eject(!ready_to_slice && removable_media_status.has_eject)) sidebar->Layout(); } } @@ -4468,8 +4568,8 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator if (printer_technology_changed) { // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type. std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA"; - if (! wxGetApp().check_unsaved_changes(from_u8((boost::format(_utf8( - L("%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).str()))) + if (! wxGetApp().check_unsaved_changes(format_wxstr(_L( + "%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))) // Don't switch the profiles. return; } @@ -4594,6 +4694,34 @@ void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bo BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); } +void Plater::priv::bring_instance_forward() const +{ +#ifdef __APPLE__ + wxGetApp().other_instance_message_handler()->bring_instance_forward(); + return; +#endif //__APPLE__ + if (main_frame == nullptr) { + BOOST_LOG_TRIVIAL(debug) << "Couldnt bring instance forward - mainframe is null"; + return; + } + BOOST_LOG_TRIVIAL(debug) << "prusaslicer window going forward"; + //this code maximize app window on Fedora + { + main_frame->Iconize(false); + if (main_frame->IsMaximized()) + main_frame->Maximize(true); + else + main_frame->Maximize(false); + } + //this code maximize window on Ubuntu + { + main_frame->Restore(); + wxGetApp().GetTopWindow()->SetFocus(); // focus on my window + wxGetApp().GetTopWindow()->Raise(); // bring window to front + wxGetApp().GetTopWindow()->Show(true); // show the window + } +} + void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const { switch (btn_type) @@ -4607,7 +4735,8 @@ void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& lab // Plater / Public Plater::Plater(wxWindow *parent, MainFrame *main_frame) - : wxPanel(parent), p(new priv(this, main_frame)) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxGetApp().get_min_size()) + , p(new priv(this, main_frame)) { // Initialization performed in the private c-tor } @@ -4644,7 +4773,7 @@ void Plater::load_project(const wxString& filename) return; // Take the Undo / Redo snapshot. - Plater::TakeSnapshot snapshot(this, _(L("Load Project")) + ": " + wxString::FromUTF8(into_path(filename).stem().string().c_str())); + Plater::TakeSnapshot snapshot(this, _L("Load Project") + ": " + wxString::FromUTF8(into_path(filename).stem().string().c_str())); p->reset(); @@ -4658,7 +4787,7 @@ void Plater::load_project(const wxString& filename) p->set_project_filename(filename); } -void Plater::add_model() +void Plater::add_model(bool imperial_units/* = false*/) { wxArrayString input_files; wxGetApp().import_model(this, input_files); @@ -4672,11 +4801,11 @@ void Plater::add_model() wxString snapshot_label; assert(! paths.empty()); if (paths.size() == 1) { - snapshot_label = _(L("Import Object")); + snapshot_label = _L("Import Object"); snapshot_label += ": "; snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); } else { - snapshot_label = _(L("Import Objects")); + snapshot_label = _L("Import Objects"); snapshot_label += ": "; snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); for (size_t i = 1; i < paths.size(); ++ i) { @@ -4686,7 +4815,12 @@ void Plater::add_model() } Plater::TakeSnapshot snapshot(this, snapshot_label); - load_files(paths, true, false); + load_files(paths, true, false, imperial_units); +} + +void Plater::import_sl1_archive() +{ + p->m_ui_jobs.import_sla_arch(); } void Plater::extract_config_from_project() @@ -4702,23 +4836,256 @@ void Plater::extract_config_from_project() load_files(input_paths, false, true); } -std::vector<size_t> Plater::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config) { return p->load_files(input_files, load_model, load_config); } +void Plater::load_gcode() +{ + // Ask user for a gcode file name. + wxString input_file; + wxGetApp().load_gcode(this, input_file); + // And finally load the gcode file. + load_gcode(input_file); +} + +void Plater::load_gcode(const wxString& filename) +{ + if (! is_gcode_file(into_u8(filename)) || m_last_loaded_gcode == filename) + return; + + m_last_loaded_gcode = filename; + + // cleanup view before to start loading/processing + p->gcode_result.reset(); + reset_gcode_toolpaths(); + p->preview->reload_print(false); + p->get_current_canvas3D()->render(); + + wxBusyCursor wait; + + // process gcode + GCodeProcessor processor; + processor.enable_producers(true); + processor.process_file(filename.ToUTF8().data(), false); + p->gcode_result = std::move(processor.extract_result()); + + // show results + p->preview->reload_print(false); + p->preview->get_canvas3d()->zoom_to_gcode(); + + if (p->preview->get_canvas3d()->get_gcode_layers_zs().empty()) { + wxMessageDialog(this, _L("The selected file") + ":\n" + filename + "\n" + _L("does not contain valid gcode."), + wxString(GCODEVIEWER_APP_NAME) + " - " + _L("Error while loading .gcode file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); + set_project_filename(wxEmptyString); + } + else + set_project_filename(filename); +} + +void Plater::reload_gcode_from_disk() +{ + wxString filename(m_last_loaded_gcode); + m_last_loaded_gcode.clear(); + load_gcode(filename); +} + +void Plater::refresh_print() +{ + p->preview->refresh_print(); +} + +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); } // To be called when providing a list of files to the GUI slic3r on command line. -std::vector<size_t> Plater::load_files(const std::vector<std::string>& input_files, bool load_model, bool load_config) +std::vector<size_t> Plater::load_files(const std::vector<std::string>& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { std::vector<fs::path> paths; paths.reserve(input_files.size()); for (const std::string& path : input_files) paths.emplace_back(path); - return p->load_files(paths, load_model, load_config); + return p->load_files(paths, load_model, load_config, imperial_units); +} + +#if ENABLE_DRAG_AND_DROP_FIX +enum class LoadType : unsigned char +{ + Unknown, + OpenProject, + LoadGeometry, + LoadConfig +}; + +class ProjectDropDialog : public DPIDialog +{ + wxRadioBox* m_action{ nullptr }; +public: + ProjectDropDialog(const std::string& filename); + + int get_action() const { return m_action->GetSelection() + 1; } + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; +}; + +ProjectDropDialog::ProjectDropDialog(const std::string& filename) + : DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, + from_u8((boost::format(_utf8(L("%s - Drop project file"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition, + wxDefaultSize, wxDEFAULT_DIALOG_STYLE) +{ + SetFont(wxGetApp().normal_font()); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + + const wxString choices[] = { _L("Open as project"), + _L("Import geometry only"), + _L("Import config only") }; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, + _L("Select an action to apply to the file") + ": " + from_u8(filename)), 0, wxEXPAND | wxALL, 10); + m_action = new wxRadioBox(this, wxID_ANY, _L("Action"), wxDefaultPosition, wxDefaultSize, + WXSIZEOF(choices), choices, 0, wxRA_SPECIFY_ROWS); + int action = std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), + static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)) - 1; + m_action->SetSelection(action); + main_sizer->Add(m_action, 1, wxEXPAND | wxRIGHT | wxLEFT, 10); + + wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL); + wxCheckBox* check = new wxCheckBox(this, wxID_ANY, _L("Don't show again")); + check->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& evt) { + wxGetApp().app_config->set("show_drop_project_dialog", evt.IsChecked() ? "0" : "1"); + }); + + bottom_sizer->Add(check, 0, wxEXPAND | wxRIGHT, 5); + bottom_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxLEFT, 5); + main_sizer->Add(bottom_sizer, 0, wxEXPAND | wxALL, 10); + + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); +} + +void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int em = em_unit(); + SetMinSize(wxSize(65 * em, 30 * em)); + Fit(); + Refresh(); +} + +bool Plater::load_files(const wxArrayString& filenames) +{ + const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", std::regex::icase); + const std::regex pattern_gcode_drop(".*[.](gcode|g)", std::regex::icase); + + std::vector<fs::path> paths; + + // gcode viewer section + if (wxGetApp().is_gcode_viewer()) { + for (const auto& filename : filenames) { + fs::path path(into_path(filename)); + if (std::regex_match(path.string(), pattern_gcode_drop)) + paths.push_back(std::move(path)); + } + + if (paths.size() > 1) { + wxMessageDialog(static_cast<wxWindow*>(this), _L("You can open only one .gcode file at a time."), + wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); + return false; + } + else if (paths.size() == 1) { + load_gcode(from_path(paths.front())); + return true; + } + return false; + } + + // editor section + for (const auto& filename : filenames) { + fs::path path(into_path(filename)); + if (std::regex_match(path.string(), pattern_drop)) + paths.push_back(std::move(path)); + else if (std::regex_match(path.string(), pattern_gcode_drop)) + start_new_gcodeviewer(&filename); + else + continue; + } + if (paths.empty()) + // Likely all paths processed were gcodes, for which a G-code viewer instance has hopefully been started. + return false; + + // searches for project files + for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) { + std::string filename = (*it).filename().string(); + if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) { + LoadType load_type = LoadType::Unknown; + if (!model().objects.empty()) { + if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") { + ProjectDropDialog dlg(filename); + if (dlg.ShowModal() == wxID_OK) { + int choice = dlg.get_action(); + load_type = static_cast<LoadType>(choice); + wxGetApp().app_config->set("drop_project_action", std::to_string(choice)); + } + } + else + load_type = static_cast<LoadType>(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), + static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig))); + } + else + load_type = LoadType::OpenProject; + + if (load_type == LoadType::Unknown) + return false; + + switch (load_type) { + case LoadType::OpenProject: { + load_project(from_path(*it)); + break; + } + case LoadType::LoadGeometry: { + Plater::TakeSnapshot snapshot(this, _L("Import Object")); + std::vector<fs::path> in_paths; + in_paths.emplace_back(*it); + load_files(in_paths, true, false); + break; + } + case LoadType::LoadConfig: { + std::vector<fs::path> in_paths; + in_paths.emplace_back(*it); + load_files(in_paths, false, true); + break; + } + } + + return true; + } + } + + // other files + wxString snapshot_label; + assert(!paths.empty()); + if (paths.size() == 1) { + snapshot_label = _L("Load File"); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + } + else { + snapshot_label = _L("Load Files"); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + for (size_t i = 1; i < paths.size(); ++i) { + snapshot_label += ", "; + snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); + } + } + Plater::TakeSnapshot snapshot(this, snapshot_label); + load_files(paths); + + return true; } +#endif // ENABLE_DRAG_AND_DROP_FIX void Plater::update() { p->update(); } void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); } -void Plater::update_ui_from_settings() { p->update_ui_from_settings(); } +void Plater::update_ui_from_settings(bool apply_free_camera_correction) { p->update_ui_from_settings(apply_free_camera_correction); } void Plater::select_view(const std::string& direction) { p->select_view(direction); } @@ -4731,6 +5098,11 @@ bool Plater::is_view3D_shown() const { return p->is_view3D_shown(); } bool Plater::are_view3D_labels_shown() const { return p->are_view3D_labels_shown(); } void Plater::show_view3D_labels(bool show) { p->show_view3D_labels(show); } +bool Plater::is_sidebar_collapsed() const { return p->is_sidebar_collapsed(); } +void Plater::collapse_sidebar(bool show) { p->collapse_sidebar(show); } + +bool Plater::is_view3D_layers_editing_enabled() const { return p->is_view3D_layers_editing_enabled(); } + void Plater::select_all() { p->select_all(); } void Plater::deselect_all() { p->deselect_all(); } @@ -4738,7 +5110,7 @@ void Plater::remove(size_t obj_idx) { p->remove(obj_idx); } void Plater::reset() { p->reset(); } void Plater::reset_with_confirm() { - if (wxMessageDialog((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) + 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) reset(); } @@ -4746,7 +5118,7 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo void Plater::remove_selected() { - Plater::TakeSnapshot snapshot(this, _(L("Delete Selected Objects"))); + Plater::TakeSnapshot snapshot(this, _L("Delete Selected Objects")); this->p->view3D->delete_selected(); } @@ -4754,7 +5126,7 @@ void Plater::increase_instances(size_t num) { if (! can_increase_instances()) { return; } - Plater::TakeSnapshot snapshot(this, _(L("Increase Instances"))); + Plater::TakeSnapshot snapshot(this, _L("Increase Instances")); int obj_idx = p->get_selected_object_idx(); @@ -4771,17 +5143,16 @@ void Plater::increase_instances(size_t num) // p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec)); } - sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num); - if (p->get_config("autocenter") == "1") - p->arrange(); + arrange(); p->update(); p->get_selection().add_instance(obj_idx, (int)model_object->instances.size() - 1); - p->selection_changed(); + sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num); + p->selection_changed(); this->p->schedule_background_process(); } @@ -4789,7 +5160,7 @@ void Plater::decrease_instances(size_t num) { if (! can_decrease_instances()) { return; } - Plater::TakeSnapshot snapshot(this, _(L("Decrease Instances"))); + Plater::TakeSnapshot snapshot(this, _L("Decrease Instances")); int obj_idx = p->get_selected_object_idx(); @@ -4820,12 +5191,12 @@ void Plater::set_number_of_copies(/*size_t num*/) ModelObject* model_object = p->model.objects[obj_idx]; - const int num = wxGetNumberFromUser( " ", _(L("Enter the number of copies:")), - _(L("Copies of the selected object")), model_object->instances.size(), 0, 1000, this ); + const int num = wxGetNumberFromUser( " ", _L("Enter the number of copies:"), + _L("Copies of the selected object"), model_object->instances.size(), 0, 1000, this ); if (num < 0) return; - Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num)); + Plater::TakeSnapshot snapshot(this, wxString::Format(_L("Set numbers of copies to %d"), num)); int diff = num - (int)model_object->instances.size(); if (diff > 0) @@ -4834,6 +5205,11 @@ void Plater::set_number_of_copies(/*size_t num*/) decrease_instances(-diff); } +void Plater::fill_bed_with_instances() +{ + p->m_ui_jobs.fill_bed(); +} + bool Plater::is_selection_empty() const { return p->get_selection().is_empty() || p->get_selection().is_wipe_tower(); @@ -4844,6 +5220,37 @@ void Plater::scale_selection_to_fit_print_volume() p->scale_selection_to_fit_print_volume(); } +void Plater::convert_unit(bool from_imperial_unit) +{ + std::vector<int> obj_idxs, volume_idxs; + wxGetApp().obj_list()->get_selection_indexes(obj_idxs, volume_idxs); + if (obj_idxs.empty() && volume_idxs.empty()) + return; + + TakeSnapshot snapshot(this, from_imperial_unit ? _L("Convert from imperial units") : _L("Revert conversion from imperial units")); + wxBusyCursor wait; + + ModelObjectPtrs objects; + for (int obj_idx : obj_idxs) { + ModelObject *object = p->model.objects[obj_idx]; + object->convert_units(objects, from_imperial_unit, volume_idxs); + remove(obj_idx); + } + p->load_model_objects(objects); + + Selection& selection = p->view3D->get_canvas3d()->get_selection(); + size_t last_obj_idx = p->model.objects.size() - 1; + + if (volume_idxs.empty()) { + for (size_t i = 0; i < objects.size(); ++i) + selection.add_object((unsigned int)(last_obj_idx - i), i == 0); + } + else { + for (int vol_idx : volume_idxs) + selection.add_volume(last_obj_idx, vol_idx, 0, false); + } +} + void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower) { wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); @@ -4855,7 +5262,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe return; } - Plater::TakeSnapshot snapshot(this, _(L("Cut by Plane"))); + Plater::TakeSnapshot snapshot(this, _L("Cut by Plane")); wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); @@ -4876,6 +5283,9 @@ void Plater::export_gcode(bool prefer_removable) if (p->model.objects.empty()) return; + if (p->process_completed_with_error) + return; + // If possible, remove accents from accented latin characters. // This function is useful for generating file names to be processed by legacy firmwares. fs::path default_output_file; @@ -4886,9 +5296,12 @@ void Plater::export_gcode(bool prefer_removable) if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) return; default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf"))); - } - catch (const std::exception &ex) { - show_error(this, ex.what()); + } catch (const Slic3r::PlaceholderParserError &ex) { + // Show the error with monospaced font. + show_error(this, ex.what(), true); + return; + } catch (const std::exception &ex) { + show_error(this, ex.what(), false); return; } default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); @@ -4906,7 +5319,7 @@ void Plater::export_gcode(bool prefer_removable) fs::path output_path; { - wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save SL1 file as:")), + wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 file as:"), start_dir, from_path(default_output_file.filename()), GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()), @@ -4918,12 +5331,16 @@ void Plater::export_gcode(bool prefer_removable) if (! output_path.empty()) { bool path_on_removable_media = removable_drive_manager.set_and_verify_last_save_path(output_path.string()); + p->notification_manager->new_export_began(path_on_removable_media); + p->exporting_status = path_on_removable_media ? ExportingStatus::EXPORTING_TO_REMOVABLE : ExportingStatus::EXPORTING_TO_LOCAL; + p->last_output_path = output_path.string(); + p->last_output_dir_path = output_path.parent_path().string(); p->export_gcode(output_path, path_on_removable_media, PrintHostJob()); // Storing a path to AppConfig either as path to removable media or a path to internal media. // 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 // while the dialog was open. appconfig.update_last_output_dir(output_path.parent_path().string(), path_on_removable_media); - p->writing_to_removable_device = path_on_removable_media; + } } @@ -5013,25 +5430,33 @@ void Plater::export_stl(bool extended, bool selection_only) ? Transform3d::Identity() : object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); + TriangleMesh inst_mesh; + if (has_pad_mesh) { TriangleMesh inst_pad_mesh = pad_mesh; inst_pad_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_pad_mesh); + inst_mesh.merge(inst_pad_mesh); } if (has_supports_mesh) { TriangleMesh inst_supports_mesh = supports_mesh; inst_supports_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_supports_mesh); + inst_mesh.merge(inst_supports_mesh); } TriangleMesh inst_object_mesh = object->get_mesh_to_print(); inst_object_mesh.transform(mesh_trafo_inv); inst_object_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_object_mesh); + inst_mesh.merge(inst_object_mesh); + + // ensure that the instance lays on the bed + inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min[2]); + + // merge instance with global mesh + mesh.merge(inst_mesh); if (one_inst_only) break; @@ -5041,7 +5466,7 @@ void Plater::export_stl(bool extended, bool selection_only) } Slic3r::store_stl(path_u8.c_str(), &mesh, true); - p->statusbar()->set_status_text(from_u8((boost::format(_utf8(L("STL file exported to %s"))) % path).str())); + p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path)); } void Plater::export_amf() @@ -5058,10 +5483,10 @@ void Plater::export_amf() bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) { // Success - p->statusbar()->set_status_text(from_u8((boost::format(_utf8(L("AMF file exported to %s"))) % path).str())); + p->statusbar()->set_status_text(format_wxstr(_L("AMF file exported to %s"), path)); } else { // Failure - p->statusbar()->set_status_text(from_u8((boost::format(_utf8(L("Error exporting AMF file %s"))) % path).str())); + p->statusbar()->set_status_text(format_wxstr(_L("Error exporting AMF file %s"), path)); } } @@ -5086,20 +5511,16 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) const std::string path_u8 = into_u8(path); wxBusyCursor wait; bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; -#if ENABLE_THUMBNAIL_GENERATOR ThumbnailData thumbnail_data; p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true); if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) { -#else - if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) { -#endif // ENABLE_THUMBNAIL_GENERATOR // Success - p->statusbar()->set_status_text(from_u8((boost::format(_utf8(L("3MF file exported to %s"))) % path).str())); + p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); p->set_project_filename(path); } else { // Failure - p->statusbar()->set_status_text(from_u8((boost::format(_utf8(L("Error exporting 3MF file %s"))) % path).str())); + p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); } } @@ -5131,13 +5552,12 @@ void Plater::export_toolpaths_to_obj() const p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str()); } -void Plater::hollow() -{ - p->hollow(); -} - void Plater::reslice() { + // There is "invalid data" button instead "slice now" + if (p->process_completed_with_error) + return; + // Stop arrange and (or) optimize rotation tasks. this->stop_jobs(); @@ -5160,21 +5580,31 @@ void Plater::reslice() if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) return; + bool clean_gcode_toolpaths = true; if (p->background_process.running()) { if (wxGetApp().get_mode() == comSimple) - p->sidebar->set_btn_label(ActionButtonType::abReslice, _(L("Slicing")) + dots); + p->sidebar->set_btn_label(ActionButtonType::abReslice, _L("Slicing") + dots); else { - p->sidebar->set_btn_label(ActionButtonType::abReslice, _(L("Slice now"))); + p->sidebar->set_btn_label(ActionButtonType::abReslice, _L("Slice now")); p->show_action_buttons(false); } } else if (!p->background_process.empty() && !p->background_process.idle()) p->show_action_buttons(true); + else + clean_gcode_toolpaths = false; + + if (clean_gcode_toolpaths) + reset_gcode_toolpaths(); +#if ENABLE_PREVIEW_TYPE_CHANGE + p->preview->reload_print(!clean_gcode_toolpaths); +#else // update type of preview - p->preview->update_view_type(true); + p->preview->update_view_type(!clean_gcode_toolpaths); +#endif // ENABLE_PREVIEW_TYPE_CHANGE } void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages) @@ -5215,10 +5645,14 @@ void Plater::reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject & void Plater::send_gcode() { - if (p->model.objects.empty()) { return; } + // if physical_printer is selected, send gcode for this printer + DynamicPrintConfig* physical_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); + if (! physical_printer_config || p->model.objects.empty()) + return; - PrintHostJob upload_job(p->config); - if (upload_job.empty()) { return; } + PrintHostJob upload_job(physical_printer_config); + if (upload_job.empty()) + return; // Obtain default output path fs::path default_output_file; @@ -5229,18 +5663,28 @@ void Plater::send_gcode() if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) return; default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf"))); - } - catch (const std::exception &ex) { - show_error(this, ex.what()); + } catch (const Slic3r::PlaceholderParserError& ex) { + // Show the error with monospaced font. + show_error(this, ex.what(), true); + return; + } catch (const std::exception& ex) { + show_error(this, ex.what(), false); return; } default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); - PrintHostSendDialog dlg(default_output_file, upload_job.printhost->can_start_print()); + // Repetier specific: Query the server for the list of file groups. + wxArrayString groups; + { + wxBusyCursor wait; + upload_job.printhost->get_groups(groups); + } + + PrintHostSendDialog dlg(default_output_file, upload_job.printhost->can_start_print(), groups); if (dlg.ShowModal() == wxID_OK) { upload_job.upload_data.upload_path = dlg.filename(); upload_job.upload_data.start_print = dlg.start_print(); - + upload_job.upload_data.group = dlg.group(); p->export_gcode(fs::path(), false, std::move(upload_job)); } } @@ -5304,6 +5748,18 @@ void Plater::undo_redo_topmost_string_getter(const bool is_undo, std::string& ou out_text = ""; } +bool Plater::search_string_getter(int idx, const char** label, const char** tooltip) +{ + const Search::OptionsSearcher& search_list = p->sidebar->get_searcher(); + + if (0 <= idx && (size_t)idx < search_list.size()) { + search_list[idx].get_marked_label_and_tooltip(label, tooltip); + return true; + } + + return false; +} + void Plater::on_extruders_change(size_t num_extruders) { auto& choices = sidebar().combos_filament(); @@ -5316,12 +5772,12 @@ void Plater::on_extruders_change(size_t num_extruders) size_t i = choices.size(); while ( i < num_extruders ) { - PresetComboBox* choice/*{ nullptr }*/; + PlaterPresetComboBox* choice/*{ nullptr }*/; sidebar().init_filament_combo(&choice, i); choices.push_back(choice); // initialize selection - wxGetApp().preset_bundle->update_plater_filament_ui(i, choice); + choice->update(); ++i; } @@ -5363,9 +5819,14 @@ void Plater::on_config_change(const DynamicPrintConfig &config) } p->config->set_key_value(opt_key, config.option(opt_key)->clone()); - if (opt_key == "printer_technology") + if (opt_key == "printer_technology") { this->set_printer_technology(config.opt_enum<PrinterTechnology>(opt_key)); - else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture") || (opt_key == "bed_custom_model")) { + // print technology is changed, so we should to update a search list + p->sidebar->update_searcher(); + p->sidebar->show_sliced_info_sizer(false); + p->reset_gcode_toolpaths(); + } + else if (opt_key == "bed_shape" || opt_key == "bed_custom_texture" || opt_key == "bed_custom_model") { bed_shape_changed = true; update_scheduled = true; } @@ -5382,7 +5843,9 @@ void Plater::on_config_change(const DynamicPrintConfig &config) } else if(opt_key == "extruder_colour") { update_scheduled = true; +#if !ENABLE_PREVIEW_TYPE_CHANGE p->preview->set_number_extruders(p->config->option<ConfigOptionStrings>(opt_key)->values.size()); +#endif // !ENABLE_PREVIEW_TYPE_CHANGE p->sidebar->obj_list()->update_extruder_colors(); } else if(opt_key == "max_print_height") { update_scheduled = true; @@ -5395,9 +5858,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) } if (bed_shape_changed) - p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values, - p->config->option<ConfigOptionString>("bed_custom_texture")->value, - p->config->option<ConfigOptionString>("bed_custom_model")->value); + set_bed_shape(); if (update_scheduled) update(); @@ -5408,9 +5869,14 @@ void Plater::on_config_change(const DynamicPrintConfig &config) void Plater::set_bed_shape() const { - p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values, - p->config->option<ConfigOptionString>("bed_custom_texture")->value, - p->config->option<ConfigOptionString>("bed_custom_model")->value); + set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values, + p->config->option<ConfigOptionString>("bed_custom_texture")->value, + p->config->option<ConfigOptionString>("bed_custom_model")->value); +} + +void Plater::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) const +{ + p->set_bed_shape(shape, custom_texture, custom_model, force_as_custom); } void Plater::force_filament_colors_update() @@ -5452,50 +5918,53 @@ void Plater::force_print_bed_update() void Plater::on_activate() { -#ifdef __linux__ +#if defined(__linux__) || defined(_WIN32) wxWindow *focus_window = wxWindow::FindFocus(); // Activating the main frame, and no window has keyboard focus. // Set the keyboard focus to the visible Canvas3D. - if (this->p->view3D->IsShown() && (!focus_window || focus_window == this->p->view3D->get_wxglcanvas())) - this->p->view3D->get_wxglcanvas()->SetFocus(); - - else if (this->p->preview->IsShown() && (!focus_window || focus_window == this->p->view3D->get_wxglcanvas())) - this->p->preview->get_wxglcanvas()->SetFocus(); + if (this->p->view3D->IsShown() && wxWindow::FindFocus() != this->p->view3D->get_wxglcanvas()) + CallAfter([this]() { this->p->view3D->get_wxglcanvas()->SetFocus(); }); + else if (this->p->preview->IsShown() && wxWindow::FindFocus() != this->p->view3D->get_wxglcanvas()) + CallAfter([this]() { this->p->preview->get_wxglcanvas()->SetFocus(); }); #endif this->p->show_delayed_error_message(); } // Get vector of extruder colors considering filament color, if extruder color is undefined. -std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const +std::vector<std::string> Plater::get_extruder_colors_from_plater_config(const GCodeProcessor::Result* const result) const { - const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; - std::vector<std::string> extruder_colors; - if (!config->has("extruder_colour")) // in case of a SLA print - return extruder_colors; + if (wxGetApp().is_gcode_viewer() && result != nullptr) + return result->extruder_colors; + else { + const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; + std::vector<std::string> extruder_colors; + if (!config->has("extruder_colour")) // in case of a SLA print + return extruder_colors; - extruder_colors = (config->option<ConfigOptionStrings>("extruder_colour"))->values; - if (!wxGetApp().plater()) - return extruder_colors; + extruder_colors = (config->option<ConfigOptionStrings>("extruder_colour"))->values; + if (!wxGetApp().plater()) + return extruder_colors; - const std::vector<std::string>& filament_colours = (p->config->option<ConfigOptionStrings>("filament_colour"))->values; - for (size_t i = 0; i < extruder_colors.size(); ++i) - if (extruder_colors[i] == "" && i < filament_colours.size()) - extruder_colors[i] = filament_colours[i]; + const std::vector<std::string>& filament_colours = (p->config->option<ConfigOptionStrings>("filament_colour"))->values; + for (size_t i = 0; i < extruder_colors.size(); ++i) + if (extruder_colors[i] == "" && i < filament_colours.size()) + extruder_colors[i] = filament_colours[i]; - return extruder_colors; + return extruder_colors; + } } /* Get vector of colors used for rendering of a Preview scene in "Color print" mode * It consists of extruder colors and colors, saved in model.custom_gcode_per_print_z */ -std::vector<std::string> Plater::get_colors_for_color_print() const +std::vector<std::string> Plater::get_colors_for_color_print(const GCodeProcessor::Result* const result) const { - std::vector<std::string> colors = get_extruder_colors_from_plater_config(); + std::vector<std::string> colors = get_extruder_colors_from_plater_config(result); colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.gcodes.size()); for (const CustomGCode::Item& code : p->model.custom_gcode_per_print_z.gcodes) - if (code.gcode == ColorChangeCode) + if (code.type == CustomGCode::ColorChange) colors.emplace_back(code.color); return colors; @@ -5516,6 +5985,11 @@ bool Plater::is_export_gcode_scheduled() const return p->background_process.is_export_scheduled(); } +const Selection &Plater::get_selection() const +{ + return p->get_selection(); +} + int Plater::get_selected_object_idx() { return p->get_selected_object_idx(); @@ -5531,6 +6005,11 @@ GLCanvas3D* Plater::canvas3D() return p->view3D->get_canvas3d(); } +const GLCanvas3D* Plater::canvas3D() const +{ + return p->view3D->get_canvas3d(); +} + GLCanvas3D* Plater::get_current_canvas3D() { return p->get_current_canvas3D(); @@ -5541,20 +6020,38 @@ BoundingBoxf Plater::bed_shape_bb() const return p->bed_shape_bb(); } +void Plater::arrange() +{ + p->m_ui_jobs.arrange(); +} + void Plater::set_current_canvas_as_dirty() { p->set_current_canvas_as_dirty(); } +void Plater::unbind_canvas_event_handlers() +{ + p->unbind_canvas_event_handlers(); +} + +void Plater::reset_canvas_volumes() +{ + p->reset_canvas_volumes(); +} + PrinterTechnology Plater::printer_technology() const { return p->printer_technology; } -void Plater::set_printer_technology(PrinterTechnology printer_technology) +const DynamicPrintConfig * Plater::config() const { return p->config; } + +bool Plater::set_printer_technology(PrinterTechnology printer_technology) { p->printer_technology = printer_technology; - if (p->background_process.select_technology(printer_technology)) { + bool ret = p->background_process.select_technology(printer_technology); + if (ret) { // Update the active presets. } //FIXME for SLA synchronize @@ -5563,8 +6060,14 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export"); p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer"); - if (wxGetApp().mainframe) + if (wxGetApp().mainframe != nullptr) wxGetApp().mainframe->update_menubar(); + + p->update_main_toolbar_tooltips(); + + p->sidebar->get_searcher().set_printer_technology(printer_technology); + + return ret; } void Plater::changed_object(int obj_idx) @@ -5637,7 +6140,10 @@ void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_acti void Plater::copy_selection_to_clipboard() { - if (can_copy_to_clipboard()) + // At first try to copy selected values to the ObjectList's clipboard + // to check if Settings or Layers are selected in the list + // and then copy to 3DCanvas's clipboard if not + if (can_copy_to_clipboard() && !p->sidebar->obj_list()->copy_to_clipboard()) p->view3D->get_canvas3d()->get_selection().copy_to_clipboard(); } @@ -5646,8 +6152,37 @@ void Plater::paste_from_clipboard() if (!can_paste_from_clipboard()) return; - Plater::TakeSnapshot snapshot(this, _(L("Paste From Clipboard"))); - p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); + Plater::TakeSnapshot snapshot(this, _L("Paste From Clipboard")); + + // At first try to paste values from the ObjectList's clipboard + // to check if Settings or Layers were copied + // and then paste from the 3DCanvas's clipboard if not + if (!p->sidebar->obj_list()->paste_from_clipboard()) + p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); +} + +void Plater::search(bool plater_is_active) +{ + if (plater_is_active) { + // plater should be focused for correct navigation inside search window + this->SetFocus(); + + wxKeyEvent evt; +#ifdef __APPLE__ + evt.m_keyCode = 'f'; +#else /* __APPLE__ */ + evt.m_keyCode = WXK_CONTROL_F; +#endif /* __APPLE__ */ + evt.SetControlDown(true); + canvas3D()->on_char(evt); + } + else + { + wxPoint pos = this->ClientToScreen(wxPoint(0, 0)); + pos.x += em_unit(this) * 40; + pos.y += em_unit(this) * 4; + p->sidebar->get_searcher().search_dialog->Popup(pos); + } } void Plater::msw_rescale() @@ -5664,11 +6199,37 @@ void Plater::msw_rescale() GetParent()->Layout(); } +void Plater::sys_color_changed() +{ + p->sidebar->sys_color_changed(); + + // msw_rescale_menu updates just icons, so use it + p->msw_rescale_object_menu(); + + Layout(); + GetParent()->Layout(); +} + bool Plater::init_view_toolbar() { return p->init_view_toolbar(); } +void Plater::enable_view_toolbar(bool enable) +{ + p->view_toolbar.set_enabled(enable); +} + +bool Plater::init_collapse_toolbar() +{ + return p->init_collapse_toolbar(); +} + +void Plater::enable_collapse_toolbar(bool enable) +{ + p->collapse_toolbar.set_enabled(enable); +} + const Camera& Plater::get_camera() const { return p->camera; @@ -5679,6 +6240,69 @@ Camera& Plater::get_camera() return p->camera; } +#if ENABLE_ENVIRONMENT_MAP +void Plater::init_environment_texture() +{ + if (p->environment_texture.get_id() == 0) + p->environment_texture.load_from_file(resources_dir() + "/icons/Pmetal_001.png", false, GLTexture::SingleThreaded, false); +} + +unsigned int Plater::get_environment_texture_id() const +{ + return p->environment_texture.get_id(); +} +#endif // ENABLE_ENVIRONMENT_MAP + +const Bed3D& Plater::get_bed() const +{ + return p->bed; +} + +Bed3D& Plater::get_bed() +{ + return p->bed; +} + +const GLToolbar& Plater::get_view_toolbar() const +{ + return p->view_toolbar; +} + +GLToolbar& Plater::get_view_toolbar() +{ + return p->view_toolbar; +} + +const GLToolbar& Plater::get_collapse_toolbar() const +{ + return p->collapse_toolbar; +} + +GLToolbar& Plater::get_collapse_toolbar() +{ + return p->collapse_toolbar; +} + +void Plater::update_preview_bottom_toolbar() +{ + p->update_preview_bottom_toolbar(); +} + +void Plater::update_preview_moves_slider() +{ + p->update_preview_moves_slider(); +} + +void Plater::enable_preview_moves_slider(bool enable) +{ + p->enable_preview_moves_slider(enable); +} + +void Plater::reset_gcode_toolpaths() +{ + p->reset_gcode_toolpaths(); +} + const Mouse3DController& Plater::get_mouse3d_controller() const { return p->mouse3d_controller; @@ -5689,6 +6313,16 @@ Mouse3DController& Plater::get_mouse3d_controller() return p->mouse3d_controller; } +const NotificationManager* Plater::get_notification_manager() const +{ + return p->notification_manager; +} + +NotificationManager* Plater::get_notification_manager() +{ + return p->notification_manager; +} + bool Plater::can_delete() const { return p->can_delete(); } bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); } @@ -5704,7 +6338,7 @@ bool Plater::can_paste_from_clipboard() const const Selection& selection = p->view3D->get_canvas3d()->get_selection(); const Selection::Clipboard& clipboard = selection.get_clipboard(); - if (clipboard.is_empty()) + if (clipboard.is_empty() && p->sidebar->obj_list()->clipboard_is_empty()) return false; if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !clipboard.is_sla_compliant()) @@ -5736,10 +6370,23 @@ bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); } bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); } const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } +void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); } void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); } +#if ENABLE_RENDER_STATISTICS +void Plater::toggle_render_statistic_dialog() +{ + p->show_render_statistic_dialog = !p->show_render_statistic_dialog; +} + +bool Plater::is_render_statistic_dialog_visible() const +{ + return p->show_render_statistic_dialog; +} +#endif // ENABLE_RENDER_STATISTICS + // Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu. bool Plater::PopupMenu(wxMenu *menu, const wxPoint& pos) { @@ -5758,6 +6405,10 @@ bool Plater::PopupMenu(wxMenu *menu, const wxPoint& pos) } return out; } +void Plater::bring_instance_forward() +{ + p->bring_instance_forward(); +} SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() : m_was_scheduled(wxGetApp().plater()->is_background_process_update_scheduled()) |