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] 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__