From dbb12e9537f027ecf96e8a0c2e2341a73b317406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Fri, 29 Nov 2024 21:19:16 +0100 Subject: [PATCH] Autoslicing and aggregated statistics - Call print apply on all print within update_background_process - Render multiple bed thumnail only when there is a reason to (by requesting extra frame) - Show status of the slicing process for each bed - Add aggregated statistics --- bundled_deps/imgui/imgui/imconfig.h | 3 + resources/icons/print_finished.svg | 4 + resources/icons/print_idle.svg | 91 ++++++ resources/icons/print_running.svg | 5 + src/libslic3r/GCode.cpp | 6 +- src/libslic3r/MultipleBeds.cpp | 118 ++++++-- src/libslic3r/MultipleBeds.hpp | 36 ++- src/libslic3r/Print.hpp | 2 + src/slic3r/GUI/GLCanvas3D.cpp | 413 ++++++++++++++++++++++++---- src/slic3r/GUI/ImGuiWrapper.cpp | 3 + src/slic3r/GUI/Plater.cpp | 82 ++++-- src/slic3r/GUI/Plater.hpp | 3 + 12 files changed, 668 insertions(+), 98 deletions(-) create mode 100644 resources/icons/print_finished.svg create mode 100644 resources/icons/print_idle.svg create mode 100644 resources/icons/print_running.svg diff --git a/bundled_deps/imgui/imgui/imconfig.h b/bundled_deps/imgui/imgui/imconfig.h index 19aed5817b..2b7e83e128 100644 --- a/bundled_deps/imgui/imgui/imconfig.h +++ b/bundled_deps/imgui/imgui/imconfig.h @@ -210,6 +210,9 @@ namespace ImGui // icon for multiple beds const wchar_t SliceAllBtnIcon = 0x2811; + const wchar_t PrintIdle = 0x2812; + const wchar_t PrintRunning = 0x2813; + const wchar_t PrintFinished = 0x2814; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/resources/icons/print_finished.svg b/resources/icons/print_finished.svg new file mode 100644 index 0000000000..88747cb95d --- /dev/null +++ b/resources/icons/print_finished.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/print_idle.svg b/resources/icons/print_idle.svg new file mode 100644 index 0000000000..6e4058d10f --- /dev/null +++ b/resources/icons/print_idle.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/print_running.svg b/resources/icons/print_running.svg new file mode 100644 index 0000000000..927c3e70ba --- /dev/null +++ b/resources/icons/print_running.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 3ba85b5bd4..9a81ed8ddc 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -466,9 +466,11 @@ namespace DoExport { static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector& extruders, PrintStatistics& print_statistics) { const GCodeProcessorResult& result = processor.get_result(); - print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); + print_statistics.normal_print_time_seconds = result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time; + print_statistics.silent_print_time_seconds = result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time; + print_statistics.estimated_normal_print_time = get_time_dhms(print_statistics.normal_print_time_seconds); print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? - get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; + get_time_dhms(print_statistics.silent_print_time_seconds) : "N/A"; // update filament statictics double total_extruded_volume = 0.0; diff --git a/src/libslic3r/MultipleBeds.cpp b/src/libslic3r/MultipleBeds.cpp index 6c8a803c06..2c31b449df 100644 --- a/src/libslic3r/MultipleBeds.cpp +++ b/src/libslic3r/MultipleBeds.cpp @@ -5,6 +5,7 @@ #include "Print.hpp" #include +#include namespace Slic3r { @@ -154,41 +155,114 @@ void MultipleBeds::set_active_bed(int i) m_active_bed = i; } -void MultipleBeds::move_active_to_first_bed(Model& model, const BuildVolume& build_volume, bool to_or_from) const -{ - static std::vector> old_state; - size_t i = 0; - assert(! to_or_from || old_state.empty()); - +namespace MultipleBedsUtils { +InstanceOffsets get_instance_offsets(Model& model) { + InstanceOffsets result; for (ModelObject* mo : model.objects) { for (ModelInstance* mi : mo->instances) { - if (to_or_from) { - old_state.resize(i+1); - old_state[i] = std::make_pair(mi->get_offset(), mi->printable); - if (this->is_instance_on_active_bed(mi->id())) - mi->set_offset(mi->get_offset() - get_bed_translation(get_active_bed())); - else - mi->printable = false; - } else { - mi->set_offset(old_state[i].first); - mi->printable = old_state[i].second; - } - ++i; + result.emplace_back(mi->get_offset()); } } - if (! to_or_from) - old_state.clear(); + return result; } +ObjectInstances get_object_instances(const Model& model) { + ObjectInstances result; + std::transform( + model.objects.begin(), + model.objects.end(), + std::back_inserter(result), + [](ModelObject *object){ + return std::pair{object, object->instances}; + } + ); -bool MultipleBeds::is_instance_on_active_bed(ObjectID id) const + return result; +} + +void restore_instance_offsets(Model& model, const InstanceOffsets &offsets) +{ + size_t i = 0; + for (ModelObject* mo : model.objects) { + for (ModelInstance* mi : mo->instances) { + mi->set_offset(offsets[i++]); + } + } +} + +void restore_object_instances(Model& model, const ObjectInstances &object_instances) { + ModelObjectPtrs objects; + + std::transform( + object_instances.begin(), + object_instances.end(), + std::back_inserter(objects), + [](const std::pair &key_value){ + auto [object, instances]{key_value}; + object->instances = std::move(instances); + return object; + } + ); + + model.objects = objects; +} + +void with_single_bed_model(Model &model, const int bed_index, const std::function &callable) { + const InstanceOffsets original_offssets{MultipleBedsUtils::get_instance_offsets(model)}; + const ObjectInstances original_objects{get_object_instances(model)}; + Slic3r::ScopeGuard guard([&]() { + restore_object_instances(model, original_objects); + restore_instance_offsets(model, original_offssets); + }); + + s_multiple_beds.move_from_bed_to_first_bed(model, bed_index); + s_multiple_beds.remove_instances_outside_outside_bed(model, bed_index); + callable(); +} + +} + +bool MultipleBeds::is_instance_on_bed(const ObjectID id, const int bed_index) const { auto it = m_inst_to_bed.find(id); - return (it != m_inst_to_bed.end() && it->second == m_active_bed); + return (it != m_inst_to_bed.end() && it->second == bed_index); } +void MultipleBeds::remove_instances_outside_outside_bed(Model& model, const int bed_index) const { + for (ModelObject* mo : model.objects) { + mo->instances.erase(std::remove_if( + mo->instances.begin(), + mo->instances.end(), + [&](const ModelInstance* instance){ + return !this->is_instance_on_bed(instance->id(), bed_index); + } + ), mo->instances.end()); + } + model.objects.erase(std::remove_if( + model.objects.begin(), + model.objects.end(), + [](const ModelObject *object){ + return object->instances.empty(); + } + ), model.objects.end()); +} + +void MultipleBeds::move_from_bed_to_first_bed(Model& model, const int bed_index) const +{ + if (bed_index < 0 || bed_index >= MAX_NUMBER_OF_BEDS) { + assert(false); + return; + } + for (ModelObject* mo : model.objects) { + for (ModelInstance* mi : mo->instances) { + if (this->is_instance_on_bed(mi->id(), bed_index)) { + mi->set_offset(mi->get_offset() - get_bed_translation(bed_index)); + } + } + } +} bool MultipleBeds::is_glvolume_on_thumbnail_bed(const Model& model, int obj_idx, int instance_idx) const { diff --git a/src/libslic3r/MultipleBeds.hpp b/src/libslic3r/MultipleBeds.hpp index d5209d2315..a31bb91cd2 100644 --- a/src/libslic3r/MultipleBeds.hpp +++ b/src/libslic3r/MultipleBeds.hpp @@ -1,6 +1,7 @@ #ifndef libslic3r_MultipleBeds_hpp_ #define libslic3r_MultipleBeds_hpp_ +#include "libslic3r/Model.hpp" #include "libslic3r/ObjectID.hpp" #include "libslic3r/Point.hpp" #include "libslic3r/BoundingBox.hpp" @@ -24,6 +25,10 @@ Index grid_coords2index(const GridCoords &coords); GridCoords index2grid_coords(Index index); } +inline std::vector s_bed_selector_thumbnail_texture_ids; +inline std::array s_bed_selector_thumbnail_changed; +inline bool bed_selector_updated{false}; + class MultipleBeds { public: MultipleBeds() = default; @@ -44,7 +49,11 @@ public: int get_active_bed() const { return m_active_bed; } void set_active_bed(int i); - void move_active_to_first_bed(Model& model, const BuildVolume& build_volume, bool to_or_from) const; + + void remove_instances_outside_outside_bed(Model& model, const int bed) const; + + // Sets !printable to all instances outside the active bed. + void move_from_bed_to_first_bed(Model& model, const int bed) const; void set_thumbnail_bed_idx(int bed_idx) { m_bed_for_thumbnails_generation = bed_idx; } int get_thumbnail_bed_idx() const { return m_bed_for_thumbnails_generation; } @@ -71,7 +80,7 @@ public: void autoslice_next_bed(); private: - bool is_instance_on_active_bed(ObjectID id) const; + bool is_instance_on_bed(const ObjectID id, const int bed_index) const; int m_number_of_beds = 1; int m_active_bed = 0; @@ -91,6 +100,29 @@ private: }; extern MultipleBeds s_multiple_beds; + +namespace MultipleBedsUtils { + +using InstanceOffsets = std::vector; +// The bool is true if the instance is printable. +// The order is from 'for o in objects; for i in o.instances. +InstanceOffsets get_instance_offsets(Model& model); + +using ObjectInstances = std::vector>; +ObjectInstances get_object_instances(const Model& model); +void restore_instance_offsets(Model& model, const InstanceOffsets &offsets); +void restore_object_instances(Model& model, const ObjectInstances &object_instances); + + +/** +For each print apply call do: +- move all instances according to their active bed +- apply +- move all instances back to their respective beds +*/ +void with_single_bed_model(Model &model, const int bed_index, const std::function &callable); +} + } // namespace Slic3r #endif // libslic3r_MultipleBeds_hpp_ diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 61d4714633..c1efc63598 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -520,6 +520,8 @@ bool is_toolchange_required( struct PrintStatistics { PrintStatistics() { clear(); } + float normal_print_time_seconds; + float silent_print_time_seconds; std::string estimated_normal_print_time; std::string estimated_silent_print_time; double total_used_filament; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7203247c3c..9b962d8a94 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -13,6 +13,7 @@ #include // IWYU pragma: keep #include +#include #include "libslic3r/BuildVolume.hpp" #include "libslic3r/ClipperUtils.hpp" @@ -44,6 +45,7 @@ #include "NotificationManager.hpp" #include "format.hpp" +#include "slic3r/GUI/BitmapCache.hpp" #include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" #include "slic3r/Utils/UndoRedo.hpp" @@ -73,6 +75,7 @@ #include #include +#include #include #include @@ -1823,6 +1826,242 @@ void GLCanvas3D::update_volumes_colors_by_extruder() m_volumes.update_colors_by_extruder(m_config); } +using PerBedStatistics = std::vector +>>; + +PerBedStatistics get_statistics(){ + PerBedStatistics result; + for (int bed_index=0; bed_indexget_fff_prints()[bed_index].get(); + if (print->empty() || !print->finished()) { + continue; + } + result.emplace_back(bed_index, std::ref(print->print_statistics())); + } + return result; +} + +struct StatisticsSum { + float cost{}; + float filement_weight{}; + float filament_length{}; + float normal_print_time{}; + float silent_print_time{}; +}; + +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; + } + + return result; +} + +void project_overview_table() { + ImGui::Text("%s", _u8L("Project overview").c_str()); + if (ImGui::BeginTable("table1", 6)) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0); + ImGui::TableSetupColumn( + _u8L("Cost").c_str(), + ImGuiTableColumnFlags_WidthFixed, + 60.0f + ); + ImGui::TableSetupColumn( + (_u8L("Filament") + " (g)").c_str(), + ImGuiTableColumnFlags_WidthFixed, + 100.0f + ); + ImGui::TableSetupColumn( + (_u8L("Filament") + " (m)").c_str(), + ImGuiTableColumnFlags_WidthFixed, + 100.0f + ); + ImGui::TableSetupColumn( + (_u8L("Estimate Time") + " (" + _u8L("Stealth mode") +")").c_str(), + ImGuiTableColumnFlags_WidthFixed, + 200.0f + ); + ImGui::TableSetupColumn( + (_u8L("Estimate Time") + " (" + _u8L("Normal mode") +")").c_str(), + ImGuiTableColumnFlags_WidthFixed, + 200.0f + ); + 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()); + } + + const StatisticsSum statistics_sum{get_statistics_sum()}; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", _u8L("Total").c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", wxString::Format("%.2f", statistics_sum.cost).ToStdString().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", wxString::Format("%.2f", statistics_sum.filement_weight).ToStdString().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", wxString::Format("%.2f", statistics_sum.filament_length / 1000).ToStdString().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", get_time_dhms(statistics_sum.silent_print_time).c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", get_time_dhms(statistics_sum.normal_print_time).c_str()); + + ImGui::EndTable(); + } +} + +struct ExtruderStatistics { + float filament_weight{}; + float filament_length{}; +}; + +using PerExtruderStatistics = std::map< + std::size_t, + ExtruderStatistics +>; + +PerExtruderStatistics get_extruder_statistics(){ + PerExtruderStatistics result; + for (int bed_index=0; bed_indexget_fff_prints()[bed_index].get(); + if (print->empty() || !print->finished()) { + continue; + } + print->print_statistics(); + const auto& extruders_filaments{wxGetApp().preset_bundle->extruders_filaments}; + for (const auto &[filament_id, filament_volume] : print->print_statistics().filament_stats) { + const Preset* preset = extruders_filaments[filament_id].get_selected_preset(); + if (preset == nullptr) { + continue; + } + + const double filament_density = preset->config.opt_float("filament_density", 0); + const double diameter = preset->config.opt_float("filament_diameter", filament_id); + result[filament_id].filament_weight += filament_volume * filament_density / 1000.0f; + result[filament_id].filament_length += filament_volume / (M_PI * diameter * diameter / 4.0) / 1000.0; + } + } + return result; +} + +ExtruderStatistics sum_extruder_statistics( + const PerExtruderStatistics &per_extruder_statistics +) { + ExtruderStatistics result; + for (const auto &[_, statistics] : per_extruder_statistics) { + result.filament_weight += statistics.filament_weight; + result.filament_length += statistics.filament_length; + } + + return result; +} + +void extruder_usage_table(const PerExtruderStatistics &extruder_statistics) { + ImGui::Text("%s", _u8L("Extruder usage breakdown").c_str()); + if (ImGui::BeginTable("table1", 3)) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn( + (_u8L("Filament") + " (g)").c_str(), + ImGuiTableColumnFlags_WidthFixed, + 100.0f + ); + ImGui::TableSetupColumn( + (_u8L("Filament") + " (m)").c_str(), + ImGuiTableColumnFlags_WidthFixed, + 100.0f + ); + ImGui::TableHeadersRow(); + + for (const auto &[extruder_index, statistics] : extruder_statistics) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", (_u8L("Extruder") + wxString::Format(" %d", extruder_index + 1)).ToStdString().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", wxString::Format("%.2f", statistics.filament_weight).ToStdString().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", wxString::Format("%.2f", statistics.filament_length).ToStdString().c_str()); + } + + const ExtruderStatistics extruder_statistics_sum{sum_extruder_statistics(extruder_statistics)}; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", _u8L("Total").c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", wxString::Format("%.2f", extruder_statistics_sum.filament_weight).ToStdString().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", wxString::Format("%.2f", extruder_statistics_sum.filament_length).ToStdString().c_str()); + + ImGui::EndTable(); + } +} + +void begin_statistics(const char *window_name) { + ImGuiWindowFlags windows_flags = + ImGuiWindowFlags_NoCollapse + | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_AlwaysAutoResize + | ImGuiWindowFlags_NoScrollbar + | ImGuiWindowFlags_NoScrollWithMouse; + + const ImVec2 center{ImGui::GetMainViewport()->GetCenter()}; + const ImVec2 position{center + ImVec2{0, -150.0f}}; + ImGui::SetNextWindowPos(position, ImGuiCond_Appearing, ImVec2{0.5f, 0.5f}); + + ImGui::Begin(window_name, nullptr, windows_flags); +} + +void render_print_statistics() { + begin_statistics(_u8L("Statistics").c_str()); + ImGui::Spacing(); + project_overview_table(); + ImGui::Separator(); + + const PerExtruderStatistics extruder_statistics{get_extruder_statistics()}; + if (extruder_statistics.size() > 1) { + ImGui::NewLine(); + extruder_usage_table(extruder_statistics); + ImGui::Separator(); + } + ImGui::End(); +} + +void render_autoslicing_wait() { + const std::string text{_u8L("Statistics will be available once all beds are sliced")}; + const float text_width = ImGui::CalcTextSize(text.c_str()).x; + + ImGui::SetNextWindowSize(ImVec2(text_width + 50, 110.0)); + begin_statistics(_u8L("Waiting for statistics").c_str()); + ImGui::NewLine(); + const float width{ImGui::GetContentRegionAvail().x}; + float offset_x = (width - text_width) / 2.0f; + if (offset_x > 0.0f){ + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offset_x); + } + ImGui::Text("%s", text.c_str()); + ImGui::NewLine(); + ImGui::End(); +} + void GLCanvas3D::render() { if (m_in_render) { @@ -1964,34 +2203,31 @@ void GLCanvas3D::render() if (m_picking_enabled && m_rectangle_selection.is_dragging()) m_rectangle_selection.render(*this); } else { - // Autoslicing. - // Render the combined statistics if all is ready. - bool valid = true; - double total_g = 0; - for (size_t i=0; iget_fff_prints()[i].get(); - if (!print->finished()) { - // TODO: Only active bed invalidation can be detected here. - valid = false; - break; - } - total_g += print->print_statistics().total_used_filament; - } - ImGui::Begin("Total stats"); - if (valid) - ImGui::Text("%s", std::to_string(total_g).c_str()); - else - ImGui::Text("Wait until all beds are sliced..."); - ImGui::End(); + const auto &prints{ + tcb::span{wxGetApp().plater()->get_fff_prints()} + .subspan(0, s_multiple_beds.get_number_of_beds()) + }; - if (!valid) { - if (fff_print()->finished()) + const bool all_finished{std::all_of( + prints.begin(), + prints.end(), + [](const std::unique_ptr &print){ + return print->finished() || print->empty(); + } + )}; + + if (!all_finished) { + render_autoslicing_wait(); + if (fff_print()->finished() || fff_print()->empty()) { s_multiple_beds.autoslice_next_bed(); - else + } else { wxGetApp().plater()->schedule_background_process(); + } + } else { + render_print_statistics(); } } - + _render_overlays(); _render_bed_selector(); @@ -2537,7 +2773,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re #endif // SLIC3R_OPENGL_ES const BoundingBoxf3& bb = volume->bounding_box(); m_wipe_tower_bounding_boxes[bed_idx] = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)}; - if(bed_idx < s_multiple_beds.get_number_of_beds()) { + if(static_cast(bed_idx) < s_multiple_beds.get_number_of_beds()) { m_volumes.volumes.emplace_back(volume); const auto volume_idx_wipe_tower_new{static_cast(m_volumes.volumes.size() - 1)}; auto it = volume_idxs_wipe_towers_old.find(m_volumes.volumes.back()->geometry_id.second); @@ -6330,20 +6566,81 @@ 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 "Idle"; + case PrintStatus::running: return "Running"; + case PrintStatus::finished: return "Finished"; + } + return {}; +} + +wchar_t get_raw_status_icon(const PrintStatus status) { + switch(status) { + case PrintStatus::finished: return ImGui::PrintFinished; + case PrintStatus::running: return ImGui::PrintRunning; + case PrintStatus::idle: return ImGui::PrintIdle; + } + return ImGui::PrintIdle; +} + +std::string get_status_icon(const PrintStatus status) { + return boost::nowide::narrow(std::wstring{get_raw_status_icon(status)}); +} + +bool bed_selector_thumbnail( + const ImVec2 size, + const ImVec2 padding, + const float side, + const float border, + const GLuint texture_id, + const PrintStatus status +) { + ImGuiWindow* window = GImGui->CurrentWindow; + const ImVec2 current_position = GImGui->CurrentWindow->DC.CursorPos; + const ImVec2 state_pos = current_position + ImVec2(border, side - 20.f - border); + + const bool clicked{ImGui::ImageButton( + (void*)(int64_t)texture_id, + size - padding, + ImVec2(0, 1), + ImVec2(1, 0), + border + )}; + + const std::string icon{get_status_icon(status)}; + + window->DrawList->AddText( + GImGui->Font, + GImGui->FontSize, + state_pos, + ImGui::GetColorU32(ImGuiCol_Text), + icon.c_str(), + icon.c_str() + icon.size() + ); + + return clicked; +} + void Slic3r::GUI::GLCanvas3D::_render_bed_selector() { static float btn_side = 80.f; static float btn_border = 2.f; - static bool hide_title = true; ImVec2 btn_size = ImVec2(btn_side, btn_side); - if (s_multiple_beds.get_number_of_beds() != 1 && wxGetApp().plater()->is_preview_shown()) { - auto render_bed_button = [btn_size, this](int i) - { - //ImGui::Text("%d", i); - //ImGui::SameLine(); + bool extra_frame{ false }; + static std::array, MAX_NUMBER_OF_BEDS> previous_print_status; + if (s_multiple_beds.get_number_of_beds() != 1 && wxGetApp().plater()->is_preview_shown()) { + auto render_bed_button = [btn_size, this, &extra_frame](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(); @@ -6355,10 +6652,36 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() bool clicked = false; ImVec2 btn_padding = ImVec2(btn_border, btn_border); - if (i >= int(s_th_tex_id.size()) || empty) + + PrintStatus 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 (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); - else - clicked = ImGui::ImageButton((void*)(int64_t)s_th_tex_id[i], btn_size - btn_padding, ImVec2(0, 1), ImVec2(1, 0), btn_border); + } else { + clicked = bed_selector_thumbnail( + btn_size, + btn_padding, + btn_side, + btn_border, + s_bed_selector_thumbnail_texture_ids[i], + print_status + ); + } if (clicked && ! empty) select_bed(i, true); @@ -6367,15 +6690,9 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() if (empty) ImGui::PopItemFlag(); - //std::string status_text; - //if (wxGetApp().plater()->get_fff_prints()[i]->finished()) - // status_text = "Finished"; - //else if (m_process->fff_print() == wxGetApp().plater()->get_fff_prints()[i].get() && m_process->running()) - // status_text = "Running"; - //else - // status_text = "Idle"; - //if (ImGui::IsItemHovered()) - // ImGui::SetTooltip(status_text.c_str()); + const std::string status_text{get_status_text(print_status)}; + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", status_text.c_str()); }; ImGuiWrapper& imgui = *wxGetApp().imgui(); @@ -6398,11 +6715,11 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2()); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2()); - // Disable for now. - //if (imgui.image_button(ImGui::SliceAllBtnIcon, "Slice All")) { - // if (!s_multiple_beds.is_autoslicing()) - // s_multiple_beds.start_autoslice([this](int i, bool user) { this->select_bed(i, user); }); - //} + if (imgui.image_button(ImGui::SliceAllBtnIcon, "Slice All")) { + if (!s_multiple_beds.is_autoslicing()) + s_multiple_beds.start_autoslice([this](int i, bool user) { this->select_bed(i, user); }); + } + ImGui::SameLine(); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, btn_border); @@ -6418,8 +6735,6 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() ImGui::PopStyleVar(3); #if use_scrolling - bool extra_frame{ false }; - ImVec2 win_size = ImGui::GetCurrentWindow()->ContentSizeIdeal + ImGui::GetCurrentWindow()->WindowPadding * 2.f + ImGui::GetCurrentWindow()->ScrollbarSizes + diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index d8799ba114..64b1ca2ad5 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -113,6 +113,9 @@ static const std::map font_icons = { {ImGui::HorizontalHide , "horizontal_hide" }, {ImGui::HorizontalShow , "horizontal_show" }, {ImGui::SliceAllBtnIcon , "slice_all" }, + {ImGui::PrintIdle , "print_idle" }, + {ImGui::PrintRunning , "print_running" }, + {ImGui::PrintFinished , "print_finished" }, }; static const std::map font_icons_large = { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f8f7154dfc..3a0cee5b03 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -18,10 +18,12 @@ ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher ///|/ #include "Plater.hpp" +#include "slic3r/GUI/BitmapCache.hpp" #include "slic3r/GUI/Jobs/UIThreadWorker.hpp" #include #include +#include #include #include #include @@ -155,8 +157,6 @@ using Slic3r::GUI::format_wxstr; static const std::pair THUMBNAIL_SIZE_3MF = { 256, 256 }; -std::vector s_th_tex_id; - namespace Slic3r { namespace GUI { @@ -603,7 +603,6 @@ private: // vector of all warnings generated by last slicing std::vector> current_warnings; bool show_warning_dialog { false }; - }; const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); @@ -2160,6 +2159,24 @@ void Plater::priv::process_validation_warning(const std::vector& wa } } +std::array apply_to_inactive_beds( + Model &model, + std::vector> &prints, + const DynamicPrintConfig &config +) { + std::array result; + for (std::size_t bed_index{0}; bed_index < prints.size(); ++bed_index) { + const std::unique_ptr &print{prints[bed_index]}; + if (!print || bed_index == s_multiple_beds.get_active_bed()) { + continue; + } + using MultipleBedsUtils::with_single_bed_model; + with_single_bed_model(model, bed_index, [&](){ + result[bed_index] = print->apply(model, config); + }); + } + return result; +} // Update background processing thread from the current config and Model. // Returns a bitmask of UpdateBackgroundProcessReturnState. @@ -2174,6 +2191,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool background_process.set_gcode_result(&gcode_results[active_bed]); background_process.select_technology(this->printer_technology); + if (s_beds_just_switched && printer_technology == ptFFF) { PrintBase::SlicingStatus status(q->active_fff_print(), -1); SlicingStatusEvent evt(EVT_SLICING_UPDATE, 0, status); @@ -2182,10 +2200,12 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool notification_manager->close_notification_of_type(NotificationType::ExportOngoing); q->sidebar().show_sliced_info_sizer(background_process.finished()); } - // bitmap of enum UpdateBackgroundProcessReturnState unsigned int return_state = 0; + if (s_multiple_beds.is_autoslicing()) { + return_state = return_state | UPDATE_BACKGROUND_PROCESS_FORCE_RESTART; + } // Get the config ready. The binary gcode flag depends on Preferences, which the backend has no access to. DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(); @@ -2211,18 +2231,29 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // Update the "out of print bed" state of ModelInstances. update_print_volume_state(); - // Move all instances according to their active bed: - s_multiple_beds.move_active_to_first_bed(q->model(), q->build_volume(), true); + Print::ApplyStatus invalidated{Print::ApplyStatus::APPLY_STATUS_INVALIDATED}; + bool was_running = background_process.running(); + using MultipleBedsUtils::with_single_bed_model; - // Apply new config to the possibly running background task. - bool was_running = background_process.running(); - Print::ApplyStatus invalidated = background_process.apply(q->model(), full_config); + std::array apply_statuses{ + apply_to_inactive_beds(q->model(), q->p->fff_prints, full_config) + }; + with_single_bed_model(q->model(), s_multiple_beds.get_active_bed(), [&](){ + // Apply new config to the possibly running background task. + invalidated = background_process.apply(q->model(), full_config); + apply_statuses[s_multiple_beds.get_active_bed()] = invalidated; + }); - // Move all instances back to their respective beds. - s_multiple_beds.move_active_to_first_bed(q->model(), q->build_volume(), false); + const bool any_status_changed{std::any_of( + apply_statuses.begin(), + apply_statuses.end(), + [](Print::ApplyStatus status){ + return status != Print::ApplyStatus::APPLY_STATUS_UNCHANGED; + } + )}; // If current bed was invalidated, update thumbnails for all beds: - if (int num = s_multiple_beds.get_number_of_beds(); num > 1 && ! (invalidated & Print::ApplyStatus::APPLY_STATUS_UNCHANGED)) { + if (int num = s_multiple_beds.get_number_of_beds(); num > 1 && any_status_changed) { ThumbnailData data; ThumbnailsParams params; params.parts_only = true; @@ -2236,19 +2267,21 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool int curr_unpack_alignment = 0; glsafe(glGetIntegerv(GL_UNPACK_ALIGNMENT, &curr_unpack_alignment)); glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - glsafe(glDeleteTextures(s_th_tex_id.size(), s_th_tex_id.data())); - - s_th_tex_id.resize(num); - glsafe(glGenTextures(num, s_th_tex_id.data())); + glsafe(glDeleteTextures(s_bed_selector_thumbnail_texture_ids.size(), s_bed_selector_thumbnail_texture_ids.data())); + s_bed_selector_thumbnail_changed.fill(false); + + s_bed_selector_thumbnail_texture_ids.resize(num); + glsafe(glGenTextures(num, s_bed_selector_thumbnail_texture_ids.data())); for (int i = 0; i < num; ++i) { s_multiple_beds.set_thumbnail_bed_idx(i); generate_thumbnail(data, w, h, params, GUI::Camera::EType::Ortho); s_multiple_beds.set_thumbnail_bed_idx(-1); - glsafe(glBindTexture(GL_TEXTURE_2D, s_th_tex_id[i])); + glsafe(glBindTexture(GL_TEXTURE_2D, s_bed_selector_thumbnail_texture_ids[i])); glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast(w), static_cast(h), 0, GL_RGBA, GL_UNSIGNED_BYTE, data.pixels.data())); + s_bed_selector_thumbnail_changed[i] = true; } glsafe(glBindTexture(GL_TEXTURE_2D, curr_bound_texture)); glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, curr_unpack_alignment)); @@ -2411,12 +2444,15 @@ bool Plater::priv::restart_background_process(unsigned int state) return false; } - if ( ! this->background_process.empty() && - (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 && - ( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) || - (state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 || - (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) { - + if ( + !this->background_process.empty() + && (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 + && ( + ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && !this->background_process.finished()) + || (state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 + || (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 + ) + ) { // The print is valid and it can be started. if (this->background_process.start()) { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index af5058869f..bbe136fb8d 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -29,6 +29,8 @@ #include "libslic3r/GCode/GCodeProcessor.hpp" #include "Jobs/Job.hpp" #include "Jobs/Worker.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" +#include "slic3r/GUI/Camera.hpp" class wxString; @@ -118,6 +120,7 @@ public: void convert_gcode_to_binary(); 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); 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.