diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 67df537d5c..bd23684bcc 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -175,7 +175,7 @@ void BackgroundSlicingProcess::process_fff() if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); - finalize_gcode(); + finalize_gcode(m_export_path, m_export_path_on_removable_media); } else if (! m_upload_job.empty()) { wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); prepare_upload(); @@ -680,12 +680,12 @@ bool BackgroundSlicingProcess::invalidate_all_steps() // G-code is generated in m_temp_output_path. // Optionally run a post-processing script on a copy of m_temp_output_path. // Copy the final G-code to target location (possibly a SD card, if it is a removable media, then verify that the file was written without an error). -void BackgroundSlicingProcess::finalize_gcode() +void BackgroundSlicingProcess::finalize_gcode(const std::string &path, const bool path_on_removable_media) { m_print->set_status(95, _u8L("Running post-processing scripts")); // Perform the final post-processing of the export path by applying the print statistics over the file name. - std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); + std::string export_path = m_fff_print->print_statistics().finalize_output_path(path); std::string output_path = m_temp_output_path; // Both output_path and export_path ar in-out parameters. // If post processed, output_path will differ from m_temp_output_path as run_post_process_scripts() will make a copy of the G-code to not @@ -707,7 +707,7 @@ void BackgroundSlicingProcess::finalize_gcode() int copy_ret_val = CopyFileResult::SUCCESS; try { - copy_ret_val = copy_file(output_path, export_path, error_message, m_export_path_on_removable_media); + copy_ret_val = copy_file(output_path, export_path, error_message, path_on_removable_media); remove_post_processed_temp_file(); } catch (...) diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 7a8e53cfeb..1153adc01f 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -175,7 +175,8 @@ public: // This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs), // and it does not account for the OctoPrint scheduling. bool finished() const { return m_print->finished(); } - + void finalize_gcode(const std::string &path, const bool path_on_removable_media); + private: void thread_proc(); // Calls thread_proc(), catches all C++ exceptions and shows them using wxApp::OnUnhandledException(). @@ -266,7 +267,6 @@ private: bool invalidate_all_steps(); // If the background processing stop was requested, throw CanceledException. void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); } - void finalize_gcode(); void prepare_upload(); // To be executed at the background thread. ThumbnailsList render_thumbnails(const ThumbnailsParams ¶ms); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a39ed13e39..1d595a8f5b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -125,9 +125,6 @@ void GLCanvas3D::select_bed(int i, bool triggered_by_user) m_sequential_print_clearance.m_evaluating = true; reset_sequential_print_clearance(); - - - // The stop call above schedules some events that would be processed after the switch. // Among else, on_process_completed would be called, which would stop slicing of // the new bed. We need to stop the process, pump all the events out of the queue @@ -2226,6 +2223,7 @@ void GLCanvas3D::render() wxGetApp().plater()->schedule_background_process(); } } else { + wxGetApp().plater()->show_autoslicing_action_buttons(); render_print_statistics(); } } @@ -6721,6 +6719,7 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() if (!s_multiple_beds.is_autoslicing()) { s_multiple_beds.start_autoslice([this](int i, bool user) { this->select_bed(i, user); }); wxGetApp().sidebar().switch_to_autoslicing_mode(); + wxGetApp().plater()->show_autoslicing_action_buttons(); } } diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 9674b2f471..4839e754b4 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -2350,7 +2350,15 @@ void NotificationManager::push_exporting_finished_notification(const std::string { close_notification_of_type(NotificationType::ExportFinished); NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotificationLevel, on_removable ? 0 : 20, _u8L("Exporting finished.") + "\n" + path }; - push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0); + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, on_removable, dir_path), 0); + set_slicing_progress_hidden(); +} + +void NotificationManager::push_bulk_exporting_finished_notification(const std::string& dir_path, bool on_removable) +{ + close_notification_of_type(NotificationType::ExportFinished); + NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotificationLevel, on_removable ? 0 : 20, _u8L("Bulk export finished.") + "\n" + dir_path}; + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, on_removable, dir_path), 0); set_slicing_progress_hidden(); } diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 57ecbf4719..5f352b814c 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -231,6 +231,7 @@ public: void set_sla(bool b) { set_fff(!b); } // Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); + void push_bulk_exporting_finished_notification(const std::string& dir_path, bool on_removable); // notifications with progress bar // print host upload void push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); @@ -783,10 +784,15 @@ private: class ExportFinishedNotification : public PopNotification { public: - ExportFinishedNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool to_removable,const std::string& export_path,const std::string& export_dir_path) - : PopNotification(n, id_provider, evt_handler) + ExportFinishedNotification( + const NotificationData& n, + NotificationIDProvider& id_provider, + wxEvtHandler* evt_handler, + bool to_removable, + const std::string& export_dir_path + ): + PopNotification(n, id_provider, evt_handler) , m_to_removable(to_removable) - , m_export_path(export_path) , m_export_dir_path(export_dir_path) { m_multiline = true; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cb9302c5e3..55006fb22b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -534,6 +534,7 @@ struct Plater::priv void on_3dcanvas_mouse_dragging_finished(SimpleEvent&); void show_action_buttons(const bool is_ready_to_slice) const; + void show_autoslicing_action_buttons() const; bool can_show_upload_to_connect() const; // Set the bed shape to a single closed 2D polygon(array of two element arrays), // triangulate the bed and store the triangles into m_bed.m_triangles, @@ -2252,6 +2253,10 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool } )}; + if (any_status_changed) { + wxGetApp().plater()->show_autoslicing_action_buttons(); + } + // If current bed was invalidated, update thumbnails for all beds: if (int num = s_multiple_beds.get_number_of_beds(); num > 1 && any_status_changed) { ThumbnailData data; @@ -2423,8 +2428,9 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ? _L("Slicing") + dots : _L("Slice now"); sidebar->set_btn_label(ActionButtonType::Reslice, slice_string); - - if (background_process.finished()) + if (background_process.empty()) { + sidebar->enable_buttons(false); + } else if (background_process.finished()) show_action_buttons(false); else if (!background_process.empty() && !background_process.running()) /* Do not update buttons if background process is running @@ -3956,6 +3962,35 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice_) const } } +void Plater::priv::show_autoslicing_action_buttons() const { + if (!s_multiple_beds.is_autoslicing()) { + return; + } + wxWindowUpdateLocker noUpdater(sidebar); + + 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("print_host") : nullptr; + const bool connect_gcode_shown = print_host_opt == nullptr && can_show_upload_to_connect(); + + RemovableDriveManager::RemovableDrivesStatus removable_media_status = wxGetApp().removable_drive_manager()->status(); + + bool updated{sidebar->show_export_all(true)}; + updated = sidebar->show_connect_all(connect_gcode_shown) || updated; + updated = sidebar->show_export_removable_all(removable_media_status.has_removable_drives) || updated; + if (updated) { + sidebar->Layout(); + } + + const bool all_finished{std::all_of( + this->fff_prints.begin(), + this->fff_prints.end(), + [](const std::unique_ptr &print){ + return print->finished() || print->empty(); + } + )}; + sidebar->enable_bulk_buttons(all_finished); +} + void Plater::priv::enter_gizmos_stack() { assert(m_undo_redo_stack_active == &m_undo_redo_stack_main); @@ -5679,14 +5714,24 @@ static wxString check_binary_vs_ascii_gcode_extension(PrinterTechnology pt, cons // This function should be deleted when binary G-codes become more common. The dialog is there to make the // transition period easier for the users, because bgcode files are not recognized by older firmwares // without any error message. -static void alert_when_exporting_binary_gcode(bool binary_output, const std::string& printer_notes) +void alert_when_exporting_binary_gcode(const std::string& printer_notes) { - if (binary_output - && (boost::algorithm::contains(printer_notes, "PRINTER_MODEL_XL") - || boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MINI") - || boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MK4") - || boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MK3.9"))) - { + const bool supports_binary = wxGetApp() + .preset_bundle->printers + .get_edited_preset() + .config.opt_bool("binary_gcode"); + const bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"); + const bool binary_output{supports_binary && uses_binary}; + + if ( + binary_output + && ( + boost::algorithm::contains(printer_notes, "PRINTER_MODEL_XL") + || boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MINI") + || boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MK4") + || boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MK3.9") + ) + ) { AppConfig* app_config = wxGetApp().app_config; wxWindow* parent = wxGetApp().mainframe; const std::string option_key = "dont_warn_about_firmware_version_when_exporting_binary_gcode"; @@ -5708,7 +5753,116 @@ static void alert_when_exporting_binary_gcode(bool binary_output, const std::str } } +std::optional Plater::get_default_output_file() { + try { + // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. + // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. + unsigned int state = this->p->update_restart_background_process(false, false); + if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) { + return std::nullopt; + } + const std::string path{ + this->p->background_process.output_filepath_for_project( + into_path(get_project_filename(".3mf")) + ) + }; + return fs::path(Slic3r::fold_utf8_to_ascii(path)); + } catch (const Slic3r::PlaceholderParserError &ex) { + // Show the error with monospaced font. + show_error(this, ex.what(), true); + return std::nullopt; + } catch (const std::exception &ex) { + show_error(this, ex.what(), false); + return std::nullopt; + } +} +std::string get_output_start_dir(const bool prefer_removable, const fs::path &default_output_file) { + const AppConfig &appconfig{*wxGetApp().app_config}; + RemovableDriveManager &removable_drive_manager{*wxGetApp().removable_drive_manager()}; + // Get a last save path, either to removable media or to an internal media. + std::string last_output_dir{appconfig.get_last_output_dir( + default_output_file.parent_path().string(), + prefer_removable + )}; + if (!prefer_removable) { + return last_output_dir; + } + + // Returns a path to a removable media if it exists, prefering start_dir. Update the internal removable drives database. + std::string removable_dir{removable_drive_manager.get_removable_drive_path(last_output_dir)}; + if (removable_dir.empty()) { + // Direct user to the last internal media. + return appconfig.get_last_output_dir(default_output_file.parent_path().string(), false); + } + + return removable_dir; +} + +std::optional Plater::check_output_path_has_error(const boost::filesystem::path& path) const { + const std::string filename = path.filename().string(); + const std::string ext = boost::algorithm::to_lower_copy(path.extension().string()); + if (has_illegal_characters(filename)) { + return { + _L("The provided file name is not valid.") + "\n" + + _L("The following characters are not allowed by a FAT file system:") + " <>:/\\|?*\"" + }; + } + if (this->printer_technology() == ptFFF) { + bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode"); + bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"); + const wxString error{check_binary_vs_ascii_gcode_extension( + printer_technology(), ext, supports_binary && uses_binary + )}; + if (!error.IsEmpty()) { + return error; + } + } + return std::nullopt; +}; + +std::optional Plater::get_output_path(const std::string &start_dir, const fs::path &default_output_file) { + const std::string ext = default_output_file.extension().string(); + wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 / SL1S file as:"), + start_dir, + from_path(default_output_file.filename()), + printer_technology() == ptFFF ? GUI::file_wildcards(FT_GCODE, ext) : + GUI::sla_wildcards(active_sla_print().printer_config().sla_archive_format.value.c_str(), ext), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT + ); + + if (dlg.ShowModal() != wxID_OK) { + return std::nullopt; + } + + const fs::path output_path{into_path(dlg.GetPath())}; + if (auto error{check_output_path_has_error(output_path)}) { + const t_link_clicked on_link_clicked = [](const std::string& key) -> void { wxGetApp().jump_to_option(key); }; + ErrorDialog(this, *error, on_link_clicked).ShowModal(); + return std::nullopt; + } + return output_path; +} + +std::optional Plater::get_multiple_output_dir(const std::string &start_dir) { + wxDirDialog dlg( + this, + _L("Choose export directory:"), + start_dir + ); + + if (dlg.ShowModal() != wxID_OK) { + return std::nullopt; + } + + const fs::path output_path{into_path(dlg.GetPath())}; + if (auto error{check_output_path_has_error(output_path)}) { + const t_link_clicked on_link_clicked = [](const std::string& key) -> void { wxGetApp().jump_to_option(key); }; + ErrorDialog(this, *error, on_link_clicked).ShowModal(); + return std::nullopt; + } + return output_path; +} void Plater::export_gcode(bool prefer_removable) { @@ -5724,91 +5878,109 @@ void Plater::export_gcode(bool prefer_removable) // 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; - try { - // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. - // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. - unsigned int state = this->p->update_restart_background_process(false, false); - 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 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); + const auto optional_default_output_file{this->get_default_output_file()}; + if (!optional_default_output_file) { return; } - default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); - AppConfig &appconfig = *wxGetApp().app_config; - RemovableDriveManager &removable_drive_manager = *wxGetApp().removable_drive_manager(); - // Get a last save path, either to removable media or to an internal media. - std::string start_dir = appconfig.get_last_output_dir(default_output_file.parent_path().string(), prefer_removable); - if (prefer_removable) { - // Returns a path to a removable media if it exists, prefering start_dir. Update the internal removable drives database. - start_dir = removable_drive_manager.get_removable_drive_path(start_dir); - if (start_dir.empty()) - // Direct user to the last internal media. - start_dir = appconfig.get_last_output_dir(default_output_file.parent_path().string(), false); - } + const fs::path &default_output_file{*optional_default_output_file}; + const std::string start_dir{get_output_start_dir(prefer_removable, default_output_file)}; - fs::path output_path; - { - std::string ext = default_output_file.extension().string(); - wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 / SL1S file as:"), - start_dir, - from_path(default_output_file.filename()), - printer_technology() == ptFFF ? GUI::file_wildcards(FT_GCODE, ext) : - GUI::sla_wildcards(active_sla_print().printer_config().sla_archive_format.value.c_str(), ext), - wxFD_SAVE | wxFD_OVERWRITE_PROMPT + const auto optional_output_path{get_output_path(start_dir, default_output_file)}; + if (!optional_output_path) { + return; + } + const fs::path &output_path{*optional_output_path}; + + if (printer_technology() == ptFFF) { + alert_when_exporting_binary_gcode( + wxGetApp() + .preset_bundle->printers + .get_edited_preset() + .config.opt_string("printer_notes") ); - if (dlg.ShowModal() == wxID_OK) { - output_path = into_path(dlg.GetPath()); + } + export_gcode_to_path(output_path, [&](const bool path_on_removable_media){ + p->export_gcode(output_path, path_on_removable_media, PrintHostJob()); + }); +} - auto check_for_error = [this](const boost::filesystem::path& path, wxString& err_out) -> bool { - const std::string filename = path.filename().string(); - const std::string ext = boost::algorithm::to_lower_copy(path.extension().string()); - if (has_illegal_characters(filename)) { - err_out = _L("The provided file name is not valid.") + "\n" + - _L("The following characters are not allowed by a FAT file system:") + " <>:/\\|?*\""; - return true; - } - if (this->printer_technology() == ptFFF) { - bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode"); - bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"); - err_out = check_binary_vs_ascii_gcode_extension(printer_technology(), ext, supports_binary && uses_binary); - } - return !err_out.IsEmpty(); - }; +void Plater::export_gcode_to_path( + const fs::path &output_path, + const std::function &export_callback +) { + AppConfig &appconfig{*wxGetApp().app_config}; + RemovableDriveManager &removable_drive_manager{*wxGetApp().removable_drive_manager()}; + 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(); + export_callback(path_on_removable_media); + // 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); +} - wxString error_str; - if (check_for_error(output_path, error_str)) { - const t_link_clicked on_link_clicked = [](const std::string& key) -> void { wxGetApp().jump_to_option(key); }; - ErrorDialog(this, error_str, on_link_clicked).ShowModal(); - output_path.clear(); - } else if (printer_technology() == ptFFF) { - bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode"); - bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"); - alert_when_exporting_binary_gcode(supports_binary && uses_binary, - wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_string("printer_notes")); - } +struct PrintToExport{ std::reference_wrapper print; + std::reference_wrapper processor_result; + fs::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) { + return; + } + const fs::path &default_output_file{*optional_default_output_file}; + const std::string start_dir{get_output_start_dir(prefer_removable, default_output_file)}; + const auto optional_output_dir{get_multiple_output_dir(start_dir)}; + if (!optional_output_dir) { + return; + } + const fs_path &output_dir{*optional_output_dir}; + + std::vector prints_to_export; + + 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]}; + if (!print || print->empty()) { + continue; } + + const fs::path filename{ + default_output_file.stem().string() + + "_bed" + + std::to_string(print_index + 1) + + default_output_file.extension().string() + }; + const fs::path output_file{output_dir / filename}; + prints_to_export.push_back({*print, this->p->gcode_results[print_index], output_file}); } - 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); - - } + //BulkExportDialog dialog{prints_to_export}; + //if (dialog.ShowModal() != wxID_OK) { + // return; + //} + //prints_to_export = dialog.get_prints_to_export(); + + bool path_on_removable_media{false}; + 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()); + export_gcode_to_path( + print_to_export.output_path, + [&](const bool on_removable){ + this->p->background_process.finalize_gcode( + print_to_export.output_path.string(), + path_on_removable_media + ); + path_on_removable_media = on_removable || path_on_removable_media; + } + ); + } + + p->notification_manager->push_bulk_exporting_finished_notification(output_dir.string(), path_on_removable_media); } void Plater::export_stl_obj(bool extended, bool selection_only) @@ -6483,10 +6655,12 @@ void Plater::send_gcode_inner(DynamicPrintConfig* physical_printer_config) return; } - bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode"); - bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"); - alert_when_exporting_binary_gcode(supports_binary && uses_binary, - wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_string("printer_notes")); + alert_when_exporting_binary_gcode( + wxGetApp(). + preset_bundle->printers. + get_edited_preset(). + config.opt_string("printer_notes") + ); } upload_job.upload_data.upload_path = dlg.filename(); @@ -7128,6 +7302,8 @@ void Plater::update_menus() { p->menus.update(); } void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_action_buttons(ready_to_slice); } void Plater::show_action_buttons() const { p->show_action_buttons(p->ready_to_slice); } +void Plater::show_autoslicing_action_buttons() const { p->show_autoslicing_action_buttons(); }; + void Plater::copy_selection_to_clipboard() { // At first try to copy selected values to the ObjectList's clipboard diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index bbe136fb8d..26471b9571 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -215,6 +215,7 @@ public: void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects); void export_gcode(bool prefer_removable); + void export_all_gcodes(bool prefer_removable); void export_stl_obj(bool extended = false, bool selection_only = false); bool export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); void reload_from_disk(); @@ -277,6 +278,7 @@ public: void update_menus(); void show_action_buttons(const bool is_ready_to_slice) const; void show_action_buttons() const; + void show_autoslicing_action_buttons() const; wxString get_project_filename(const wxString& extension = wxEmptyString) const; void set_project_filename(const wxString& filename); @@ -447,6 +449,12 @@ public: wxMenu* multi_selection_menu(); private: + std::optional get_default_output_file(); + std::optional check_output_path_has_error(const boost::filesystem::path& path) const; + std::optional get_output_path(const std::string &start_dir, const fs_path &default_output_file); + std::optional get_multiple_output_dir(const std::string &start_dir); + + void export_gcode_to_path(const fs_path &output_path, const std::function &export_callback); void reslice_until_step_inner(int step, const ModelObject &object, bool postpone_error_messages); struct priv; diff --git a/src/slic3r/GUI/Sidebar.cpp b/src/slic3r/GUI/Sidebar.cpp index 86f5a5e855..1ce1c771c8 100644 --- a/src/slic3r/GUI/Sidebar.cpp +++ b/src/slic3r/GUI/Sidebar.cpp @@ -513,11 +513,30 @@ Sidebar::Sidebar(Plater *parent) auto *sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(m_scrolled_panel, 1, wxEXPAND); - sizer->Add(m_btns_sizer, 0, wxEXPAND | wxLEFT | wxBOTTOM + + const int buttons_sizer_flags{ + wxEXPAND + | wxLEFT + | wxBOTTOM #ifndef _WIN32 | wxRIGHT #endif // __linux__ - , margin_5); + }; + sizer->Add(m_btns_sizer, 0, buttons_sizer_flags, margin_5); + + m_autoslicing_btns_sizer = new wxBoxSizer(wxHORIZONTAL); + + init_scalable_btn(&m_btn_export_all_gcode_removable, "export_to_sd", _L("Export all to SD card / Flash drive") + " " + GUI::shortkey_ctrl_prefix() + "U"); + init_btn(&m_btn_export_all_gcode, _L("Export all G-codes") + dots, scaled_height); + init_btn(&m_btn_connect_gcode_all, _L("Send all to Connect"), scaled_height); + + m_autoslicing_btns_sizer->Add(m_btn_export_all_gcode, 1, wxEXPAND); + m_autoslicing_btns_sizer->Add(m_btn_connect_gcode_all, 1, wxEXPAND | wxLEFT, margin_5); + m_autoslicing_btns_sizer->Add(m_btn_export_all_gcode_removable, 0, wxLEFT, margin_5); + + m_autoslicing_btns_sizer->Show(false); + + sizer->Add(m_autoslicing_btns_sizer, 0, buttons_sizer_flags | wxTOP, margin_5); SetSizer(sizer); // Events @@ -552,6 +571,14 @@ Sidebar::Sidebar(Plater *parent) this->Bind(wxEVT_COMBOBOX, &Sidebar::on_select_preset, this); + m_btn_export_all_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + this->m_plater->export_all_gcodes(false); + }); + + m_btn_export_all_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + this->m_plater->export_all_gcodes(true); + }); + } Sidebar::~Sidebar() {} @@ -1098,6 +1125,15 @@ void Sidebar::show_btns_sizer(const bool show) m_scrolled_panel->Refresh(); } +void Sidebar::show_bulk_btns_sizer(const bool show) +{ + wxWindowUpdateLocker freeze_guard(this); + m_autoslicing_btns_sizer->Show(show); + + Layout(); + m_scrolled_panel->Refresh(); +} + void Sidebar::enable_buttons(bool enable) { m_btn_reslice->Enable(enable); @@ -1107,19 +1143,26 @@ void Sidebar::enable_buttons(bool enable) m_btn_connect_gcode->Enable(enable); } -bool Sidebar::show_reslice(bool show) const { +void Sidebar::enable_bulk_buttons(bool enable) +{ + m_btn_export_all_gcode->Enable(enable); + m_btn_export_all_gcode_removable->Enable(enable); + m_btn_connect_gcode_all->Enable(enable); +} + +bool Sidebar::show_reslice(bool show) const { if (this->m_autoslicing_mode) { return false; } return m_btn_reslice->Show(show); } -bool Sidebar::show_export(bool show) const { +bool Sidebar::show_export(bool show) const { if (this->m_autoslicing_mode) { return false; } return m_btn_export_gcode->Show(show); } -bool Sidebar::show_send(bool show) const { +bool Sidebar::show_send(bool show) const { if (this->m_autoslicing_mode) { return false; } @@ -1131,13 +1174,23 @@ bool Sidebar::show_export_removable(bool show) const { } return m_btn_export_gcode_removable->Show(show); } -bool Sidebar::show_connect(bool show) const { +bool Sidebar::show_connect(bool show) const { if (this->m_autoslicing_mode) { return false; } return m_btn_connect_gcode->Show(show); } +bool Sidebar::show_export_all(bool show) const { + return m_btn_export_all_gcode->Show(show); +}; +bool Sidebar::show_export_removable_all(bool show) const { + return m_btn_export_all_gcode_removable->Show(show); +}; +bool Sidebar::show_connect_all(bool show) const { + return m_btn_connect_gcode_all->Show(show); +}; + void Sidebar::switch_to_autoslicing_mode() { this->show_sliced_info_sizer(false); this->show_btns_sizer(false); @@ -1150,6 +1203,7 @@ void Sidebar::switch_from_autoslicing_mode() { } this->m_autoslicing_mode = false; this->show_sliced_info_sizer(true); + this->show_bulk_btns_sizer(false); } diff --git a/src/slic3r/GUI/Sidebar.hpp b/src/slic3r/GUI/Sidebar.hpp index 0f53abf5b7..70c2ebcf4c 100644 --- a/src/slic3r/GUI/Sidebar.hpp +++ b/src/slic3r/GUI/Sidebar.hpp @@ -76,12 +76,18 @@ class Sidebar : public wxPanel ObjectInfo* m_object_info { nullptr }; SlicedInfo* m_sliced_info { nullptr }; wxBoxSizer* m_btns_sizer { nullptr }; + wxBoxSizer* m_autoslicing_btns_sizer { nullptr }; + wxButton* m_btn_export_gcode { nullptr }; wxButton* m_btn_reslice { nullptr }; wxButton* m_btn_connect_gcode { nullptr }; ScalableButton* m_btn_send_gcode { nullptr }; ScalableButton* m_btn_export_gcode_removable{ nullptr }; //exports to removable drives (appears only if removable drive is connected) + // + wxButton* m_btn_export_all_gcode { nullptr }; + wxButton* m_btn_connect_gcode_all { nullptr }; + ScalableButton* m_btn_export_all_gcode_removable{ nullptr }; std::unique_ptr m_frequently_changed_parameters; std::unique_ptr m_object_manipulation; @@ -120,6 +126,7 @@ public: void show_info_sizer(); void show_sliced_info_sizer(const bool show); void show_btns_sizer(const bool show); + void show_bulk_btns_sizer(const bool show); void update_sliced_info_sizer(); @@ -131,6 +138,11 @@ public: bool show_export_removable(bool show) const; bool show_connect(bool show) const; + void enable_bulk_buttons(bool enable); + bool show_export_all(bool show) const; + bool show_export_removable_all(bool show) const; + bool show_connect_all(bool show) const; + void switch_to_autoslicing_mode(); void switch_from_autoslicing_mode();