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/resources/icons/numbers.png b/resources/icons/numbers.png new file mode 100644 index 0000000000..25eb17b360 Binary files /dev/null and b/resources/icons/numbers.png differ 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..28679ca665 100644 --- a/src/libslic3r/MultipleBeds.hpp +++ b/src/libslic3r/MultipleBeds.hpp @@ -29,6 +29,20 @@ 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, + toolpath_outside +}; + +bool is_sliceable(const PrintStatus status); + +inline std::array s_print_statuses; + class MultipleBeds { public: MultipleBeds() = default; diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 81b39dec6d..4a6f6850c8 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -145,6 +145,58 @@ void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Tra mat.translate(s_multiple_beds.get_bed_translation(i)); render_internal(canvas, mat, projection_matrix, bottom, scale_factor, show_texture, false, is_thumbnail || i == bed_to_highlight); } + + if (m_digits_models.empty()) { + for (size_t i = 0; i < 10; ++i) { + GLModel::Geometry g; + g.format.vertex_layout = GLModel::Geometry::EVertexLayout::P3T2; + const double digit_part = 94./1024.; + g.add_vertex(Vec3f(0, 0, 0), Vec2f(digit_part * i, 1.)); + g.add_vertex(Vec3f(1, 0, 0), Vec2f(digit_part * (i+1), 1.)); + g.add_vertex(Vec3f(1, 1, 0), Vec2f(digit_part * (i+1), 0)); + g.add_vertex(Vec3f(0, 1, 0), Vec2f(digit_part * i, 0)); + g.add_triangle(0, 1, 3); + g.add_triangle(3, 1, 2); + m_digits_models.emplace_back(std::make_unique()); + 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) @@ -421,6 +473,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/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; diff --git a/src/slic3r/GUI/BulkExportDialog.cpp b/src/slic3r/GUI/BulkExportDialog.cpp index 2e1b764246..27705e6a29 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* input_path_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; @@ -37,31 +37,52 @@ void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, 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); }); - input_path_sizer->Add(m_text_ctrl, 1, wxEXPAND, BORDER_W); + row_sizer->Add(m_text_ctrl, 1, wxEXPAND); } + +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()); + wxGetApp().UpdateDarkUI(m_checkbox); + m_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { + this->selected = event.IsChecked(); + }); + + row_sizer->Add(m_checkbox, 0, wxALIGN_CENTER_VERTICAL); + m_checkbox->SetValue(this->selected); +} + BulkExportDialog::Item::Item( wxWindow *parent, - wxBoxSizer *sizer, - const boost::filesystem::path &path, + wxFlexGridSizer*sizer, + 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_valid_label(new wxStaticText(m_parent, wxID_ANY, "")), - m_validator(std::move(validator)), - m_directory(path.parent_path()) + m_validator(std::move(validator)) { - m_valid_label->SetFont(wxGetApp().bold_font()); + if (path_opt) { + path = *path_opt; + m_directory = path.parent_path(); + } - 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()); + 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); - sizer->Add(input_path_sizer,0, wxEXPAND | wxTOP, BORDER_W); - sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*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(); } @@ -157,13 +178,11 @@ 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(); - - m_parent->Layout(); } std::string get_bmp_name(const BulkExportDialog::ItemStatus status) { @@ -181,11 +200,11 @@ 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, - 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 @@ -199,15 +218,16 @@ BulkExportDialog::BulkExportDialog(const std::vector &paths): 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 fs::path& path : paths) { - AddItem(path); + for (const auto&[bed_index, path] : paths) { + AddItem(path, bed_index); } // 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); @@ -224,41 +244,66 @@ BulkExportDialog::BulkExportDialog(const std::vector &paths): #endif } -void BulkExportDialog::AddItem(const fs::path& path) +void BulkExportDialog::AddItem(const std::optional& path, int bed_index) { - 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, 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) - 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() -{ - const bool ret = DPIDialog::Layout(); - this->Fit(); - 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){ - return item->path; + [](const auto &item) -> std::pair> { + if (!item->selected) { + return {item->bed_index, std::nullopt}; + } + return {item->bed_index, item->path}; } ); 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 ba8a9e265b..83b72f4fba 100644 --- a/src/slic3r/GUI/BulkExportDialog.hpp +++ b/src/slic3r/GUI/BulkExportDialog.hpp @@ -9,11 +9,15 @@ #include #include "wxExtensions.hpp" #include "GUI_Utils.hpp" +#include + +#include "Widgets/CheckBox.hpp" class wxString; class wxStaticText; class wxTextCtrl; class wxStaticBitmap; +class wxFlexGridSizer; namespace Slic3r { class Print; @@ -36,8 +40,9 @@ public: >; Item( wxWindow *parent, - wxBoxSizer *sizer, - const boost::filesystem::path &path, + wxFlexGridSizer *sizer, + const std::optional& path, + const int bed_index, Validator validator ); Item(const Item &) = delete; @@ -49,39 +54,45 @@ 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{}; + 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(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; + + BulkExportDialog(const std::vector>> &paths); + std::vector>> get_paths() const; + bool has_warnings() 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 std::optional& path, int bed_index); + void accept(); bool enable_ok_btn() const; }; 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 f094a965f9..8dc8c56c8b 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()) @@ -1827,7 +1830,7 @@ void GLCanvas3D::update_volumes_colors_by_extruder() using PerBedStatistics = std::vector + std::optional> >>; PerBedStatistics get_statistics(){ @@ -1835,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; } @@ -1853,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; @@ -1897,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); @@ -2211,22 +2235,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 +2869,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 +2892,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(); @@ -6233,10 +6251,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,17 +6609,15 @@ 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::running: return _u8L("Slicing") + "..."; case PrintStatus::finished: return _u8L("Sliced"); + 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 {}; } @@ -6601,6 +6627,10 @@ 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; + case PrintStatus::toolpath_outside: return ImGui::PrintIdle; } return ImGui::PrintIdle; } @@ -6615,13 +6645,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) * wxGetApp().imgui()->get_style_scaling(); + 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, @@ -6643,26 +6674,66 @@ 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, const ImVec2 padding) +bool button_with_icon(const wchar_t icon, const std::string& tooltip, 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); + std::string btn_name = boost::nowide::narrow(std::wstring{ icon }); + + 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(); - 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); + 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); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s", _u8L("Slice all").c_str()); - } + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); - ImGui::PopStyleColor(4); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", tooltip.c_str()); - return clicked; + return pressed; } void Slic3r::GUI::GLCanvas3D::_render_bed_selector() @@ -6685,39 +6756,56 @@ 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) + ) { + 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()) + ) { + clicked = ImGui::Button( + std::to_string(i + 1).c_str(), btn_size + btn_padding + ); } else { clicked = bed_selector_thumbnail( btn_size, @@ -6725,20 +6813,23 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() btn_side, btn_border, scale, - s_bed_selector_thumbnail_texture_ids[i], - print_status + i, + 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()); } @@ -6782,7 +6873,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); }); 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(); 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" }, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 67f5ec33d2..cbead96a7b 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; @@ -581,8 +582,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; @@ -772,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: @@ -1891,10 +1892,39 @@ void Plater::priv::selection_changed() 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) { + 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()) { + 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() @@ -2182,7 +2212,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; @@ -2302,6 +2332,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(), @@ -2316,7 +2374,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. @@ -2346,7 +2404,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 +2467,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 +2480,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 +2495,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 +2511,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 +2521,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 +3131,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 +3315,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 +3407,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 @@ -4008,13 +4070,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); } @@ -4710,10 +4774,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. @@ -5887,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; } @@ -5904,7 +5959,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. @@ -5956,15 +6011,40 @@ 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( + 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)}; @@ -5973,58 +6053,76 @@ 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 < s_multiple_beds.get_number_of_beds(); ++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])) { + paths.emplace_back(print_index, std::nullopt); 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}); - 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; } - 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 (auto &[bed_index, optional_path] : output_paths) { + if (!optional_path) { + continue; + } - 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 + 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, + print_to_export.bed, + [&](){ + this->p->background_process.set_temp_output_path(print_to_export.bed); + export_gcode_to_path( + path, + [&](const bool on_removable){ + this->p->background_process.finalize_gcode( + 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; } ); } @@ -6395,7 +6493,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 @@ -6605,35 +6703,55 @@ 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()}; + std::vector >> paths; - 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); - }}; - - 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; + } + + const fs::path filename{upload_job_template.upload_data.upload_path}; + 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()}; + + 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()[bed_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 = - filename.stem().string() - + "_bed" + std::to_string(print_index + 1) - + filename.extension().string(); - this->p->background_process.prepare_upload(upload_job); + with_mocked_fff_background_process( + *print, + this->p->gcode_results[bed_index], + bed_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 = path; + this->p->background_process.prepare_upload(upload_job); + } + ); } } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 37c229721c..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. @@ -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); diff --git a/src/slic3r/GUI/Sidebar.cpp b/src/slic3r/GUI/Sidebar.cpp index 3476a97507..cc39d38e17 100644 --- a/src/slic3r/GUI/Sidebar.cpp +++ b/src/slic3r/GUI/Sidebar.cpp @@ -761,8 +761,9 @@ 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); } #ifdef __WXMSW__