diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 1153adc01f..406caf3d87 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -92,6 +92,8 @@ public: void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; } + GCodeProcessorResult *get_gcode_result() { return m_gcode_result; } + // The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished // and the background processing will transition into G-code export. // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. diff --git a/src/slic3r/GUI/BulkExportDialog.cpp b/src/slic3r/GUI/BulkExportDialog.cpp index 452123fc6d..2e1b764246 100644 --- a/src/slic3r/GUI/BulkExportDialog.cpp +++ b/src/slic3r/GUI/BulkExportDialog.cpp @@ -20,24 +20,19 @@ #include "format.hpp" #include "Tab.hpp" -using Slic3r::GUI::format_wxstr; +namespace fs = boost::filesystem; namespace Slic3r { namespace GUI { constexpr auto BORDER_W = 10; -//----------------------------------------------- -// BulkExportDialog::Item -//----------------------------------------------- - - -void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, const std::string path) +void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, const std::string &path) { #ifdef _WIN32 - long style = wxBORDER_SIMPLE; + const long style = wxBORDER_SIMPLE; #else - long style = 0L; + const long style = 0L; #endif m_text_ctrl = new wxTextCtrl(m_parent, wxID_ANY, from_u8(path), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), style); wxGetApp().UpdateDarkUI(m_text_ctrl); @@ -45,18 +40,24 @@ void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, input_path_sizer->Add(m_text_ctrl, 1, wxEXPAND, BORDER_W); } - -BulkExportDialog::Item::Item(wxWindow* parent, wxBoxSizer* sizer, PrintToExport& pte): +BulkExportDialog::Item::Item( + wxWindow *parent, + wxBoxSizer *sizer, + const boost::filesystem::path &path, + Validator validator +): + path(path), m_parent(parent), - m_print_to_export(&pte), m_valid_bmp(new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark"))), - m_valid_label(new wxStaticText(m_parent, wxID_ANY, "")) + m_valid_label(new wxStaticText(m_parent, wxID_ANY, "")), + m_validator(std::move(validator)), + m_directory(path.parent_path()) { m_valid_label->SetFont(wxGetApp().bold_font()); wxBoxSizer* input_path_sizer = new wxBoxSizer(wxHORIZONTAL); - input_path_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W); - init_input_name_ctrl(input_path_sizer, pte.output_path.filename().string()); + input_path_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W); + init_input_name_ctrl(input_path_sizer, path.filename().string()); sizer->Add(input_path_sizer,0, wxEXPAND | wxTOP, BORDER_W); sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W); @@ -64,82 +65,131 @@ BulkExportDialog::Item::Item(wxWindow* parent, wxBoxSizer* sizer, PrintToExport& update(); } -void BulkExportDialog::Item::update() -{ - m_path = into_u8(m_text_ctrl->GetValue()); - - m_valid_type = ValidationType::Valid; - wxString info_line; - - const char* unusable_symbols = "<>[]:/\\|?*\""; - - for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { - if (m_path.find_first_of(unusable_symbols[i]) != std::string::npos) { - info_line = _L("The following characters are not allowed in the name") + ": " + unusable_symbols; - m_valid_type = ValidationType::NoValid; - break; - } - } - - bool existing = false;// is_existing(m_path); - if (m_valid_type == ValidationType::Valid && existing) { - info_line = _L("This name is already used, use another."); - m_valid_type = ValidationType::Warning; - } - - if (m_valid_type == ValidationType::Valid && m_path.empty()) { - info_line = _L("The name cannot be empty."); - m_valid_type = ValidationType::NoValid; - } - #ifdef __WXMSW__ - const int max_path_length = MAX_PATH; +constexpr int max_path_length = MAX_PATH; #else - const int max_path_length = 255; +constexpr int max_path_length = 255; #endif - if (m_valid_type == ValidationType::Valid && m_path.length() >= max_path_length) { - info_line = _L("The name is too long."); - m_valid_type = ValidationType::NoValid; +struct PathValidator { + std::reference_wrapper>> items; + using ItemStatus = BulkExportDialog::ItemStatus; + + bool is_duplicate(const fs::path &path) { + const int64_t count{std::count_if( + items.get().begin(), + items.get().end(), + [&](const auto &item){ + return item->path == path; + } + )}; + + return count >= 2; } - if (m_valid_type == ValidationType::Valid && m_path.find_first_of(' ') == 0) { - info_line = _L("The name cannot start with space character."); - m_valid_type = ValidationType::NoValid; - } + std::pair operator()( + const fs::path &path, + const std::string &filename + ) { + const char* unusable_symbols = "<>[]:/\\|?*\""; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (filename.find_first_of(unusable_symbols[i]) != std::string::npos) { + return { + ItemStatus::NoValid, + _L("The following characters are not allowed in the name") + ": " + unusable_symbols + }; + } + } - if (m_valid_type == ValidationType::Valid && m_path.find_last_of(' ') == m_path.length()-1) { - info_line = _L("The name cannot end with space character."); - m_valid_type = ValidationType::NoValid; + if (filename.empty()) { + return { + ItemStatus::NoValid, + _L("The name cannot be empty.") + }; + } + + if (path.string().length() >= max_path_length) { + return { + ItemStatus::NoValid, + _L("The name is too long.") + }; + } + + if (filename.find_first_of(' ') == 0) { + return { + ItemStatus::NoValid, + _L("The name cannot start with space character.") + }; + } + + if (filename.find_last_of(' ') == filename.length()-1) { + return { + ItemStatus::NoValid, + _L("The name cannot end with space character.") + }; + } + + if (is_duplicate(path)) { + return { + ItemStatus::NoValid, + _L("This name is already used, use another.") + }; + } + + if (fs::exists(path)) { + return { + ItemStatus::Warning, + _L("The file already exists!") + }; + } + + return {ItemStatus::Valid, ""}; } +}; + +void BulkExportDialog::Item::update() +{ + std::string filename{into_u8(m_text_ctrl->GetValue())}; + path = m_directory / filename; + + // Validator needs to be called after path is set! + // It has has a reference to all items and searches + // for duplicates; + const auto [status, info_line]{m_validator(path, filename)}; m_valid_label->SetLabel(info_line); m_valid_label->Show(!info_line.IsEmpty()); + m_status = status; update_valid_bmp(); - if (is_valid()) { - boost::filesystem::path field = m_print_to_export->output_path.parent_path(); - m_print_to_export->output_path = field / m_path; - } - m_parent->Layout(); } -void BulkExportDialog::Item::update_valid_bmp() -{ - std::string bmp_name = m_valid_type == ValidationType::Warning ? "exclamation_manifold" : - m_valid_type == ValidationType::NoValid ? "exclamation" : "tick_mark" ; - m_valid_bmp->SetBitmap(*get_bmp_bundle(bmp_name)); +std::string get_bmp_name(const BulkExportDialog::ItemStatus status) { + using ItemStatus = BulkExportDialog::ItemStatus; + switch(status) { + case ItemStatus::Warning: return "exclamation_manifold"; + case ItemStatus::NoValid: return "exclamation"; + case ItemStatus::Valid: return "tick_mark"; + } + return ""; // unreachable } -//----------------------------------------------- -// BulkExportDialog -//----------------------------------------------- +void BulkExportDialog::Item::update_valid_bmp() +{ + m_valid_bmp->SetBitmap(*get_bmp_bundle(get_bmp_name(m_status))); +} -BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector& exports) - : DPIDialog(parent, wxID_ANY, exports.size() == 1 ? _L("Save bed") : _L("Save beds"), - wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING) +BulkExportDialog::BulkExportDialog(const std::vector &paths): + DPIDialog( + nullptr, + wxID_ANY, + paths.size() == 1 ? _L("Save bed") : _L("Save beds"), + wxDefaultPosition, + wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), + wxDEFAULT_DIALOG_STYLE | wxICON_WARNING + ) { this->SetFont(wxGetApp().normal_font()); @@ -151,8 +201,9 @@ BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector& m_sizer = new wxBoxSizer(wxVERTICAL); - for (PrintToExport& exp : exports) - AddItem(exp); + for (const fs::path& path : paths) { + AddItem(path); + } // Add dialog's buttons wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); @@ -173,22 +224,17 @@ BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector& #endif } -BulkExportDialog::~BulkExportDialog() +void BulkExportDialog::AddItem(const fs::path& path) { - for (auto item : m_items) - delete item; -} - -void BulkExportDialog::AddItem(PrintToExport& pte) -{ - m_items.emplace_back(new Item{ this, m_sizer, pte }); + m_items.push_back(std::make_unique(this, m_sizer, path, PathValidator{m_items})); } bool BulkExportDialog::enable_ok_btn() const { - for (const Item* item : m_items) - if (!item->is_valid()) + for (const auto &item : m_items) + if (!item->is_valid()) { return false; + } return true; } @@ -200,13 +246,26 @@ bool BulkExportDialog::Layout() return ret; } -void BulkExportDialog::on_dpi_changed(const wxRect& suggested_rect) +std::vector BulkExportDialog::get_paths() const { + std::vector result; + std::transform( + m_items.begin(), + m_items.end(), + std::back_inserter(result), + [](const auto &item){ + return item->path; + } + ); + return result; +} + +void BulkExportDialog::on_dpi_changed(const wxRect&) { const int& em = em_unit(); msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - for (Item* item : m_items) + for (auto &item : m_items) item->update_valid_bmp(); const wxSize& size = wxSize(65 * em, 35 * em); diff --git a/src/slic3r/GUI/BulkExportDialog.hpp b/src/slic3r/GUI/BulkExportDialog.hpp index cc526000b8..ba8a9e265b 100644 --- a/src/slic3r/GUI/BulkExportDialog.hpp +++ b/src/slic3r/GUI/BulkExportDialog.hpp @@ -16,79 +16,74 @@ class wxTextCtrl; class wxStaticBitmap; namespace Slic3r { - class Print; struct GCodeProcessorResult; namespace GUI { -struct PrintToExport { - std::reference_wrapper print; - std::reference_wrapper processor_result; - boost::filesystem::path output_path; -}; - class BulkExportDialog : public DPIDialog { public: + enum class ItemStatus { Valid, NoValid, Warning }; + struct Item { - enum class ValidationType - { - Valid, - NoValid, - Warning - }; + using Validator = std::function< + std::pair( + boost::filesystem::path, + std::string + ) + >; + Item( + wxWindow *parent, + wxBoxSizer *sizer, + const boost::filesystem::path &path, + Validator validator + ); + Item(const Item &) = delete; + Item& operator=(const Item &) = delete; + Item(Item &&) = delete; + Item& operator=(Item &&) = delete; - // Item as a separate control(f.e. as a part of ConfigWizard to check name of the new custom priter) - Item(wxWindow* parent, wxBoxSizer* sizer, PrintToExport& path); + // Item cannot have copy or move constructors, because a wx event binds + // directly to its address. - void update_valid_bmp(); - bool is_valid() const { return m_valid_type != ValidationType::NoValid; } + void update_valid_bmp(); + bool is_valid() const { return m_status != ItemStatus::NoValid; } + + boost::filesystem::path path; private: - std::string m_path; - PrintToExport* m_print_to_export { nullptr }; + ItemStatus m_status{ItemStatus::NoValid}; + wxWindow *m_parent{nullptr}; + wxStaticBitmap *m_valid_bmp{nullptr}; + wxTextCtrl *m_text_ctrl{nullptr}; + wxStaticText *m_valid_label{nullptr}; + Validator m_validator; + boost::filesystem::path m_directory{}; - ValidationType m_valid_type {ValidationType::NoValid}; - wxWindow* m_parent {nullptr}; - wxStaticBitmap* m_valid_bmp {nullptr}; - wxTextCtrl* m_text_ctrl {nullptr}; - wxStaticText* m_valid_label {nullptr}; - - - void init_input_name_ctrl(wxBoxSizer *input_name_sizer, std::string path); - void update(); + void init_input_name_ctrl(wxBoxSizer *input_name_sizer, const std::string &path); + void update(); }; private: - std::vector m_items; - - std::vector* m_exports {nullptr}; - - wxBoxSizer* m_sizer {nullptr}; - wxStaticText* m_label {nullptr}; - - std::string m_ph_printer_name; - std::string m_old_preset_name; - wxString m_info_line_extention{wxEmptyString}; + // This must be a unique ptr, because Item does not have copy nor move constructors. + std::vector> m_items; + wxBoxSizer *m_sizer{nullptr}; public: - - BulkExportDialog(wxWindow* parent, std::vector& exports); - ~BulkExportDialog() override; + BulkExportDialog(const std::vector &paths); bool Layout() override; - - void AddItem(PrintToExport& pte); + std::vector get_paths() const; protected: - void on_dpi_changed(const wxRect& suggested_rect) override; + void on_dpi_changed(const wxRect &) override; void on_sys_color_changed() override {} private: + void AddItem(const boost::filesystem::path &path); bool enable_ok_btn() const; }; -} - +} // namespace GUI } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8e1a974863..7eec9d1282 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5923,6 +5923,12 @@ void Plater::export_gcode_to_path( appconfig.update_last_output_dir(output_path.parent_path().string(), path_on_removable_media); } +struct PrintToExport { + std::reference_wrapper print; + std::reference_wrapper processor_result; + boost::filesystem::path output_path; +}; + void Plater::export_all_gcodes(bool prefer_removable) { const auto optional_default_output_file{this->get_default_output_file()}; if (!optional_default_output_file) { @@ -5937,6 +5943,7 @@ void Plater::export_all_gcodes(bool prefer_removable) { const fs_path &output_dir{*optional_output_dir}; std::vector prints_to_export; + std::vector paths; for (std::size_t print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) { const std::unique_ptr &print{this->get_fff_prints()[print_index]}; @@ -5952,14 +5959,27 @@ void Plater::export_all_gcodes(bool prefer_removable) { }; const fs::path output_file{output_dir / filename}; prints_to_export.push_back({*print, this->p->gcode_results[print_index], output_file}); + paths.push_back(output_file); } - BulkExportDialog dialog(nullptr, prints_to_export); + BulkExportDialog dialog{paths}; if (dialog.ShowModal() != wxID_OK) { return; } + paths = dialog.get_paths(); + for (std::size_t path_index{0}; path_index < paths.size(); ++path_index) { + prints_to_export[path_index].output_path = paths[path_index]; + } bool path_on_removable_media{false}; + + Print *original_print{&active_fff_print()}; + GCodeProcessorResult *original_result{this->p->background_process.get_gcode_result()}; + ScopeGuard guard{[&](){ + this->p->background_process.set_fff_print(original_print); + this->p->background_process.set_gcode_result(original_result); + }}; + for (const PrintToExport &print_to_export : prints_to_export) { this->p->background_process.set_fff_print(&print_to_export.print.get()); this->p->background_process.set_gcode_result(&print_to_export.processor_result.get()); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 26471b9571..bfdc9bf771 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -31,6 +31,7 @@ #include "Jobs/Worker.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" #include "slic3r/GUI/Camera.hpp" +#include "slic3r/Utils/PrintHost.hpp" class wxString; @@ -240,6 +241,8 @@ public: void send_gcode(); void send_gcode_inner(DynamicPrintConfig* physical_printer_config); void eject_drive(); + + std::optional get_connect_print_host_job(); void connect_gcode(); void printables_to_connect_gcode(const std::string& url); std::string get_upload_filename();