From 0dcc654d3901e1fc8884d8b658c9b86efafbb58b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Tue, 10 Dec 2024 15:50:28 +0100 Subject: [PATCH 01/18] Add multiple beds error states handling Print can be unslicable for various reasons: - object partially on bed - invalid data (e.g. when sequential printing) - bed is empty Keep this information for each bed a behave accordingly. --- src/libslic3r/MultipleBeds.cpp | 13 ++++ src/libslic3r/MultipleBeds.hpp | 13 ++++ src/slic3r/GUI/GLCanvas3D.cpp | 95 ++++++++++++++------------- src/slic3r/GUI/Plater.cpp | 115 +++++++++++++++++++++++++++------ src/slic3r/GUI/Sidebar.cpp | 1 + 5 files changed, 173 insertions(+), 64 deletions(-) diff --git a/src/libslic3r/MultipleBeds.cpp b/src/libslic3r/MultipleBeds.cpp index 66847e28d4..6ff38a95cf 100644 --- a/src/libslic3r/MultipleBeds.cpp +++ b/src/libslic3r/MultipleBeds.cpp @@ -13,6 +13,19 @@ MultipleBeds s_multiple_beds; bool s_reload_preview_after_switching_beds = false; bool s_beds_just_switched = false; +bool is_sliceable(const PrintStatus status) { + if (status == PrintStatus::empty) { + return false; + } + if (status == PrintStatus::invalid) { + return false; + } + if (status == PrintStatus::outside) { + return false; + } + return true; +} + namespace BedsGrid { Index grid_coords_abs2index(GridCoords coords) { coords = {std::abs(coords.x()), std::abs(coords.y())}; diff --git a/src/libslic3r/MultipleBeds.hpp b/src/libslic3r/MultipleBeds.hpp index 9f70bcaa9f..bbf5ba4088 100644 --- a/src/libslic3r/MultipleBeds.hpp +++ b/src/libslic3r/MultipleBeds.hpp @@ -29,6 +29,19 @@ inline std::vector s_bed_selector_thumbnail_texture_ids; inline std::array s_bed_selector_thumbnail_changed; inline bool bed_selector_updated{false}; +enum class PrintStatus { + idle, + running, + finished, + outside, + invalid, + empty +}; + +bool is_sliceable(const PrintStatus status); + +inline std::array s_print_statuses; + class MultipleBeds { public: MultipleBeds() = default; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f094a965f9..5784cbddf2 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -125,6 +125,8 @@ void GLCanvas3D::select_bed(int i, bool triggered_by_user) m_sequential_print_clearance.m_evaluating = true; reset_sequential_print_clearance(); + post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, is_sliceable(s_print_statuses[i]))); + // 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 @@ -1560,8 +1562,9 @@ bool GLCanvas3D::check_volumes_outside_state(GLVolumeCollection& volumes, ModelI if (volume->printable) { if (overall_state == ModelInstancePVS_Inside && volume->is_outside) overall_state = ModelInstancePVS_Fully_Outside; - if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && state == BuildVolume::ObjectState::Colliding) + if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && state == BuildVolume::ObjectState::Colliding) { overall_state = ModelInstancePVS_Partly_Outside; + } contained_min_one |= !volume->is_outside; if (bed_idx != -1 && bed_idx == s_multiple_beds.get_number_of_beds()) @@ -2211,22 +2214,20 @@ void GLCanvas3D::render() if (m_picking_enabled && m_rectangle_selection.is_dragging()) m_rectangle_selection.render(*this); } else { - const auto &prints{ - tcb::span{wxGetApp().plater()->get_fff_prints()} - .subspan(0, s_multiple_beds.get_number_of_beds()) - }; + const auto &prints{wxGetApp().plater()->get_fff_prints()}; - const bool all_finished{std::all_of( - prints.begin(), - prints.end(), - [](const std::unique_ptr &print){ - return print->finished() || print->empty(); + bool all_finished{true}; + for (std::size_t bed_index{}; bed_index < s_multiple_beds.get_number_of_beds(); ++bed_index) { + const std::unique_ptr &print{prints[bed_index]}; + if (!print->finished() && is_sliceable(s_print_statuses[bed_index])) { + all_finished = false; + break; } - )}; + } if (!all_finished) { render_autoslicing_wait(); - if (fff_print()->finished() || fff_print()->empty()) { + if (fff_print()->finished() || !is_sliceable(s_print_statuses[s_multiple_beds.get_active_bed()])) { s_multiple_beds.autoslice_next_bed(); wxYield(); } else { @@ -2847,7 +2848,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // checks for geometry outside the print volume to render it accordingly if (!m_volumes.empty()) { ModelInstanceEPrintVolumeState state; - const bool contained_min_one = check_volumes_outside_state(m_volumes, &state, !force_full_scene_refresh); + check_volumes_outside_state(m_volumes, &state, !force_full_scene_refresh); const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside); const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside); @@ -2870,15 +2871,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re _set_warning_notification(EWarning::SlaSupportsOutside, false); } } - - post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, - contained_min_one && !m_model->objects.empty() && !partlyOut)); } else { _set_warning_notification(EWarning::ObjectOutside, false); _set_warning_notification(EWarning::ObjectClashed, false); _set_warning_notification(EWarning::SlaSupportsOutside, false); - post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); } refresh_camera_scene_box(); @@ -6581,17 +6578,14 @@ void GLCanvas3D::_render_overlays() #define use_scrolling 1 -enum class PrintStatus { - idle, - running, - finished -}; - std::string get_status_text(PrintStatus status) { switch(status) { case PrintStatus::idle: return _u8L("Unsliced"); case PrintStatus::running: return _u8L("Slicing..."); case PrintStatus::finished: return _u8L("Sliced"); + case PrintStatus::outside: return _u8L("Outside"); + case PrintStatus::invalid: return _u8L("Invalid"); + case PrintStatus::empty: return _u8L("Empty"); } return {}; } @@ -6601,6 +6595,9 @@ wchar_t get_raw_status_icon(const PrintStatus status) { case PrintStatus::finished: return ImGui::PrintFinished; case PrintStatus::running: return ImGui::PrintRunning; case PrintStatus::idle: return ImGui::PrintIdle; + case PrintStatus::outside: return ImGui::PrintIdle; + case PrintStatus::invalid: return ImGui::PrintIdle; + case PrintStatus::empty: return ImGui::PrintIdle; } return ImGui::PrintIdle; } @@ -6685,39 +6682,42 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() auto render_bed_button = [btn_side, btn_border, btn_size, btn_padding, this, &extra_frame, scale](int i) { - bool empty = ! s_multiple_beds.is_bed_occupied(i); bool inactive = i != s_multiple_beds.get_active_bed() || s_multiple_beds.is_autoslicing(); ImGui::PushStyleColor(ImGuiCol_Button, ImGuiPureWrap::COL_GREY_DARK); ImGui::PushStyleColor(ImGuiCol_Border, inactive ? ImGuiPureWrap::COL_GREY_DARK : ImGuiPureWrap::COL_BUTTON_ACTIVE); - if (empty) - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + const PrintStatus print_status{s_print_statuses[i]}; - bool clicked = false; - - std::optional print_status; if (current_printer_technology() == ptFFF) { - print_status = PrintStatus::idle; - if (wxGetApp().plater()->get_fff_prints()[i]->finished()) { - print_status = PrintStatus::finished; - } else if (m_process->fff_print() == wxGetApp().plater()->get_fff_prints()[i].get() && m_process->running()) { - print_status = PrintStatus::running; + if ( !previous_print_status[i] + || print_status != previous_print_status[i] + ) { + extra_frame = true; } + previous_print_status[i] = print_status; } - if (!previous_print_status[i] || print_status != previous_print_status[i]) { - extra_frame = true; - } - previous_print_status[i] = print_status; - if (s_bed_selector_thumbnail_changed[i]) { extra_frame = true; s_bed_selector_thumbnail_changed[i] = false; } - if (i >= int(s_bed_selector_thumbnail_texture_ids.size()) || empty) { - clicked = ImGui::Button(empty ? "empty" : std::to_string(i + 1).c_str(), btn_size + btn_padding); + if ( + !is_sliceable(print_status) + ) { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + } + + bool clicked = false; + if (!is_sliceable(print_status)) { + ImGui::Button(get_status_text(print_status).c_str(), btn_size + btn_padding); + } else if ( + i >= int(s_bed_selector_thumbnail_texture_ids.size()) + ) { + clicked = ImGui::Button( + std::to_string(i + 1).c_str(), btn_size + btn_padding + ); } else { clicked = bed_selector_thumbnail( btn_size, @@ -6726,19 +6726,22 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() btn_border, scale, s_bed_selector_thumbnail_texture_ids[i], - print_status + current_printer_technology() == ptFFF ? std::optional{print_status} : std::nullopt ); } - if (clicked && ! empty) + if (clicked && is_sliceable(print_status)) select_bed(i, true); ImGui::PopStyleColor(2); - if (empty) + if ( + !is_sliceable(print_status) + ) { ImGui::PopItemFlag(); + } - if (print_status) { - const std::string status_text{get_status_text(*print_status)}; + if (current_printer_technology() == ptFFF) { + const std::string status_text{get_status_text(print_status)}; if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", status_text.c_str()); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 67f5ec33d2..480a2ef0f0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -581,8 +581,7 @@ struct Plater::priv 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 layers_height_allowed() const; @@ -1888,13 +1887,61 @@ void Plater::priv::selection_changed() view3D->render(); } +std::size_t count_instances(SpanOfConstPtrs objects) { + return std::accumulate( + objects.begin(), + objects.end(), + std::size_t{}, + [](const std::size_t result, const PrintObject *object){ + return result + object->instances().size(); + } + ); +} + +std::size_t count_instances(const std::map &bed_instances, const int bed_index) { + return std::accumulate( + bed_instances.begin(), + bed_instances.end(), + std::size_t{}, + [&](const std::size_t result, const auto &key_value){ + const auto &[object_id, _bed_index]{key_value}; + if (_bed_index != bed_index) { + return result; + } + return result + 1; + } + ); +} + 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->get_canvas3d()->check_volumes_outside_state() == ModelInstancePVS_Inside; + // + if (printer_technology == ptFFF) { + for (std::size_t bed_index{}; bed_index < s_multiple_beds.get_number_of_beds(); ++bed_index) { + const std::size_t print_instances_count{ + count_instances(wxGetApp().plater()->get_fff_prints()[bed_index]->objects()) + }; + const std::size_t bed_instances_count{ + count_instances(s_multiple_beds.get_inst_map(), bed_index) + }; + if (print_instances_count != bed_instances_count) { + s_print_statuses[bed_index] = PrintStatus::outside; + } else if (print_instances_count == 0) { + s_print_statuses[bed_index] = PrintStatus::empty; + } + } + } else { + if (model.objects.empty()) { + s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::empty; + } + } - sidebar->enable_buttons(s_multiple_beds.is_bed_occupied(s_multiple_beds.get_active_bed()) && !model.objects.empty() && !export_in_progress && model_fits); + sidebar->enable_buttons( + s_multiple_beds.is_bed_occupied(s_multiple_beds.get_active_bed()) + && !export_in_progress + && is_sliceable(s_print_statuses[s_multiple_beds.get_active_bed()]) + ); } void Plater::priv::select_all() @@ -2302,6 +2349,34 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool throw std::runtime_error{"Ivalid printer technology!"}; } + for (std::size_t bed_index{}; bed_index < s_multiple_beds.get_number_of_beds(); ++bed_index) { + if (printer_technology == ptFFF) { + if (apply_statuses[bed_index] != Print::ApplyStatus::APPLY_STATUS_UNCHANGED) { + s_print_statuses[bed_index] = PrintStatus::idle; + } + } else if (printer_technology == ptSLA) { + if (apply_statuses[0] != Print::ApplyStatus::APPLY_STATUS_UNCHANGED) { + s_print_statuses[bed_index] = PrintStatus::idle; + } + } else { + throw std::runtime_error{"Ivalid printer technology!"}; + } + } + + if (printer_technology == ptFFF) { + for (std::size_t bed_index{0}; bed_index < q->p->fff_prints.size(); ++bed_index) { + const std::unique_ptr &print{q->p->fff_prints[bed_index]}; + using MultipleBedsUtils::with_single_bed_model_fff; + with_single_bed_model_fff(model, bed_index, [&](){ + std::vector warnings; + std::string err{print->validate(&warnings)}; + if (!err.empty()) { + s_print_statuses[bed_index] = PrintStatus::invalid; + } + }); + } + } + const bool any_status_changed{std::any_of( apply_statuses.begin(), apply_statuses.end(), @@ -2346,7 +2421,6 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool notification_manager->set_slicing_progress_hidden(); } - if ((invalidated != Print::APPLY_STATUS_UNCHANGED || force_validation) && ! background_process.empty()) { // The delayed error message is no more valid. delayed_error_message.clear(); @@ -2410,7 +2484,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool return return_state; } } - + if (! this->delayed_error_message.empty()) // Reusing the old state. return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; @@ -2423,7 +2497,6 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool actualize_slicing_warnings(*this->background_process.current_print()); actualize_object_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() && @@ -2439,7 +2512,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool const wxString invalid_str = _L("Invalid data"); for (auto btn : {ActionButtonType::Reslice, ActionButtonType::SendGCode, ActionButtonType::Export}) sidebar->set_btn_label(btn, invalid_str); - process_completed_with_error = true; + s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::invalid; } else { @@ -2455,9 +2528,7 @@ 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.empty()) { - sidebar->enable_buttons(false); - } else if (background_process.finished()) + 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 @@ -2467,6 +2538,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool show_action_buttons(true); } + this->q->object_list_changed(); return return_state; } @@ -3076,10 +3148,12 @@ void Plater::priv::set_current_panel(wxPanel* panel) if (wxGetApp().is_editor()) { // 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->get_canvas3d()->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside; - if (s_multiple_beds.is_bed_occupied(s_multiple_beds.get_active_bed()) && !model.objects.empty() && !export_in_progress && model_fits) { + if ( + s_multiple_beds.is_bed_occupied(s_multiple_beds.get_active_bed()) + && !export_in_progress + && is_sliceable(s_print_statuses[s_multiple_beds.get_active_bed()]) + ) { preview->get_canvas3d()->init_gcode_viewer(); preview->get_canvas3d()->load_gcode_shells(); q->reslice(); @@ -3258,6 +3332,7 @@ void Plater::priv::on_slicing_began() notification_manager->close_notification_of_type(NotificationType::SignDetected); notification_manager->close_notification_of_type(NotificationType::ExportFinished); notification_manager->set_slicing_progress_began(); + s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::running; } void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) { @@ -3349,15 +3424,19 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) const wxString invalid_str = _L("Invalid data"); for (auto btn : { ActionButtonType::Reslice, ActionButtonType::SendGCode, ActionButtonType::Export }) sidebar->set_btn_label(btn, invalid_str); - process_completed_with_error = true; } has_error = true; + s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::invalid; } if (evt.cancelled()) { this->notification_manager->set_slicing_progress_canceled(_u8L("Slicing Cancelled.")); + s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::idle; } this->sidebar->show_sliced_info_sizer(evt.success()); + if (evt.success()) { + s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::finished; + } // 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 @@ -5904,7 +5983,7 @@ void Plater::export_gcode(bool prefer_removable) return; - if (p->process_completed_with_error) + if (!is_sliceable(s_print_statuses[s_multiple_beds.get_active_bed()])) return; // If possible, remove accents from accented latin characters. @@ -6395,7 +6474,7 @@ void Plater::export_toolpaths_to_obj() const void Plater::reslice() { // There is "invalid data" button instead "slice now" - if (p->process_completed_with_error) + if (!is_sliceable(s_print_statuses[s_multiple_beds.get_active_bed()])) return; // In case SLA gizmo is in editing mode, refuse to continue diff --git a/src/slic3r/GUI/Sidebar.cpp b/src/slic3r/GUI/Sidebar.cpp index 3476a97507..2f7df4ca37 100644 --- a/src/slic3r/GUI/Sidebar.cpp +++ b/src/slic3r/GUI/Sidebar.cpp @@ -763,6 +763,7 @@ void Sidebar::on_select_preset(wxCommandEvent& evt) this->switch_from_autoslicing_mode(); this->m_plater->regenerate_thumbnails(); this->m_plater->update(); + s_print_statuses.fill(PrintStatus::idle); } #ifdef __WXMSW__ From 4348417ca82b76a0ef25073afd1607ea2220e88e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Tue, 10 Dec 2024 17:56:09 +0100 Subject: [PATCH 02/18] Better filenames during bulk export/send - Pick better default names during bulk export - Let user change the filenames after picking a printer during send to connect --- src/slic3r/GUI/Plater.cpp | 132 ++++++++++++++++++++++++++------------ src/slic3r/GUI/Plater.hpp | 6 ++ 2 files changed, 98 insertions(+), 40 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 480a2ef0f0..7ca787dbfe 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6039,11 +6039,37 @@ struct PrintToExport { std::size_t bed{}; }; +void Plater::with_mocked_fff_background_process( + Print &print, + GCodeProcessorResult &result, + const int bed_index, + const std::function &callable +) { + Print *original_print{&active_fff_print()}; + GCodeProcessorResult *original_result{this->p->background_process.get_gcode_result()}; + const int original_bed{s_multiple_beds.get_active_bed()}; + PrinterTechnology original_technology{this->printer_technology()}; + ScopeGuard guard{[&](){ + this->p->background_process.set_fff_print(original_print); + this->p->background_process.set_gcode_result(original_result); + this->p->background_process.select_technology(original_technology); + s_multiple_beds.set_active_bed(original_bed); + }}; + + this->p->background_process.set_fff_print(&print); + this->p->background_process.set_gcode_result(&result); + this->p->background_process.select_technology(this->p->printer_technology); + s_multiple_beds.set_active_bed(bed_index); + + callable(); +} + 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)}; @@ -6052,20 +6078,37 @@ 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]}; if (!print || print->empty()) { continue; } + fs::path default_filename{default_output_file.filename()}; + this->with_mocked_fff_background_process( + *print, + this->p->gcode_results[print_index], + print_index, + [&](){ + const auto optional_file{this->get_default_output_file()}; + if (!optional_file) { + return; + } + const fs::path &default_file{*optional_file}; + default_filename = default_file.filename(); + } + ); + const fs::path filename{ - default_output_file.stem().string() + default_filename.stem().string() + "_bed" + std::to_string(print_index + 1) - + default_output_file.extension().string() + + default_filename.extension().string() }; const fs::path output_file{output_dir / filename}; prints_to_export.push_back({*print, this->p->gcode_results[print_index], output_file, print_index}); @@ -6083,27 +6126,24 @@ void Plater::export_all_gcodes(bool prefer_removable) { bool path_on_removable_media{false}; - Print *original_print{&active_fff_print()}; - GCodeProcessorResult *original_result{this->p->background_process.get_gcode_result()}; - const int original_bed{s_multiple_beds.get_active_bed()}; - ScopeGuard guard{[&](){ - this->p->background_process.set_fff_print(original_print); - this->p->background_process.set_gcode_result(original_result); - s_multiple_beds.set_active_bed(original_bed); - }}; 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()); - this->p->background_process.set_temp_output_path(print_to_export.bed); - 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 + with_mocked_fff_background_process( + print_to_export.print, + print_to_export.processor_result, + print_to_export.bed, + [&](){ + this->p->background_process.set_temp_output_path(print_to_export.bed); + 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; + } ); - path_on_removable_media = on_removable || path_on_removable_media; } ); } @@ -6684,35 +6724,47 @@ void Plater::connect_gcode_all() { } const PrusaConnectNew connect{*print_host_ptr}; - Print *original_print{&active_fff_print()}; - const int original_bed{s_multiple_beds.get_active_bed()}; - PrinterTechnology original_technology{this->printer_technology()}; - - ScopeGuard guard{[&](){ - this->p->background_process.set_fff_print(original_print); - s_multiple_beds.set_active_bed(original_bed); - this->p->background_process.select_technology(original_technology); - }}; + 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]}; if (!print || print->empty()) { continue; } - this->p->background_process.set_fff_print(print.get()); - this->p->background_process.set_temp_output_path(print_index); - this->p->background_process.select_technology(this->p->printer_technology); - PrintHostJob upload_job; - upload_job.upload_data = upload_job_template.upload_data; - upload_job.printhost = std::make_unique(connect); - upload_job.cancelled = upload_job_template.cancelled; - const fs::path filename{upload_job.upload_data.upload_path}; - upload_job.upload_data.upload_path = + const fs::path filename{upload_job_template.upload_data.upload_path}; + paths.emplace_back( filename.stem().string() + "_bed" + std::to_string(print_index + 1) - + filename.extension().string(); - this->p->background_process.prepare_upload(upload_job); + + filename.extension().string() + ); + } + + BulkExportDialog dialog{paths}; + if (dialog.ShowModal() != wxID_OK) { + return; + } + paths = dialog.get_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]}; + if (!print || print->empty()) { + continue; + } + with_mocked_fff_background_process( + *print, + this->p->gcode_results[print_index], + print_index, + [&](){ + this->p->background_process.set_temp_output_path(print_index); + PrintHostJob upload_job; + upload_job.upload_data = upload_job_template.upload_data; + upload_job.printhost = std::make_unique(connect); + upload_job.cancelled = upload_job_template.cancelled; + upload_job.upload_data.upload_path = paths[print_index]; + this->p->background_process.prepare_upload(upload_job); + } + ); } } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 37c229721c..fff0159614 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -216,6 +216,12 @@ public: void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects); + void with_mocked_fff_background_process( + Print &print, + GCodeProcessorResult &result, + const int bed_index, + const std::function &callable + ); void export_gcode(bool prefer_removable); void export_all_gcodes(bool prefer_removable); void export_stl_obj(bool extended = false, bool selection_only = false); From b4ab9883c257a82c9e528e84e5704ab04144fc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Wed, 11 Dec 2024 10:45:11 +0100 Subject: [PATCH 03/18] Reload even empty preview When Preview::load_print is called and even if the print has no layers, call load_gcode_preview to show an empty bed for print with no layers. --- src/slic3r/GUI/GUI_Preview.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 06249fbf53..76619eeeab 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -938,6 +938,7 @@ void Preview::load_print_as_fff(bool keep_z_range) } if (wxGetApp().is_editor() && !has_layers) { + m_canvas->reset_gcode_toolpaths(); m_canvas->reset_gcode_layers_times_cache(); m_canvas->load_gcode_shells(); hide_layers_slider(); From 567a2cdc38fb6d940e38ede0e333659ac46ed956 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 11 Dec 2024 15:11:01 +0100 Subject: [PATCH 04/18] BedSelector: Implemented "Slice all" button with correct centering of the icon on it. + Retina specific: Fixed bed_selector_thumbnail() --- src/slic3r/GUI/GLCanvas3D.cpp | 54 +++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5784cbddf2..d8f81f72c9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6617,7 +6617,7 @@ bool bed_selector_thumbnail( ) { ImGuiWindow* window = GImGui->CurrentWindow; const ImVec2 current_position = GImGui->CurrentWindow->DC.CursorPos; - const ImVec2 state_pos = current_position + ImVec2(3.f * border, side - 20.f * scale) * wxGetApp().imgui()->get_style_scaling(); + const ImVec2 state_pos = current_position + ImVec2(3.f * border, side - 20.f * scale); const bool clicked{ImGui::ImageButton( (void*)(int64_t)texture_id, @@ -6643,23 +6643,53 @@ bool bed_selector_thumbnail( return clicked; } -bool slice_all_beds_button(bool is_active, const ImVec2 size, const ImVec2 padding) +bool slice_all_beds_button(bool is_active, const ImVec2 size) { - ImGui::PushStyleColor(ImGuiCol_Button, ImGuiPureWrap::COL_GREY_DARK); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGuiPureWrap::COL_ORANGE_DARK); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGuiPureWrap::COL_ORANGE_DARK); - ImGui::PushStyleColor(ImGuiCol_Border, is_active ? ImGuiPureWrap::COL_BUTTON_ACTIVE : ImGuiPureWrap::COL_GREY_DARK); + const wchar_t icon = ImGui::SliceAllBtnIcon; + std::string btn_name = boost::nowide::narrow(std::wstring{ icon }); - std::string slice_all_btn_name = boost::nowide::narrow(std::wstring{ ImGui::SliceAllBtnIcon }); - bool clicked = ImGui::Button(slice_all_btn_name.c_str(), size + padding); + ImGuiButtonFlags flags = ImGuiButtonFlags_None; + + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(btn_name.c_str()); + const ImFontAtlasCustomRect* const rect = wxGetApp().imgui()->GetTextureCustomRect(icon); + const ImVec2 label_size = ImVec2(rect->Width, rect->Height); + + ImVec2 pos = window->DC.CursorPos; + const ImRect bb(pos, pos + size); + ImGui::ItemSize(size, style.FramePadding.y); + if (!ImGui::ItemAdd(bb, id)) + return false; + + if (g.CurrentItemFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiPureWrap::COL_ORANGE_DARK : hovered ? ImGuiPureWrap::COL_ORANGE_LIGHT : ImGuiPureWrap::COL_GREY_DARK); + ImGui::RenderNavHighlight(bb, id); + ImGui::PushStyleColor(ImGuiCol_Border, is_active ? ImGuiPureWrap::COL_BUTTON_ACTIVE : ImGuiPureWrap::COL_GREY_DARK); + ImGui::RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + ImGui::PopStyleColor(); + + if (g.LogEnabled) + ImGui::LogSetNextTextDecoration("[", "]"); + ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, btn_name.c_str(), NULL, &label_size, style.ButtonTextAlign, &bb); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Slice all").c_str()); } - ImGui::PopStyleColor(4); - - return clicked; + return pressed; } void Slic3r::GUI::GLCanvas3D::_render_bed_selector() @@ -6785,7 +6815,7 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() if ( current_printer_technology() == ptFFF && - slice_all_beds_button(s_multiple_beds.is_autoslicing(), btn_size, btn_padding) + slice_all_beds_button(s_multiple_beds.is_autoslicing(), btn_size + btn_padding) ) { if (!s_multiple_beds.is_autoslicing()) { s_multiple_beds.start_autoslice([this](int i, bool user) { this->select_bed(i, user); }); From 5e6a7216831cd6269a2fb41f481c7bcded8a1a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Wed, 11 Dec 2024 17:06:06 +0100 Subject: [PATCH 05/18] Make thumbnail regeneration a wxEvent Also call the event when a bed texture is fully loaded. --- src/slic3r/GUI/3DBed.cpp | 1 + src/slic3r/GUI/Plater.cpp | 12 +++++------- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/Sidebar.cpp | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 81b39dec6d..b211445fda 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -421,6 +421,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& v else if (m_texture.unsent_compressed_data_available()) { // sends to gpu the already available compressed levels of the main texture m_texture.send_compressed_data_to_gpu(); + wxQueueEvent(wxGetApp().plater(), new SimpleEvent(EVT_REGENERATE_BED_THUMBNAILS)); // the temporary texture is not needed anymore, reset it if (m_temp_texture.get_id() != 0) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7ca787dbfe..661932065e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -170,6 +170,7 @@ wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); // BackgroundSlicingProcess finished either with success or error. wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, SlicingProcessCompletedEvent); wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent); +wxDEFINE_EVENT(EVT_REGENERATE_BED_THUMBNAILS, SimpleEvent); // Plater::DropTarget @@ -563,7 +564,7 @@ struct Plater::priv void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type); ThumbnailsList generate_thumbnails(const ThumbnailsParams& params, Camera::EType camera_type); - void regenerate_thumbnails(); + void regenerate_thumbnails(SimpleEvent&); void bring_instance_forward() const; @@ -771,6 +772,7 @@ void Plater::priv::init() q->Bind(EVT_EXPORT_BEGAN, &priv::on_export_began, this); q->Bind(EVT_GLVIEWTOOLBAR_3D, [this](SimpleEvent&) { q->select_view_3D("3D"); }); q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [this](SimpleEvent&) { q->select_view_3D("Preview"); }); + q->Bind(EVT_REGENERATE_BED_THUMBNAILS, &priv::regenerate_thumbnails, this); } // Drop target: @@ -2229,7 +2231,7 @@ std::vector apply_to_inactive_beds( return result; } -void Plater::priv::regenerate_thumbnails() { +void Plater::priv::regenerate_thumbnails(SimpleEvent&) { const int num{s_multiple_beds.get_number_of_beds()}; if (num <= 1 || num > MAX_NUMBER_OF_BEDS) { return; @@ -2391,7 +2393,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // If current bed was invalidated, update thumbnails for all beds: if (any_status_changed) { - regenerate_thumbnails(); + wxQueueEvent(this->q, new SimpleEvent(EVT_REGENERATE_BED_THUMBNAILS)); } // Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile. @@ -4789,10 +4791,6 @@ void Plater::object_list_changed() p->object_list_changed(); } -void Plater::regenerate_thumbnails() { - p->regenerate_thumbnails(); -} - std::vector Plater::load_files(const std::vector& 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. diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index fff0159614..10e5d769fb 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -58,6 +58,7 @@ namespace UndoRedo { namespace GUI { wxDECLARE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); +wxDECLARE_EVENT(EVT_REGENERATE_BED_THUMBNAILS, SimpleEvent); class MainFrame; class GLCanvas3D; @@ -122,7 +123,6 @@ public: void reload_print(); void object_list_changed(); void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type); - void regenerate_thumbnails(); std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false); // To be called when providing a list of files to the GUI slic3r on command line. diff --git a/src/slic3r/GUI/Sidebar.cpp b/src/slic3r/GUI/Sidebar.cpp index 2f7df4ca37..cc39d38e17 100644 --- a/src/slic3r/GUI/Sidebar.cpp +++ b/src/slic3r/GUI/Sidebar.cpp @@ -761,7 +761,7 @@ void Sidebar::on_select_preset(wxCommandEvent& evt) m_object_list->update_object_list_by_printer_technology(); s_multiple_beds.stop_autoslice(false); this->switch_from_autoslicing_mode(); - this->m_plater->regenerate_thumbnails(); + wxQueueEvent(this->m_plater, new SimpleEvent(EVT_REGENERATE_BED_THUMBNAILS)); this->m_plater->update(); s_print_statuses.fill(PrintStatus::idle); } From 6001759e6cf6c69d007c79c4e886735463eb9160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Thu, 12 Dec 2024 12:56:44 +0100 Subject: [PATCH 06/18] Add invalid state toolpath_outside and display it accordingly --- src/libslic3r/MultipleBeds.hpp | 3 ++- src/slic3r/GUI/GCodeViewer.cpp | 9 ++++++++- src/slic3r/GUI/GLCanvas3D.cpp | 33 ++++++++++++++++++++++++--------- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/MultipleBeds.hpp b/src/libslic3r/MultipleBeds.hpp index bbf5ba4088..28679ca665 100644 --- a/src/libslic3r/MultipleBeds.hpp +++ b/src/libslic3r/MultipleBeds.hpp @@ -35,7 +35,8 @@ enum class PrintStatus { finished, outside, invalid, - empty + empty, + toolpath_outside }; bool is_sliceable(const PrintStatus status); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 8db4c200b5..158f2b39fe 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1022,8 +1022,12 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const }); m_paths_bounding_box = BoundingBoxf3(libvgcode::convert(bbox[0]).cast(), libvgcode::convert(bbox[1]).cast()); - if (wxGetApp().is_editor()) + if (wxGetApp().is_editor()) { m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box); + if (!m_contained_in_bed) { + s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::toolpath_outside; + } + } m_extruders_count = gcode_result.extruders_count; m_sequential_view.gcode_window.load_gcode(gcode_result); @@ -1115,6 +1119,9 @@ void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data) const libvgcode::AABox bbox = m_viewer.get_extrusion_bounding_box(); const BoundingBoxf3 paths_bounding_box(libvgcode::convert(bbox[0]).cast(), libvgcode::convert(bbox[1]).cast()); m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(GCodeProcessorResult(), paths_bounding_box); + if (!m_contained_in_bed) { + s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::toolpath_outside; + } } void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d8f81f72c9..aeab9a33b5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6230,10 +6230,20 @@ void GLCanvas3D::_render_background() use_error_color = m_dynamic_background_enabled && (current_printer_technology() != ptSLA || !m_volumes.empty()); - if (!m_volumes.empty()) - use_error_color &= _is_any_volume_outside().first; - else - use_error_color &= m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); + if (s_multiple_beds.is_autoslicing()) { + use_error_color &= std::any_of( + s_print_statuses.begin(), + s_print_statuses.end(), + [](const PrintStatus status){ + return status == PrintStatus::toolpath_outside; + } + ); + } else { + if (!m_volumes.empty()) + use_error_color &= _is_any_volume_outside().first; + else + use_error_color &= m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); + } } // Draws a bottom to top gradient over the complete screen. @@ -6581,11 +6591,12 @@ void GLCanvas3D::_render_overlays() std::string get_status_text(PrintStatus status) { switch(status) { case PrintStatus::idle: return _u8L("Unsliced"); - case PrintStatus::running: return _u8L("Slicing..."); + case PrintStatus::running: return _u8L("Slicing") + "..."; case PrintStatus::finished: return _u8L("Sliced"); - case PrintStatus::outside: return _u8L("Outside"); - case PrintStatus::invalid: return _u8L("Invalid"); + case PrintStatus::outside: return _u8L("Object at boundary"); + case PrintStatus::invalid: return _u8L("Invalid data"); case PrintStatus::empty: return _u8L("Empty"); + case PrintStatus::toolpath_outside: return _u8L("Toolpath exceeds bounds"); } return {}; } @@ -6598,6 +6609,7 @@ wchar_t get_raw_status_icon(const PrintStatus status) { case PrintStatus::outside: return ImGui::PrintIdle; case PrintStatus::invalid: return ImGui::PrintIdle; case PrintStatus::empty: return ImGui::PrintIdle; + case PrintStatus::toolpath_outside: return ImGui::PrintIdle; } return ImGui::PrintIdle; } @@ -6740,8 +6752,11 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() } bool clicked = false; - if (!is_sliceable(print_status)) { - ImGui::Button(get_status_text(print_status).c_str(), btn_size + btn_padding); + if ( + !is_sliceable(print_status) + || print_status == PrintStatus::toolpath_outside + ) { + clicked = ImGui::Button(get_status_text(print_status).c_str(), btn_size + btn_padding); } else if ( i >= int(s_bed_selector_thumbnail_texture_ids.size()) ) { From 0f6c90ab3bd9f8ba9d675e990f54db801720ce1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Thu, 12 Dec 2024 13:29:39 +0100 Subject: [PATCH 07/18] Correctly enable bulk export even if some print statuses are invalid --- src/slic3r/GUI/Plater.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 661932065e..dbf31b7349 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4089,13 +4089,15 @@ void Plater::priv::show_autoslicing_action_buttons() const { 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(); + bool all_finished{true}; + for (std::size_t bed_index{}; bed_index < s_multiple_beds.get_number_of_beds(); ++bed_index) { + const std::unique_ptr &print{this->fff_prints[bed_index]}; + if (!print->finished() && is_sliceable(s_print_statuses[bed_index])) { + all_finished = false; + break; } - )}; + } + sidebar->enable_bulk_buttons(all_finished); } From 763dc015a77b27a1b8aab27765d28eae536819ab Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 15 Nov 2024 09:48:04 +0100 Subject: [PATCH 08/18] Draw numbers on beds (in the scene) --- resources/icons/numbers.png | Bin 0 -> 91136 bytes src/slic3r/GUI/3DBed.cpp | 52 ++++++++++++++++++++++++++++++++++++ src/slic3r/GUI/3DBed.hpp | 3 +++ 3 files changed, 55 insertions(+) create mode 100644 resources/icons/numbers.png diff --git a/resources/icons/numbers.png b/resources/icons/numbers.png new file mode 100644 index 0000000000000000000000000000000000000000..25eb17b3602a098c5b27d7c7ba8811db9dfbb765 GIT binary patch literal 91136 zcmaI7Q*@_K@UQ#E-*BRdZF^$d6HRQ}wry)-I}_WsZ9AE0;{7}8?0s?8`k#H%T^H3= ztGZU-)aU641$hZXI2>t1%jbn5)d;D4|A^JdXfOc{)dl7cEI3>!~G1lGqQ{>3`I!#)}vLJ|x~?U+I& zR)^q=G01N87F`HZ62?R>ga{^~DNqtVuP(iAUw-X&t9Ei^q(WA@?t(pekLK9SZkXA= z(ejj_klJ0mT0$*A=J}n&iKPx)2;~J`w*{$3`&!CoFRb zeaa|*a3V;-E925F{;mH{#qNmWz&%FirR()yoX(dknVfi()iW^vTsqy!r_9GR?9_OP z1hyPK$Lt`R3RAL}iW{a;6k>E@X%yjbG63~7kAx;Ul2^O%S_F4MiZ$fqsxD6u`Q9Fw;=O1WcWGrhPq^|>zh&`+i;ZLh$e#ugnyOzAl_eRFA?J)6 zO#W)i&;CVx@U6_Hm9F+YswIzXc!b1+Gg|!w`*HyJ8n=wiK zlIY8(6qX{4YV$PTTY7%5yW(mRSc&bW71Liji-HUI@lL@}KGk61npZif4ZTKIqXMRK zPE{-j4rc15P$?D<4_6PVBnP>ea>cJMw@66Bqy9ti^k&`Y@u={lm-5@aW0fl)l_

U{kfvqmpuddPD9uUFSB6lCLLwegE&u8bnmnQXE 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ⓈiW+ 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%0APok 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 + m_digits_models.back()->init_from(std::move(g)); + m_digits_models.back()->set_color(ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f)); + } + m_digits_texture = std::make_unique(); + m_digits_texture->load_from_file(resources_dir() + "/icons/numbers.png", true, GLTexture::ECompressionType::None, false); + m_digits_texture->send_compressed_data_to_gpu(); + } + if (!is_thumbnail && s_multiple_beds.get_number_of_beds() > 1) { + GLShaderProgram* shader = wxGetApp().get_shader("flat_texture"); + shader->start_using(); + shader->set_uniform("projection_matrix", projection_matrix); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_digits_texture->get_id())); + + const BoundingBoxf bb = this->build_volume().bounding_volume2d(); + + for (int i : beds_to_render) { + if (i + 1 >= m_digits_models.size()) + break; + + double size_x = std::max(10., std::min(bb.size().x(), bb.size().y()) * 0.11); + double aspect = 1.2; + Transform3d mat = view_matrix; + mat.translate(Vec3d(bb.min.x(), bb.min.y(), 0.)); + mat.translate(s_multiple_beds.get_bed_translation(i)); + if (build_volume().type() != BuildVolume::Type::Circle) + mat.translate(Vec3d(0.3 * size_x, 0.3 * size_x, 0.)); + mat.translate(Vec3d(0., 0., 0.5)); + mat.scale(Vec3d(size_x, size_x * aspect, 1.)); + + shader->set_uniform("view_model_matrix", mat); + m_digits_models[i + 1]->render(); + } + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + glsafe(::glDisable(GL_DEPTH_TEST)); + shader->stop_using(); + } } void Bed3D::render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor) diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 95f39f082c..3c48ddeea2 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -54,6 +54,9 @@ private: float m_scale_factor{ 1.0f }; + std::vector> m_digits_models; + std::unique_ptr m_digits_texture; + public: Bed3D() = default; ~Bed3D() = default; From a89a1fe3b92cb50509fa63d016b0791abe0e1dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Thu, 12 Dec 2024 10:15:14 +0100 Subject: [PATCH 09/18] Add checkbox to bulk export and send to connect If the checkbox is not checked the path is not exported. --- src/slic3r/GUI/BulkExportDialog.cpp | 58 ++++++++++++++++++----------- src/slic3r/GUI/BulkExportDialog.hpp | 16 +++++--- src/slic3r/GUI/Plater.cpp | 17 +++++++-- 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/src/slic3r/GUI/BulkExportDialog.cpp b/src/slic3r/GUI/BulkExportDialog.cpp index 2e1b764246..6cb55ad5ce 100644 --- a/src/slic3r/GUI/BulkExportDialog.cpp +++ b/src/slic3r/GUI/BulkExportDialog.cpp @@ -27,7 +27,7 @@ namespace GUI { constexpr auto BORDER_W = 10; -void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, const std::string &path) +void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* row_sizer, const std::string &path) { #ifdef _WIN32 const long style = wxBORDER_SIMPLE; @@ -38,29 +38,41 @@ void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, wxGetApp().UpdateDarkUI(m_text_ctrl); m_text_ctrl->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); }); - input_path_sizer->Add(m_text_ctrl, 1, wxEXPAND, BORDER_W); + row_sizer->Add(m_text_ctrl, 1, wxEXPAND, BORDER_W); } + +void BulkExportDialog::Item::init_selection_ctrl(wxBoxSizer* row_sizer, int id) +{ + m_checkbox = new ::CheckBox(m_parent, std::to_string(id)); + m_checkbox->SetFont(wxGetApp().bold_font()); + wxGetApp().UpdateDarkUI(m_checkbox); + m_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { + this->selected = event.IsChecked(); + }); + + row_sizer->Add(m_checkbox, 0, wxRIGHT, BORDER_W); + m_checkbox->SetValue(this->selected); +} + BulkExportDialog::Item::Item( wxWindow *parent, wxBoxSizer *sizer, - const boost::filesystem::path &path, - Validator validator + const fs::path &path, + Validator validator, + int id ): path(path), m_parent(parent), m_valid_bmp(new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark"))), - 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* row_sizer = new wxBoxSizer(wxHORIZONTAL); + init_selection_ctrl(row_sizer, id); + init_input_name_ctrl(row_sizer, path.filename().string()); + row_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, BORDER_W); - 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, path.filename().string()); - - sizer->Add(input_path_sizer,0, wxEXPAND | wxTOP, BORDER_W); - sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W); + sizer->Add(row_sizer,0, wxEXPAND | wxTOP, BORDER_W); update(); } @@ -157,8 +169,8 @@ void BulkExportDialog::Item::update() // 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_valid_bmp->SetToolTip(info_line); + m_status = status; update_valid_bmp(); @@ -185,7 +197,7 @@ BulkExportDialog::BulkExportDialog(const std::vector &paths): DPIDialog( nullptr, wxID_ANY, - paths.size() == 1 ? _L("Save bed") : _L("Save beds"), + paths.size() == 1 ? _L("Export bed") : _L("Export beds"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING @@ -201,8 +213,9 @@ BulkExportDialog::BulkExportDialog(const std::vector &paths): m_sizer = new wxBoxSizer(wxVERTICAL); + int id{ 0 }; for (const fs::path& path : paths) { - AddItem(path); + AddItem(path, ++id); } // Add dialog's buttons @@ -224,9 +237,9 @@ BulkExportDialog::BulkExportDialog(const std::vector &paths): #endif } -void BulkExportDialog::AddItem(const fs::path& path) +void BulkExportDialog::AddItem(const fs::path& path, int id) { - m_items.push_back(std::make_unique(this, m_sizer, path, PathValidator{m_items})); + m_items.push_back(std::make_unique(this, m_sizer, path, PathValidator{m_items}, id)); } bool BulkExportDialog::enable_ok_btn() const @@ -246,13 +259,16 @@ bool BulkExportDialog::Layout() return ret; } -std::vector BulkExportDialog::get_paths() const { - std::vector result; +std::vector> BulkExportDialog::get_paths() const { + std::vector> result; std::transform( m_items.begin(), m_items.end(), std::back_inserter(result), - [](const auto &item){ + [](const auto &item) -> std::optional { + if (!item->selected) { + return std::nullopt; + } return item->path; } ); diff --git a/src/slic3r/GUI/BulkExportDialog.hpp b/src/slic3r/GUI/BulkExportDialog.hpp index ba8a9e265b..fc07c07f9b 100644 --- a/src/slic3r/GUI/BulkExportDialog.hpp +++ b/src/slic3r/GUI/BulkExportDialog.hpp @@ -9,6 +9,9 @@ #include #include "wxExtensions.hpp" #include "GUI_Utils.hpp" +#include + +#include "Widgets/CheckBox.hpp" class wxString; class wxStaticText; @@ -38,7 +41,8 @@ public: wxWindow *parent, wxBoxSizer *sizer, const boost::filesystem::path &path, - Validator validator + Validator validator, + int id ); Item(const Item &) = delete; Item& operator=(const Item &) = delete; @@ -52,17 +56,19 @@ public: bool is_valid() const { return m_status != ItemStatus::NoValid; } boost::filesystem::path path; + bool selected{true}; private: ItemStatus m_status{ItemStatus::NoValid}; wxWindow *m_parent{nullptr}; wxStaticBitmap *m_valid_bmp{nullptr}; wxTextCtrl *m_text_ctrl{nullptr}; - wxStaticText *m_valid_label{nullptr}; + ::CheckBox *m_checkbox{nullptr}; Validator m_validator; boost::filesystem::path m_directory{}; - void init_input_name_ctrl(wxBoxSizer *input_name_sizer, const std::string &path); + void init_input_name_ctrl(wxBoxSizer *row_sizer, const std::string &path); + void init_selection_ctrl(wxBoxSizer *row_sizer, int id); void update(); }; @@ -74,14 +80,14 @@ private: public: BulkExportDialog(const std::vector &paths); bool Layout() override; - std::vector get_paths() const; + std::vector> get_paths() const; protected: void on_dpi_changed(const wxRect &) override; void on_sys_color_changed() override {} private: - void AddItem(const boost::filesystem::path &path); + void AddItem(const boost::filesystem::path &path, int id); bool enable_ok_btn() const; }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index dbf31b7349..e0efd941e4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6119,7 +6119,7 @@ void Plater::export_all_gcodes(bool prefer_removable) { if (dialog.ShowModal() != wxID_OK) { return; } - paths = dialog.get_paths(); + std::vector> output_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]; } @@ -6127,7 +6127,12 @@ void Plater::export_all_gcodes(bool prefer_removable) { bool path_on_removable_media{false}; - for (const PrintToExport &print_to_export : prints_to_export) { + for (std::size_t path_index{0}; path_index < paths.size(); ++path_index) { + PrintToExport print_to_export{prints_to_export[path_index]}; + if (!output_paths[path_index]) { + continue; + } + print_to_export.output_path = *output_paths[path_index]; with_mocked_fff_background_process( print_to_export.print, print_to_export.processor_result, @@ -6744,9 +6749,13 @@ void Plater::connect_gcode_all() { if (dialog.ShowModal() != wxID_OK) { return; } - paths = dialog.get_paths(); + const std::vector> output_paths{dialog.get_paths()}; for (std::size_t print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) { + if (!output_paths[print_index]) { + continue; + } + const std::unique_ptr &print{this->get_fff_prints()[print_index]}; if (!print || print->empty()) { continue; @@ -6761,7 +6770,7 @@ void Plater::connect_gcode_all() { upload_job.upload_data = upload_job_template.upload_data; upload_job.printhost = std::make_unique(connect); upload_job.cancelled = upload_job_template.cancelled; - upload_job.upload_data.upload_path = paths[print_index]; + upload_job.upload_data.upload_path = *output_paths[print_index]; this->p->background_process.prepare_upload(upload_job); } ); From fdfc5d800a2146546034f6c7c63a161056e2e840 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 12 Dec 2024 13:32:30 +0100 Subject: [PATCH 10/18] Bed Selector: Added bed ID + slice_all_beds_button is switched into generat button_with_icon --- src/slic3r/GUI/GLCanvas3D.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index aeab9a33b5..7e8d21cd47 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6624,13 +6624,14 @@ bool bed_selector_thumbnail( const float side, const float border, const float scale, - const GLuint texture_id, + const int bed_id, const std::optional status ) { ImGuiWindow* window = GImGui->CurrentWindow; const ImVec2 current_position = GImGui->CurrentWindow->DC.CursorPos; const ImVec2 state_pos = current_position + ImVec2(3.f * border, side - 20.f * scale); + const GLuint texture_id = s_bed_selector_thumbnail_texture_ids[bed_id]; const bool clicked{ImGui::ImageButton( (void*)(int64_t)texture_id, size - padding, @@ -6652,12 +6653,23 @@ bool bed_selector_thumbnail( ); } + const ImVec2 id_pos = current_position + ImVec2(3.f * border, 1.5f * border); + const std::string id = std::to_string(bed_id+1); + + window->DrawList->AddText( + GImGui->Font, + GImGui->FontSize * 1.5f, + id_pos, + ImGui::GetColorU32(ImGuiCol_Text), + id.c_str(), + id.c_str() + id.size() + ); + return clicked; } -bool slice_all_beds_button(bool is_active, const ImVec2 size) +bool button_with_icon(const wchar_t icon, const std::string& tooltip, bool is_active, const ImVec2 size) { - const wchar_t icon = ImGui::SliceAllBtnIcon; std::string btn_name = boost::nowide::narrow(std::wstring{ icon }); ImGuiButtonFlags flags = ImGuiButtonFlags_None; @@ -6697,9 +6709,8 @@ bool slice_all_beds_button(bool is_active, const ImVec2 size) IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s", _u8L("Slice all").c_str()); - } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", tooltip.c_str()); return pressed; } @@ -6770,7 +6781,7 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() btn_side, btn_border, scale, - s_bed_selector_thumbnail_texture_ids[i], + i, current_printer_technology() == ptFFF ? std::optional{print_status} : std::nullopt ); } @@ -6830,7 +6841,7 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() if ( current_printer_technology() == ptFFF && - slice_all_beds_button(s_multiple_beds.is_autoslicing(), btn_size + btn_padding) + button_with_icon(ImGui::SliceAllBtnIcon, _u8L("Slice all"), s_multiple_beds.is_autoslicing(), btn_size + btn_padding) ) { if (!s_multiple_beds.is_autoslicing()) { s_multiple_beds.start_autoslice([this](int i, bool user) { this->select_bed(i, user); }); From 532180a23da97a169d751dce004ebc00e1eeea5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Thu, 12 Dec 2024 14:13:42 +0100 Subject: [PATCH 11/18] Add invalid states icons --- bundled_deps/imgui/imgui/imconfig.h | 1 + resources/icons/notification_warning_grey.svg | 70 +++++++++++++++++++ src/slic3r/GUI/BulkExportDialog.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 15 +++- src/slic3r/GUI/ImGuiWrapper.cpp | 1 + 5 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 resources/icons/notification_warning_grey.svg diff --git a/bundled_deps/imgui/imgui/imconfig.h b/bundled_deps/imgui/imgui/imconfig.h index 2b7e83e128..c8f5b0f023 100644 --- a/bundled_deps/imgui/imgui/imconfig.h +++ b/bundled_deps/imgui/imgui/imconfig.h @@ -213,6 +213,7 @@ namespace ImGui const wchar_t PrintIdle = 0x2812; const wchar_t PrintRunning = 0x2813; const wchar_t PrintFinished = 0x2814; + const wchar_t WarningMarkerDisabled = 0x2815; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/resources/icons/notification_warning_grey.svg b/resources/icons/notification_warning_grey.svg new file mode 100644 index 0000000000..a593f6a7bf --- /dev/null +++ b/resources/icons/notification_warning_grey.svg @@ -0,0 +1,70 @@ + +image/svg+xml + + + + + + + + + + diff --git a/src/slic3r/GUI/BulkExportDialog.cpp b/src/slic3r/GUI/BulkExportDialog.cpp index 6cb55ad5ce..b6d16fd6b0 100644 --- a/src/slic3r/GUI/BulkExportDialog.cpp +++ b/src/slic3r/GUI/BulkExportDialog.cpp @@ -50,7 +50,7 @@ void BulkExportDialog::Item::init_selection_ctrl(wxBoxSizer* row_sizer, int id) this->selected = event.IsChecked(); }); - row_sizer->Add(m_checkbox, 0, wxRIGHT, BORDER_W); + row_sizer->Add(m_checkbox, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BORDER_W); m_checkbox->SetValue(this->selected); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7e8d21cd47..2212990d73 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6765,9 +6765,20 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() bool clicked = false; if ( !is_sliceable(print_status) - || print_status == PrintStatus::toolpath_outside ) { - clicked = ImGui::Button(get_status_text(print_status).c_str(), btn_size + btn_padding); + clicked = button_with_icon( + ImGui::WarningMarkerDisabled, + get_status_text(print_status), + !inactive, + btn_size + btn_padding + ); + } else if (print_status == PrintStatus::toolpath_outside) { + clicked = button_with_icon( + ImGui::WarningMarker, + get_status_text(print_status), + !inactive, + btn_size + btn_padding + ); } else if ( i >= int(s_bed_selector_thumbnail_texture_ids.size()) ) { diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index c652b25e24..019b481b47 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -135,6 +135,7 @@ static const std::map font_icons_large = { {ImGui::EjectButton , "notification_eject_sd" }, {ImGui::EjectHoverButton , "notification_eject_sd_hover" }, {ImGui::WarningMarker , "notification_warning" }, + {ImGui::WarningMarkerDisabled , "notification_warning_grey" }, {ImGui::ErrorMarker , "notification_error" }, {ImGui::CancelButton , "notification_cancel" }, {ImGui::CancelHoverButton , "notification_cancel_hover" }, From 642d0529fbf4cfca269cc995d837f177f9b2140a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Thu, 12 Dec 2024 16:04:20 +0100 Subject: [PATCH 12/18] Fix bulk output indexing and path to print association --- src/slic3r/GUI/BulkExportDialog.cpp | 32 +++++++------- src/slic3r/GUI/BulkExportDialog.hpp | 14 +++--- src/slic3r/GUI/Plater.cpp | 66 ++++++++++++++--------------- 3 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/slic3r/GUI/BulkExportDialog.cpp b/src/slic3r/GUI/BulkExportDialog.cpp index b6d16fd6b0..e6a61a614b 100644 --- a/src/slic3r/GUI/BulkExportDialog.cpp +++ b/src/slic3r/GUI/BulkExportDialog.cpp @@ -41,9 +41,9 @@ void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* row_sizer, const s row_sizer->Add(m_text_ctrl, 1, wxEXPAND, BORDER_W); } -void BulkExportDialog::Item::init_selection_ctrl(wxBoxSizer* row_sizer, int id) +void BulkExportDialog::Item::init_selection_ctrl(wxBoxSizer* row_sizer, int bed_index) { - m_checkbox = new ::CheckBox(m_parent, std::to_string(id)); + m_checkbox = new ::CheckBox(m_parent, std::to_string(bed_index + 1)); m_checkbox->SetFont(wxGetApp().bold_font()); wxGetApp().UpdateDarkUI(m_checkbox); m_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { @@ -58,17 +58,18 @@ BulkExportDialog::Item::Item( wxWindow *parent, wxBoxSizer *sizer, const fs::path &path, - Validator validator, - int id + const int bed_index, + Validator validator ): path(path), + bed_index(bed_index), m_parent(parent), m_valid_bmp(new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark"))), m_validator(std::move(validator)), m_directory(path.parent_path()) { wxBoxSizer* row_sizer = new wxBoxSizer(wxHORIZONTAL); - init_selection_ctrl(row_sizer, id); + init_selection_ctrl(row_sizer, bed_index); init_input_name_ctrl(row_sizer, path.filename().string()); row_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, BORDER_W); @@ -193,7 +194,7 @@ void BulkExportDialog::Item::update_valid_bmp() m_valid_bmp->SetBitmap(*get_bmp_bundle(get_bmp_name(m_status))); } -BulkExportDialog::BulkExportDialog(const std::vector &paths): +BulkExportDialog::BulkExportDialog(const std::vector> &paths): DPIDialog( nullptr, wxID_ANY, @@ -213,9 +214,8 @@ BulkExportDialog::BulkExportDialog(const std::vector &paths): m_sizer = new wxBoxSizer(wxVERTICAL); - int id{ 0 }; - for (const fs::path& path : paths) { - AddItem(path, ++id); + for (const auto&[bed_index, path] : paths) { + AddItem(path, bed_index); } // Add dialog's buttons @@ -237,9 +237,9 @@ BulkExportDialog::BulkExportDialog(const std::vector &paths): #endif } -void BulkExportDialog::AddItem(const fs::path& path, int id) +void BulkExportDialog::AddItem(const fs::path& path, int bed_index) { - m_items.push_back(std::make_unique(this, m_sizer, path, PathValidator{m_items}, id)); + m_items.push_back(std::make_unique(this, m_sizer, path, bed_index, PathValidator{m_items})); } bool BulkExportDialog::enable_ok_btn() const @@ -259,17 +259,17 @@ bool BulkExportDialog::Layout() return ret; } -std::vector> BulkExportDialog::get_paths() const { - std::vector> result; +std::vector>> BulkExportDialog::get_paths() const { + std::vector>> result; std::transform( m_items.begin(), m_items.end(), std::back_inserter(result), - [](const auto &item) -> std::optional { + [](const auto &item) -> std::pair> { if (!item->selected) { - return std::nullopt; + return {item->bed_index, std::nullopt}; } - return item->path; + return {item->bed_index, item->path}; } ); return result; diff --git a/src/slic3r/GUI/BulkExportDialog.hpp b/src/slic3r/GUI/BulkExportDialog.hpp index fc07c07f9b..deff9b2b08 100644 --- a/src/slic3r/GUI/BulkExportDialog.hpp +++ b/src/slic3r/GUI/BulkExportDialog.hpp @@ -41,8 +41,8 @@ public: wxWindow *parent, wxBoxSizer *sizer, const boost::filesystem::path &path, - Validator validator, - int id + const int bed_index, + Validator validator ); Item(const Item &) = delete; Item& operator=(const Item &) = delete; @@ -56,6 +56,7 @@ public: bool is_valid() const { return m_status != ItemStatus::NoValid; } boost::filesystem::path path; + int bed_index{}; bool selected{true}; private: @@ -68,7 +69,7 @@ public: boost::filesystem::path m_directory{}; void init_input_name_ctrl(wxBoxSizer *row_sizer, const std::string &path); - void init_selection_ctrl(wxBoxSizer *row_sizer, int id); + void init_selection_ctrl(wxBoxSizer *row_sizer, int bed_index); void update(); }; @@ -78,16 +79,17 @@ private: wxBoxSizer *m_sizer{nullptr}; public: - BulkExportDialog(const std::vector &paths); + + BulkExportDialog(const std::vector> &paths); bool Layout() override; - std::vector> get_paths() const; + std::vector>> get_paths() const; protected: void on_dpi_changed(const wxRect &) override; void on_sys_color_changed() override {} private: - void AddItem(const boost::filesystem::path &path, int id); + void AddItem(const boost::filesystem::path &path, int bed_index); bool enable_ok_btn() const; }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e0efd941e4..8530e538a9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6035,8 +6035,7 @@ void Plater::export_gcode_to_path( struct PrintToExport { std::reference_wrapper print; std::reference_wrapper processor_result; - boost::filesystem::path output_path; - std::size_t bed{}; + int bed{}; }; void Plater::with_mocked_fff_background_process( @@ -6079,13 +6078,12 @@ 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) { + std::map prints_to_export; + std::vector> paths; + for (int 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()) { + if (!print || !is_sliceable(s_print_statuses[print_index])) { continue; } @@ -6111,28 +6109,27 @@ void Plater::export_all_gcodes(bool prefer_removable) { + default_filename.extension().string() }; const fs::path output_file{output_dir / filename}; - prints_to_export.push_back({*print, this->p->gcode_results[print_index], output_file, print_index}); - paths.push_back(output_file); + prints_to_export.insert({ + print_index, + {*print, this->p->gcode_results[print_index], print_index} + }); + paths.emplace_back(print_index, output_file); } BulkExportDialog dialog{paths}; if (dialog.ShowModal() != wxID_OK) { return; } - std::vector> output_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]; - } + const std::vector>> output_paths{dialog.get_paths()}; bool path_on_removable_media{false}; - - - for (std::size_t path_index{0}; path_index < paths.size(); ++path_index) { - PrintToExport print_to_export{prints_to_export[path_index]}; - if (!output_paths[path_index]) { + for (auto &[bed_index, optional_path] : output_paths) { + if (!optional_path) { continue; } - print_to_export.output_path = *output_paths[path_index]; + + const PrintToExport &print_to_export{prints_to_export.at(bed_index)}; + const fs::path &path{*optional_path}; with_mocked_fff_background_process( print_to_export.print, print_to_export.processor_result, @@ -6140,10 +6137,10 @@ void Plater::export_all_gcodes(bool prefer_removable) { [&](){ this->p->background_process.set_temp_output_path(print_to_export.bed); export_gcode_to_path( - print_to_export.output_path, + path, [&](const bool on_removable){ this->p->background_process.finalize_gcode( - print_to_export.output_path.string(), + path.string(), path_on_removable_media ); path_on_removable_media = on_removable || path_on_removable_media; @@ -6729,48 +6726,51 @@ void Plater::connect_gcode_all() { } const PrusaConnectNew connect{*print_host_ptr}; - std::vector paths; + 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]}; - if (!print || print->empty()) { + if (!print || !is_sliceable(s_print_statuses[print_index])) { continue; } const fs::path filename{upload_job_template.upload_data.upload_path}; - paths.emplace_back( + paths.emplace_back(print_index, fs::path{ filename.stem().string() + "_bed" + std::to_string(print_index + 1) + filename.extension().string() - ); + }); } BulkExportDialog dialog{paths}; if (dialog.ShowModal() != wxID_OK) { return; } - const std::vector> output_paths{dialog.get_paths()}; + const std::vector>> output_paths{dialog.get_paths()}; - for (std::size_t print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) { - if (!output_paths[print_index]) { + for (const auto &key_value : output_paths) { + const int bed_index{key_value.first}; + const std::optional &optional_path{key_value.second}; + if (!optional_path) { continue; } + const fs::path &path{*optional_path}; - const std::unique_ptr &print{this->get_fff_prints()[print_index]}; + const std::unique_ptr &print{this->get_fff_prints()[bed_index]}; if (!print || print->empty()) { continue; } with_mocked_fff_background_process( *print, - this->p->gcode_results[print_index], - print_index, + this->p->gcode_results[bed_index], + bed_index, [&](){ - this->p->background_process.set_temp_output_path(print_index); + this->p->background_process.set_temp_output_path(bed_index); PrintHostJob upload_job; upload_job.upload_data = upload_job_template.upload_data; upload_job.printhost = std::make_unique(connect); upload_job.cancelled = upload_job_template.cancelled; - upload_job.upload_data.upload_path = *output_paths[print_index]; + upload_job.upload_data.upload_path = path; this->p->background_process.prepare_upload(upload_job); } ); From 834930fbe6b1ae01aca661574c8e3ddcebdf2258 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 12 Dec 2024 16:52:50 +0100 Subject: [PATCH 13/18] BulkExportDialog: Added enable/disable state for text control and status icon --- src/slic3r/GUI/BulkExportDialog.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/BulkExportDialog.cpp b/src/slic3r/GUI/BulkExportDialog.cpp index e6a61a614b..21356f043d 100644 --- a/src/slic3r/GUI/BulkExportDialog.cpp +++ b/src/slic3r/GUI/BulkExportDialog.cpp @@ -37,6 +37,7 @@ void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* row_sizer, const s 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); m_text_ctrl->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); }); + m_text_ctrl->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& event) { event.Enable(selected); }); row_sizer->Add(m_text_ctrl, 1, wxEXPAND, BORDER_W); } @@ -75,6 +76,7 @@ BulkExportDialog::Item::Item( sizer->Add(row_sizer,0, wxEXPAND | wxTOP, BORDER_W); + m_valid_bmp->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& event) { event.Show(selected); }); update(); } @@ -245,11 +247,18 @@ void BulkExportDialog::AddItem(const fs::path& path, int bed_index) bool BulkExportDialog::enable_ok_btn() const { for (const auto &item : m_items) - if (!item->is_valid()) { + if (item->selected && !item->is_valid()) { return false; } - return true; + bool all_unselected{ true }; + for (const auto& item : m_items) + if (item->selected) { + all_unselected = false; + break; + } + + return !all_unselected; } bool BulkExportDialog::Layout() From 4ec68c219c27a5775e129da6d0275fd6e462debd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Thu, 12 Dec 2024 17:02:51 +0100 Subject: [PATCH 14/18] Add empty lines to all print statistics --- src/slic3r/GUI/GLCanvas3D.cpp | 65 +++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2212990d73..8dc8c56c8b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1830,7 +1830,7 @@ void GLCanvas3D::update_volumes_colors_by_extruder() using PerBedStatistics = std::vector + std::optional> >>; PerBedStatistics get_statistics(){ @@ -1838,9 +1838,10 @@ PerBedStatistics get_statistics(){ for (int bed_index=0; bed_indexget_fff_prints()[bed_index].get(); if (print->empty() || !print->finished()) { - continue; + result.emplace_back(bed_index, std::nullopt); + } else { + result.emplace_back(bed_index, std::optional{std::ref(print->print_statistics())}); } - result.emplace_back(bed_index, std::ref(print->print_statistics())); } return result; } @@ -1856,11 +1857,14 @@ struct StatisticsSum { StatisticsSum get_statistics_sum() { StatisticsSum result; for (const auto &[_, statistics] : get_statistics()) { - result.cost += statistics.get().total_cost; - result.filement_weight += statistics.get().total_weight; - result.filament_length += statistics.get().total_used_filament; - result.normal_print_time += statistics.get().normal_print_time_seconds; - result.silent_print_time += statistics.get().silent_print_time_seconds; + if (!statistics) { + continue; + } + result.cost += statistics->get().total_cost; + result.filement_weight += statistics->get().total_weight; + result.filament_length += statistics->get().total_used_filament; + result.normal_print_time += statistics->get().normal_print_time_seconds; + result.silent_print_time += statistics->get().silent_print_time_seconds; } return result; @@ -1900,20 +1904,37 @@ float project_overview_table(float scale) { ); ImGui::TableHeadersRow(); - for (const auto &[bed_index, statistics] : get_statistics()) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%s", (_u8L("Plate") + wxString::Format(" %d", bed_index + 1)).ToStdString().c_str()); - ImGui::TableNextColumn(); - ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_cost).ToStdString().c_str()); - ImGui::TableNextColumn(); - ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_weight).ToStdString().c_str()); - ImGui::TableNextColumn(); - ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_used_filament / 1000).ToStdString().c_str()); - ImGui::TableNextColumn(); - ImGui::Text("%s", statistics.get().estimated_silent_print_time.c_str()); - ImGui::TableNextColumn(); - ImGui::Text("%s", statistics.get().estimated_normal_print_time.c_str()); + for (const auto &[bed_index, optional_statistics] : get_statistics()) { + if (optional_statistics) { + const std::reference_wrapper statistics{*optional_statistics}; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", (_u8L("Bed") + wxString::Format(" %d", bed_index + 1)).ToStdString().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_cost).ToStdString().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_weight).ToStdString().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_used_filament / 1000).ToStdString().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", statistics.get().estimated_silent_print_time.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", statistics.get().estimated_normal_print_time.c_str()); + } else { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", (_u8L("Bed") + wxString::Format(" %d", bed_index + 1)).ToStdString().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("-"); + ImGui::TableNextColumn(); + ImGui::Text("-"); + ImGui::TableNextColumn(); + ImGui::Text("-"); + ImGui::TableNextColumn(); + ImGui::Text("-"); + ImGui::TableNextColumn(); + ImGui::Text("-"); + } } ImGui::PushStyleColor(ImGuiCol_Text, ImGuiPureWrap::COL_ORANGE_LIGHT); From cbd85c75287775399107994da090d189240ced10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Thu, 12 Dec 2024 18:00:02 +0100 Subject: [PATCH 15/18] Fix detection of instance on a bed boundary --- src/slic3r/GUI/Plater.cpp | 51 ++++++++++++--------------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8530e538a9..9bd6da6e80 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1889,49 +1889,30 @@ void Plater::priv::selection_changed() view3D->render(); } -std::size_t count_instances(SpanOfConstPtrs objects) { - return std::accumulate( - objects.begin(), - objects.end(), - std::size_t{}, - [](const std::size_t result, const PrintObject *object){ - return result + object->instances().size(); - } - ); -} - -std::size_t count_instances(const std::map &bed_instances, const int bed_index) { - return std::accumulate( - bed_instances.begin(), - bed_instances.end(), - std::size_t{}, - [&](const std::size_t result, const auto &key_value){ - const auto &[object_id, _bed_index]{key_value}; - if (_bed_index != bed_index) { - return result; - } - return result + 1; - } - ); -} - void Plater::priv::object_list_changed() { const bool export_in_progress = this->background_process.is_export_scheduled(); // || ! send_gcode_file.empty()); // if (printer_technology == ptFFF) { for (std::size_t bed_index{}; bed_index < s_multiple_beds.get_number_of_beds(); ++bed_index) { - const std::size_t print_instances_count{ - count_instances(wxGetApp().plater()->get_fff_prints()[bed_index]->objects()) - }; - const std::size_t bed_instances_count{ - count_instances(s_multiple_beds.get_inst_map(), bed_index) - }; - if (print_instances_count != bed_instances_count) { - s_print_statuses[bed_index] = PrintStatus::outside; - } else if (print_instances_count == 0) { + if ( + wxGetApp().plater()->get_fff_prints()[bed_index]->empty()) { s_print_statuses[bed_index] = PrintStatus::empty; } + for (const ModelObject *object : wxGetApp().model().objects) { + for (const ModelInstance *instance : object->instances) { + const auto it{s_multiple_beds.get_inst_map().find(instance->id())}; + if ( + it != s_multiple_beds.get_inst_map().end() + && it->second == bed_index + && instance->printable + && instance->print_volume_state == ModelInstancePVS_Partly_Outside + ) { + s_print_statuses[bed_index] = PrintStatus::outside; + break; + } + } + } } } else { if (model.objects.empty()) { From 0de63d0db8b0712c9ee8791967c2248e0d17607e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 12 Dec 2024 23:05:54 +0100 Subject: [PATCH 16/18] BulkExportDialog: Added question dialog for the case, when already existed bed name is used + Use wxFlexGridSizer instead of nested wxBoxSizers and Layout() calls --- src/slic3r/GUI/BulkExportDialog.cpp | 55 +++++++++++++++++------------ src/slic3r/GUI/BulkExportDialog.hpp | 15 ++++---- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/BulkExportDialog.cpp b/src/slic3r/GUI/BulkExportDialog.cpp index 21356f043d..fffb0d455f 100644 --- a/src/slic3r/GUI/BulkExportDialog.cpp +++ b/src/slic3r/GUI/BulkExportDialog.cpp @@ -18,7 +18,7 @@ #include "GUI.hpp" #include "GUI_App.hpp" #include "format.hpp" -#include "Tab.hpp" +#include "MsgDialog.hpp" namespace fs = boost::filesystem; @@ -27,7 +27,7 @@ namespace GUI { constexpr auto BORDER_W = 10; -void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* row_sizer, const std::string &path) +void BulkExportDialog::Item::init_input_name_ctrl(wxFlexGridSizer* row_sizer, const std::string &path) { #ifdef _WIN32 const long style = wxBORDER_SIMPLE; @@ -39,10 +39,10 @@ void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* row_sizer, const s m_text_ctrl->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); }); m_text_ctrl->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& event) { event.Enable(selected); }); - row_sizer->Add(m_text_ctrl, 1, wxEXPAND, BORDER_W); + row_sizer->Add(m_text_ctrl, 1, wxEXPAND); } -void BulkExportDialog::Item::init_selection_ctrl(wxBoxSizer* row_sizer, int bed_index) +void BulkExportDialog::Item::init_selection_ctrl(wxFlexGridSizer* row_sizer, int bed_index) { m_checkbox = new ::CheckBox(m_parent, std::to_string(bed_index + 1)); m_checkbox->SetFont(wxGetApp().bold_font()); @@ -51,13 +51,13 @@ void BulkExportDialog::Item::init_selection_ctrl(wxBoxSizer* row_sizer, int bed_ this->selected = event.IsChecked(); }); - row_sizer->Add(m_checkbox, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BORDER_W); + row_sizer->Add(m_checkbox, 0, wxALIGN_CENTER_VERTICAL); m_checkbox->SetValue(this->selected); } BulkExportDialog::Item::Item( wxWindow *parent, - wxBoxSizer *sizer, + wxFlexGridSizer*sizer, const fs::path &path, const int bed_index, Validator validator @@ -69,12 +69,9 @@ BulkExportDialog::Item::Item( m_validator(std::move(validator)), m_directory(path.parent_path()) { - wxBoxSizer* row_sizer = new wxBoxSizer(wxHORIZONTAL); - init_selection_ctrl(row_sizer, bed_index); - init_input_name_ctrl(row_sizer, path.filename().string()); - row_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, BORDER_W); - - sizer->Add(row_sizer,0, wxEXPAND | wxTOP, BORDER_W); + init_selection_ctrl(sizer, bed_index); + init_input_name_ctrl(sizer, path.filename().string()); + sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, BORDER_W); m_valid_bmp->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& event) { event.Show(selected); }); update(); @@ -177,8 +174,6 @@ void BulkExportDialog::Item::update() m_status = status; update_valid_bmp(); - - m_parent->Layout(); } std::string get_bmp_name(const BulkExportDialog::ItemStatus status) { @@ -214,7 +209,7 @@ BulkExportDialog::BulkExportDialog(const std::vector> & wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - m_sizer = new wxBoxSizer(wxVERTICAL); + m_sizer = new wxFlexGridSizer(paths.size(), 3, wxSize(0.5*BORDER_W, BORDER_W)); for (const auto&[bed_index, path] : paths) { AddItem(path, bed_index); @@ -223,6 +218,7 @@ BulkExportDialog::BulkExportDialog(const std::vector> & // Add dialog's buttons wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); + btnOK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(enable_ok_btn()); }); topSizer->Add(m_sizer, 0, wxEXPAND | wxALL, BORDER_W); @@ -244,6 +240,19 @@ void BulkExportDialog::AddItem(const fs::path& path, int bed_index) m_items.push_back(std::make_unique(this, m_sizer, path, bed_index, PathValidator{m_items})); } +void BulkExportDialog::accept() +{ + if (has_warnings()) { + MessageDialog dialog(nullptr, + _L("Some of the selected files already exists. Do you want to replace them?"), + _L("Export Beds"), wxYES_NO | wxICON_QUESTION); + if (dialog.ShowModal() == wxID_NO) + return; + } + + EndModal(wxID_OK); +} + bool BulkExportDialog::enable_ok_btn() const { for (const auto &item : m_items) @@ -261,13 +270,6 @@ bool BulkExportDialog::enable_ok_btn() const return !all_unselected; } -bool BulkExportDialog::Layout() -{ - const bool ret = DPIDialog::Layout(); - this->Fit(); - return ret; -} - std::vector>> BulkExportDialog::get_paths() const { std::vector>> result; std::transform( @@ -284,6 +286,15 @@ std::vector>> BulkExportDialog::get_paths return result; } +bool BulkExportDialog::has_warnings() const +{ + for (const auto& item : m_items) + if (item->selected && item->is_warning()) { + return true; + } + return false; +} + void BulkExportDialog::on_dpi_changed(const wxRect&) { const int& em = em_unit(); diff --git a/src/slic3r/GUI/BulkExportDialog.hpp b/src/slic3r/GUI/BulkExportDialog.hpp index deff9b2b08..83c47cdcf6 100644 --- a/src/slic3r/GUI/BulkExportDialog.hpp +++ b/src/slic3r/GUI/BulkExportDialog.hpp @@ -17,6 +17,7 @@ class wxString; class wxStaticText; class wxTextCtrl; class wxStaticBitmap; +class wxFlexGridSizer; namespace Slic3r { class Print; @@ -39,7 +40,7 @@ public: >; Item( wxWindow *parent, - wxBoxSizer *sizer, + wxFlexGridSizer *sizer, const boost::filesystem::path &path, const int bed_index, Validator validator @@ -53,7 +54,8 @@ public: // directly to its address. void update_valid_bmp(); - bool is_valid() const { return m_status != ItemStatus::NoValid; } + bool is_valid() const { return m_status != ItemStatus::NoValid; } + bool is_warning() const { return m_status == ItemStatus::Warning; } boost::filesystem::path path; int bed_index{}; @@ -68,21 +70,21 @@ public: Validator m_validator; boost::filesystem::path m_directory{}; - void init_input_name_ctrl(wxBoxSizer *row_sizer, const std::string &path); - void init_selection_ctrl(wxBoxSizer *row_sizer, int bed_index); + void init_input_name_ctrl(wxFlexGridSizer*row_sizer, const std::string &path); + void init_selection_ctrl(wxFlexGridSizer*row_sizer, int bed_index); void update(); }; private: // This must be a unique ptr, because Item does not have copy nor move constructors. std::vector> m_items; - wxBoxSizer *m_sizer{nullptr}; + wxFlexGridSizer*m_sizer{nullptr}; public: BulkExportDialog(const std::vector> &paths); - bool Layout() override; std::vector>> get_paths() const; + bool has_warnings() const; protected: void on_dpi_changed(const wxRect &) override; @@ -90,6 +92,7 @@ protected: private: void AddItem(const boost::filesystem::path &path, int bed_index); + void accept(); bool enable_ok_btn() const; }; From 7cd7af573037f2f2fa4f9e9c2cd6c98ac50bd13b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 13 Dec 2024 10:44:39 +0100 Subject: [PATCH 17/18] BulkExportDialog: Show not valid beds too --- src/slic3r/GUI/BulkExportDialog.cpp | 21 +++++++++++++++------ src/slic3r/GUI/BulkExportDialog.hpp | 6 +++--- src/slic3r/GUI/Plater.cpp | 10 ++++++---- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/BulkExportDialog.cpp b/src/slic3r/GUI/BulkExportDialog.cpp index fffb0d455f..27705e6a29 100644 --- a/src/slic3r/GUI/BulkExportDialog.cpp +++ b/src/slic3r/GUI/BulkExportDialog.cpp @@ -58,21 +58,30 @@ void BulkExportDialog::Item::init_selection_ctrl(wxFlexGridSizer* row_sizer, int BulkExportDialog::Item::Item( wxWindow *parent, wxFlexGridSizer*sizer, - const fs::path &path, + const std::optional& path_opt, const int bed_index, Validator validator ): - path(path), bed_index(bed_index), m_parent(parent), m_valid_bmp(new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark"))), - m_validator(std::move(validator)), - m_directory(path.parent_path()) + m_validator(std::move(validator)) { + if (path_opt) { + path = *path_opt; + m_directory = path.parent_path(); + } + init_selection_ctrl(sizer, bed_index); init_input_name_ctrl(sizer, path.filename().string()); sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, BORDER_W); + if (!path_opt) { + m_checkbox->Enable(false); + m_checkbox->SetValue(false); + selected = false; + } + m_valid_bmp->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& event) { event.Show(selected); }); update(); } @@ -191,7 +200,7 @@ void BulkExportDialog::Item::update_valid_bmp() m_valid_bmp->SetBitmap(*get_bmp_bundle(get_bmp_name(m_status))); } -BulkExportDialog::BulkExportDialog(const std::vector> &paths): +BulkExportDialog::BulkExportDialog(const std::vector>> &paths): DPIDialog( nullptr, wxID_ANY, @@ -235,7 +244,7 @@ BulkExportDialog::BulkExportDialog(const std::vector> & #endif } -void BulkExportDialog::AddItem(const fs::path& path, int bed_index) +void BulkExportDialog::AddItem(const std::optional& path, int bed_index) { m_items.push_back(std::make_unique(this, m_sizer, path, bed_index, PathValidator{m_items})); } diff --git a/src/slic3r/GUI/BulkExportDialog.hpp b/src/slic3r/GUI/BulkExportDialog.hpp index 83c47cdcf6..83b72f4fba 100644 --- a/src/slic3r/GUI/BulkExportDialog.hpp +++ b/src/slic3r/GUI/BulkExportDialog.hpp @@ -41,7 +41,7 @@ public: Item( wxWindow *parent, wxFlexGridSizer *sizer, - const boost::filesystem::path &path, + const std::optional& path, const int bed_index, Validator validator ); @@ -82,7 +82,7 @@ private: public: - BulkExportDialog(const std::vector> &paths); + BulkExportDialog(const std::vector>> &paths); std::vector>> get_paths() const; bool has_warnings() const; @@ -91,7 +91,7 @@ protected: void on_sys_color_changed() override {} private: - void AddItem(const boost::filesystem::path &path, int bed_index); + void AddItem(const std::optional& path, int bed_index); void accept(); bool enable_ok_btn() const; }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9bd6da6e80..09586ff644 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6060,11 +6060,12 @@ void Plater::export_all_gcodes(bool prefer_removable) { std::map prints_to_export; - std::vector> paths; + std::vector >> paths; - for (int print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) { + for (int print_index{0}; print_index < s_multiple_beds.get_number_of_beds(); ++print_index) { const std::unique_ptr &print{this->get_fff_prints()[print_index]}; if (!print || !is_sliceable(s_print_statuses[print_index])) { + paths.emplace_back(print_index, std::nullopt); continue; } @@ -6707,11 +6708,12 @@ void Plater::connect_gcode_all() { } const PrusaConnectNew connect{*print_host_ptr}; - std::vector> paths; + std::vector >> paths; - for (std::size_t print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) { + for (std::size_t print_index{0}; print_index < s_multiple_beds.get_number_of_beds(); ++print_index) { const std::unique_ptr &print{this->get_fff_prints()[print_index]}; if (!print || !is_sliceable(s_print_statuses[print_index])) { + paths.emplace_back(print_index, std::nullopt); continue; } From 3896ddc40c2d38bcf50f035a699cee995a20c839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Fri, 13 Dec 2024 10:00:48 +0100 Subject: [PATCH 18/18] Fix error checking during bulk export There is no reason to check for file extension nor special characters when a directory is picked. --- src/slic3r/GUI/Plater.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 09586ff644..cbead96a7b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5947,11 +5947,6 @@ std::optional Plater::get_multiple_output_dir(const std::string &start } 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; }