Add export all functionality backend

- Missing BulkExportDialog
This commit is contained in:
Martin Šach 2024-12-05 12:43:12 +01:00 committed by Lukas Matena
parent 62f5fafe15
commit ab58ccff60
9 changed files with 371 additions and 108 deletions

View File

@ -175,7 +175,7 @@ void BackgroundSlicingProcess::process_fff()
if (this->set_step_started(bspsGCodeFinalize)) { if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) { if (! m_export_path.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); 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()) { } else if (! m_upload_job.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
prepare_upload(); prepare_upload();
@ -680,12 +680,12 @@ bool BackgroundSlicingProcess::invalidate_all_steps()
// G-code is generated in m_temp_output_path. // G-code is generated in m_temp_output_path.
// Optionally run a post-processing script on a copy of 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). // 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")); 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. // 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; std::string output_path = m_temp_output_path;
// Both output_path and export_path ar in-out parameters. // 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 // 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; int copy_ret_val = CopyFileResult::SUCCESS;
try 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(); remove_post_processed_temp_file();
} }
catch (...) catch (...)

View File

@ -175,7 +175,8 @@ public:
// This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs), // 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. // and it does not account for the OctoPrint scheduling.
bool finished() const { return m_print->finished(); } bool finished() const { return m_print->finished(); }
void finalize_gcode(const std::string &path, const bool path_on_removable_media);
private: private:
void thread_proc(); void thread_proc();
// Calls thread_proc(), catches all C++ exceptions and shows them using wxApp::OnUnhandledException(). // Calls thread_proc(), catches all C++ exceptions and shows them using wxApp::OnUnhandledException().
@ -266,7 +267,6 @@ private:
bool invalidate_all_steps(); bool invalidate_all_steps();
// If the background processing stop was requested, throw CanceledException. // If the background processing stop was requested, throw CanceledException.
void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); } void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
void finalize_gcode();
void prepare_upload(); void prepare_upload();
// To be executed at the background thread. // To be executed at the background thread.
ThumbnailsList render_thumbnails(const ThumbnailsParams &params); ThumbnailsList render_thumbnails(const ThumbnailsParams &params);

View File

@ -125,9 +125,6 @@ void GLCanvas3D::select_bed(int i, bool triggered_by_user)
m_sequential_print_clearance.m_evaluating = true; m_sequential_print_clearance.m_evaluating = true;
reset_sequential_print_clearance(); reset_sequential_print_clearance();
// The stop call above schedules some events that would be processed after the switch. // 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 // 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 // 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(); wxGetApp().plater()->schedule_background_process();
} }
} else { } else {
wxGetApp().plater()->show_autoslicing_action_buttons();
render_print_statistics(); render_print_statistics();
} }
} }
@ -6721,6 +6719,7 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector()
if (!s_multiple_beds.is_autoslicing()) { if (!s_multiple_beds.is_autoslicing()) {
s_multiple_beds.start_autoslice([this](int i, bool user) { this->select_bed(i, user); }); s_multiple_beds.start_autoslice([this](int i, bool user) { this->select_bed(i, user); });
wxGetApp().sidebar().switch_to_autoslicing_mode(); wxGetApp().sidebar().switch_to_autoslicing_mode();
wxGetApp().plater()->show_autoslicing_action_buttons();
} }
} }

View File

@ -2350,7 +2350,15 @@ void NotificationManager::push_exporting_finished_notification(const std::string
{ {
close_notification_of_type(NotificationType::ExportFinished); close_notification_of_type(NotificationType::ExportFinished);
NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotificationLevel, on_removable ? 0 : 20, _u8L("Exporting finished.") + "\n" + path }; NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotificationLevel, on_removable ? 0 : 20, _u8L("Exporting finished.") + "\n" + path };
push_notification_data(std::make_unique<NotificationManager::ExportFinishedNotification>(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0); push_notification_data(std::make_unique<NotificationManager::ExportFinishedNotification>(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<NotificationManager::ExportFinishedNotification>(data, m_id_provider, m_evt_handler, on_removable, dir_path), 0);
set_slicing_progress_hidden(); set_slicing_progress_hidden();
} }

View File

@ -231,6 +231,7 @@ public:
void set_sla(bool b) { set_fff(!b); } 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 // 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_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 // notifications with progress bar
// print host upload // print host upload
void push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); 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 class ExportFinishedNotification : public PopNotification
{ {
public: 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) ExportFinishedNotification(
: PopNotification(n, id_provider, evt_handler) 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_to_removable(to_removable)
, m_export_path(export_path)
, m_export_dir_path(export_dir_path) , m_export_dir_path(export_dir_path)
{ {
m_multiline = true; m_multiline = true;

View File

@ -534,6 +534,7 @@ struct Plater::priv
void on_3dcanvas_mouse_dragging_finished(SimpleEvent&); void on_3dcanvas_mouse_dragging_finished(SimpleEvent&);
void show_action_buttons(const bool is_ready_to_slice) const; void show_action_buttons(const bool is_ready_to_slice) const;
void show_autoslicing_action_buttons() const;
bool can_show_upload_to_connect() const; bool can_show_upload_to_connect() const;
// Set the bed shape to a single closed 2D polygon(array of two element arrays), // 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, // 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 current bed was invalidated, update thumbnails for all beds:
if (int num = s_multiple_beds.get_number_of_beds(); num > 1 && any_status_changed) { if (int num = s_multiple_beds.get_number_of_beds(); num > 1 && any_status_changed) {
ThumbnailData data; 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 ? 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::Reslice, slice_string); sidebar->set_btn_label(ActionButtonType::Reslice, slice_string);
if (background_process.empty()) {
if (background_process.finished()) sidebar->enable_buttons(false);
} else if (background_process.finished())
show_action_buttons(false); show_action_buttons(false);
else if (!background_process.empty() && else if (!background_process.empty() &&
!background_process.running()) /* Do not update buttons if background process is running !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<ConfigOptionString>("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> &print){
return print->finished() || print->empty();
}
)};
sidebar->enable_bulk_buttons(all_finished);
}
void Plater::priv::enter_gizmos_stack() void Plater::priv::enter_gizmos_stack()
{ {
assert(m_undo_redo_stack_active == &m_undo_redo_stack_main); 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 // 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 // transition period easier for the users, because bgcode files are not recognized by older firmwares
// without any error message. // 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 const bool supports_binary = wxGetApp()
&& (boost::algorithm::contains(printer_notes, "PRINTER_MODEL_XL") .preset_bundle->printers
|| boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MINI") .get_edited_preset()
|| boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MK4") .config.opt_bool("binary_gcode");
|| boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MK3.9"))) 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; AppConfig* app_config = wxGetApp().app_config;
wxWindow* parent = wxGetApp().mainframe; wxWindow* parent = wxGetApp().mainframe;
const std::string option_key = "dont_warn_about_firmware_version_when_exporting_binary_gcode"; 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<fs::path> 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<wxString> 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<fs::path> 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<fs::path> 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) 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. // If possible, remove accents from accented latin characters.
// This function is useful for generating file names to be processed by legacy firmwares. // This function is useful for generating file names to be processed by legacy firmwares.
fs::path default_output_file; const auto optional_default_output_file{this->get_default_output_file()};
try { if (!optional_default_output_file) {
// 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);
return; return;
} }
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); const fs::path &default_output_file{*optional_default_output_file};
AppConfig &appconfig = *wxGetApp().app_config; const std::string start_dir{get_output_start_dir(prefer_removable, default_output_file)};
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);
}
fs::path output_path; const auto optional_output_path{get_output_path(start_dir, default_output_file)};
{ if (!optional_output_path) {
std::string ext = default_output_file.extension().string(); return;
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 / SL1S file as:"), }
start_dir, const fs::path &output_path{*optional_output_path};
from_path(default_output_file.filename()),
printer_technology() == ptFFF ? GUI::file_wildcards(FT_GCODE, ext) : if (printer_technology() == ptFFF) {
GUI::sla_wildcards(active_sla_print().printer_config().sla_archive_format.value.c_str(), ext), alert_when_exporting_binary_gcode(
wxFD_SAVE | wxFD_OVERWRITE_PROMPT 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 { void Plater::export_gcode_to_path(
const std::string filename = path.filename().string(); const fs::path &output_path,
const std::string ext = boost::algorithm::to_lower_copy(path.extension().string()); const std::function<void(bool)> &export_callback
if (has_illegal_characters(filename)) { ) {
err_out = _L("The provided file name is not valid.") + "\n" + AppConfig &appconfig{*wxGetApp().app_config};
_L("The following characters are not allowed by a FAT file system:") + " <>:/\\|?*\""; RemovableDriveManager &removable_drive_manager{*wxGetApp().removable_drive_manager()};
return true; 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);
if (this->printer_technology() == ptFFF) { p->exporting_status = path_on_removable_media ? ExportingStatus::EXPORTING_TO_REMOVABLE : ExportingStatus::EXPORTING_TO_LOCAL;
bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode"); p->last_output_path = output_path.string();
bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"); p->last_output_dir_path = output_path.parent_path().string();
err_out = check_binary_vs_ascii_gcode_extension(printer_technology(), ext, supports_binary && uses_binary); export_callback(path_on_removable_media);
} // Storing a path to AppConfig either as path to removable media or a path to internal media.
return !err_out.IsEmpty(); // 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; struct PrintToExport{ std::reference_wrapper<Print> print;
if (check_for_error(output_path, error_str)) { std::reference_wrapper<GCodeProcessorResult> processor_result;
const t_link_clicked on_link_clicked = [](const std::string& key) -> void { wxGetApp().jump_to_option(key); }; fs::path output_path;
ErrorDialog(this, error_str, on_link_clicked).ShowModal(); };
output_path.clear();
} else if (printer_technology() == ptFFF) { void Plater::export_all_gcodes(bool prefer_removable) {
bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode"); const auto optional_default_output_file{this->get_default_output_file()};
bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"); if (!optional_default_output_file) {
alert_when_exporting_binary_gcode(supports_binary && uses_binary, return;
wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_string("printer_notes")); }
} 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<PrintToExport> prints_to_export;
for (std::size_t print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) {
const std::unique_ptr<Print> &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()) { //BulkExportDialog dialog{prints_to_export};
bool path_on_removable_media = removable_drive_manager.set_and_verify_last_save_path(output_path.string()); //if (dialog.ShowModal() != wxID_OK) {
p->notification_manager->new_export_began(path_on_removable_media); // return;
p->exporting_status = path_on_removable_media ? ExportingStatus::EXPORTING_TO_REMOVABLE : ExportingStatus::EXPORTING_TO_LOCAL; //}
p->last_output_path = output_path.string(); //prints_to_export = dialog.get_prints_to_export();
p->last_output_dir_path = output_path.parent_path().string();
p->export_gcode(output_path, path_on_removable_media, PrintHostJob()); bool path_on_removable_media{false};
// Storing a path to AppConfig either as path to removable media or a path to internal media. for (const PrintToExport &print_to_export : prints_to_export) {
// 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 this->p->background_process.set_fff_print(&print_to_export.print.get());
// while the dialog was open. this->p->background_process.set_gcode_result(&print_to_export.processor_result.get());
appconfig.update_last_output_dir(output_path.parent_path().string(), path_on_removable_media); 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) void Plater::export_stl_obj(bool extended, bool selection_only)
@ -6483,10 +6655,12 @@ void Plater::send_gcode_inner(DynamicPrintConfig* physical_printer_config)
return; return;
} }
bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode"); alert_when_exporting_binary_gcode(
bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"); wxGetApp().
alert_when_exporting_binary_gcode(supports_binary && uses_binary, preset_bundle->printers.
wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_string("printer_notes")); get_edited_preset().
config.opt_string("printer_notes")
);
} }
upload_job.upload_data.upload_path = dlg.filename(); 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 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_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() void Plater::copy_selection_to_clipboard()
{ {
// At first try to copy selected values to the ObjectList's clipboard // At first try to copy selected values to the ObjectList's clipboard

View File

@ -215,6 +215,7 @@ public:
void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects); void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects);
void export_gcode(bool prefer_removable); void export_gcode(bool prefer_removable);
void export_all_gcodes(bool prefer_removable);
void export_stl_obj(bool extended = false, bool selection_only = false); void export_stl_obj(bool extended = false, bool selection_only = false);
bool export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); bool export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
void reload_from_disk(); void reload_from_disk();
@ -277,6 +278,7 @@ public:
void update_menus(); void update_menus();
void show_action_buttons(const bool is_ready_to_slice) const; void show_action_buttons(const bool is_ready_to_slice) const;
void show_action_buttons() const; void show_action_buttons() const;
void show_autoslicing_action_buttons() const;
wxString get_project_filename(const wxString& extension = wxEmptyString) const; wxString get_project_filename(const wxString& extension = wxEmptyString) const;
void set_project_filename(const wxString& filename); void set_project_filename(const wxString& filename);
@ -447,6 +449,12 @@ public:
wxMenu* multi_selection_menu(); wxMenu* multi_selection_menu();
private: private:
std::optional<fs_path> get_default_output_file();
std::optional<wxString> check_output_path_has_error(const boost::filesystem::path& path) const;
std::optional<fs_path> get_output_path(const std::string &start_dir, const fs_path &default_output_file);
std::optional<fs_path> get_multiple_output_dir(const std::string &start_dir);
void export_gcode_to_path(const fs_path &output_path, const std::function<void(bool)> &export_callback);
void reslice_until_step_inner(int step, const ModelObject &object, bool postpone_error_messages); void reslice_until_step_inner(int step, const ModelObject &object, bool postpone_error_messages);
struct priv; struct priv;

View File

@ -513,11 +513,30 @@ Sidebar::Sidebar(Plater *parent)
auto *sizer = new wxBoxSizer(wxVERTICAL); auto *sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_scrolled_panel, 1, wxEXPAND); 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 #ifndef _WIN32
| wxRIGHT | wxRIGHT
#endif // __linux__ #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); SetSizer(sizer);
// Events // Events
@ -552,6 +571,14 @@ Sidebar::Sidebar(Plater *parent)
this->Bind(wxEVT_COMBOBOX, &Sidebar::on_select_preset, this); 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() {} Sidebar::~Sidebar() {}
@ -1098,6 +1125,15 @@ void Sidebar::show_btns_sizer(const bool show)
m_scrolled_panel->Refresh(); 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) void Sidebar::enable_buttons(bool enable)
{ {
m_btn_reslice->Enable(enable); m_btn_reslice->Enable(enable);
@ -1107,19 +1143,26 @@ void Sidebar::enable_buttons(bool enable)
m_btn_connect_gcode->Enable(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) { if (this->m_autoslicing_mode) {
return false; return false;
} }
return m_btn_reslice->Show(show); 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) { if (this->m_autoslicing_mode) {
return false; return false;
} }
return m_btn_export_gcode->Show(show); 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) { if (this->m_autoslicing_mode) {
return false; return false;
} }
@ -1131,13 +1174,23 @@ bool Sidebar::show_export_removable(bool show) const {
} }
return m_btn_export_gcode_removable->Show(show); 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) { if (this->m_autoslicing_mode) {
return false; return false;
} }
return m_btn_connect_gcode->Show(show); 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() { void Sidebar::switch_to_autoslicing_mode() {
this->show_sliced_info_sizer(false); this->show_sliced_info_sizer(false);
this->show_btns_sizer(false); this->show_btns_sizer(false);
@ -1150,6 +1203,7 @@ void Sidebar::switch_from_autoslicing_mode() {
} }
this->m_autoslicing_mode = false; this->m_autoslicing_mode = false;
this->show_sliced_info_sizer(true); this->show_sliced_info_sizer(true);
this->show_bulk_btns_sizer(false);
} }

View File

@ -76,12 +76,18 @@ class Sidebar : public wxPanel
ObjectInfo* m_object_info { nullptr }; ObjectInfo* m_object_info { nullptr };
SlicedInfo* m_sliced_info { nullptr }; SlicedInfo* m_sliced_info { nullptr };
wxBoxSizer* m_btns_sizer { nullptr }; wxBoxSizer* m_btns_sizer { nullptr };
wxBoxSizer* m_autoslicing_btns_sizer { nullptr };
wxButton* m_btn_export_gcode { nullptr }; wxButton* m_btn_export_gcode { nullptr };
wxButton* m_btn_reslice { nullptr }; wxButton* m_btn_reslice { nullptr };
wxButton* m_btn_connect_gcode { nullptr }; wxButton* m_btn_connect_gcode { nullptr };
ScalableButton* m_btn_send_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) 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<FreqChangedParams> m_frequently_changed_parameters; std::unique_ptr<FreqChangedParams> m_frequently_changed_parameters;
std::unique_ptr<ObjectManipulation> m_object_manipulation; std::unique_ptr<ObjectManipulation> m_object_manipulation;
@ -120,6 +126,7 @@ public:
void show_info_sizer(); void show_info_sizer();
void show_sliced_info_sizer(const bool show); void show_sliced_info_sizer(const bool show);
void show_btns_sizer(const bool show); void show_btns_sizer(const bool show);
void show_bulk_btns_sizer(const bool show);
void update_sliced_info_sizer(); void update_sliced_info_sizer();
@ -131,6 +138,11 @@ public:
bool show_export_removable(bool show) const; bool show_export_removable(bool show) const;
bool show_connect(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_to_autoslicing_mode();
void switch_from_autoslicing_mode(); void switch_from_autoslicing_mode();