From 144e37c2745b188c99e8c88fde7c6f4da3603ef3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 6 Apr 2021 10:00:17 +0200 Subject: [PATCH 01/75] 1st installment of project dirty state manager --- src/libslic3r/Technologies.hpp | 5 +++ src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GLCanvas3D.cpp | 5 +++ src/slic3r/GUI/Plater.cpp | 17 ++++++-- src/slic3r/GUI/Plater.hpp | 6 ++- src/slic3r/GUI/ProjectDirtyStateManager.cpp | 47 +++++++++++++++++++++ src/slic3r/GUI/ProjectDirtyStateManager.hpp | 35 +++++++++++++++ 7 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 src/slic3r/GUI/ProjectDirtyStateManager.cpp create mode 100644 src/slic3r/GUI/ProjectDirtyStateManager.hpp diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index d6b2cff8e..fcb59f1a1 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -58,5 +58,10 @@ // Enable exporting lines M73 for remaining time to next printer stop to gcode #define ENABLE_EXTENDED_M73_LINES (1 && ENABLE_VALIDATE_CUSTOM_GCODE) +// Enable project dirty state manager +#define ENABLE_PROJECT_DIRTY_STATE (1 && ENABLE_2_4_0_ALPHA0) +// Enable project dirty state manager debug window +#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW (1 && ENABLE_PROJECT_DIRTY_STATE) + #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 4b3a1c6ca..5b904c87d 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -189,6 +189,8 @@ set(SLIC3R_GUI_SOURCES GUI/UnsavedChangesDialog.hpp GUI/ExtraRenderers.cpp GUI/ExtraRenderers.hpp + GUI/ProjectDirtyStateManager.hpp + GUI/ProjectDirtyStateManager.cpp Utils/Http.cpp Utils/Http.hpp Utils/FixModelByWin10.cpp diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7bbdc72b1..4570670cb 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1718,6 +1718,11 @@ void GLCanvas3D::render() } #endif // ENABLE_RENDER_STATISTICS +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + if (wxGetApp().is_editor() && wxGetApp().plater()->is_view3D_shown()) + wxGetApp().plater()->render_project_state_debug_window(); +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + #if ENABLE_CAMERA_STATISTICS camera.debug_render(); #endif // ENABLE_CAMERA_STATISTICS diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b4b025cd2..0434d2555 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -81,6 +81,9 @@ #include "InstanceCheck.hpp" #include "NotificationManager.hpp" #include "PresetComboBoxes.hpp" +#if ENABLE_PROJECT_DIRTY_STATE +#include "ProjectDirtyStateManager.hpp" +#endif // ENABLE_PROJECT_DIRTY_STATE #ifdef __APPLE__ #include "Gizmos/GLGizmosManager.hpp" @@ -1434,6 +1437,10 @@ struct Plater::priv Preview *preview; NotificationManager* notification_manager { nullptr }; +#if ENABLE_PROJECT_DIRTY_STATE + ProjectDirtyStateManager dirty_state; +#endif // ENABLE_PROJECT_DIRTY_STATE + BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; @@ -1504,6 +1511,10 @@ struct Plater::priv priv(Plater *q, MainFrame *main_frame); ~priv(); +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + void render_project_state_debug_window() const { dirty_state.render_debug_window(); } +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + enum class UpdateParams { FORCE_FULL_SCREEN_REFRESH = 1, FORCE_BACKGROUND_PROCESSING_UPDATE = 2, @@ -4418,9 +4429,9 @@ Plater::Plater(wxWindow *parent, MainFrame *main_frame) // Initialization performed in the private c-tor } -Plater::~Plater() -{ -} +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +void Plater::render_project_state_debug_window() const { p->render_project_state_debug_window(); } +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW Sidebar& Plater::sidebar() { return *p->sidebar; } Model& Plater::model() { return p->model; } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index ff81dad26..f2d60d801 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -128,7 +128,11 @@ public: Plater(const Plater &) = delete; Plater &operator=(Plater &&) = delete; Plater &operator=(const Plater &) = delete; - ~Plater(); + ~Plater() = default; + +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + void render_project_state_debug_window() const; +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW Sidebar& sidebar(); Model& model(); diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp new file mode 100644 index 000000000..9a19676b2 --- /dev/null +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -0,0 +1,47 @@ +#include "libslic3r/libslic3r.h" +#include "ProjectDirtyStateManager.hpp" +#include "ImGuiWrapper.hpp" +#include "GUI_App.hpp" + +#if ENABLE_PROJECT_DIRTY_STATE + +namespace Slic3r { +namespace GUI { + +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +void ProjectDirtyStateManager::render_debug_window() const +{ + auto color = [](bool value) { + return value ? ImVec4(1.0f, 0.49f, 0.216f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + }; + auto text = [](bool value) { + return value ? "true" : "false"; + }; + + std::string title = "Project dirty state statistics"; + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(title, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + bool dirty = is_dirty(); + imgui.text_colored(color(dirty), "State:"); + ImGui::SameLine(); + imgui.text_colored(color(dirty), text(dirty)); + + ImGui::Separator(); + imgui.text_colored(color(m_state.plater), "Plater:"); + ImGui::SameLine(); + imgui.text_colored(color(m_state.plater), text(m_state.plater)); + + imgui.text_colored(color(m_state.presets), "Presets:"); + ImGui::SameLine(); + imgui.text_colored(color(m_state.presets), text(m_state.presets)); + + imgui.end(); +} +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + +} // namespace GUI +} // namespace Slic3r + +#endif // ENABLE_PROJECT_DIRTY_STATE + diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp new file mode 100644 index 000000000..81ac28915 --- /dev/null +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -0,0 +1,35 @@ +#ifndef slic3r_ProjectDirtyStateManager_hpp_ +#define slic3r_ProjectDirtyStateManager_hpp_ + +#if ENABLE_PROJECT_DIRTY_STATE + +namespace Slic3r { +namespace GUI { + +class ProjectDirtyStateManager +{ + struct DirtyState + { + bool plater{ false }; + bool presets{ false }; + + bool is_dirty() const { return plater || presets; } + }; + + DirtyState m_state; + +public: + bool is_dirty() const { return m_state.is_dirty(); } + +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + void render_debug_window() const; +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // ENABLE_PROJECT_DIRTY_STATE + +#endif // slic3r_ProjectDirtyStateManager_hpp_ + From 5d4b7c03b603945cec03e270faca14f957f08cb0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 6 Apr 2021 13:17:29 +0200 Subject: [PATCH 02/75] Extended interface of project dirty state manager --- src/slic3r/GUI/GUI_App.cpp | 8 ++ src/slic3r/GUI/GUI_ObjectList.cpp | 10 +- src/slic3r/GUI/MainFrame.cpp | 48 +++++++++ src/slic3r/GUI/MainFrame.hpp | 9 ++ src/slic3r/GUI/Plater.cpp | 103 +++++++++++++++++++- src/slic3r/GUI/Plater.hpp | 10 ++ src/slic3r/GUI/ProjectDirtyStateManager.cpp | 20 ++++ src/slic3r/GUI/ProjectDirtyStateManager.hpp | 10 ++ src/slic3r/GUI/Tab.cpp | 7 +- 9 files changed, 219 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b22cd6009..793ef80b7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -904,6 +904,14 @@ bool GUI_App::on_init_inner() } else load_current_presets(); + +#if ENABLE_PROJECT_DIRTY_STATE + if (plater_ != nullptr) { +// plater_->reset_project_initial_presets(); + plater_->update_project_dirty_from_presets(); + } +#endif // ENABLE_PROJECT_DIRTY_STATE + mainframe->Show(true); obj_list()->set_min_height(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 58560c8bd..bce64f9ea 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -7,6 +7,9 @@ #include "GUI_App.hpp" #include "I18N.hpp" #include "Plater.hpp" +#if ENABLE_PROJECT_DIRTY_STATE +#include "MainFrame.hpp" +#endif // ENABLE_PROJECT_DIRTY_STATE #include "OptionsGroup.hpp" #include "Tab.hpp" @@ -1457,12 +1460,15 @@ void ObjectList::load_shape_object(const std::string& type_name) if (obj_idx < 0) return; - take_snapshot(_(L("Add Shape"))); + take_snapshot(_L("Add Shape")); // Create mesh BoundingBoxf3 bb; TriangleMesh mesh = create_mesh(type_name, bb); - load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name)); + load_mesh_object(mesh, _L("Shape") + "-" + _(type_name)); +#if ENABLE_PROJECT_DIRTY_STATE + wxGetApp().mainframe->update_title(); +#endif // ENABLE_PROJECT_DIRTY_STATE } void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 35b1c16d8..832ebc257 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -206,6 +206,11 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // declare events Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { +#if ENABLE_PROJECT_DIRTY_STATE + if (m_plater != nullptr) + m_plater->save_project_if_dirty(); +#endif // ENABLE_PROJECT_DIRTY_STATE + if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) { event.Veto(); return; @@ -487,8 +492,14 @@ void MainFrame::update_title() // m_plater->get_project_filename() produces file name including path, but excluding extension. // Don't try to remove the extension, it would remove part of the file name after the last dot! wxString project = from_path(into_path(m_plater->get_project_filename()).filename()); +#if ENABLE_PROJECT_DIRTY_STATE + wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : ""; + if (!dirty_marker.empty() || !project.empty()) + title = dirty_marker + project + " - "; +#else if (!project.empty()) title += (project + " - "); +#endif // ENABLE_PROJECT_DIRTY_STATE } std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID; @@ -668,10 +679,36 @@ bool MainFrame::can_start_new_project() const return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || !m_plater->model().objects.empty()); } +#if ENABLE_PROJECT_DIRTY_STATE +bool MainFrame::can_save() const +{ + return (m_plater != nullptr) && !m_plater->model().objects.empty() && !m_plater->get_project_filename().empty() && m_plater->is_project_dirty(); +} + +bool MainFrame::can_save_as() const +{ + return (m_plater != nullptr) && !m_plater->model().objects.empty(); +} + +void MainFrame::save_project() +{ + save_project_as(m_plater->get_project_filename(".3mf")); +} + +void MainFrame::save_project_as(const wxString& filename) +{ + bool ret = (m_plater != nullptr) ? m_plater->export_3mf(into_path(filename)) : false; + if (ret) { +// wxGetApp().update_saved_preset_from_current_preset(); + m_plater->reset_project_dirty_after_save(); + } +} +#else bool MainFrame::can_save() const { return (m_plater != nullptr) && !m_plater->model().objects.empty(); } +#endif // ENABLE_PROJECT_DIRTY_STATE bool MainFrame::can_export_model() const { @@ -977,16 +1014,27 @@ void MainFrame::init_menubar_as_editor() Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); +#if ENABLE_PROJECT_DIRTY_STATE + append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"), + [this](wxCommandEvent&) { save_project(); }, "save", nullptr, + [this](){return m_plater != nullptr && can_save(); }, this); +#else append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr, [this](){return m_plater != nullptr && can_save(); }, this); +#endif // ENABLE_PROJECT_DIRTY_STATE #ifdef __APPLE__ append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"), #else append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"), #endif // __APPLE__ +#if ENABLE_PROJECT_DIRTY_STATE + [this](wxCommandEvent&) { save_project_as(); }, "save", nullptr, + [this](){return m_plater != nullptr && can_save_as(); }, this); +#else [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save", nullptr, [this](){return m_plater != nullptr && can_save(); }, this); +#endif // ENABLE_PROJECT_DIRTY_STATE fileMenu->AppendSeparator(); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 0971fdc77..307cdf1ae 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -91,7 +91,9 @@ class MainFrame : public DPIFrame void on_value_changed(wxCommandEvent&); bool can_start_new_project() const; +#if !ENABLE_PROJECT_DIRTY_STATE bool can_save() const; +#endif // !ENABLE_PROJECT_DIRTY_STATE bool can_export_model() const; bool can_export_toolpaths() const; bool can_export_supports() const; @@ -184,6 +186,13 @@ public: // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig void on_config_changed(DynamicPrintConfig* cfg) const ; +#if ENABLE_PROJECT_DIRTY_STATE + bool can_save() const; + bool can_save_as() const; + void save_project(); + void save_project_as(const wxString& filename = wxString()); +#endif // ENABLE_PROJECT_DIRTY_STATE + void add_to_recent_projects(const wxString& filename); PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0434d2555..9de4641c0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1393,7 +1393,13 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi this->MSWUpdateDragImageOnLeave(); #endif // WIN32 +#if ENABLE_PROJECT_DIRTY_STATE + bool res = (m_plater != nullptr) ? m_plater->load_files(filenames) : false; + wxGetApp().mainframe->update_title(); + return res; +#else return (m_plater != nullptr) ? m_plater->load_files(filenames) : false; +#endif // ENABLE_PROJECT_DIRTY_STATE } // State to manage showing after export notifications and device ejecting @@ -1511,9 +1517,26 @@ struct Plater::priv priv(Plater *q, MainFrame *main_frame); ~priv(); +#if ENABLE_PROJECT_DIRTY_STATE + bool is_project_dirty() const { return dirty_state.is_dirty(); } + void update_project_dirty_from_presets() { dirty_state.update_from_presets(); } + bool save_project_if_dirty() { + if (dirty_state.is_dirty()) { + MainFrame* mainframe = wxGetApp().mainframe; + if (mainframe->can_save_as()) { + wxMessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL); + if (dlg.ShowModal() == wxID_CANCEL) + return false; + mainframe->save_project_as(wxGetApp().plater()->get_project_filename()); + } + } + return true; + } + void reset_project_dirty_after_save() { dirty_state.reset_after_save(); } #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_project_state_debug_window() const { dirty_state.render_debug_window(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +#endif // ENABLE_PROJECT_DIRTY_STATE enum class UpdateParams { FORCE_FULL_SCREEN_REFRESH = 1, @@ -4216,6 +4239,11 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) } this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data); this->undo_redo_stack().release_least_recently_used(); + +#if ENABLE_PROJECT_DIRTY_STATE + dirty_state.update_from_undo_redo_stack(undo_redo_stack_main(), undo_redo_stack()); +#endif // ENABLE_PROJECT_DIRTY_STATE + // Save the last active preset name of a particular printer technology. ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); @@ -4346,6 +4374,10 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); } + +#if ENABLE_PROJECT_DIRTY_STATE + dirty_state.update_from_undo_redo_stack(undo_redo_stack_main(), undo_redo_stack()); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */) @@ -4429,9 +4461,15 @@ Plater::Plater(wxWindow *parent, MainFrame *main_frame) // Initialization performed in the private c-tor } +#if ENABLE_PROJECT_DIRTY_STATE +bool Plater::is_project_dirty() const { return p->is_project_dirty(); } +void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_presets(); } +bool Plater::save_project_if_dirty() { return p->save_project_if_dirty(); } +void Plater::reset_project_dirty_after_save() { p->reset_project_dirty_after_save(); } #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void Plater::render_project_state_debug_window() const { p->render_project_state_debug_window(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +#endif // ENABLE_PROJECT_DIRTY_STATE Sidebar& Plater::sidebar() { return *p->sidebar; } Model& Plater::model() { return p->model; } @@ -4442,12 +4480,30 @@ SLAPrint& Plater::sla_print() { return p->sla_print; } void Plater::new_project() { +#if ENABLE_PROJECT_DIRTY_STATE + if (!p->save_project_if_dirty()) + return; +#endif // ENABLE_PROJECT_DIRTY_STATE + p->select_view_3D("3D"); +#if ENABLE_PROJECT_DIRTY_STATE + take_snapshot(_L("New Project")); + Plater::SuppressSnapshots suppress(this); + reset(); +// reset_project_initial_presets(); + update_project_dirty_from_presets(); +#else wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::load_project() { +#if ENABLE_PROJECT_DIRTY_STATE + if (!p->save_project_if_dirty()) + return; +#endif // ENABLE_PROJECT_DIRTY_STATE + // Ask user for a project file name. wxString input_file; wxGetApp().load_project(this, input_file); @@ -4471,8 +4527,16 @@ void Plater::load_project(const wxString& filename) std::vector res = load_files(input_paths); // if res is empty no data has been loaded +#if ENABLE_PROJECT_DIRTY_STATE + if (!res.empty()) { + p->set_project_filename(filename); +// reset_project_initial_presets(); + update_project_dirty_from_presets(); + } +#else if (!res.empty()) p->set_project_filename(filename); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::add_model(bool imperial_units/* = false*/) @@ -4503,7 +4567,13 @@ void Plater::add_model(bool imperial_units/* = false*/) } Plater::TakeSnapshot snapshot(this, snapshot_label); +#if ENABLE_PROJECT_DIRTY_STATE + std::vector res = load_files(paths, true, false, imperial_units); + if (!res.empty()) + wxGetApp().mainframe->update_title(); +#else load_files(paths, true, false, imperial_units); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::import_sl1_archive() @@ -5187,24 +5257,39 @@ void Plater::export_amf() } } +#if ENABLE_PROJECT_DIRTY_STATE +bool Plater::export_3mf(const boost::filesystem::path& output_path) +#else void Plater::export_3mf(const boost::filesystem::path& output_path) +#endif // ENABLE_PROJECT_DIRTY_STATE { if (p->model.objects.empty() || canvas3D()->get_gizmos_manager().is_in_editing_mode(true)) +#if ENABLE_PROJECT_DIRTY_STATE + return false; +#else return; +#endif // ENABLE_PROJECT_DIRTY_STATE wxString path; bool export_config = true; - if (output_path.empty()) - { + if (output_path.empty()) { path = p->get_export_file(FT_3MF); +#if ENABLE_PROJECT_DIRTY_STATE + if (path.empty()) { return false; } +#else if (path.empty()) { return; } +#endif // ENABLE_PROJECT_DIRTY_STATE } else path = from_path(output_path); if (!path.Lower().EndsWith(".3mf")) +#if ENABLE_PROJECT_DIRTY_STATE + return false; +#else return; +#endif // ENABLE_PROJECT_DIRTY_STATE DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); const std::string path_u8 = into_u8(path); @@ -5212,6 +5297,19 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; ThumbnailData thumbnail_data; p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true); +#if ENABLE_PROJECT_DIRTY_STATE + bool ret = Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data); + if (ret) { + // Success + p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); + p->set_project_filename(path); + } + else { + // Failure + p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); + } + return ret; +#else if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) { // Success p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); @@ -5221,6 +5319,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) // Failure p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); } +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::reload_from_disk() diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index f2d60d801..ead9679c7 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -130,9 +130,15 @@ public: Plater &operator=(const Plater &) = delete; ~Plater() = default; +#if ENABLE_PROJECT_DIRTY_STATE + bool is_project_dirty() const; + void update_project_dirty_from_presets(); + bool save_project_if_dirty(); + void reset_project_dirty_after_save(); #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_project_state_debug_window() const; #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +#endif // ENABLE_PROJECT_DIRTY_STATE Sidebar& sidebar(); Model& model(); @@ -201,7 +207,11 @@ public: void export_gcode(bool prefer_removable); void export_stl(bool extended = false, bool selection_only = false); void export_amf(); +#if ENABLE_PROJECT_DIRTY_STATE + bool export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); +#else void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); +#endif // ENABLE_PROJECT_DIRTY_STATE void reload_from_disk(); void reload_all_from_disk(); bool has_toolpaths_to_export() const; diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 9a19676b2..5cf7274bb 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -2,12 +2,32 @@ #include "ProjectDirtyStateManager.hpp" #include "ImGuiWrapper.hpp" #include "GUI_App.hpp" +#include "MainFrame.hpp" #if ENABLE_PROJECT_DIRTY_STATE namespace Slic3r { namespace GUI { +void ProjectDirtyStateManager::update_from_undo_redo_stack(const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack) +{ + if (!wxGetApp().initialized()) + return; + + wxGetApp().mainframe->update_title(); +} + +void ProjectDirtyStateManager::update_from_presets() +{ + wxGetApp().mainframe->update_title(); +} + +void ProjectDirtyStateManager::reset_after_save() +{ + m_state.reset(); + wxGetApp().mainframe->update_title(); +} + #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void ProjectDirtyStateManager::render_debug_window() const { diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index 81ac28915..b488c00bb 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -4,6 +4,9 @@ #if ENABLE_PROJECT_DIRTY_STATE namespace Slic3r { +namespace UndoRedo { +class Stack; +} // namespace UndoRedo namespace GUI { class ProjectDirtyStateManager @@ -14,12 +17,19 @@ class ProjectDirtyStateManager bool presets{ false }; bool is_dirty() const { return plater || presets; } + void reset() { + plater = false; + presets = false; + } }; DirtyState m_state; public: bool is_dirty() const { return m_state.is_dirty(); } + void update_from_undo_redo_stack(const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack); + void update_from_presets(); + void reset_after_save(); #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_debug_window() const; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 11c4875eb..97117f418 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1215,9 +1215,8 @@ void Tab::apply_config_from_cache() // to update number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { - if (wxGetApp().plater() == nullptr) { + if (wxGetApp().plater() == nullptr) return; - } // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets wxGetApp().plater()->sidebar().update_presets(m_type); @@ -1235,6 +1234,10 @@ void Tab::on_presets_changed() // clear m_dependent_tabs after first update from select_preset() // to avoid needless preset loading from update() function m_dependent_tabs.clear(); + +#if ENABLE_PROJECT_DIRTY_STATE + wxGetApp().plater()->update_project_dirty_from_presets(); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup) From edbb1d0f69b6dddc46420acada77c7f5d4d45130 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 6 Apr 2021 16:29:05 +0200 Subject: [PATCH 03/75] Project dirty state manager -> presets dirty state --- src/libslic3r/Preset.cpp | 14 ++++- src/libslic3r/Preset.hpp | 30 +++++++++- src/slic3r/GUI/GUI_App.cpp | 63 ++++++++++++++++++++- src/slic3r/GUI/GUI_App.hpp | 10 +++- src/slic3r/GUI/MainFrame.cpp | 16 +++++- src/slic3r/GUI/Plater.cpp | 18 ++++-- src/slic3r/GUI/Plater.hpp | 1 + src/slic3r/GUI/ProjectDirtyStateManager.cpp | 17 ++++++ src/slic3r/GUI/ProjectDirtyStateManager.hpp | 7 ++- src/slic3r/GUI/Tab.cpp | 6 ++ src/slic3r/GUI/Tab.hpp | 42 ++++++++++++-- 11 files changed, 206 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ecb20a18e..aae6a29dc 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -617,11 +617,17 @@ const std::vector& Preset::sla_printer_options() PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) : m_type(type), m_edited_preset(type, "", false), +#if ENABLE_PROJECT_DIRTY_STATE + m_saved_preset(type, "", false), +#endif // ENABLE_PROJECT_DIRTY_STATE m_idx_selected(0) { // Insert just the default preset. this->add_default_preset(keys, defaults, default_name); m_edited_preset.config.apply(m_presets.front().config); +#if ENABLE_PROJECT_DIRTY_STATE + update_saved_preset_from_current_preset(); +#endif // ENABLE_PROJECT_DIRTY_STATE } void PresetCollection::reset(bool delete_files) @@ -798,7 +804,10 @@ std::pair PresetCollection::load_external_preset( // The source config may contain keys from many possible preset types. Just copy those that relate to this preset. this->get_edited_preset().config.apply_only(combined_config, keys, true); this->update_dirty(); - assert(this->get_edited_preset().is_dirty); +#if ENABLE_PROJECT_DIRTY_STATE + update_saved_preset_from_current_preset(); +#endif // ENABLE_PROJECT_DIRTY_STATE + assert(this->get_edited_preset().is_dirty); return std::make_pair(&(*it), this->get_edited_preset().is_dirty); } if (inherits.empty()) { @@ -1208,6 +1217,9 @@ Preset& PresetCollection::select_preset(size_t idx) idx = first_visible_idx(); m_idx_selected = idx; m_edited_preset = m_presets[idx]; +#if ENABLE_PROJECT_DIRTY_STATE + update_saved_preset_from_current_preset(); +#endif // ENABLE_PROJECT_DIRTY_STATE bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets; for (size_t i = 0; i < m_num_default_presets; ++i) m_presets[i].is_visible = default_visible; diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 6e56ad911..8d407fb64 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -346,6 +346,11 @@ public: Preset& get_edited_preset() { return m_edited_preset; } const Preset& get_edited_preset() const { return m_edited_preset; } +#if ENABLE_PROJECT_DIRTY_STATE + // Return the last saved preset. + const Preset& get_saved_preset() const { return m_saved_preset; } +#endif // ENABLE_PROJECT_DIRTY_STATE + // Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist. PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const; PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); } @@ -365,8 +370,16 @@ public: // Return a preset by an index. If the preset is active, a temporary copy is returned. Preset& preset(size_t idx) { return (idx == m_idx_selected) ? m_edited_preset : m_presets[idx]; } const Preset& preset(size_t idx) const { return const_cast(this)->preset(idx); } +#if ENABLE_PROJECT_DIRTY_STATE + void discard_current_changes() { + m_presets[m_idx_selected].reset_dirty(); + m_edited_preset = m_presets[m_idx_selected]; + update_saved_preset_from_current_preset(); + } +#else void discard_current_changes() { m_presets[m_idx_selected].reset_dirty(); m_edited_preset = m_presets[m_idx_selected]; } - +#endif // ENABLE_PROJECT_DIRTY_STATE + // Return a preset by its name. If the preset is active, a temporary copy is returned. // If a preset is not found by its name, null is returned. Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false); @@ -440,6 +453,16 @@ public: std::vector current_different_from_parent_options(const bool deep_compare = false) const { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); } +#if ENABLE_PROJECT_DIRTY_STATE + // Compare the content of get_saved_preset() with get_edited_preset() configs, return true if they differ. + bool saved_is_dirty() const { return !this->saved_dirty_options().empty(); } + // Compare the content of get_saved_preset() with get_edited_preset() configs, return the list of keys where they differ. + std::vector saved_dirty_options(const bool deep_compare = false) const + { return dirty_options(&this->get_edited_preset(), &this->get_saved_preset(), deep_compare); } + // Copy edited preset into saved preset. + void update_saved_preset_from_current_preset() { m_saved_preset = m_edited_preset; } +#endif // ENABLE_PROJECT_DIRTY_STATE + // Return a sorted list of system preset names. // Used for validating the "inherits" flag when importing user's config bundles. // Returns names of all system presets including the former names of these presets. @@ -527,6 +550,11 @@ private: std::map m_map_system_profile_renamed; // Initially this preset contains a copy of the selected preset. Later on, this copy may be modified by the user. Preset m_edited_preset; +#if ENABLE_PROJECT_DIRTY_STATE + // Contains a copy of the last saved selected preset. + Preset m_saved_preset; +#endif // ENABLE_PROJECT_DIRTY_STATE + // Selected preset. size_t m_idx_selected; // Is the "- default -" preset suppressed? diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 793ef80b7..610e5f07a 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -907,7 +907,7 @@ bool GUI_App::on_init_inner() #if ENABLE_PROJECT_DIRTY_STATE if (plater_ != nullptr) { -// plater_->reset_project_initial_presets(); + plater_->reset_project_dirty_initial_presets(); plater_->update_project_dirty_from_presets(); } #endif // ENABLE_PROJECT_DIRTY_STATE @@ -1673,7 +1673,11 @@ void GUI_App::add_config_menu(wxMenuBar *menu) break; case ConfigMenuTakeSnapshot: // Take a configuration snapshot. +#if ENABLE_PROJECT_DIRTY_STATE + if (check_and_save_current_preset_changes()) { +#else if (check_unsaved_changes()) { +#endif // ENABLE_PROJECT_DIRTY_STATE wxTextEntryDialog dlg(nullptr, _L("Taking configuration snapshot"), _L("Snapshot name")); // set current normal font for dialog children, @@ -1688,7 +1692,11 @@ void GUI_App::add_config_menu(wxMenuBar *menu) } break; case ConfigMenuSnapshots: +#if ENABLE_PROJECT_DIRTY_STATE + if (check_and_save_current_preset_changes()) { +#else if (check_unsaved_changes()) { +#endif // ENABLE_PROJECT_DIRTY_STATE std::string on_snapshot; if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) on_snapshot = app_config->get("on_snapshot"); @@ -1789,8 +1797,57 @@ void GUI_App::add_config_menu(wxMenuBar *menu) menu->Append(local_menu, _L("&Configuration")); } +#if ENABLE_PROJECT_DIRTY_STATE +bool GUI_App::has_unsaved_preset_changes() const +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty()) + return true; + } + return false; +} + +bool GUI_App::has_current_preset_changes() const +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + return true; + } + return false; +} + +void GUI_App::update_saved_preset_from_current_preset() +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology)) + tab->update_saved_preset_from_current_preset(); + } +} + +std::vector> GUI_App::get_selected_presets() const +{ + std::vector> ret; + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology)) { + const PresetCollection* presets = tab->get_presets(); + ret.push_back({ static_cast(presets->type()), presets->get_selected_preset_name() }); + } + } + return ret; +} +#endif // ENABLE_PROJECT_DIRTY_STATE + // This is called when closing the application, when loading a config file or when starting the config wizard // to notify the user whether he is aware that some preset changes will be lost. +#if ENABLE_PROJECT_DIRTY_STATE +bool GUI_App::check_and_save_current_preset_changes(const wxString& header) +{ + if (this->plater()->model().objects.empty() && has_current_preset_changes()) { +#else bool GUI_App::check_unsaved_changes(const wxString &header) { PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); @@ -1802,8 +1859,8 @@ bool GUI_App::check_unsaved_changes(const wxString &header) break; } - if (has_unsaved_changes) - { + if (has_unsaved_changes) { +#endif // ENABLE_PROJECT_DIRTY_STATE UnsavedChangesDialog dlg(header); if (wxGetApp().app_config->get("default_action_on_close_application") == "none" && dlg.ShowModal() == wxID_CANCEL) return false; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index f1ee0746a..d1df4212b 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -209,7 +209,15 @@ public: void update_mode(); void add_config_menu(wxMenuBar *menu); - bool check_unsaved_changes(const wxString &header = wxString()); +#if ENABLE_PROJECT_DIRTY_STATE + bool has_unsaved_preset_changes() const; + bool has_current_preset_changes() const; + void update_saved_preset_from_current_preset(); + std::vector> get_selected_presets() const; + bool check_and_save_current_preset_changes(const wxString& header = wxString()); +#else + bool check_unsaved_changes(const wxString& header = wxString()); +#endif // ENABLE_PROJECT_DIRTY_STATE bool check_print_host_queue(); bool checked_tab(Tab* tab); void load_current_presets(bool check_printer_presets = true); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 832ebc257..f556431f6 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -209,9 +209,11 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S #if ENABLE_PROJECT_DIRTY_STATE if (m_plater != nullptr) m_plater->save_project_if_dirty(); -#endif // ENABLE_PROJECT_DIRTY_STATE + if (event.CanVeto() && !wxGetApp().check_and_save_current_preset_changes()) { +#else if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) { +#endif // ENABLE_PROJECT_DIRTY_STATE event.Veto(); return; } @@ -1559,7 +1561,11 @@ void MainFrame::export_config() // Load a config file containing a Print, Filament & Printer preset. void MainFrame::load_config_file() { +#if ENABLE_PROJECT_DIRTY_STATE + if (!wxGetApp().check_and_save_current_preset_changes()) +#else if (!wxGetApp().check_unsaved_changes()) +#endif // ENABLE_PROJECT_DIRTY_STATE return; wxFileDialog dlg(this, _L("Select configuration to load:"), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), @@ -1588,7 +1594,11 @@ bool MainFrame::load_config_file(const std::string &path) void MainFrame::export_configbundle(bool export_physical_printers /*= false*/) { +#if ENABLE_PROJECT_DIRTY_STATE + if (!wxGetApp().check_and_save_current_preset_changes()) +#else if (!wxGetApp().check_unsaved_changes()) +#endif // ENABLE_PROJECT_DIRTY_STATE return; // validate current configuration in case it's dirty auto err = wxGetApp().preset_bundle->full_config().validate(); @@ -1620,7 +1630,11 @@ void MainFrame::export_configbundle(bool export_physical_printers /*= false*/) // but that behavior was not documented and likely buggy. void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/) { +#if ENABLE_PROJECT_DIRTY_STATE + if (!wxGetApp().check_and_save_current_preset_changes()) +#else if (!wxGetApp().check_unsaved_changes()) +#endif // ENABLE_PROJECT_DIRTY_STATE return; if (file.IsEmpty()) { wxFileDialog dlg(this, _L("Select configuration to load:"), diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9de4641c0..0ea20bd5e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1525,14 +1525,18 @@ struct Plater::priv MainFrame* mainframe = wxGetApp().mainframe; if (mainframe->can_save_as()) { wxMessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL); - if (dlg.ShowModal() == wxID_CANCEL) + int res = dlg.ShowModal(); + if (res == wxID_YES) + mainframe->save_project_as(wxGetApp().plater()->get_project_filename()); + else if (res == wxID_CANCEL) return false; - mainframe->save_project_as(wxGetApp().plater()->get_project_filename()); } } return true; } void reset_project_dirty_after_save() { dirty_state.reset_after_save(); } + void reset_project_dirty_initial_presets() { dirty_state.reset_initial_presets(); } + #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_project_state_debug_window() const { dirty_state.render_debug_window(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW @@ -4284,8 +4288,13 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator if (printer_technology_changed) { // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type. std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA"; +#if ENABLE_PROJECT_DIRTY_STATE + if (!wxGetApp().check_and_save_current_preset_changes(format_wxstr(_L( + "%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt))) +#else if (! wxGetApp().check_unsaved_changes(format_wxstr(_L( "%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt))) +#endif // ENABLE_PROJECT_DIRTY_STATE // Don't switch the profiles. return; } @@ -4466,6 +4475,7 @@ bool Plater::is_project_dirty() const { return p->is_project_dirty(); } void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_presets(); } bool Plater::save_project_if_dirty() { return p->save_project_if_dirty(); } void Plater::reset_project_dirty_after_save() { p->reset_project_dirty_after_save(); } +void Plater::reset_project_dirty_initial_presets() { p->reset_project_dirty_initial_presets(); } #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void Plater::render_project_state_debug_window() const { p->render_project_state_debug_window(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW @@ -4490,7 +4500,7 @@ void Plater::new_project() take_snapshot(_L("New Project")); Plater::SuppressSnapshots suppress(this); reset(); -// reset_project_initial_presets(); + reset_project_dirty_initial_presets(); update_project_dirty_from_presets(); #else wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); @@ -4530,7 +4540,7 @@ void Plater::load_project(const wxString& filename) #if ENABLE_PROJECT_DIRTY_STATE if (!res.empty()) { p->set_project_filename(filename); -// reset_project_initial_presets(); + reset_project_dirty_initial_presets(); update_project_dirty_from_presets(); } #else diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index ead9679c7..9b42863ec 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -135,6 +135,7 @@ public: void update_project_dirty_from_presets(); bool save_project_if_dirty(); void reset_project_dirty_after_save(); + void reset_project_dirty_initial_presets(); #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_project_state_debug_window() const; #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 5cf7274bb..5fc45ccec 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -19,15 +19,32 @@ void ProjectDirtyStateManager::update_from_undo_redo_stack(const Slic3r::UndoRed void ProjectDirtyStateManager::update_from_presets() { + m_state.presets = false; + std::vector> selected_presets = wxGetApp().get_selected_presets(); + for (const auto& [type, name] : selected_presets) { + m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name; + } + m_state.presets |= wxGetApp().has_unsaved_preset_changes(); wxGetApp().mainframe->update_title(); } void ProjectDirtyStateManager::reset_after_save() { + reset_initial_presets(); + m_state.reset(); wxGetApp().mainframe->update_title(); } +void ProjectDirtyStateManager::reset_initial_presets() +{ + m_initial_presets = std::array(); + std::vector> selected_presets = wxGetApp().get_selected_presets(); + for (const auto& [type, name] : selected_presets) { + m_initial_presets[type] = name; + } +} + #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void ProjectDirtyStateManager::render_debug_window() const { diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index b488c00bb..2aa6680c6 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_ProjectDirtyStateManager_hpp_ #define slic3r_ProjectDirtyStateManager_hpp_ +#include "libslic3r/Preset.hpp" + #if ENABLE_PROJECT_DIRTY_STATE namespace Slic3r { @@ -25,12 +27,15 @@ class ProjectDirtyStateManager DirtyState m_state; + // keeps track of initial selected presets + std::array m_initial_presets; + public: bool is_dirty() const { return m_state.is_dirty(); } void update_from_undo_redo_stack(const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack); void update_from_presets(); void reset_after_save(); - + void reset_initial_presets(); #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_debug_window() const; #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 97117f418..294af5f76 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2113,10 +2113,16 @@ wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticTex return sizer; } +#if ENABLE_PROJECT_DIRTY_STATE +bool Tab::saved_preset_is_dirty() const { return m_presets->saved_is_dirty(); } +void Tab::update_saved_preset_from_current_preset() { m_presets->update_saved_preset_from_current_preset(); } +bool Tab::current_preset_is_dirty() const { return m_presets->current_is_dirty(); } +#else bool Tab::current_preset_is_dirty() { return m_presets->current_is_dirty(); } +#endif // ENABLE_PROJECT_DIRTY_STATE void TabPrinter::build() { diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 8cbc6585a..0a11e838a 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -270,7 +270,11 @@ public: Preset::Type type() const { return m_type; } // The tab is already constructed. bool completed() const { return m_completed; } - virtual bool supports_printer_technology(const PrinterTechnology tech) = 0; +#if ENABLE_PROJECT_DIRTY_STATE + virtual bool supports_printer_technology(const PrinterTechnology tech) const = 0; +#else + virtual bool supports_printer_technology(const PrinterTechnology tech) = 0; +#endif // ENABLE_PROJECT_DIRTY_STATE void create_preset_tab(); void add_scaled_button(wxWindow* parent, ScalableButton** btn, const std::string& icon_name, @@ -333,7 +337,13 @@ public: Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1); void toggle_option(const std::string& opt_key, bool toggle, int opt_index = -1); wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText, wxString text = wxEmptyString); +#if ENABLE_PROJECT_DIRTY_STATE + bool current_preset_is_dirty() const; + bool saved_preset_is_dirty() const; + void update_saved_preset_from_current_preset(); +#else bool current_preset_is_dirty(); +#endif // ENABLE_PROJECT_DIRTY_STATE DynamicPrintConfig* get_config() { return m_config; } PresetCollection* get_presets() { return m_presets; } @@ -387,7 +397,11 @@ public: void toggle_options() override; void update() override; void clear_pages() override; - bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } +#if ENABLE_PROJECT_DIRTY_STATE + bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; } +#else + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } +#endif // ENABLE_PROJECT_DIRTY_STATE private: ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr; @@ -417,7 +431,11 @@ public: void toggle_options() override; void update() override; void clear_pages() override; - bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } +#if ENABLE_PROJECT_DIRTY_STATE + bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; } +#else + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } +#endif // ENABLE_PROJECT_DIRTY_STATE }; class TabPrinter : public Tab @@ -471,7 +489,11 @@ public: void init_options_list() override; void msw_rescale() override; void sys_color_changed() override; - bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } +#if ENABLE_PROJECT_DIRTY_STATE + bool supports_printer_technology(const PrinterTechnology /* tech */) const override { return true; } +#else + bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } +#endif // ENABLE_PROJECT_DIRTY_STATE wxSizer* create_bed_shape_widget(wxWindow* parent); void cache_extruder_cnt(); @@ -491,7 +513,11 @@ public: void toggle_options() override {}; void update() override; void init_options_list() override; - bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } +#if ENABLE_PROJECT_DIRTY_STATE + bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; } +#else + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } +#endif // ENABLE_PROJECT_DIRTY_STATE }; class TabSLAPrint : public Tab @@ -510,7 +536,11 @@ public: void toggle_options() override; void update() override; void clear_pages() override; - bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } +#if ENABLE_PROJECT_DIRTY_STATE + bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; } +#else + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } +#endif // ENABLE_PROJECT_DIRTY_STATE }; } // GUI From 926ecd0585d3d40b8f9e9adc9e968a01351ea723 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 7 Apr 2021 12:58:14 +0200 Subject: [PATCH 04/75] Project dirty state manager -> plater dirty state --- src/slic3r/GUI/Plater.cpp | 3 + src/slic3r/GUI/Plater.hpp | 3 + src/slic3r/GUI/ProjectDirtyStateManager.cpp | 131 +++++++++++++++++--- src/slic3r/GUI/ProjectDirtyStateManager.hpp | 20 ++- src/slic3r/GUI/Tab.hpp | 18 +-- 5 files changed, 149 insertions(+), 26 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0ea20bd5e..5ffd4e51c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6190,6 +6190,9 @@ bool Plater::can_mirror() const { return p->can_mirror(); } bool Plater::can_split(bool to_objects) const { return p->can_split(to_objects); } const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); } +#if ENABLE_PROJECT_DIRTY_STATE +const UndoRedo::Stack& Plater::undo_redo_stack_active() const { return p->undo_redo_stack(); } +#endif // ENABLE_PROJECT_DIRTY_STATE void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 9b42863ec..8d228e68f 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -242,6 +242,9 @@ public: // For the memory statistics. const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; void clear_undo_redo_stack_main(); +#if ENABLE_PROJECT_DIRTY_STATE + const Slic3r::UndoRedo::Stack& undo_redo_stack_active() const; +#endif // ENABLE_PROJECT_DIRTY_STATE // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo. void enter_gizmos_stack(); void leave_gizmos_stack(); diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 5fc45ccec..5d4cd8605 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -1,19 +1,68 @@ #include "libslic3r/libslic3r.h" + #include "ProjectDirtyStateManager.hpp" #include "ImGuiWrapper.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" +#include "I18N.hpp" +#include "Plater.hpp" +#include "../Utils/UndoRedo.hpp" + +#include + +#include +#include #if ENABLE_PROJECT_DIRTY_STATE namespace Slic3r { namespace GUI { +enum class EStackType +{ + Main, + Gizmo +}; + +static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stack) { + const std::vector& snapshots = stack.snapshots(); + const size_t active_snapshot_time = stack.active_snapshot_time(); + const auto it = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot_time)); + const int idx = it - snapshots.begin() - 1; + const Slic3r::UndoRedo::Snapshot* ret = (0 < idx && (size_t)idx < snapshots.size() - 1) ? + &snapshots[idx] : nullptr; + + assert(ret != nullptr); + + return ret; +} + +static const UndoRedo::Snapshot* get_last_valid_snapshot(EStackType type, const UndoRedo::Stack& stack) { + auto skip_main = [](const UndoRedo::Snapshot& snapshot) { + return boost::starts_with(snapshot.name, _utf8("Selection")); + }; + + const UndoRedo::Snapshot* snapshot = get_active_snapshot(stack); + + const UndoRedo::Snapshot* curr = snapshot; + const std::vector& snapshots = stack.snapshots(); + while (type == EStackType::Main && skip_main(*curr)) { + curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - 1))); + } + + return curr; +} + void ProjectDirtyStateManager::update_from_undo_redo_stack(const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack) { if (!wxGetApp().initialized()) return; + if (&main_stack == &active_stack) + update_from_undo_redo_main_stack(main_stack); + else + update_from_undo_redo_gizmo_stack(active_stack); + wxGetApp().mainframe->update_title(); } @@ -31,8 +80,27 @@ void ProjectDirtyStateManager::update_from_presets() void ProjectDirtyStateManager::reset_after_save() { reset_initial_presets(); - m_state.reset(); + + const Plater* plater = wxGetApp().plater(); + const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main(); + const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active(); + + if (&main_stack == &active_stack) { + const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(main_stack); + const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, main_stack); + +// std::cout << "SAVE - active: " << active_snapshot->timestamp << " - " << active_snapshot->name << "\n"; +// std::cout << "SAVE - valid: " << valid_snapshot->timestamp << " - " << valid_snapshot->name << "\n"; + + m_last_save.main = valid_snapshot->timestamp; + } + else { + const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(active_stack); + +// std::cout << "SAVE - active: " << active_snapshot->timestamp << " - " << active_snapshot->name << "\n"; + } + wxGetApp().mainframe->update_title(); } @@ -48,35 +116,66 @@ void ProjectDirtyStateManager::reset_initial_presets() #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void ProjectDirtyStateManager::render_debug_window() const { + ImGuiWrapper& imgui = *wxGetApp().imgui(); + auto color = [](bool value) { return value ? ImVec4(1.0f, 0.49f, 0.216f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); }; auto text = [](bool value) { return value ? "true" : "false"; }; + auto append_item = [color, text, &imgui](const std::string& name, bool value) { + imgui.text_colored(color(value), name); + ImGui::SameLine(); + imgui.text_colored(color(value), text(value)); + }; - std::string title = "Project dirty state statistics"; - ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(title, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - bool dirty = is_dirty(); - imgui.text_colored(color(dirty), "State:"); - ImGui::SameLine(); - imgui.text_colored(color(dirty), text(dirty)); + imgui.begin(std::string( "Project dirty state statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + append_item("State:", is_dirty()); ImGui::Separator(); - imgui.text_colored(color(m_state.plater), "Plater:"); - ImGui::SameLine(); - imgui.text_colored(color(m_state.plater), text(m_state.plater)); - - imgui.text_colored(color(m_state.presets), "Presets:"); - ImGui::SameLine(); - imgui.text_colored(color(m_state.presets), text(m_state.presets)); + append_item("Plater:", m_state.plater); + append_item("Presets:", m_state.presets); + append_item("Current gizmo:", m_state.current_gizmo); imgui.end(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +void ProjectDirtyStateManager::update_from_undo_redo_main_stack(const Slic3r::UndoRedo::Stack& stack) +{ + m_state.plater = false; + + const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); + +// std::cout << "UPDATE - active: " << active_snapshot->timestamp << " - " << active_snapshot->name << "\n"; + + if (active_snapshot->name == _utf8("New Project") || + active_snapshot->name == _utf8("Reset Project") || + boost::starts_with(active_snapshot->name, _utf8("Load Project:"))) + return; + + const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, stack); + +// std::cout << "UPDATE - valid: " << valid_snapshot->timestamp << " - " << valid_snapshot->name << "\n"; + + m_state.plater = valid_snapshot->timestamp != m_last_save.main; +} + +void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(const Slic3r::UndoRedo::Stack& stack) +{ + m_state.current_gizmo = false; + + const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); + +// std::cout << "UPDATE - active: " << active_snapshot->timestamp << " - " << active_snapshot->name << "\n"; + + if (active_snapshot->name == "Gizmos-Initial") + return; + + m_state.current_gizmo = true; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index 2aa6680c6..2eac4ba92 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -17,15 +17,29 @@ class ProjectDirtyStateManager { bool plater{ false }; bool presets{ false }; + bool current_gizmo{ false }; - bool is_dirty() const { return plater || presets; } + bool is_dirty() const { return plater || presets || current_gizmo; } void reset() { plater = false; presets = false; + current_gizmo = false; + } + }; + + struct Timestamps + { + size_t main{ 0 }; + size_t gizmo{ 0 }; + + void reset() { + main = 0; + gizmo = 0; } }; DirtyState m_state; + Timestamps m_last_save; // keeps track of initial selected presets std::array m_initial_presets; @@ -39,6 +53,10 @@ public: #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_debug_window() const; #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + +private: + void update_from_undo_redo_main_stack(const Slic3r::UndoRedo::Stack& stack); + void update_from_undo_redo_gizmo_stack(const Slic3r::UndoRedo::Stack& stack); }; } // namespace GUI diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 0a11e838a..c1806cfcd 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -387,8 +387,8 @@ class TabPrint : public Tab { public: TabPrint(wxNotebook* parent) : -// Tab(parent, _(L("Print Settings")), L("print")) {} - Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_PRINT) {} +// Tab(parent, _L("Print Settings"), L("print")) {} + Tab(parent, _L("Print Settings"), Slic3r::Preset::TYPE_PRINT) {} ~TabPrint() {} void build() override; @@ -421,8 +421,8 @@ private: std::map m_overrides_options; public: TabFilament(wxNotebook* parent) : -// Tab(parent, _(L("Filament Settings")), L("filament")) {} - Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FILAMENT) {} +// Tab(parent, _L("Filament Settings"), L("filament")) {} + Tab(parent, _L("Filament Settings"), Slic3r::Preset::TYPE_FILAMENT) {} ~TabFilament() {} void build() override; @@ -467,7 +467,7 @@ public: // TabPrinter(wxNotebook* parent) : Tab(parent, _(L("Printer Settings")), L("printer")) {} TabPrinter(wxNotebook* parent) : - Tab(parent, _(L("Printer Settings")), Slic3r::Preset::TYPE_PRINTER) {} + Tab(parent, _L("Printer Settings"), Slic3r::Preset::TYPE_PRINTER) {} ~TabPrinter() {} void build() override; @@ -504,8 +504,8 @@ class TabSLAMaterial : public Tab { public: TabSLAMaterial(wxNotebook* parent) : -// Tab(parent, _(L("Material Settings")), L("sla_material")) {} - Tab(parent, _(L("Material Settings")), Slic3r::Preset::TYPE_SLA_MATERIAL) {} +// Tab(parent, _L("Material Settings"), L("sla_material")) {} + Tab(parent, _L("Material Settings"), Slic3r::Preset::TYPE_SLA_MATERIAL) {} ~TabSLAMaterial() {} void build() override; @@ -524,8 +524,8 @@ class TabSLAPrint : public Tab { public: TabSLAPrint(wxNotebook* parent) : -// Tab(parent, _(L("Print Settings")), L("sla_print")) {} - Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {} +// Tab(parent, _L("Print Settings"), L("sla_print")) {} + Tab(parent, _L("Print Settings"), Slic3r::Preset::TYPE_SLA_PRINT) {} ~TabSLAPrint() {} ogStaticText* m_support_object_elevation_description_line = nullptr; From bfbc683a59c00487eba8f1b01f2aeaf1bac586d0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 7 Apr 2021 14:26:04 +0200 Subject: [PATCH 05/75] Follow-up of 926ecd0585d3d40b8f9e9adc9e968a01351ea723 -> Improved management of plater dirty state --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 97 +++++++++++--------- src/slic3r/GUI/ProjectDirtyStateManager.cpp | 20 +--- 2 files changed, 57 insertions(+), 60 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index b200623f4..7ff274aac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -45,18 +45,18 @@ bool GLGizmoSlaSupports::on_init() { m_shortcut_key = WXK_CONTROL_L; - m_desc["head_diameter"] = _(L("Head diameter")) + ": "; - m_desc["lock_supports"] = _(L("Lock supports under new islands")); - m_desc["remove_selected"] = _(L("Remove selected points")); - m_desc["remove_all"] = _(L("Remove all points")); - m_desc["apply_changes"] = _(L("Apply changes")); - m_desc["discard_changes"] = _(L("Discard changes")); - m_desc["minimal_distance"] = _(L("Minimal points distance")) + ": "; - m_desc["points_density"] = _(L("Support points density")) + ": "; - m_desc["auto_generate"] = _(L("Auto-generate points")); - m_desc["manual_editing"] = _(L("Manual editing")); - m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": "; - m_desc["reset_direction"] = _(L("Reset direction")); + m_desc["head_diameter"] = _L("Head diameter") + ": "; + m_desc["lock_supports"] = _L("Lock supports under new islands"); + m_desc["remove_selected"] = _L("Remove selected points"); + m_desc["remove_all"] = _L("Remove all points"); + m_desc["apply_changes"] = _L("Apply changes"); + m_desc["discard_changes"] = _L("Discard changes"); + m_desc["minimal_distance"] = _L("Minimal points distance") + ": "; + m_desc["points_density"] = _L("Support points density") + ": "; + m_desc["auto_generate"] = _L("Auto-generate points"); + m_desc["manual_editing"] = _L("Manual editing"); + m_desc["clipping_of_view"] = _L("Clipping of view")+ ": "; + m_desc["reset_direction"] = _L("Reset direction"); return true; } @@ -372,7 +372,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_selection_empty) { std::pair pos_and_normal; if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add support point"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point")); m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); m_parent.set_as_dirty(); m_wait_for_up_event = true; @@ -512,7 +512,7 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) std::abort(); } - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete support point"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point")); for (unsigned int idx=0; idxconfig.set("support_points_minimal_distance", m_minimal_point_distance_stash); mo->config.set("support_points_density_relative", (int)m_density_stash); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change")); mo->config.set("support_points_minimal_distance", minimal_point_distance); mo->config.set("support_points_density_relative", (int)density); wxGetApp().obj_list()->update_and_show_object_settings_item(); @@ -867,10 +867,9 @@ bool GLGizmoSlaSupports::on_is_selectable() const std::string GLGizmoSlaSupports::on_get_name() const { - return (_(L("SLA Support Points")) + " [L]").ToUTF8().data(); + return (_L("SLA Support Points") + " [L]").ToUTF8().data(); } - CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const { return CommonGizmosDataID( @@ -895,7 +894,11 @@ void GLGizmoSlaSupports::on_set_state() // data are not yet available, the CallAfter will postpone taking the // snapshot until they are. No, it does not feel right. wxGetApp().CallAfter([]() { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on"))); +#if ENABLE_PROJECT_DIRTY_STATE + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Entering SLA gizmo")); +#else + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("SLA gizmo turned on")); +#endif // ENABLE_PROJECT_DIRTY_STATE }); } @@ -909,8 +912,8 @@ void GLGizmoSlaSupports::on_set_state() wxGetApp().CallAfter([this]() { // Following is called through CallAfter, because otherwise there was a problem // on OSX with the wxMessageDialog being shown several times when clicked into. - wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually " - "edited support points?")) + "\n",_(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + wxMessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually " + "edited support points?") + "\n",_L("Save changes?"), wxICON_QUESTION | wxYES | wxNO); if (dlg.ShowModal() == wxID_YES) editing_mode_apply_changes(); else @@ -922,7 +925,11 @@ void GLGizmoSlaSupports::on_set_state() else { // we are actually shutting down disable_editing_mode(); // so it is not active next time the gizmo opens - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off"))); +#if ENABLE_PROJECT_DIRTY_STATE + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Leaving SLA gizmo")); +#else + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("SLA gizmo turned off")); +#endif // ENABLE_PROJECT_DIRTY_STATE m_normal_cache.clear(); m_old_mo_id = -1; } @@ -953,7 +960,7 @@ void GLGizmoSlaSupports::on_stop_dragging() && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected { m_editing_cache[m_hover_id] = m_point_before_drag; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move support point"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move support point")); m_editing_cache[m_hover_id] = backup; } } @@ -1046,7 +1053,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken if (unsaved_changes()) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support points edit"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support points edit")); m_normal_cache.clear(); for (const CacheEntry& ce : m_editing_cache) @@ -1125,14 +1132,14 @@ void GLGizmoSlaSupports::get_data_from_backend() void GLGizmoSlaSupports::auto_generate() { wxMessageDialog dlg(GUI::wxGetApp().plater(), - _(L("Autogeneration will erase all manually edited points.")) + "\n\n" + - _(L("Are you sure you want to do it?")) + "\n", - _(L("Warning")), wxICON_WARNING | wxYES | wxNO); + _L("Autogeneration will erase all manually edited points.") + "\n\n" + + _L("Are you sure you want to do it?") + "\n", + _L("Warning"), wxICON_WARNING | wxYES | wxNO); ModelObject* mo = m_c->selection_info()->model_object(); if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points")); wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); mo->sla_points_status = sla::PointsStatus::Generating; } @@ -1180,7 +1187,7 @@ bool GLGizmoSlaSupports::unsaved_changes() const } SlaGizmoHelpDialog::SlaGizmoHelpDialog() -: wxDialog(nullptr, wxID_ANY, _(L("SLA gizmo keyboard shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) +: wxDialog(nullptr, wxID_ANY, _L("SLA gizmo keyboard shortcuts"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); const wxString ctrl = GUI::shortkey_ctrl_prefix(); @@ -1191,7 +1198,7 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog() const wxFont& font = wxGetApp().small_font(); const wxFont& bold_font = wxGetApp().bold_font(); - auto note_text = new wxStaticText(this, wxID_ANY, _(L("Note: some shortcuts work in (non)editing mode only."))); + auto note_text = new wxStaticText(this, wxID_ANY, _L("Note: some shortcuts work in (non)editing mode only.")); note_text->SetFont(font); auto vsizer = new wxBoxSizer(wxVERTICAL); @@ -1209,21 +1216,21 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog() vsizer->AddSpacer(20); std::vector> shortcuts; - shortcuts.push_back(std::make_pair(_(L("Left click")), _(L("Add point")))); - shortcuts.push_back(std::make_pair(_(L("Right click")), _(L("Remove point")))); - shortcuts.push_back(std::make_pair(_(L("Drag")), _(L("Move point")))); - shortcuts.push_back(std::make_pair(ctrl+_(L("Left click")), _(L("Add point to selection")))); - shortcuts.push_back(std::make_pair(alt+_(L("Left click")), _(L("Remove point from selection")))); - shortcuts.push_back(std::make_pair(wxString("Shift+")+_(L("Drag")), _(L("Select by rectangle")))); - shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _(L("Deselect by rectangle")))); - shortcuts.push_back(std::make_pair(ctrl+"A", _(L("Select all points")))); - shortcuts.push_back(std::make_pair("Delete", _(L("Remove selected points")))); - shortcuts.push_back(std::make_pair(ctrl+_(L("Mouse wheel")), _(L("Move clipping plane")))); - shortcuts.push_back(std::make_pair("R", _(L("Reset clipping plane")))); - shortcuts.push_back(std::make_pair("Enter", _(L("Apply changes")))); - shortcuts.push_back(std::make_pair("Esc", _(L("Discard changes")))); - shortcuts.push_back(std::make_pair("M", _(L("Switch to editing mode")))); - shortcuts.push_back(std::make_pair("A", _(L("Auto-generate points")))); + shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add point"))); + shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove point"))); + shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move point"))); + shortcuts.push_back(std::make_pair(ctrl+_L("Left click"), _L("Add point to selection"))); + shortcuts.push_back(std::make_pair(alt+_L("Left click"), _L("Remove point from selection"))); + shortcuts.push_back(std::make_pair(wxString("Shift+")+_L("Drag"), _L("Select by rectangle"))); + shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _L("Deselect by rectangle"))); + shortcuts.push_back(std::make_pair(ctrl+"A", _L("Select all points"))); + shortcuts.push_back(std::make_pair("Delete", _L("Remove selected points"))); + shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane"))); + shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane"))); + shortcuts.push_back(std::make_pair("Enter", _L("Apply changes"))); + shortcuts.push_back(std::make_pair("Esc", _L("Discard changes"))); + shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode"))); + shortcuts.push_back(std::make_pair("A", _L("Auto-generate points"))); for (const auto& pair : shortcuts) { auto shortcut = new wxStaticText(this, wxID_ANY, pair.first); diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 5d4cd8605..a14674ed8 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -29,7 +29,7 @@ static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stac const size_t active_snapshot_time = stack.active_snapshot_time(); const auto it = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot_time)); const int idx = it - snapshots.begin() - 1; - const Slic3r::UndoRedo::Snapshot* ret = (0 < idx && (size_t)idx < snapshots.size() - 1) ? + const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && (size_t)idx < snapshots.size() - 1) ? &snapshots[idx] : nullptr; assert(ret != nullptr); @@ -39,7 +39,9 @@ static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stac static const UndoRedo::Snapshot* get_last_valid_snapshot(EStackType type, const UndoRedo::Stack& stack) { auto skip_main = [](const UndoRedo::Snapshot& snapshot) { - return boost::starts_with(snapshot.name, _utf8("Selection")); + return boost::starts_with(snapshot.name, _utf8("Selection")) || + boost::starts_with(snapshot.name, _utf8("Entering")) || + boost::starts_with(snapshot.name, _utf8("Leaving")); }; const UndoRedo::Snapshot* snapshot = get_active_snapshot(stack); @@ -90,15 +92,12 @@ void ProjectDirtyStateManager::reset_after_save() const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(main_stack); const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, main_stack); -// std::cout << "SAVE - active: " << active_snapshot->timestamp << " - " << active_snapshot->name << "\n"; -// std::cout << "SAVE - valid: " << valid_snapshot->timestamp << " - " << valid_snapshot->name << "\n"; - m_last_save.main = valid_snapshot->timestamp; } else { const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(active_stack); + const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, main_stack); -// std::cout << "SAVE - active: " << active_snapshot->timestamp << " - " << active_snapshot->name << "\n"; } wxGetApp().mainframe->update_title(); @@ -147,18 +146,12 @@ void ProjectDirtyStateManager::update_from_undo_redo_main_stack(const Slic3r::Un m_state.plater = false; const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); - -// std::cout << "UPDATE - active: " << active_snapshot->timestamp << " - " << active_snapshot->name << "\n"; - if (active_snapshot->name == _utf8("New Project") || active_snapshot->name == _utf8("Reset Project") || boost::starts_with(active_snapshot->name, _utf8("Load Project:"))) return; const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, stack); - -// std::cout << "UPDATE - valid: " << valid_snapshot->timestamp << " - " << valid_snapshot->name << "\n"; - m_state.plater = valid_snapshot->timestamp != m_last_save.main; } @@ -167,9 +160,6 @@ void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(const Slic3r::U m_state.current_gizmo = false; const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); - -// std::cout << "UPDATE - active: " << active_snapshot->timestamp << " - " << active_snapshot->name << "\n"; - if (active_snapshot->name == "Gizmos-Initial") return; From e89a14c8a7343753b3390e1317eaecc32f31b638 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 9 Apr 2021 08:26:48 +0200 Subject: [PATCH 06/75] Project dirty state manager -> current gizmo dirty state --- src/slic3r/GUI/ProjectDirtyStateManager.cpp | 104 +++++++++++++++----- src/slic3r/GUI/ProjectDirtyStateManager.hpp | 8 +- 2 files changed, 86 insertions(+), 26 deletions(-) diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index a14674ed8..82557c000 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -37,24 +37,38 @@ static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stac return ret; } -static const UndoRedo::Snapshot* get_last_valid_snapshot(EStackType type, const UndoRedo::Stack& stack) { - auto skip_main = [](const UndoRedo::Snapshot& snapshot) { +static const UndoRedo::Snapshot* get_last_valid_snapshot(EStackType type, const UndoRedo::Stack& stack, size_t last_save_timestamp) { + auto skip_main = [last_save_timestamp](const UndoRedo::Snapshot& snapshot) { return boost::starts_with(snapshot.name, _utf8("Selection")) || - boost::starts_with(snapshot.name, _utf8("Entering")) || - boost::starts_with(snapshot.name, _utf8("Leaving")); + ((boost::starts_with(snapshot.name, _utf8("Entering")) || boost::starts_with(snapshot.name, _utf8("Leaving"))) && + (last_save_timestamp == 0 || last_save_timestamp != snapshot.timestamp)); }; - const UndoRedo::Snapshot* snapshot = get_active_snapshot(stack); - - const UndoRedo::Snapshot* curr = snapshot; + const UndoRedo::Snapshot* curr = get_active_snapshot(stack); const std::vector& snapshots = stack.snapshots(); + size_t shift = 1; while (type == EStackType::Main && skip_main(*curr)) { - curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - 1))); + const UndoRedo::Snapshot* temp = curr; + curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - shift))); + shift = (curr == temp) ? shift + 1 : 1; } - return curr; } +static std::string extract_gizmo_name(const std::string& s) { + static const std::array prefixes = { _utf8("Entering"), _utf8("Leaving") }; + + std::string ret; + for (const std::string& prefix : prefixes) { + if (boost::starts_with(s, prefix)) + ret = s.substr(prefix.length() + 1); + + if (!ret.empty()) + break; + } + return ret; +} + void ProjectDirtyStateManager::update_from_undo_redo_stack(const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack) { if (!wxGetApp().initialized()) @@ -81,25 +95,44 @@ void ProjectDirtyStateManager::update_from_presets() void ProjectDirtyStateManager::reset_after_save() { - reset_initial_presets(); - m_state.reset(); - const Plater* plater = wxGetApp().plater(); const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main(); const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active(); if (&main_stack == &active_stack) { const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(main_stack); - const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, main_stack); + const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, main_stack, m_last_save.main); + +// if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) { +// if (m_state.current_gizmo) { +// int a = 0; +// } +// else { +// int a = 0; +// } +// } m_last_save.main = valid_snapshot->timestamp; } else { const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(active_stack); - const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, main_stack); + const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Gizmo, active_stack, m_last_save.gizmo); + const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); + if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) { + if (m_state.current_gizmo) { + m_last_save.main = main_active_snapshot->timestamp; + } +// else { +// int a = 0; +// } + } + + m_last_save.gizmo = valid_snapshot->timestamp; } + reset_initial_presets(); + m_state.reset(); wxGetApp().mainframe->update_title(); } @@ -123,19 +156,32 @@ void ProjectDirtyStateManager::render_debug_window() const auto text = [](bool value) { return value ? "true" : "false"; }; - auto append_item = [color, text, &imgui](const std::string& name, bool value) { + auto append_bool_item = [color, text, &imgui](const std::string& name, bool value) { imgui.text_colored(color(value), name); ImGui::SameLine(); imgui.text_colored(color(value), text(value)); }; + auto append_int_item = [color, text, &imgui](const std::string& name, int value) { + imgui.text(name); + ImGui::SameLine(); + imgui.text(std::to_string(value)); + }; imgui.begin(std::string( "Project dirty state statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - append_item("State:", is_dirty()); - ImGui::Separator(); - append_item("Plater:", m_state.plater); - append_item("Presets:", m_state.presets); - append_item("Current gizmo:", m_state.current_gizmo); + if (ImGui::CollapsingHeader("Dirty state")) { + append_bool_item("Overall:", is_dirty()); + ImGui::Separator(); + append_bool_item("Plater:", m_state.plater); + append_bool_item("Presets:", m_state.presets); + append_bool_item("Current gizmo:", m_state.current_gizmo); + append_bool_item("Any gizmo:", !m_state.gizmos.empty()); + } + + if (ImGui::CollapsingHeader("Last save timestamps")) { + append_int_item("Main:", m_last_save.main); + append_int_item("Gizmo:", m_last_save.gizmo); + } imgui.end(); } @@ -151,7 +197,16 @@ void ProjectDirtyStateManager::update_from_undo_redo_main_stack(const Slic3r::Un boost::starts_with(active_snapshot->name, _utf8("Load Project:"))) return; - const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, stack); + size_t search_timestamp = 0; + if (boost::starts_with(active_snapshot->name, _utf8("Leaving")) || boost::starts_with(active_snapshot->name, _utf8("Entering"))) { + if (m_state.current_gizmo) + m_state.gizmos.push_back(extract_gizmo_name(active_snapshot->name)); + m_state.current_gizmo = false; + m_last_save.gizmo = 0; + search_timestamp = m_last_save.main; + } + + const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, stack, search_timestamp); m_state.plater = valid_snapshot->timestamp != m_last_save.main; } @@ -160,10 +215,13 @@ void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(const Slic3r::U m_state.current_gizmo = false; const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); - if (active_snapshot->name == "Gizmos-Initial") + if (active_snapshot->name == "Gizmos-Initial") { + m_state.current_gizmo = (m_last_save.gizmo != 0); return; + } - m_state.current_gizmo = true; + const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Gizmo, stack, m_last_save.gizmo); + m_state.current_gizmo = valid_snapshot->timestamp != m_last_save.gizmo; } } // namespace GUI diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index 2eac4ba92..a1fdd1fda 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -18,16 +18,18 @@ class ProjectDirtyStateManager bool plater{ false }; bool presets{ false }; bool current_gizmo{ false }; + std::vector gizmos; - bool is_dirty() const { return plater || presets || current_gizmo; } + bool is_dirty() const { return plater || presets || current_gizmo || !gizmos.empty(); } void reset() { plater = false; presets = false; current_gizmo = false; + gizmos.clear(); } }; - struct Timestamps + struct LastSaveTimestamps { size_t main{ 0 }; size_t gizmo{ 0 }; @@ -39,7 +41,7 @@ class ProjectDirtyStateManager }; DirtyState m_state; - Timestamps m_last_save; + LastSaveTimestamps m_last_save; // keeps track of initial selected presets std::array m_initial_presets; From 8c3d098ff68674939ce8617ed177d6116cdbe456 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 15 Apr 2021 15:19:03 +0200 Subject: [PATCH 07/75] Project dirty state manager -> management of gizmos dirty state WIP --- src/slic3r/GUI/ImGuiWrapper.cpp | 3 +- src/slic3r/GUI/Plater.cpp | 4 +- src/slic3r/GUI/ProjectDirtyStateManager.cpp | 225 +++++++++++++++----- src/slic3r/GUI/ProjectDirtyStateManager.hpp | 43 +++- 4 files changed, 212 insertions(+), 63 deletions(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index db7af046b..5726477ab 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -488,8 +488,7 @@ bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool ( int i=0; const char* item_text; - while (items_getter(is_undo, i, &item_text)) - { + while (items_getter(is_undo, i, &item_text)) { ImGui::Selectable(item_text, i < hovered); if (ImGui::IsItemHovered()) { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5ffd4e51c..dad3fcef0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4245,7 +4245,7 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) this->undo_redo_stack().release_least_recently_used(); #if ENABLE_PROJECT_DIRTY_STATE - dirty_state.update_from_undo_redo_stack(undo_redo_stack_main(), undo_redo_stack()); + dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::TakeSnapshot, undo_redo_stack_main(), undo_redo_stack()); #endif // ENABLE_PROJECT_DIRTY_STATE // Save the last active preset name of a particular printer technology. @@ -4385,7 +4385,7 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator } #if ENABLE_PROJECT_DIRTY_STATE - dirty_state.update_from_undo_redo_stack(undo_redo_stack_main(), undo_redo_stack()); + dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::UndoRedoTo, undo_redo_stack_main(), undo_redo_stack()); #endif // ENABLE_PROJECT_DIRTY_STATE } diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 82557c000..dfdeabd02 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -37,22 +37,59 @@ static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stac return ret; } -static const UndoRedo::Snapshot* get_last_valid_snapshot(EStackType type, const UndoRedo::Stack& stack, size_t last_save_timestamp) { - auto skip_main = [last_save_timestamp](const UndoRedo::Snapshot& snapshot) { - return boost::starts_with(snapshot.name, _utf8("Selection")) || - ((boost::starts_with(snapshot.name, _utf8("Entering")) || boost::starts_with(snapshot.name, _utf8("Leaving"))) && - (last_save_timestamp == 0 || last_save_timestamp != snapshot.timestamp)); +static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, const UndoRedo::Stack& stack, const ProjectDirtyStateManager::DirtyState::Gizmos& gizmos) { + auto is_gizmo_with_modifications = [&gizmos, &stack](const UndoRedo::Snapshot& snapshot) { + if (boost::starts_with(snapshot.name, _utf8("Entering"))) { + if (gizmos.current) + return true; + + std::string topmost_redo; + wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); + if (boost::starts_with(topmost_redo, _utf8("Leaving"))) { + const std::vector& snapshots = stack.snapshots(); + const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(snapshot.timestamp + 1))); + if (gizmos.is_used_and_modified(*leaving_snapshot)) + return true; + } + } + return false; + }; + + auto skip_main = [&gizmos, is_gizmo_with_modifications](const UndoRedo::Snapshot& snapshot) { + if (snapshot.name == _utf8("New Project")) + return true; + else if (snapshot.name == _utf8("Reset Project")) + return true; + else if (boost::starts_with(snapshot.name, _utf8("Load Project:"))) + return true; + else if (boost::starts_with(snapshot.name, _utf8("Selection"))) + return true; + else if (boost::starts_with(snapshot.name, _utf8("Entering"))) { + if (!is_gizmo_with_modifications(snapshot)) + return true; + } + else if (boost::starts_with(snapshot.name, _utf8("Leaving"))) { + if (!gizmos.is_used_and_modified(snapshot)) + return true; + } + + return false; + }; + + auto skip_gizmo = [&gizmos](const UndoRedo::Snapshot& snapshot) { + // put here any needed condition to skip the snapshot + return false; }; const UndoRedo::Snapshot* curr = get_active_snapshot(stack); const std::vector& snapshots = stack.snapshots(); size_t shift = 1; - while (type == EStackType::Main && skip_main(*curr)) { + while (curr->timestamp > 0 && ((type == EStackType::Main && skip_main(*curr)) || (type == EStackType::Gizmo && skip_gizmo(*curr)))) { const UndoRedo::Snapshot* temp = curr; curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - shift))); shift = (curr == temp) ? shift + 1 : 1; } - return curr; + return curr->timestamp > 0 ? curr : nullptr; } static std::string extract_gizmo_name(const std::string& s) { @@ -69,15 +106,63 @@ static std::string extract_gizmo_name(const std::string& s) { return ret; } -void ProjectDirtyStateManager::update_from_undo_redo_stack(const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack) +void ProjectDirtyStateManager::DirtyState::Gizmos::add_used(const UndoRedo::Snapshot& snapshot) +{ + const std::string name = extract_gizmo_name(snapshot.name); + auto it = used.find(name); + if (it == used.end()) + it = used.insert({ name, { {} } }).first; + + it->second.modified_timestamps.push_back(snapshot.timestamp); +} + +void ProjectDirtyStateManager::DirtyState::Gizmos::remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack) +{ + const std::vector& snapshots = main_stack.snapshots(); + for (auto& [name, gizmo] : used) { + auto it = gizmo.modified_timestamps.begin(); + while (it != gizmo.modified_timestamps.end()) { + size_t timestamp = *it; + auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [timestamp](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == timestamp; }); + if (snapshot_it == snapshots.end()) + it = gizmo.modified_timestamps.erase(it); + else + ++it; + } + } +} + +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +bool ProjectDirtyStateManager::DirtyState::Gizmos::any_used_modified() const +{ + for (auto& [name, gizmo] : used) { + if (!gizmo.modified_timestamps.empty()) + return true; + } + return false; +} +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + +bool ProjectDirtyStateManager::DirtyState::Gizmos::is_used_and_modified(const UndoRedo::Snapshot& snapshot) const +{ + for (auto& [name, gizmo] : used) { + for (size_t i : gizmo.modified_timestamps) { + if (i == snapshot.timestamp) + return true; + } + } + return false; +} + +void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack) { if (!wxGetApp().initialized()) return; if (&main_stack == &active_stack) - update_from_undo_redo_main_stack(main_stack); + update_from_undo_redo_main_stack(type, main_stack); else - update_from_undo_redo_gizmo_stack(active_stack); + update_from_undo_redo_gizmo_stack(type, active_stack); wxGetApp().mainframe->update_title(); } @@ -101,34 +186,21 @@ void ProjectDirtyStateManager::reset_after_save() if (&main_stack == &active_stack) { const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(main_stack); - const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, main_stack, m_last_save.main); - -// if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) { -// if (m_state.current_gizmo) { -// int a = 0; -// } -// else { -// int a = 0; -// } -// } - - m_last_save.main = valid_snapshot->timestamp; + const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos); + assert(saveable_snapshot != nullptr); + m_last_save.main = saveable_snapshot->timestamp; } else { const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(active_stack); - const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Gizmo, active_stack, m_last_save.gizmo); - + const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, active_stack, m_state.gizmos); const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) { - if (m_state.current_gizmo) { + if (m_state.gizmos.current) { m_last_save.main = main_active_snapshot->timestamp; } -// else { -// int a = 0; -// } } - m_last_save.gizmo = valid_snapshot->timestamp; + m_last_save.gizmo = saveable_snapshot->timestamp; } reset_initial_presets(); @@ -153,75 +225,128 @@ void ProjectDirtyStateManager::render_debug_window() const auto color = [](bool value) { return value ? ImVec4(1.0f, 0.49f, 0.216f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); }; - auto text = [](bool value) { + auto bool_to_text = [](bool value) { return value ? "true" : "false"; }; - auto append_bool_item = [color, text, &imgui](const std::string& name, bool value) { + auto append_bool_item = [color, bool_to_text, &imgui](const std::string& name, bool value) { imgui.text_colored(color(value), name); ImGui::SameLine(); - imgui.text_colored(color(value), text(value)); + imgui.text_colored(color(value), bool_to_text(value)); }; - auto append_int_item = [color, text, &imgui](const std::string& name, int value) { + auto append_int_item = [&imgui](const std::string& name, int value) { imgui.text(name); ImGui::SameLine(); imgui.text(std::to_string(value)); }; + auto append_snapshot_item = [&imgui](const std::string& label, const UndoRedo::Snapshot* snapshot) { + imgui.text(label); + ImGui::SameLine(100); + if (snapshot != nullptr) + imgui.text(snapshot->name + " (" + std::to_string(snapshot->timestamp) + ")"); + else + imgui.text("-"); + }; - imgui.begin(std::string( "Project dirty state statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + imgui.begin(std::string("Project dirty state statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - if (ImGui::CollapsingHeader("Dirty state")) { + if (ImGui::CollapsingHeader("Dirty state", ImGuiTreeNodeFlags_DefaultOpen)) { append_bool_item("Overall:", is_dirty()); ImGui::Separator(); append_bool_item("Plater:", m_state.plater); append_bool_item("Presets:", m_state.presets); - append_bool_item("Current gizmo:", m_state.current_gizmo); - append_bool_item("Any gizmo:", !m_state.gizmos.empty()); + append_bool_item("Current gizmo:", m_state.gizmos.current); } - if (ImGui::CollapsingHeader("Last save timestamps")) { + if (ImGui::CollapsingHeader("Last save timestamps", ImGuiTreeNodeFlags_DefaultOpen)) { append_int_item("Main:", m_last_save.main); append_int_item("Gizmo:", m_last_save.gizmo); } + if (ImGui::CollapsingHeader("Main snapshots", ImGuiTreeNodeFlags_DefaultOpen)) { + const UndoRedo::Stack& stack = wxGetApp().plater()->undo_redo_stack_main(); + const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); + append_snapshot_item("Active:", active_snapshot); + const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos); + append_snapshot_item("Last saveable:", last_saveable_snapshot); + if (ImGui::CollapsingHeader("Main undo/redo stack", ImGuiTreeNodeFlags_DefaultOpen)) { + const std::vector& snapshots = stack.snapshots(); + for (const UndoRedo::Snapshot& snapshot : snapshots) { + bool active = active_snapshot->timestamp == snapshot.timestamp; + imgui.text_colored(color(active), snapshot.name); + ImGui::SameLine(150); + imgui.text_colored(color(active), " (" + std::to_string(snapshot.timestamp) + ")"); + if (&snapshot == last_saveable_snapshot) { + ImGui::SameLine(); + imgui.text_colored(color(active), " (S)"); + } + } + } + } + + if (m_state.gizmos.any_used_modified()) { + if (ImGui::CollapsingHeader("Gizmos", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Indent(10.0f); + for (const auto& [name, gizmo] : m_state.gizmos.used) { + if (!gizmo.modified_timestamps.empty()) { + if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + for (size_t i : gizmo.modified_timestamps) { + imgui.text(std::to_string(i)); + } + } + } + } + ImGui::Unindent(10.0f); + } + } + imgui.end(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW -void ProjectDirtyStateManager::update_from_undo_redo_main_stack(const Slic3r::UndoRedo::Stack& stack) +void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack) { m_state.plater = false; const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); + + if (type == UpdateType::TakeSnapshot) + m_state.gizmos.remove_obsolete_used(stack); + if (active_snapshot->name == _utf8("New Project") || active_snapshot->name == _utf8("Reset Project") || boost::starts_with(active_snapshot->name, _utf8("Load Project:"))) return; size_t search_timestamp = 0; - if (boost::starts_with(active_snapshot->name, _utf8("Leaving")) || boost::starts_with(active_snapshot->name, _utf8("Entering"))) { - if (m_state.current_gizmo) - m_state.gizmos.push_back(extract_gizmo_name(active_snapshot->name)); - m_state.current_gizmo = false; + if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) { + m_state.gizmos.current = false; + m_last_save.gizmo = 0; + search_timestamp = m_last_save.main; + } + else if (boost::starts_with(active_snapshot->name, _utf8("Leaving"))) { + if (m_state.gizmos.current) + m_state.gizmos.add_used(*active_snapshot); + m_state.gizmos.current = false; m_last_save.gizmo = 0; search_timestamp = m_last_save.main; } - const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, stack, search_timestamp); - m_state.plater = valid_snapshot->timestamp != m_last_save.main; + const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos); + m_state.plater = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.main); } -void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(const Slic3r::UndoRedo::Stack& stack) +void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack) { - m_state.current_gizmo = false; + m_state.gizmos.current = false; const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); if (active_snapshot->name == "Gizmos-Initial") { - m_state.current_gizmo = (m_last_save.gizmo != 0); + m_state.gizmos.current = (m_last_save.gizmo != 0); return; } - const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Gizmo, stack, m_last_save.gizmo); - m_state.current_gizmo = valid_snapshot->timestamp != m_last_save.gizmo; + const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, stack, m_state.gizmos); + m_state.gizmos.current = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.gizmo); } } // namespace GUI diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index a1fdd1fda..a7c16705b 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -8,27 +8,52 @@ namespace Slic3r { namespace UndoRedo { class Stack; +struct Snapshot; } // namespace UndoRedo -namespace GUI { +namespace GUI { class ProjectDirtyStateManager { +public: + enum class UpdateType : unsigned char + { + TakeSnapshot, + UndoRedoTo + }; + struct DirtyState { + struct Gizmos + { + struct Gizmo + { + std::vector modified_timestamps; + }; + + bool current{ false }; + std::map used; + + void add_used(const UndoRedo::Snapshot& snapshot); + void remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack); +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + bool any_used_modified() const; +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + bool is_used_and_modified(const UndoRedo::Snapshot& snapshot) const; + }; + bool plater{ false }; bool presets{ false }; - bool current_gizmo{ false }; - std::vector gizmos; + Gizmos gizmos; - bool is_dirty() const { return plater || presets || current_gizmo || !gizmos.empty(); } + bool is_dirty() const { return plater || presets || gizmos.current; } void reset() { plater = false; presets = false; - current_gizmo = false; - gizmos.clear(); + gizmos.current = false; } }; +private: struct LastSaveTimestamps { size_t main{ 0 }; @@ -48,7 +73,7 @@ class ProjectDirtyStateManager public: bool is_dirty() const { return m_state.is_dirty(); } - void update_from_undo_redo_stack(const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack); + void update_from_undo_redo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack); void update_from_presets(); void reset_after_save(); void reset_initial_presets(); @@ -57,8 +82,8 @@ public: #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW private: - void update_from_undo_redo_main_stack(const Slic3r::UndoRedo::Stack& stack); - void update_from_undo_redo_gizmo_stack(const Slic3r::UndoRedo::Stack& stack); + void update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack); + void update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack); }; } // namespace GUI From ce73671f475583b4cedab2d8f17b1d695e16fd03 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 16 Apr 2021 09:36:19 +0200 Subject: [PATCH 08/75] Project dirty state manager -> Improvements to management of gizmos dirty state --- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 37 ++++++++++++++++++++ src/slic3r/GUI/ProjectDirtyStateManager.cpp | 22 ++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 91aef75d9..3a932c598 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -32,6 +32,42 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic +#if ENABLE_PROJECT_DIRTY_STATE +// port of 948bc382655993721d93d3b9fce9b0186fcfb211 +void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) +{ + Plater* plater = wxGetApp().plater(); + + // Following is needed to prevent taking an extra snapshot when the activation of + // the internal stack happens when the gizmo is already active (such as open gizmo, + // close gizmo, undo, start painting). The internal stack does not activate on the + // undo, because that would obliterate all future of the main stack (user would + // have to close the gizmo himself, he has no access to main undo/redo after the + // internal stack opens). We don't want the "entering" snapshot taken in this case, + // because there already is one. + std::string last_snapshot_name; + plater->undo_redo_topmost_string_getter(plater->can_undo(), last_snapshot_name); + + if (activate && !m_internal_stack_active) { + std::string str = get_painter_type() == PainterGizmoType::FDM_SUPPORTS + ? _u8L("Entering Paint-on supports") + : _u8L("Entering Seam painting"); + if (last_snapshot_name != str) + Plater::TakeSnapshot(plater, str); + plater->enter_gizmos_stack(); + m_internal_stack_active = true; + } + if (!activate && m_internal_stack_active) { + plater->leave_gizmos_stack(); + std::string str = get_painter_type() == PainterGizmoType::SEAM + ? _u8L("Leaving Seam painting") + : _u8L("Leaving Paint-on supports"); + if (last_snapshot_name != str) + Plater::TakeSnapshot(plater, str); + m_internal_stack_active = false; + } +} +#else void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) { if (activate && ! m_internal_stack_active) { @@ -51,6 +87,7 @@ void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) m_internal_stack_active = false; } } +#endif // ENABLE_PROJECT_DIRTY_STATE diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index dfdeabd02..06f54359f 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -259,7 +259,7 @@ void ProjectDirtyStateManager::render_debug_window() const if (ImGui::CollapsingHeader("Last save timestamps", ImGuiTreeNodeFlags_DefaultOpen)) { append_int_item("Main:", m_last_save.main); - append_int_item("Gizmo:", m_last_save.gizmo); + append_int_item("Current gizmo:", m_last_save.gizmo); } if (ImGui::CollapsingHeader("Main snapshots", ImGuiTreeNodeFlags_DefaultOpen)) { @@ -279,6 +279,10 @@ void ProjectDirtyStateManager::render_debug_window() const ImGui::SameLine(); imgui.text_colored(color(active), " (S)"); } + if (m_last_save.main > 0 && m_last_save.main == snapshot.timestamp) { + ImGui::SameLine(); + imgui.text_colored(color(active), " (LS)"); + } } } } @@ -307,11 +311,11 @@ void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, { m_state.plater = false; - const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); - if (type == UpdateType::TakeSnapshot) m_state.gizmos.remove_obsolete_used(stack); + const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); + if (active_snapshot->name == _utf8("New Project") || active_snapshot->name == _utf8("Reset Project") || boost::starts_with(active_snapshot->name, _utf8("Load Project:"))) @@ -319,6 +323,18 @@ void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, size_t search_timestamp = 0; if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) { + if (type == UpdateType::UndoRedoTo) { + std::string topmost_redo; + wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); + if (boost::starts_with(topmost_redo, _utf8("Leaving"))) { + const std::vector& snapshots = stack.snapshots(); + const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot->timestamp + 1))); + if (m_state.gizmos.is_used_and_modified(*leaving_snapshot)) { + m_state.plater = (leaving_snapshot != nullptr && leaving_snapshot->timestamp != m_last_save.main); + return; + } + } + } m_state.gizmos.current = false; m_last_save.gizmo = 0; search_timestamp = m_last_save.main; From c691464659e634934a889dd6226697671ff27cf1 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 20 Apr 2021 09:09:06 +0200 Subject: [PATCH 09/75] Project dirty state manager -> Improvements update of plater dirty state after save commands --- src/slic3r/GUI/ProjectDirtyStateManager.cpp | 102 +++++++++++++------- src/slic3r/GUI/ProjectDirtyStateManager.hpp | 1 + 2 files changed, 70 insertions(+), 33 deletions(-) diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 06f54359f..9c6d097d7 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -37,7 +37,8 @@ static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stac return ret; } -static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, const UndoRedo::Stack& stack, const ProjectDirtyStateManager::DirtyState::Gizmos& gizmos) { +static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, const UndoRedo::Stack& stack, + const ProjectDirtyStateManager::DirtyState::Gizmos& gizmos, size_t last_save_main) { auto is_gizmo_with_modifications = [&gizmos, &stack](const UndoRedo::Snapshot& snapshot) { if (boost::starts_with(snapshot.name, _utf8("Entering"))) { if (gizmos.current) @@ -55,7 +56,7 @@ static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, con return false; }; - auto skip_main = [&gizmos, is_gizmo_with_modifications](const UndoRedo::Snapshot& snapshot) { + auto skip_main = [&gizmos, last_save_main, is_gizmo_with_modifications](const UndoRedo::Snapshot& snapshot) { if (snapshot.name == _utf8("New Project")) return true; else if (snapshot.name == _utf8("Reset Project")) @@ -65,11 +66,11 @@ static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, con else if (boost::starts_with(snapshot.name, _utf8("Selection"))) return true; else if (boost::starts_with(snapshot.name, _utf8("Entering"))) { - if (!is_gizmo_with_modifications(snapshot)) + if (last_save_main != snapshot.timestamp + 1 && !is_gizmo_with_modifications(snapshot)) return true; } else if (boost::starts_with(snapshot.name, _utf8("Leaving"))) { - if (!gizmos.is_used_and_modified(snapshot)) + if (last_save_main != snapshot.timestamp && !gizmos.is_used_and_modified(snapshot)) return true; } @@ -89,6 +90,12 @@ static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, con curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - shift))); shift = (curr == temp) ? shift + 1 : 1; } + if (boost::starts_with(curr->name, _utf8("Entering")) && last_save_main == curr->timestamp + 1) { + std::string topmost_redo; + wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); + if (boost::starts_with(topmost_redo, _utf8("Leaving"))) + curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp + 1))); + } return curr->timestamp > 0 ? curr : nullptr; } @@ -154,6 +161,11 @@ bool ProjectDirtyStateManager::DirtyState::Gizmos::is_used_and_modified(const Un return false; } +void ProjectDirtyStateManager::DirtyState::Gizmos::reset() +{ + used.clear(); +} + void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack) { if (!wxGetApp().initialized()) @@ -186,20 +198,18 @@ void ProjectDirtyStateManager::reset_after_save() if (&main_stack == &active_stack) { const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(main_stack); - const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos); + const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main); assert(saveable_snapshot != nullptr); m_last_save.main = saveable_snapshot->timestamp; } else { const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(active_stack); - const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, active_stack, m_state.gizmos); const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) { - if (m_state.gizmos.current) { - m_last_save.main = main_active_snapshot->timestamp; - } + if (m_state.gizmos.current) + m_last_save.main = main_active_snapshot->timestamp + 1; } - + const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, active_stack, m_state.gizmos, m_last_save.main); m_last_save.gizmo = saveable_snapshot->timestamp; } @@ -262,27 +272,43 @@ void ProjectDirtyStateManager::render_debug_window() const append_int_item("Current gizmo:", m_last_save.gizmo); } + const UndoRedo::Stack& main_stack = wxGetApp().plater()->undo_redo_stack_main(); + const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); + const UndoRedo::Snapshot* main_last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main); + const std::vector& main_snapshots = main_stack.snapshots(); + if (ImGui::CollapsingHeader("Main snapshots", ImGuiTreeNodeFlags_DefaultOpen)) { - const UndoRedo::Stack& stack = wxGetApp().plater()->undo_redo_stack_main(); - const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); - append_snapshot_item("Active:", active_snapshot); - const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos); - append_snapshot_item("Last saveable:", last_saveable_snapshot); - if (ImGui::CollapsingHeader("Main undo/redo stack", ImGuiTreeNodeFlags_DefaultOpen)) { - const std::vector& snapshots = stack.snapshots(); - for (const UndoRedo::Snapshot& snapshot : snapshots) { - bool active = active_snapshot->timestamp == snapshot.timestamp; + append_snapshot_item("Active:", main_active_snapshot); + append_snapshot_item("Last saveable:", main_last_saveable_snapshot); + } + + if (ImGui::CollapsingHeader("Main undo/redo stack", ImGuiTreeNodeFlags_DefaultOpen)) { + for (const UndoRedo::Snapshot& snapshot : main_snapshots) { + bool active = main_active_snapshot->timestamp == snapshot.timestamp; + imgui.text_colored(color(active), snapshot.name); + ImGui::SameLine(150); + imgui.text_colored(color(active), " (" + std::to_string(snapshot.timestamp) + ")"); + if (&snapshot == main_last_saveable_snapshot) { + ImGui::SameLine(); + imgui.text_colored(color(active), " (S)"); + } + if (m_last_save.main > 0 && m_last_save.main == snapshot.timestamp) { + ImGui::SameLine(); + imgui.text_colored(color(active), " (LS)"); + } + } + } + + const UndoRedo::Stack& active_stack = wxGetApp().plater()->undo_redo_stack_active(); + if (&active_stack != &main_stack) { + if (ImGui::CollapsingHeader("Gizmo undo/redo stack", ImGuiTreeNodeFlags_DefaultOpen)) { + const UndoRedo::Snapshot* active_active_snapshot = get_active_snapshot(active_stack); + const std::vector& active_snapshots = active_stack.snapshots(); + for (const UndoRedo::Snapshot& snapshot : active_snapshots) { + bool active = active_active_snapshot->timestamp == snapshot.timestamp; imgui.text_colored(color(active), snapshot.name); ImGui::SameLine(150); imgui.text_colored(color(active), " (" + std::to_string(snapshot.timestamp) + ")"); - if (&snapshot == last_saveable_snapshot) { - ImGui::SameLine(); - imgui.text_colored(color(active), " (S)"); - } - if (m_last_save.main > 0 && m_last_save.main == snapshot.timestamp) { - ImGui::SameLine(); - imgui.text_colored(color(active), " (LS)"); - } } } } @@ -293,9 +319,13 @@ void ProjectDirtyStateManager::render_debug_window() const for (const auto& [name, gizmo] : m_state.gizmos.used) { if (!gizmo.modified_timestamps.empty()) { if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { - for (size_t i : gizmo.modified_timestamps) { - imgui.text(std::to_string(i)); + std::string modified_timestamps; + for (size_t i = 0; i < gizmo.modified_timestamps.size(); ++i) { + if (i > 0) + modified_timestamps += " | "; + modified_timestamps += std::to_string(gizmo.modified_timestamps[i]); } + imgui.text(modified_timestamps); } } } @@ -311,11 +341,17 @@ void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, { m_state.plater = false; - if (type == UpdateType::TakeSnapshot) + if (type == UpdateType::TakeSnapshot) { + if (m_last_save.main != 0) { + const std::vector& snapshots = stack.snapshots(); + auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [this](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == m_last_save.main; }); + if (snapshot_it == snapshots.end()) + m_last_save.main = 0; + } m_state.gizmos.remove_obsolete_used(stack); + } const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); - if (active_snapshot->name == _utf8("New Project") || active_snapshot->name == _utf8("Reset Project") || boost::starts_with(active_snapshot->name, _utf8("Load Project:"))) @@ -347,7 +383,7 @@ void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, search_timestamp = m_last_save.main; } - const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos); + const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos, m_last_save.main); m_state.plater = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.main); } @@ -361,7 +397,7 @@ void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(UpdateType type return; } - const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, stack, m_state.gizmos); + const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, stack, m_state.gizmos, m_last_save.main); m_state.gizmos.current = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.gizmo); } diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index a7c16705b..79a16edc4 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -39,6 +39,7 @@ public: bool any_used_modified() const; #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW bool is_used_and_modified(const UndoRedo::Snapshot& snapshot) const; + void reset(); }; bool plater{ false }; From f486dedb52754d9db3ba060b75650b089f005b9c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 20 Apr 2021 10:41:38 +0200 Subject: [PATCH 10/75] Disabled tech ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW --- src/libslic3r/Technologies.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index fcb59f1a1..a74ff55ae 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -61,7 +61,7 @@ // Enable project dirty state manager #define ENABLE_PROJECT_DIRTY_STATE (1 && ENABLE_2_4_0_ALPHA0) // Enable project dirty state manager debug window -#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW (1 && ENABLE_PROJECT_DIRTY_STATE) +#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW (0 && ENABLE_PROJECT_DIRTY_STATE) #endif // _prusaslicer_technologies_h_ From 9cd5ba13f24161b341466d6e2a40150705b5ff85 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 20 Apr 2021 16:07:39 +0200 Subject: [PATCH 11/75] Some refactoring into ProjectDirtyStateManager --- src/slic3r/GUI/Plater.cpp | 4 +- src/slic3r/GUI/ProjectDirtyStateManager.cpp | 66 ++++++++++++--------- src/slic3r/GUI/ProjectDirtyStateManager.hpp | 2 +- 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index dad3fcef0..0da345c1e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4245,7 +4245,7 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) this->undo_redo_stack().release_least_recently_used(); #if ENABLE_PROJECT_DIRTY_STATE - dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::TakeSnapshot, undo_redo_stack_main(), undo_redo_stack()); + dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::TakeSnapshot); #endif // ENABLE_PROJECT_DIRTY_STATE // Save the last active preset name of a particular printer technology. @@ -4385,7 +4385,7 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator } #if ENABLE_PROJECT_DIRTY_STATE - dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::UndoRedoTo, undo_redo_stack_main(), undo_redo_stack()); + dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::UndoRedoTo); #endif // ENABLE_PROJECT_DIRTY_STATE } diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 9c6d097d7..95cfa042e 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -24,6 +24,7 @@ enum class EStackType Gizmo }; +// returns the current active snapshot (the topmost snapshot in the undo part of the stack) in the given stack static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stack) { const std::vector& snapshots = stack.snapshots(); const size_t active_snapshot_time = stack.active_snapshot_time(); @@ -33,30 +34,32 @@ static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stac &snapshots[idx] : nullptr; assert(ret != nullptr); - return ret; } +// returns the last saveable snapshot (the topmost snapshot in the undo part of the stack that can be saved) in the given stack static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, const UndoRedo::Stack& stack, const ProjectDirtyStateManager::DirtyState::Gizmos& gizmos, size_t last_save_main) { - auto is_gizmo_with_modifications = [&gizmos, &stack](const UndoRedo::Snapshot& snapshot) { - if (boost::starts_with(snapshot.name, _utf8("Entering"))) { - if (gizmos.current) - return true; - std::string topmost_redo; - wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); - if (boost::starts_with(topmost_redo, _utf8("Leaving"))) { - const std::vector& snapshots = stack.snapshots(); - const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(snapshot.timestamp + 1))); - if (gizmos.is_used_and_modified(*leaving_snapshot)) + // returns true if the given snapshot is not saveable + auto skip_main = [&gizmos, last_save_main, &stack](const UndoRedo::Snapshot& snapshot) { + auto is_gizmo_with_modifications = [&gizmos, &stack](const UndoRedo::Snapshot& snapshot) { + if (boost::starts_with(snapshot.name, _utf8("Entering"))) { + if (gizmos.current) return true; - } - } - return false; - }; - auto skip_main = [&gizmos, last_save_main, is_gizmo_with_modifications](const UndoRedo::Snapshot& snapshot) { + std::string topmost_redo; + wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); + if (boost::starts_with(topmost_redo, _utf8("Leaving"))) { + const std::vector& snapshots = stack.snapshots(); + const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(snapshot.timestamp + 1))); + if (gizmos.is_used_and_modified(*leaving_snapshot)) + return true; + } + } + return false; + }; + if (snapshot.name == _utf8("New Project")) return true; else if (snapshot.name == _utf8("Reset Project")) @@ -77,7 +80,8 @@ static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, con return false; }; - auto skip_gizmo = [&gizmos](const UndoRedo::Snapshot& snapshot) { + // returns true if the given snapshot is not saveable + auto skip_gizmo = [](const UndoRedo::Snapshot& snapshot) { // put here any needed condition to skip the snapshot return false; }; @@ -90,15 +94,18 @@ static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, con curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - shift))); shift = (curr == temp) ? shift + 1 : 1; } - if (boost::starts_with(curr->name, _utf8("Entering")) && last_save_main == curr->timestamp + 1) { - std::string topmost_redo; - wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); - if (boost::starts_with(topmost_redo, _utf8("Leaving"))) - curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp + 1))); + if (type == EStackType::Main) { + if (boost::starts_with(curr->name, _utf8("Entering")) && last_save_main == curr->timestamp + 1) { + std::string topmost_redo; + wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); + if (boost::starts_with(topmost_redo, _utf8("Leaving"))) + curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp + 1))); + } } return curr->timestamp > 0 ? curr : nullptr; } +// returns the name of the gizmo contained in the given string static std::string extract_gizmo_name(const std::string& s) { static const std::array prefixes = { _utf8("Entering"), _utf8("Leaving") }; @@ -150,6 +157,7 @@ bool ProjectDirtyStateManager::DirtyState::Gizmos::any_used_modified() const } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +// returns true if the given snapshot is contained in any of the gizmos caches bool ProjectDirtyStateManager::DirtyState::Gizmos::is_used_and_modified(const UndoRedo::Snapshot& snapshot) const { for (auto& [name, gizmo] : used) { @@ -166,11 +174,15 @@ void ProjectDirtyStateManager::DirtyState::Gizmos::reset() used.clear(); } -void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack) +void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type) { if (!wxGetApp().initialized()) return; + const Plater* plater = wxGetApp().plater(); + const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main(); + const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active(); + if (&main_stack == &active_stack) update_from_undo_redo_main_stack(type, main_stack); else @@ -197,13 +209,11 @@ void ProjectDirtyStateManager::reset_after_save() const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active(); if (&main_stack == &active_stack) { - const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(main_stack); const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main); assert(saveable_snapshot != nullptr); m_last_save.main = saveable_snapshot->timestamp; } else { - const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(active_stack); const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) { if (m_state.gizmos.current) @@ -233,7 +243,7 @@ void ProjectDirtyStateManager::render_debug_window() const ImGuiWrapper& imgui = *wxGetApp().imgui(); auto color = [](bool value) { - return value ? ImVec4(1.0f, 0.49f, 0.216f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + return value ? ImVec4(1.0f, 0.49f, 0.216f, 1.0f) /* orange */: ImVec4(1.0f, 1.0f, 1.0f, 1.0f) /* white */; }; auto bool_to_text = [](bool value) { return value ? "true" : "false"; @@ -392,10 +402,8 @@ void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(UpdateType type m_state.gizmos.current = false; const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); - if (active_snapshot->name == "Gizmos-Initial") { - m_state.gizmos.current = (m_last_save.gizmo != 0); + if (active_snapshot->name == "Gizmos-Initial") return; - } const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, stack, m_state.gizmos, m_last_save.main); m_state.gizmos.current = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.gizmo); diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index 79a16edc4..f7ce81a62 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -74,7 +74,7 @@ private: public: bool is_dirty() const { return m_state.is_dirty(); } - void update_from_undo_redo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack); + void update_from_undo_redo_stack(UpdateType type); void update_from_presets(); void reset_after_save(); void reset_initial_presets(); From c8b83ae01164b6afc8e2fa70c86b55522352d166 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 21 Apr 2021 12:41:43 +0200 Subject: [PATCH 12/75] Tech ENABLE_ALLOW_NEGATIVE_Z -> Allow move gizmo to place an object under the printbed --- src/libslic3r/Technologies.hpp | 2 ++ src/slic3r/GUI/GLCanvas3D.cpp | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 303ffe927..369996fba 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,6 +59,8 @@ #define ENABLE_EXTENDED_M73_LINES (1 && ENABLE_VALIDATE_CUSTOM_GCODE) // Enable a modified version of automatic downscale on load of objects too big #define ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG (1 && ENABLE_2_4_0_ALPHA0) +// Enable to push object instances under the bed +#define ENABLE_ALLOW_NEGATIVE_Z (1 && ENABLE_2_4_0_ALPHA0) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 97038723b..59a4a62a4 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3572,12 +3572,25 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) wipe_tower_origin = v->get_volume_offset(); } +#if ENABLE_ALLOW_NEGATIVE_Z + // Fixes flying instances +#else // Fixes sinking/flying instances +#endif // ENABLE_ALLOW_NEGATIVE_Z for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; +#if ENABLE_ALLOW_NEGATIVE_Z + double shift_z = m->get_instance_min_z(i.second); + if (shift_z > 0.0) { + Vec3d shift(0.0, 0.0, -shift_z); +#else Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); +#endif // ENABLE_ALLOW_NEGATIVE_Z m_selection.translate(i.first, i.second, shift); m->translate_instance(i.second, shift); +#if ENABLE_ALLOW_NEGATIVE_Z + } +#endif // ENABLE_ALLOW_NEGATIVE_Z } if (object_moved) From 8f385aac4408fb5272357021653081f21329303f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 22 Apr 2021 13:18:36 +0200 Subject: [PATCH 13/75] Tech ENABLE_ALLOW_NEGATIVE_Z -> Shading of sinking instances --- resources/shaders/gouraud.fs | 18 ++++++++++++++-- resources/shaders/gouraud.vs | 16 +++++++++------ src/slic3r/GUI/3DScene.cpp | 40 ++++++++++++++++++++++++++++++++---- src/slic3r/GUI/3DScene.hpp | 5 +++++ 4 files changed, 67 insertions(+), 12 deletions(-) diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 850d69b08..ed03bfe64 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -4,7 +4,9 @@ const vec3 ZERO = vec3(0.0, 0.0, 0.0); const vec3 GREEN = vec3(0.0, 0.7, 0.0); const vec3 YELLOW = vec3(0.5, 0.7, 0.0); const vec3 RED = vec3(0.7, 0.0, 0.0); +const vec3 WHITE = vec3(1.0, 1.0, 1.0); const float EPSILON = 0.0001; +const float BANDS_WIDTH = 10.0; struct SlopeDetection { @@ -15,6 +17,7 @@ struct SlopeDetection uniform vec4 uniform_color; uniform SlopeDetection slope; +uniform bool sinking; #ifdef ENABLE_ENVIRONMENT_MAP uniform sampler2D environment_tex; @@ -23,27 +26,38 @@ uniform SlopeDetection slope; varying vec3 clipping_planes_dots; -// x = tainted, y = specular; +// x = diffuse, y = specular; varying vec2 intensity; varying vec3 delta_box_min; varying vec3 delta_box_max; +varying vec4 model_pos; +varying float world_pos_z; varying float world_normal_z; varying vec3 eye_normal; +vec3 sinking_color(vec3 color) +{ + return (mod(model_pos.x + model_pos.y + model_pos.z, BANDS_WIDTH) < (0.5 * BANDS_WIDTH)) ? mix(color, ZERO, 0.6666) : color; +} + void main() { if (any(lessThan(clipping_planes_dots, ZERO))) discard; vec3 color = uniform_color.rgb; float alpha = uniform_color.a; - if (slope.actived && world_normal_z < slope.normal_z - EPSILON) { + if (slope.actived && world_normal_z < slope.normal_z - EPSILON) + { color = vec3(0.7, 0.7, 1.0); alpha = 1.0; } // if the fragment is outside the print volume -> use darker color color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(color, ZERO, 0.3333) : color; + // if the object is sinking, shade it with inclined bands or white around world z = 0 + if (sinking) + color = (abs(world_pos_z) < 0.05) ? WHITE : sinking_color(color); #ifdef ENABLE_ENVIRONMENT_MAP if (use_environment_tex) gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity.x, alpha); diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index ed7e3f56b..f2706b386 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -41,7 +41,7 @@ uniform vec2 z_range; // Clipping plane - general orientation. Used by the SLA gizmo. uniform vec4 clipping_plane; -// x = tainted, y = specular; +// x = diffuse, y = specular; varying vec2 intensity; varying vec3 delta_box_min; @@ -49,6 +49,8 @@ varying vec3 delta_box_max; varying vec3 clipping_planes_dots; +varying vec4 model_pos; +varying float world_pos_z; varying float world_normal_z; varying vec3 eye_normal; @@ -69,12 +71,16 @@ void main() NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + model_pos = gl_Vertex; + // Point in homogenous coordinates. + vec4 world_pos = print_box.volume_world_matrix * gl_Vertex; + world_pos_z = world_pos.z; + // compute deltas for out of print volume detection (world coordinates) if (print_box.actived) { - vec3 v = (print_box.volume_world_matrix * gl_Vertex).xyz; - delta_box_min = v - print_box.min; - delta_box_max = v - print_box.max; + delta_box_min = world_pos.xyz - print_box.min; + delta_box_max = world_pos.xyz - print_box.max; } else { @@ -86,8 +92,6 @@ void main() world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0; gl_Position = ftransform(); - // Point in homogenous coordinates. - vec4 world_pos = print_box.volume_world_matrix * gl_Vertex; // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); } diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index ba62576f2..57ec1e0d9 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -345,9 +345,16 @@ void GLVolume::set_render_color(const float* rgba, unsigned int size) void GLVolume::set_render_color() { - if (force_native_color || force_neutral_color) - { +#if ENABLE_ALLOW_NEGATIVE_Z + bool outside = is_outside || is_below_printbed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z + + if (force_native_color || force_neutral_color) { +#if ENABLE_ALLOW_NEGATIVE_Z + if (outside && shader_outside_printer_detection_enabled) +#else if (is_outside && shader_outside_printer_detection_enabled) +#endif // ENABLE_ALLOW_NEGATIVE_Z set_render_color(OUTSIDE_COLOR, 4); else { if (force_native_color) @@ -362,17 +369,24 @@ void GLVolume::set_render_color() else if (hover == HS_Deselect) set_render_color(HOVER_DESELECT_COLOR, 4); else if (selected) +#if ENABLE_ALLOW_NEGATIVE_Z + set_render_color(outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4); +#else set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4); +#endif // ENABLE_ALLOW_NEGATIVE_Z else if (disabled) set_render_color(DISABLED_COLOR, 4); +#if ENABLE_ALLOW_NEGATIVE_Z + else if (outside && shader_outside_printer_detection_enabled) +#else else if (is_outside && shader_outside_printer_detection_enabled) +#endif // ENABLE_ALLOW_NEGATIVE_Z set_render_color(OUTSIDE_COLOR, 4); else set_render_color(color, 4); } - if (!printable) - { + if (!printable) { render_color[0] /= 4; render_color[1] /= 4; render_color[2] /= 4; @@ -504,6 +518,21 @@ void GLVolume::render() const bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); } bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); } +#if ENABLE_ALLOW_NEGATIVE_Z +bool GLVolume::is_sinking() const +{ + if (is_modifier) + return false; + const BoundingBoxf3& box = transformed_convex_hull_bounding_box(); + return box.min(2) < 0.0 && box.max(2) >= 0.0; +} + +bool GLVolume::is_below_printbed() const +{ + return transformed_convex_hull_bounding_box().max(2) < 0.0; +} +#endif // ENABLE_ALLOW_NEGATIVE_Z + std::vector GLVolumeCollection::load_object( const ModelObject *model_object, int obj_idx, @@ -778,6 +807,9 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix()); shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); shader->set_uniform("slope.volume_world_normal_matrix", static_cast(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast())); +#if ENABLE_ALLOW_NEGATIVE_Z + shader->set_uniform("sinking", volume.first->is_sinking()); +#endif // ENABLE_ALLOW_NEGATIVE_Z volume.first->render(); } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 25c5443cd..2d46a4524 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -453,6 +453,11 @@ public: bool is_sla_support() const; bool is_sla_pad() const; +#if ENABLE_ALLOW_NEGATIVE_Z + bool is_sinking() const; + bool is_below_printbed() const; +#endif // ENABLE_ALLOW_NEGATIVE_Z + // Return an estimate of the memory consumed by this class. size_t cpu_memory_used() const { //FIXME what to do wih m_convex_hull? From d4695827ce0fc1e40590b448ccd934be79b930a2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 23 Apr 2021 08:29:29 +0200 Subject: [PATCH 14/75] Tech ENABLE_ALLOW_NEGATIVE_Z -> Keep sinking instances as sinking after applying rotate gizmo --- src/libslic3r/Model.cpp | 13 ++++------- src/slic3r/GUI/3DScene.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 43 +++++++++++++++++++++++------------ 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d48443181..208cf9b66 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1430,11 +1430,9 @@ double ModelObject::get_min_z() const { if (instances.empty()) return 0.0; - else - { + else { double min_z = DBL_MAX; - for (size_t i = 0; i < instances.size(); ++i) - { + for (size_t i = 0; i < instances.size(); ++i) { min_z = std::min(min_z, get_instance_min_z(i)); } return min_z; @@ -1445,15 +1443,14 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const { double min_z = DBL_MAX; - ModelInstance* inst = instances[instance_idx]; + const ModelInstance* inst = instances[instance_idx]; const Transform3d& mi = inst->get_matrix(true); - for (const ModelVolume* v : volumes) - { + for (const ModelVolume* v : volumes) { if (!v->is_model_part()) continue; - Transform3d mv = mi * v->get_matrix(); + const Transform3d mv = mi * v->get_matrix(); const TriangleMesh& hull = v->get_convex_hull(); for (const stl_facet &facet : hull.stl.facet_start) for (int i = 0; i < 3; ++ i) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 57ec1e0d9..c64c36ea8 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -524,7 +524,7 @@ bool GLVolume::is_sinking() const if (is_modifier) return false; const BoundingBoxf3& box = transformed_convex_hull_bounding_box(); - return box.min(2) < 0.0 && box.max(2) >= 0.0; + return box.min(2) < -EPSILON && box.max(2) >= -EPSILON; } bool GLVolume::is_below_printbed() const diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 59a4a62a4..1513c5a39 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3610,18 +3610,30 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) if (!snapshot_type.empty()) wxGetApp().plater()->take_snapshot(_(snapshot_type)); +#if ENABLE_ALLOW_NEGATIVE_Z + // stores current min_z of instances + std::map, double> min_zs; + if (!snapshot_type.empty()) { + for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { + const ModelObject* obj = m_model->objects[i]; + for (int j = 0; j < static_cast(obj->instances.size()); ++j) { + min_zs[{ i, j }] = obj->instance_bounding_box(j).min(2); + } + } + } +#endif // ENABLE_ALLOW_NEGATIVE_Z + std::set> done; // keeps track of modified instances Selection::EMode selection_mode = m_selection.get_mode(); - for (const GLVolume* v : m_volumes.volumes) - { + for (const GLVolume* v : m_volumes.volumes) { int object_idx = v->object_idx(); if (object_idx == 1000) { // the wipe tower Vec3d offset = v->get_volume_offset(); post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2)))); } - if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) continue; int instance_idx = v->instance_idx(); @@ -3631,15 +3643,12 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) // Rotate instances/volumes. ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) - { - if (selection_mode == Selection::Instance) - { + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) { model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); } - else if (selection_mode == Selection::Volume) - { + else if (selection_mode == Selection::Volume) { model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); } @@ -3648,12 +3657,18 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) } // Fixes sinking/flying instances - for (const std::pair& i : done) - { + for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; - Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); - m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); +#if ENABLE_ALLOW_NEGATIVE_Z + // leave sinking instances as sinking + if (min_zs.empty() || min_zs.find({i.first, i.second})->second >= 0.0) { +#endif // ENABLE_ALLOW_NEGATIVE_Z + Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); +#if ENABLE_ALLOW_NEGATIVE_Z + } +#endif // ENABLE_ALLOW_NEGATIVE_Z } if (!done.empty()) From b6005404119f1a47f84599d2e79a01f70f415996 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 23 Apr 2021 09:11:55 +0200 Subject: [PATCH 15/75] Tech ENABLE_ALLOW_NEGATIVE_Z -> Keep sinking instances as sinking after applying scale gizmo --- src/slic3r/GUI/GLCanvas3D.cpp | 49 +++++++++++++++++++-------- src/slic3r/GUI/Selection.cpp | 62 ++++++++++++++--------------------- 2 files changed, 59 insertions(+), 52 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1513c5a39..f4a0d7412 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3660,10 +3660,13 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; #if ENABLE_ALLOW_NEGATIVE_Z + double shift_z = m->get_instance_min_z(i.second); // leave sinking instances as sinking - if (min_zs.empty() || min_zs.find({i.first, i.second})->second >= 0.0) { + if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= 0.0 || shift_z > 0.0) { + Vec3d shift(0.0, 0.0, -shift_z); +#else + Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); #endif // ENABLE_ALLOW_NEGATIVE_Z - Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); m_selection.translate(i.first, i.second, shift); m->translate_instance(i.second, shift); #if ENABLE_ALLOW_NEGATIVE_Z @@ -3685,14 +3688,26 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) if (!snapshot_type.empty()) wxGetApp().plater()->take_snapshot(_(snapshot_type)); +#if ENABLE_ALLOW_NEGATIVE_Z + // stores current min_z of instances + std::map, double> min_zs; + if (!snapshot_type.empty()) { + for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { + const ModelObject* obj = m_model->objects[i]; + for (int j = 0; j < static_cast(obj->instances.size()); ++j) { + min_zs[{ i, j }] = obj->instance_bounding_box(j).min(2); + } + } + } +#endif // ENABLE_ALLOW_NEGATIVE_Z + std::set> done; // keeps track of modified instances Selection::EMode selection_mode = m_selection.get_mode(); - for (const GLVolume* v : m_volumes.volumes) - { + for (const GLVolume* v : m_volumes.volumes) { int object_idx = v->object_idx(); - if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) continue; int instance_idx = v->instance_idx(); @@ -3702,15 +3717,12 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) // Rotate instances/volumes ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) - { - if (selection_mode == Selection::Instance) - { + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) { model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); } - else if (selection_mode == Selection::Volume) - { + else if (selection_mode == Selection::Volume) { model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); @@ -3720,16 +3732,25 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) } // Fixes sinking/flying instances - for (const std::pair& i : done) - { + for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; +#if ENABLE_ALLOW_NEGATIVE_Z + double shift_z = m->get_instance_min_z(i.second); + // leave sinking instances as sinking + if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= 0.0 || shift_z > 0.0) { + Vec3d shift(0.0, 0.0, -shift_z); +#else Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); +#endif // ENABLE_ALLOW_NEGATIVE_Z m_selection.translate(i.first, i.second, shift); m->translate_instance(i.second, shift); +#if ENABLE_ALLOW_NEGATIVE_Z + } +#endif // ENABLE_ALLOW_NEGATIVE_Z } if (!done.empty()) - post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED)); m_dirty = true; } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 2acb8cb85..2da521d47 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -866,12 +866,10 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type if (!m_valid) return; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { GLVolume &volume = *(*m_volumes)[i]; if (is_single_full_instance()) { - if (transformation_type.relative()) - { + if (transformation_type.relative()) { Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation @@ -881,8 +879,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type volume.set_instance_scaling_factor(new_scale); } - else - { + else { if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { // Non-uniform scaling. Transform the scaling factors into the local coordinate system. // This is only possible, if the instance rotation is mulitples of ninety degrees. @@ -895,11 +892,9 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type } else if (is_single_volume() || is_single_modifier()) volume.set_volume_scaling_factor(scale); - else - { + else { Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); - if (m_mode == Instance) - { + if (m_mode == Instance) { Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); @@ -908,13 +903,11 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type volume.set_instance_scaling_factor(new_scale); } - else if (m_mode == Volume) - { + else if (m_mode == Volume) { Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (transformation_type.joint()) - { + if (transformation_type.joint()) { Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); volume.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); } @@ -930,34 +923,34 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH +#if !ENABLE_ALLOW_NEGATIVE_Z ensure_on_bed(); +#endif // !ENABLE_ALLOW_NEGATIVE_Z this->set_bounding_boxes_dirty(); } void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) { - if (is_empty() || (m_mode == Volume)) + if (is_empty() || m_mode == Volume) return; // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings Vec3d box_size = get_bounding_box().size() + 0.01 * Vec3d::Ones(); const ConfigOptionPoints* opt = dynamic_cast(config.option("bed_shape")); - if (opt != nullptr) - { + if (opt != nullptr) { BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); - BoundingBoxf3 print_volume(Vec3d(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Vec3d(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config.opt_float("max_print_height"))); + BoundingBoxf3 print_volume({ unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0 }, { unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config.opt_float("max_print_height") }); Vec3d print_volume_size = print_volume.size(); double sx = (box_size(0) != 0.0) ? print_volume_size(0) / box_size(0) : 0.0; double sy = (box_size(1) != 0.0) ? print_volume_size(1) / box_size(1) : 0.0; double sz = (box_size(2) != 0.0) ? print_volume_size(2) / box_size(2) : 0.0; - if ((sx != 0.0) && (sy != 0.0) && (sz != 0.0)) + if (sx != 0.0 && sy != 0.0 && sz != 0.0) { double s = std::min(sx, std::min(sy, sz)); - if (s != 1.0) - { - wxGetApp().plater()->take_snapshot(_(L("Scale To Fit"))); + if (s != 1.0) { + wxGetApp().plater()->take_snapshot(_L("Scale To Fit")); TransformationType type; type.set_world(); @@ -987,8 +980,7 @@ void Selection::mirror(Axis axis) bool single_full_instance = is_single_full_instance(); - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if (single_full_instance) (*m_volumes)[i]->set_instance_mirror(axis, -(*m_volumes)[i]->get_instance_mirror(axis)); else if (m_mode == Volume) @@ -1010,8 +1002,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) if (!m_valid) return; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { GLVolume* v = (*m_volumes)[i]; if (v->object_idx() == (int)object_idx) v->set_instance_offset(v->get_instance_offset() + displacement); @@ -1020,8 +1011,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) std::set done; // prevent processing volumes twice done.insert(m_list.begin(), m_list.end()); - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if (done.size() == m_volumes->size()) break; @@ -1030,8 +1020,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) continue; // Process unselected volumes of the object. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { if (done.size() == m_volumes->size()) break; @@ -1055,18 +1044,16 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co if (!m_valid) return; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == (int)object_idx) && (v->instance_idx() == (int)instance_idx)) + if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) v->set_instance_offset(v->get_instance_offset() + displacement); } std::set done; // prevent processing volumes twice done.insert(m_list.begin(), m_list.end()); - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if (done.size() == m_volumes->size()) break; @@ -1075,8 +1062,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co continue; // Process unselected volumes of the object. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { if (done.size() == m_volumes->size()) break; @@ -1084,7 +1070,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co continue; GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->instance_idx() != (int)instance_idx)) + if (v->object_idx() != object_idx || v->instance_idx() != (int)instance_idx) continue; v->set_instance_offset(v->get_instance_offset() + displacement); From a83cd647da7ca2c1d9ad4ce0f194b02ad67918e4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 28 Apr 2021 08:49:32 +0200 Subject: [PATCH 16/75] Small refactoring in GLCanvas3D::LayersEditing --- src/slic3r/GUI/GLCanvas3D.cpp | 34 ++++++------------------ src/slic3r/GUI/GLCanvas3D.hpp | 49 ++++++++++++++++------------------- 2 files changed, 31 insertions(+), 52 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f4a0d7412..fcd6da83f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -135,27 +135,9 @@ void Size::set_scale_factor(int scale_factor) m_scale_factor = scale_factor; } -GLCanvas3D::LayersEditing::LayersEditing() - : m_enabled(false) - , m_z_texture_id(0) - , m_model_object(nullptr) - , m_object_max_z(0.f) - , m_slicing_parameters(nullptr) - , m_layer_height_profile_modified(false) - , m_adaptive_quality(0.5f) - , state(Unknown) - , band_width(2.0f) - , strength(0.005f) - , last_object_id(-1) - , last_z(0.0f) - , last_action(LAYER_HEIGHT_EDIT_ACTION_INCREASE) -{ -} - GLCanvas3D::LayersEditing::~LayersEditing() { - if (m_z_texture_id != 0) - { + if (m_z_texture_id != 0) { glsafe(::glDeleteTextures(1, &m_z_texture_id)); m_z_texture_id = 0; } @@ -219,7 +201,7 @@ void GLCanvas3D::LayersEditing::set_enabled(bool enabled) m_enabled = is_allowed() && enabled; } -float GLCanvas3D::LayersEditing::s_overelay_window_width; +float GLCanvas3D::LayersEditing::s_overlay_window_width; void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const { @@ -303,7 +285,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const if (imgui.button(_L("Reset"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); - GLCanvas3D::LayersEditing::s_overelay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/; + GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/; imgui.end(); const Rect& bar_rect = get_bar_rect_viewport(canvas); @@ -320,7 +302,7 @@ float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) float t = rect.get_top(); float b = rect.get_bottom(); - return ((rect.get_left() <= x) && (x <= rect.get_right()) && (t <= y) && (y <= b)) ? + return (rect.get_left() <= x && x <= rect.get_right() && t <= y && y <= b) ? // Inside the bar. (b - y - 1.0f) / (b - t - 1.0f) : // Outside the bar. @@ -330,7 +312,7 @@ float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y) { const Rect& rect = get_bar_rect_screen(canvas); - return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom()); + return rect.get_left() <= x && x <= rect.get_right() && rect.get_top() <= y && y <= rect.get_bottom(); } Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) @@ -339,7 +321,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) float w = (float)cnv_size.get_width(); float h = (float)cnv_size.get_height(); - return Rect(w - thickness_bar_width(canvas), 0.0f, w, h); + return { w - thickness_bar_width(canvas), 0.0f, w, h }; } Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) @@ -348,7 +330,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) float half_w = 0.5f * (float)cnv_size.get_width(); float half_h = 0.5f * (float)cnv_size.get_height(); float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom); + return { (half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom }; } bool GLCanvas3D::LayersEditing::is_initialized() const @@ -565,7 +547,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) { if (last_object_id >= 0) { if (m_layer_height_profile_modified) { - wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Manual edit"))); + wxGetApp().plater()->take_snapshot(_L("Variable layer height - Manual edit")); const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); wxGetApp().obj_list()->update_info_items(last_object_id); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9e9a2501e..bff8d6e05 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -154,53 +154,50 @@ class GLCanvas3D static const float THICKNESS_BAR_WIDTH; private: - bool m_enabled; - unsigned int m_z_texture_id; + bool m_enabled{ false }; + unsigned int m_z_texture_id{ 0 }; // Not owned by LayersEditing. - const DynamicPrintConfig *m_config; + const DynamicPrintConfig *m_config{ nullptr }; // ModelObject for the currently selected object (Model::objects[last_object_id]). - const ModelObject *m_model_object; + const ModelObject *m_model_object{ nullptr }; // Maximum z of the currently selected object (Model::objects[last_object_id]). - float m_object_max_z; + float m_object_max_z{ 0.0f }; // Owned by LayersEditing. - SlicingParameters *m_slicing_parameters; + SlicingParameters *m_slicing_parameters{ nullptr }; std::vector m_layer_height_profile; - bool m_layer_height_profile_modified; + bool m_layer_height_profile_modified{ false }; - mutable float m_adaptive_quality; + mutable float m_adaptive_quality{ 0.5f }; mutable HeightProfileSmoothingParams m_smooth_params; - static float s_overelay_window_width; + static float s_overlay_window_width; - class LayersTexture + struct LayersTexture { - public: - LayersTexture() : width(0), height(0), levels(0), cells(0), valid(false) {} - // Texture data std::vector data; // Width of the texture, top level. - size_t width; + size_t width{ 0 }; // Height of the texture, top level. - size_t height; + size_t height{ 0 }; // For how many levels of detail is the data allocated? - size_t levels; + size_t levels{ 0 }; // Number of texture cells allocated for the height texture. - size_t cells; + size_t cells{ 0 }; // Does it need to be refreshed? - bool valid; + bool valid{ false }; }; LayersTexture m_layers_texture; public: - EState state; - float band_width; - float strength; - int last_object_id; - float last_z; - LayerHeightEditActionType last_action; + EState state{ Unknown }; + float band_width{ 2.0f }; + float strength{ 0.005f }; + int last_object_id{ -1 }; + float last_z{ 0.0f }; + LayerHeightEditActionType last_action{ LAYER_HEIGHT_EDIT_ACTION_INCREASE }; - LayersEditing(); + LayersEditing() = default; ~LayersEditing(); void init(); @@ -226,7 +223,7 @@ class GLCanvas3D static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y); static Rect get_bar_rect_screen(const GLCanvas3D& canvas); static Rect get_bar_rect_viewport(const GLCanvas3D& canvas); - static float get_overlay_window_width() { return LayersEditing::s_overelay_window_width; } + static float get_overlay_window_width() { return LayersEditing::s_overlay_window_width; } float object_max_z() const { return m_object_max_z; } From c58572deaa184fc9e4cdc1bcaec39457956cc8e6 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 28 Apr 2021 11:07:15 +0200 Subject: [PATCH 17/75] Tech ENABLE_ALLOW_NEGATIVE_Z->Fixed object popping up after editing layer range fields --- src/libslic3r/Model.cpp | 40 ++++++++++++++--------------- src/libslic3r/Model.hpp | 4 +++ src/slic3r/GUI/GUI_ObjectLayers.cpp | 26 ++++++++----------- src/slic3r/GUI/GUI_ObjectList.cpp | 27 +++++++++---------- src/slic3r/GUI/Plater.cpp | 7 +++-- src/slic3r/GUI/Selection.cpp | 23 ++++++----------- 6 files changed, 59 insertions(+), 68 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 208cf9b66..c82627b38 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -954,30 +954,39 @@ void ModelObject::center_around_origin(bool include_modifiers) { // calculate the displacements needed to // center this object around the origin - BoundingBoxf3 bb = include_modifiers ? full_raw_mesh_bounding_box() : raw_mesh_bounding_box(); + const BoundingBoxf3 bb = include_modifiers ? full_raw_mesh_bounding_box() : raw_mesh_bounding_box(); // Shift is the vector from the center of the bounding box to the origin - Vec3d shift = -bb.center(); + const Vec3d shift = -bb.center(); this->translate(shift); this->origin_translation += shift; } +#if ENABLE_ALLOW_NEGATIVE_Z +void ModelObject::ensure_on_bed(bool allow_negative_z) +{ + const double min_z = get_min_z(); + if (!allow_negative_z || min_z > 0.0) + translate_instances({ 0.0, 0.0, -min_z }); +} +#else void ModelObject::ensure_on_bed() { - translate_instances(Vec3d(0.0, 0.0, -get_min_z())); + translate_instances({ 0.0, 0.0, -get_min_z() }); } +#endif // ENABLE_ALLOW_NEGATIVE_Z void ModelObject::translate_instances(const Vec3d& vector) { - for (size_t i = 0; i < instances.size(); ++i) - { + for (size_t i = 0; i < instances.size(); ++i) { translate_instance(i, vector); } } void ModelObject::translate_instance(size_t instance_idx, const Vec3d& vector) { + assert(instance_idx < instances.size()); ModelInstance* i = instances[instance_idx]; i->set_offset(i->get_offset() + vector); invalidate_bounding_box(); @@ -985,8 +994,7 @@ void ModelObject::translate_instance(size_t instance_idx, const Vec3d& vector) void ModelObject::translate(double x, double y, double z) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->translate(x, y, z); } @@ -996,8 +1004,7 @@ void ModelObject::translate(double x, double y, double z) void ModelObject::scale(const Vec3d &versor) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->scale(versor); } this->invalidate_bounding_box(); @@ -1005,41 +1012,34 @@ void ModelObject::scale(const Vec3d &versor) void ModelObject::rotate(double angle, Axis axis) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->rotate(angle, axis); } - center_around_origin(); this->invalidate_bounding_box(); } void ModelObject::rotate(double angle, const Vec3d& axis) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->rotate(angle, axis); } - center_around_origin(); this->invalidate_bounding_box(); } void ModelObject::mirror(Axis axis) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->mirror(axis); } - this->invalidate_bounding_box(); } // This method could only be called before the meshes of this ModelVolumes are not shared! void ModelObject::scale_mesh_after_creation(const Vec3d &versor) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->scale_geometry_after_creation(versor); v->set_offset(versor.cwiseProduct(v->get_offset())); } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 868639ee8..3e2c9a3c5 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -308,7 +308,11 @@ public: void center_around_origin(bool include_modifiers = true); +#if ENABLE_ALLOW_NEGATIVE_Z + void ensure_on_bed(bool allow_negative_z = false); +#else void ensure_on_bed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z void translate_instances(const Vec3d& vector); void translate_instance(size_t instance_idx, const Vec3d& vector); void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); } diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 725a396c3..d079a6ebd 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -142,7 +142,7 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinus auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(editor); - auto temp = new wxStaticText(m_parent, wxID_ANY, _(L("mm"))); + auto temp = new wxStaticText(m_parent, wxID_ANY, _L("mm")); temp->SetBackgroundStyle(wxBG_STYLE_PAINT); temp->SetFont(wxGetApp().normal_font()); sizer->Add(temp, 0, wxLEFT, wxGetApp().em_unit()); @@ -154,15 +154,14 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinus void ObjectLayers::create_layers_list() { - for (const auto &layer : m_object->layer_config_ranges) - { + for (const auto &layer : m_object->layer_config_ranges) { const t_layer_height_range& range = layer.first; auto del_btn = new PlusMinusButton(m_parent, m_bmp_delete, range); - del_btn->SetToolTip(_(L("Remove layer range"))); + del_btn->SetToolTip(_L("Remove layer range")); auto add_btn = new PlusMinusButton(m_parent, m_bmp_add, range); wxString tooltip = wxGetApp().obj_list()->can_add_new_range_after_current(range); - add_btn->SetToolTip(tooltip.IsEmpty() ? _(L("Add layer range")) : tooltip); + add_btn->SetToolTip(tooltip.IsEmpty() ? _L("Add layer range") : tooltip); add_btn->Enable(tooltip.IsEmpty()); auto sizer = create_layer(range, del_btn, add_btn); @@ -242,11 +241,9 @@ void ObjectLayers::msw_rescale() // rescale edit-boxes const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount(); - for (int i = 0; i < cells_cnt; i++) - { + for (int i = 0; i < cells_cnt; ++i) { const wxSizerItem* item = m_grid_sizer->GetItem(i); - if (item->IsWindow()) - { + if (item->IsWindow()) { LayerRangeEditor* editor = dynamic_cast(item->GetWindow()); if (editor != nullptr) editor->msw_rescale(); @@ -283,8 +280,7 @@ void ObjectLayers::sys_color_changed() // rescale edit-boxes const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount(); - for (int i = 0; i < cells_cnt; i++) - { + for (int i = 0; i < cells_cnt; ++i) { const wxSizerItem* item = m_grid_sizer->GetItem(i); if (item->IsSizer()) {// case when we have editor with buttons const std::vector btns = {2, 3}; // del_btn, add_btn @@ -405,11 +401,9 @@ coordf_t LayerRangeEditor::get_value() str.Replace(",", ".", false); if (str == ".") layer_height = 0.0; - else - { - if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) - { - show_error(m_parent, _(L("Invalid numeric input."))); + else { + if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) { + show_error(m_parent, _L("Invalid numeric input.")); SetValue(double_to_string(layer_height)); } } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 15c4578d8..b5c2bb221 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2113,18 +2113,15 @@ void ObjectList::part_selection_changed() const auto item = GetSelection(); - if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) - { - og_name = _(L("Group manipulation")); + if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) { + og_name = _L("Group manipulation"); const Selection& selection = scene_selection(); // don't show manipulation panel for case of all Object's parts selection update_and_show_manipulations = !selection.is_single_full_instance(); } - else - { - if (item) - { + else { + if (item) { const ItemType type = m_objects_model->GetItemType(item); const wxDataViewItem parent = m_objects_model->GetParent(item); const ItemType parent_type = m_objects_model->GetItemType(parent); @@ -2132,7 +2129,7 @@ void ObjectList::part_selection_changed() if (parent == wxDataViewItem(nullptr) || type == itInfo) { - og_name = _(L("Object manipulation")); + og_name = _L("Object manipulation"); m_config = &(*m_objects)[obj_idx]->config; update_and_show_manipulations = true; @@ -2152,35 +2149,35 @@ void ObjectList::part_selection_changed() else { if (type & itSettings) { if (parent_type & itObject) { - og_name = _(L("Object Settings to modify")); + og_name = _L("Object Settings to modify"); m_config = &(*m_objects)[obj_idx]->config; } else if (parent_type & itVolume) { - og_name = _(L("Part Settings to modify")); + og_name = _L("Part Settings to modify"); volume_id = m_objects_model->GetVolumeIdByItem(parent); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; } else if (parent_type & itLayer) { - og_name = _(L("Layer range Settings to modify")); + og_name = _L("Layer range Settings to modify"); m_config = &get_item_config(parent); } update_and_show_settings = true; } else if (type & itVolume) { - og_name = _(L("Part manipulation")); + og_name = _L("Part manipulation"); volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; } else if (type & itInstance) { - og_name = _(L("Instance manipulation")); + og_name = _L("Instance manipulation"); update_and_show_manipulations = true; // fill m_config by object's values m_config = &(*m_objects)[obj_idx]->config; } else if (type & (itLayerRoot|itLayer)) { - og_name = type & itLayerRoot ? _(L("Height ranges")) : _(L("Settings for height range")); + og_name = type & itLayerRoot ? _L("Height ranges") : _L("Settings for height range"); update_and_show_layers = true; if (type & itLayer) @@ -2815,7 +2812,7 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay const int obj_idx = m_selected_object_id; if (obj_idx < 0) return false; - take_snapshot(_(L("Edit Height Range"))); + take_snapshot(_L("Edit Height Range")); const ItemType sel_type = m_objects_model->GetItemType(GetSelection()); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5db983f43..210753705 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5805,7 +5805,11 @@ void Plater::changed_object(int obj_idx) return; // recenter and re - align to Z = 0 auto model_object = p->model.objects[obj_idx]; +#if ENABLE_ALLOW_NEGATIVE_Z + model_object->ensure_on_bed(true); +#else model_object->ensure_on_bed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z if (this->p->printer_technology == ptSLA) { // Update the SLAPrint from the current Model, so that the reload_scene() // pulls the correct data, update the 3D scene. @@ -5823,8 +5827,7 @@ void Plater::changed_objects(const std::vector& object_idxs) if (object_idxs.empty()) return; - for (size_t obj_idx : object_idxs) - { + for (size_t obj_idx : object_idxs) { if (obj_idx < p->model.objects.size()) // recenter and re - align to Z = 0 p->model.objects[obj_idx]->ensure_on_bed(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 2da521d47..f52e2f0aa 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -672,24 +672,20 @@ void Selection::translate(const Vec3d& displacement, bool local) EMode translation_type = m_mode; - for (unsigned int i : m_list) - { - if ((m_mode == Volume) || (*m_volumes)[i]->is_wipe_tower) + for (unsigned int i : m_list) { + if (m_mode == Volume || (*m_volumes)[i]->is_wipe_tower) { if (local) (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); - else - { + else { Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); } } - else if (m_mode == Instance) - { + else if (m_mode == Instance) { if (is_from_fully_selected_instance(i)) (*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); - else - { + else { Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); translation_type = Volume; @@ -2153,10 +2149,8 @@ void Selection::ensure_on_bed() typedef std::map, double> InstancesToZMap; InstancesToZMap instances_min_z; - for (GLVolume* volume : *m_volumes) - { - if (!volume->is_wipe_tower && !volume->is_modifier) - { + for (GLVolume* volume : *m_volumes) { + if (!volume->is_wipe_tower && !volume->is_modifier) { double min_z = volume->transformed_convex_hull_bounding_box().min(2); std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); InstancesToZMap::iterator it = instances_min_z.find(instance); @@ -2167,8 +2161,7 @@ void Selection::ensure_on_bed() } } - for (GLVolume* volume : *m_volumes) - { + for (GLVolume* volume : *m_volumes) { std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); InstancesToZMap::iterator it = instances_min_z.find(instance); if (it != instances_min_z.end()) From a3f03ac188543ccedd61915a1b6d7441357fc4be Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 28 Apr 2021 16:02:51 +0200 Subject: [PATCH 18/75] Tech ENABLE_ALLOW_NEGATIVE_Z->Keep as sinking objects saved in project files --- src/slic3r/GUI/Plater.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 210753705..690ffdb16 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1555,7 +1555,11 @@ struct Plater::priv BoundingBox scaled_bed_shape_bb() const; std::vector load_files(const std::vector& input_files, bool load_model, bool load_config, bool used_inches = false); +#if ENABLE_ALLOW_NEGATIVE_Z + std::vector load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false); +#else std::vector load_model_objects(const ModelObjectPtrs &model_objects); +#endif // ENABLE_ALLOW_NEGATIVE_Z wxString get_export_file(GUI::FileType file_type); const Selection& get_selection() const; @@ -2303,11 +2307,19 @@ std::vector Plater::priv::load_files(const std::vector& input_ return obj_idxs; } +#if ENABLE_ALLOW_NEGATIVE_Z + for (ModelObject* model_object : model.objects) { + if (!type_3mf && !type_zip_amf) + model_object->center_around_origin(false); + model_object->ensure_on_bed(is_project_file); + } +#else for (ModelObject* model_object : model.objects) { if (!type_3mf && !type_zip_amf) model_object->center_around_origin(false); model_object->ensure_on_bed(); } +#endif // ENABLE_ALLOW_NEGATIVE_Z // check multi-part object adding for the SLA-printing if (printer_technology == ptSLA) { @@ -2321,7 +2333,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ } if (one_by_one) { +#if ENABLE_ALLOW_NEGATIVE_Z + auto loaded_idxs = load_model_objects(model.objects, is_project_file); +#else auto loaded_idxs = load_model_objects(model.objects); +#endif // ENABLE_ALLOW_NEGATIVE_Z obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); } else { // This must be an .stl or .obj file, which may contain a maximum of one volume. @@ -2373,7 +2389,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ // #define AUTOPLACEMENT_ON_LOAD +#if ENABLE_ALLOW_NEGATIVE_Z +std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z) +#else std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects) +#endif // ENABLE_ALLOW_NEGATIVE_Z { const BoundingBoxf bed_shape = bed_shape_bb(); const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast(), 1.0) - 2.0 * Vec3d::Ones(); @@ -2450,7 +2470,11 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode } #endif // ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG +#if ENABLE_ALLOW_NEGATIVE_Z + object->ensure_on_bed(allow_negative_z); +#else object->ensure_on_bed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z } #ifdef AUTOPLACEMENT_ON_LOAD From 628af89c4a8ba0c9bb85f143a6628d5506fe4d0a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 28 Apr 2021 16:04:24 +0200 Subject: [PATCH 19/75] Small refactoring into variable_layer_height.fs shader --- resources/shaders/variable_layer_height.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/shaders/variable_layer_height.fs b/resources/shaders/variable_layer_height.fs index f87e6627e..934eb7758 100644 --- a/resources/shaders/variable_layer_height.fs +++ b/resources/shaders/variable_layer_height.fs @@ -36,5 +36,5 @@ void main() texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod); // Mix the final color. - gl_FragColor = vec4(intensity.y, intensity.y, intensity.y, 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend); + gl_FragColor = vec4(vec3(intensity.y), 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend); } From 1c3090b11f21cde2cf6f5829ce902cdc928fe5ea Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 3 May 2021 14:28:55 +0200 Subject: [PATCH 20/75] Tech ENABLE_ALLOW_NEGATIVE_Z-> Layers height editing related fixes --- resources/shaders/variable_layer_height.fs | 9 +++--- src/libslic3r/PrintObject.cpp | 16 +++++++++++ src/slic3r/GUI/GLCanvas3D.cpp | 33 ++++++++++++++++------ src/slic3r/GUI/Plater.cpp | 16 ++++++----- src/slic3r/GUI/Selection.cpp | 14 ++++----- 5 files changed, 61 insertions(+), 27 deletions(-) diff --git a/resources/shaders/variable_layer_height.fs b/resources/shaders/variable_layer_height.fs index 934eb7758..693c1c6a0 100644 --- a/resources/shaders/variable_layer_height.fs +++ b/resources/shaders/variable_layer_height.fs @@ -24,7 +24,7 @@ void main() float z_texture_col = object_z_row - z_texture_row; float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25; // Calculate level of detail from the object Z coordinate. - // This makes the slowly sloping surfaces to be show with high detail (with stripes), + // This makes the slowly sloping surfaces to be shown with high detail (with stripes), // and the vertical surfaces to be shown with low detail (no stripes) float z_in_cells = object_z_row * 190.; // Gradient of Z projected on the screen. @@ -32,9 +32,10 @@ void main() float dy_vtc = dFdy(z_in_cells); float lod = clamp(0.5 * log2(max(dx_vtc * dx_vtc, dy_vtc * dy_vtc)), 0., 1.); // Sample the Z texture. Texture coordinates are normalized to <0, 1>. - vec4 color = mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.), - texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod); - + vec4 color = vec4(0.25, 0.25, 0.25, 1.0); + if (z_texture_row >= 0.0) + color = mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.), + texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod); // Mix the final color. gl_FragColor = vec4(vec3(intensity.y), 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend); } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index cbf3e71ab..ab39fbbde 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1639,9 +1639,15 @@ PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegion void PrintObject::update_slicing_parameters() { +#if ENABLE_ALLOW_NEGATIVE_Z + if (!m_slicing_params.valid) + m_slicing_params = SlicingParameters::create_from_config( + this->print()->config(), m_config, this->model_object()->bounding_box().max.z(), this->object_extruders()); +#else if (! m_slicing_params.valid) m_slicing_params = SlicingParameters::create_from_config( this->print()->config(), m_config, unscale(this->height()), this->object_extruders()); +#endif // ENABLE_ALLOW_NEGATIVE_Z } SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) @@ -1703,6 +1709,15 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c updated = true; } +#if ENABLE_ALLOW_NEGATIVE_Z + // Verify the layer_height_profile. + if (!layer_height_profile.empty() && + // Must not be of even length. + ((layer_height_profile.size() & 1) != 0 || + // Last entry must be at the top of the object. + std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_parameters.object_print_z_max) > 1e-3)) + layer_height_profile.clear(); +#else // Verify the layer_height_profile. if (! layer_height_profile.empty() && // Must not be of even length. @@ -1710,6 +1725,7 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c // Last entry must be at the top of the object. std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_parameters.object_print_z_height()) > 1e-3)) layer_height_profile.clear(); +#endif // ENABLE_ALLOW_NEGATIVE_Z if (layer_height_profile.empty()) { //layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index fcd6da83f..cc8658fc2 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -169,11 +169,18 @@ void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config) void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) { const ModelObject *model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr; +#if ENABLE_ALLOW_NEGATIVE_Z + // Maximum height of an object changes when the object gets rotated or scaled. + // Changing maximum height of an object will invalidate the layer heigth editing profile. + // m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently. + const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast(model_object_new->bounding_box().max.z()); +#else // Maximum height of an object changes when the object gets rotated or scaled. // Changing maximum height of an object will invalidate the layer heigth editing profile. // m_model_object->raw_bounding_box() is cached, therefore it is cheap even if this method is called frequently. float new_max_z = (model_object_new == nullptr) ? 0.f : model_object_new->raw_bounding_box().size().z(); - if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || +#endif // ENABLE_ALLOW_NEGATIVE_Z + if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) { m_layer_height_profile.clear(); m_layer_height_profile_modified = false; @@ -348,11 +355,12 @@ std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) con float h = 0.0f; for (size_t i = m_layer_height_profile.size() - 2; i >= 2; i -= 2) { - float zi = m_layer_height_profile[i]; - float zi_1 = m_layer_height_profile[i - 2]; + const float zi = static_cast(m_layer_height_profile[i]); + const float zi_1 = static_cast(m_layer_height_profile[i - 2]); if (zi_1 <= z && z <= zi) { float dz = zi - zi_1; - h = (dz != 0.0f) ? lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz) : m_layer_height_profile[i + 1]; + h = (dz != 0.0f) ? static_cast(lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz)) : + static_cast(m_layer_height_profile[i + 1]); break; } } @@ -381,10 +389,10 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); // Render the color bar - float l = bar_rect.get_left(); - float r = bar_rect.get_right(); - float t = bar_rect.get_top(); - float b = bar_rect.get_bottom(); + const float l = bar_rect.get_left(); + const float r = bar_rect.get_right(); + const float t = bar_rect.get_top(); + const float b = bar_rect.get_bottom(); ::glBegin(GL_QUADS); ::glNormal3f(0.0f, 0.0f, 1.0f); @@ -560,7 +568,7 @@ void GLCanvas3D::LayersEditing::update_slicing_parameters() { if (m_slicing_parameters == nullptr) { m_slicing_parameters = new SlicingParameters(); - *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); + *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); } } @@ -3575,6 +3583,13 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) #endif // ENABLE_ALLOW_NEGATIVE_Z } +#if ENABLE_ALLOW_NEGATIVE_Z + // if the selection is not valid to allow for layer editing after the move, we need to turn off the tool if it is running + // similar to void Plater::priv::selection_changed() + if (!wxGetApp().plater()->can_layers_editing() && is_layers_editing_enabled()) + post_event(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); +#endif // ENABLE_ALLOW_NEGATIVE_Z + if (object_moved) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED)); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 690ffdb16..05090399d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2630,11 +2630,8 @@ int Plater::priv::get_selected_volume_idx() const void Plater::priv::selection_changed() { // if the selection is not valid to allow for layer editing, we need to turn off the tool if it is running - bool enable_layer_editing = layers_height_allowed(); - if (!enable_layer_editing && view3D->is_layers_editing_enabled()) { - SimpleEvent evt(EVT_GLTOOLBAR_LAYERSEDITING); - on_action_layersediting(evt); - } + if (!layers_height_allowed() && view3D->is_layers_editing_enabled()) + on_action_layersediting(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); // forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears) view3D->render(); @@ -4024,7 +4021,7 @@ void Plater::priv::reset_gcode_toolpaths() bool Plater::priv::can_set_instance_to_object() const { const int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); + return 0 <= obj_idx && obj_idx < (int)model.objects.size() && model.objects[obj_idx]->instances.size() > 1; } bool Plater::priv::can_split(bool to_objects) const @@ -4038,7 +4035,12 @@ bool Plater::priv::layers_height_allowed() const return false; int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed(); +#if ENABLE_ALLOW_NEGATIVE_Z + return 0 <= obj_idx && obj_idx < (int)model.objects.size() && model.objects[obj_idx]->bounding_box().max.z() > 0.0 && + config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed(); +#else + return 0 <= obj_idx && obj_idx < (int)model.objects.size() && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z } bool Plater::priv::can_mirror() const diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index f52e2f0aa..3ab546abe 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -169,7 +169,7 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec if (!already_contained || needs_reset) { - wxGetApp().plater()->take_snapshot(_(L("Selection-Add"))); + wxGetApp().plater()->take_snapshot(_L("Selection-Add")); if (needs_reset) clear(); @@ -210,7 +210,7 @@ void Selection::remove(unsigned int volume_idx) if (!contains_volume(volume_idx)) return; - wxGetApp().plater()->take_snapshot(_(L("Selection-Remove"))); + wxGetApp().plater()->take_snapshot(_L("Selection-Remove")); GLVolume* volume = (*m_volumes)[volume_idx]; @@ -242,7 +242,7 @@ void Selection::add_object(unsigned int object_idx, bool as_single_selection) (as_single_selection && matches(volume_idxs))) return; - wxGetApp().plater()->take_snapshot(_(L("Selection-Add Object"))); + wxGetApp().plater()->take_snapshot(_L("Selection-Add Object")); // resets the current list if needed if (as_single_selection) @@ -261,7 +261,7 @@ void Selection::remove_object(unsigned int object_idx) if (!m_valid) return; - wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Object"))); + wxGetApp().plater()->take_snapshot(_L("Selection-Remove Object")); do_remove_object(object_idx); @@ -274,12 +274,12 @@ void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, if (!m_valid) return; - std::vector volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx); + const std::vector volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx); if ((!as_single_selection && contains_all_volumes(volume_idxs)) || (as_single_selection && matches(volume_idxs))) return; - wxGetApp().plater()->take_snapshot(_(L("Selection-Add Instance"))); + wxGetApp().plater()->take_snapshot(_L("Selection-Add Instance")); // resets the current list if needed if (as_single_selection) @@ -298,7 +298,7 @@ void Selection::remove_instance(unsigned int object_idx, unsigned int instance_i if (!m_valid) return; - wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Instance"))); + wxGetApp().plater()->take_snapshot(_L("Selection-Remove Instance")); do_remove_instance(object_idx, instance_idx); From 57199b41167f575894d99e733c4d286fcde7c727 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 3 May 2021 15:47:16 +0200 Subject: [PATCH 21/75] Tech ENABLE_ALLOW_NEGATIVE_Z-> Fixed build on non-Windows OS --- src/slic3r/GUI/Plater.cpp | 87 ++++++++++++++------------------------- 1 file changed, 32 insertions(+), 55 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 05090399d..d78b36924 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2630,8 +2630,10 @@ int Plater::priv::get_selected_volume_idx() const void Plater::priv::selection_changed() { // if the selection is not valid to allow for layer editing, we need to turn off the tool if it is running - if (!layers_height_allowed() && view3D->is_layers_editing_enabled()) - on_action_layersediting(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); + if (!layers_height_allowed() && view3D->is_layers_editing_enabled()) { + SimpleEvent evt(EVT_GLTOOLBAR_LAYERSEDITING); + on_action_layersediting(evt); + } // forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears) view3D->render(); @@ -3069,21 +3071,19 @@ void Plater::priv::reload_from_disk() int volume_idx; // operators needed by std::algorithms - bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); } - bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); } + bool operator < (const SelectedVolume& other) const { return object_idx < other.object_idx || (object_idx == other.object_idx && volume_idx < other.volume_idx); } + bool operator == (const SelectedVolume& other) const { return object_idx == other.object_idx && volume_idx == other.volume_idx; } }; std::vector selected_volumes; // collects selected ModelVolumes const std::set& selected_volumes_idxs = selection.get_volume_idxs(); - for (unsigned int idx : selected_volumes_idxs) - { + for (unsigned int idx : selected_volumes_idxs) { const GLVolume* v = selection.get_volume(idx); int v_idx = v->volume_idx(); - if (v_idx >= 0) - { + if (v_idx >= 0) { int o_idx = v->object_idx(); - if ((0 <= o_idx) && (o_idx < (int)model.objects.size())) + if (0 <= o_idx && o_idx < (int)model.objects.size()) selected_volumes.push_back({ o_idx, v_idx }); } } @@ -3093,13 +3093,11 @@ void Plater::priv::reload_from_disk() // collects paths of files to load std::vector input_paths; std::vector missing_input_paths; - for (const SelectedVolume& v : selected_volumes) - { + for (const SelectedVolume& v : selected_volumes) { const ModelObject* object = model.objects[v.object_idx]; const ModelVolume* volume = object->volumes[v.volume_idx]; - if (!volume->source.input_file.empty()) - { + if (!volume->source.input_file.empty()) { if (fs::exists(volume->source.input_file)) input_paths.push_back(volume->source.input_file); else @@ -3112,8 +3110,7 @@ void Plater::priv::reload_from_disk() std::sort(missing_input_paths.begin(), missing_input_paths.end()); missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end()); - while (!missing_input_paths.empty()) - { + while (!missing_input_paths.empty()) { // ask user to select the missing file fs::path search = missing_input_paths.back(); wxString title = _L("Please select the file to reload"); @@ -3127,21 +3124,18 @@ void Plater::priv::reload_from_disk() std::string sel_filename_path = dialog.GetPath().ToUTF8().data(); std::string sel_filename = fs::path(sel_filename_path).filename().string(); - if (boost::algorithm::iequals(search.filename().string(), sel_filename)) - { + if (boost::algorithm::iequals(search.filename().string(), sel_filename)) { input_paths.push_back(sel_filename_path); missing_input_paths.pop_back(); fs::path sel_path = fs::path(sel_filename_path).remove_filename().string(); std::vector::iterator it = missing_input_paths.begin(); - while (it != missing_input_paths.end()) - { + while (it != missing_input_paths.end()) { // try to use the path of the selected file with all remaining missing files fs::path repathed_filename = sel_path; repathed_filename /= it->filename(); - if (fs::exists(repathed_filename)) - { + if (fs::exists(repathed_filename)) { input_paths.push_back(repathed_filename.string()); it = missing_input_paths.erase(it); } @@ -3149,8 +3143,7 @@ void Plater::priv::reload_from_disk() ++it; } } - else - { + else { wxString message = _L("It is not allowed to change the file to reload") + " (" + from_u8(search.filename().string()) + ").\n" + _L("Do you want to retry") + " ?"; wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() != wxID_YES) @@ -3164,8 +3157,7 @@ void Plater::priv::reload_from_disk() std::vector fail_list; // load one file at a time - for (size_t i = 0; i < input_paths.size(); ++i) - { + for (size_t i = 0; i < input_paths.size(); ++i) { const auto& path = input_paths[i].string(); wxBusyCursor wait; @@ -3175,8 +3167,7 @@ void Plater::priv::reload_from_disk() try { new_model = Model::read_from_file(path, nullptr, true, false); - for (ModelObject* model_object : new_model.objects) - { + for (ModelObject* model_object : new_model.objects) { model_object->center_around_origin(); model_object->ensure_on_bed(); } @@ -3188,34 +3179,27 @@ void Plater::priv::reload_from_disk() } // update the selected volumes whose source is the current file - for (const SelectedVolume& sel_v : selected_volumes) - { + for (const SelectedVolume& sel_v : selected_volumes) { ModelObject* old_model_object = model.objects[sel_v.object_idx]; ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx]; bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string()); bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string()); - if (has_source || has_name) - { + if (has_source || has_name) { int new_volume_idx = -1; int new_object_idx = -1; - if (has_source) - { + if (has_source) { // take idxs from source new_volume_idx = old_volume->source.volume_idx; new_object_idx = old_volume->source.object_idx; } - else - { + else { // take idxs from the 1st matching volume - for (size_t o = 0; o < new_model.objects.size(); ++o) - { + for (size_t o = 0; o < new_model.objects.size(); ++o) { ModelObject* obj = new_model.objects[o]; bool found = false; - for (size_t v = 0; v < obj->volumes.size(); ++v) - { - if (obj->volumes[v]->name == old_volume->name) - { + for (size_t v = 0; v < obj->volumes.size(); ++v) { + if (obj->volumes[v]->name == old_volume->name) { new_volume_idx = (int)v; new_object_idx = (int)o; found = true; @@ -3227,19 +3211,16 @@ void Plater::priv::reload_from_disk() } } - if ((new_object_idx < 0) && ((int)new_model.objects.size() <= new_object_idx)) - { + if (new_object_idx < 0 && (int)new_model.objects.size() <= new_object_idx) { fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); continue; } ModelObject* new_model_object = new_model.objects[new_object_idx]; - if ((new_volume_idx < 0) && ((int)new_model.objects.size() <= new_volume_idx)) - { + if (new_volume_idx < 0 && (int)new_model.objects.size() <= new_volume_idx) { fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); continue; } - if (new_volume_idx < (int)new_model_object->volumes.size()) - { + if (new_volume_idx < (int)new_model_object->volumes.size()) { old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]); ModelVolume* new_volume = old_model_object->volumes.back(); new_volume->set_new_unique_id(); @@ -3264,11 +3245,9 @@ void Plater::priv::reload_from_disk() } } - if (!fail_list.empty()) - { + if (!fail_list.empty()) { wxString message = _L("Unable to reload:") + "\n"; - for (const wxString& s : fail_list) - { + for (const wxString& s : fail_list) { message += s + "\n"; } wxMessageDialog dlg(q, message, _L("Error during reload"), wxOK | wxOK_DEFAULT | wxICON_WARNING); @@ -3279,8 +3258,7 @@ void Plater::priv::reload_from_disk() update(); // new GLVolumes have been created at this point, so update their printable state - for (size_t i = 0; i < model.objects.size(); ++i) - { + for (size_t i = 0; i < model.objects.size(); ++i) { view3D->get_canvas3d()->update_instance_printable_state_for_object(i); } } @@ -3300,8 +3278,7 @@ void Plater::priv::reload_all_from_disk() reload_from_disk(); // restore previous selection selection.clear(); - for (unsigned int idx : curr_idxs) - { + for (unsigned int idx : curr_idxs) { selection.add(idx, false); } } From 164af0255af7aee1c3c6495b7f0c2d2dde9c18e7 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 3 May 2021 16:02:06 +0200 Subject: [PATCH 22/75] Tech ENABLE_ALLOW_NEGATIVE_Z -> Keep sinking objects as sinking after reload from disk --- src/slic3r/GUI/Plater.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d78b36924..cce2dd319 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3183,6 +3183,10 @@ void Plater::priv::reload_from_disk() ModelObject* old_model_object = model.objects[sel_v.object_idx]; ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx]; +#if ENABLE_ALLOW_NEGATIVE_Z + bool sinking = old_model_object->bounding_box().min.z() < 0.0; +#endif // ENABLE_ALLOW_NEGATIVE_Z + bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string()); bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string()); if (has_source || has_name) { @@ -3237,7 +3241,10 @@ void Plater::priv::reload_from_disk() new_volume->seam_facets.assign(old_volume->seam_facets); std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); - old_model_object->ensure_on_bed(); +#if ENABLE_ALLOW_NEGATIVE_Z + if (!sinking) +#endif // ENABLE_ALLOW_NEGATIVE_Z + old_model_object->ensure_on_bed(); sla::reproject_points_and_holes(old_model_object); } From 67572fad3f957bd5e40f3575e5e7f8fa3aebc66a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 4 May 2021 14:48:30 +0200 Subject: [PATCH 23/75] Tech ENABLE_ALLOW_NEGATIVE_Z-> Keep sinking objects and instances as sinking after copy/paste or add instance commands --- src/slic3r/GUI/Plater.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cce2dd319..32ecef438 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5838,9 +5838,17 @@ void Plater::changed_objects(const std::vector& object_idxs) return; for (size_t obj_idx : object_idxs) { +#if ENABLE_ALLOW_NEGATIVE_Z + if (obj_idx < p->model.objects.size()) { + if (p->model.objects[obj_idx]->bounding_box().min.z() >= 0.0) + // re - align to Z = 0 + p->model.objects[obj_idx]->ensure_on_bed(); + } +#else if (obj_idx < p->model.objects.size()) // recenter and re - align to Z = 0 p->model.objects[obj_idx]->ensure_on_bed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z } if (this->p->printer_technology == ptSLA) { // Update the SLAPrint from the current Model, so that the reload_scene() From 3f6123e653fea186523c6e895bc77f47dca66efa Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 5 May 2021 09:15:33 +0200 Subject: [PATCH 24/75] Tech ENABLE_ALLOW_NEGATIVE_Z-> Added button in object manipulator to drop to bed a sinking object --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 37 ++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index cba7cef36..095a926ad 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -26,21 +26,26 @@ const double ObjectManipulation::mm_to_in = 0.0393700787; // Helper function to be used by drop to bed button. Returns lowest point of this // volume in world coordinate system. -static double get_volume_min_z(const GLVolume* volume) +static double get_volume_min_z(const GLVolume& volume) { - const Transform3f& world_matrix = volume->world_matrix().cast(); +#if ENABLE_ALLOW_NEGATIVE_Z + return volume.transformed_convex_hull_bounding_box().min.z(); +#else + const Transform3f& world_matrix = volume.world_matrix().cast(); // need to get the ModelVolume pointer - const ModelObject* mo = wxGetApp().model().objects[volume->composite_id.object_id]; - const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id]; + const ModelObject* mo = wxGetApp().model().objects[volume.composite_id.object_id]; + const ModelVolume* mv = mo->volumes[volume.composite_id.volume_id]; const TriangleMesh& hull = mv->get_convex_hull(); float min_z = std::numeric_limits::max(); for (const stl_facet& facet : hull.stl.facet_start) { - for (int i = 0; i < 3; ++ i) + for (int i = 0; i < 3; ++i) min_z = std::min(min_z, Vec3f::UnitZ().dot(world_matrix * facet.vertex[i])); } + return min_z; +#endif // ENABLE_ALLOW_NEGATIVE_Z } @@ -341,13 +346,27 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); - Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); + const Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(*volume)); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); change_position_value(1, diff.y()); change_position_value(2, diff.z()); } +#if ENABLE_ALLOW_NEGATIVE_Z + else if (selection.is_single_full_instance()) { + const ModelObjectPtrs& objects = wxGetApp().model().objects; + const int idx = selection.get_object_idx(); + if (0 <= idx && idx < static_cast(objects.size())) { + const ModelObject* mo = wxGetApp().model().objects[idx]; + const double min_z = mo->bounding_box().min.z(); + if (std::abs(min_z) > EPSILON) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(2, m_cache.position.z() - min_z); + } + } + } +#endif // ENABLE_ALLOW_NEGATIVE_Z }); editors_grid_sizer->Add(m_drop_to_bed_button); @@ -671,11 +690,15 @@ void ObjectManipulation::update_reset_buttons_visibility() if (selection.is_single_full_instance()) { rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); +#if ENABLE_ALLOW_NEGATIVE_Z + min_z = wxGetApp().model().objects[volume->composite_id.object_id]->bounding_box().min.z(); +#endif // ENABLE_ALLOW_NEGATIVE_Z + } else { rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); - min_z = get_volume_min_z(volume); + min_z = get_volume_min_z(*volume); } show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); From bb18edde0ab384276d1ee94219cd76b6d8f4310c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 5 May 2021 11:53:24 +0200 Subject: [PATCH 25/75] Tech ENABLE_ALLOW_NEGATIVE_Z-> Synchronize sinking instances --- src/slic3r/GUI/Selection.cpp | 178 +++++++++++++++-------------------- src/slic3r/GUI/Selection.hpp | 2 +- 2 files changed, 79 insertions(+), 101 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 3ab546abe..8d7878650 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -58,13 +58,11 @@ bool Selection::Clipboard::is_sla_compliant() const if (m_mode == Selection::Volume) return false; - for (const ModelObject* o : m_model->objects) - { + for (const ModelObject* o : m_model->objects) { if (o->is_multiparts()) return false; - for (const ModelVolume* v : o->volumes) - { + for (const ModelVolume* v : o->volumes) { if (v->is_modifier()) return false; } @@ -78,7 +76,8 @@ Selection::Clipboard::Clipboard() m_model.reset(new Model); } -void Selection::Clipboard::reset() { +void Selection::Clipboard::reset() +{ m_model->clear_objects(); } @@ -149,7 +148,7 @@ void Selection::set_model(Model* model) void Selection::add(unsigned int volume_idx, bool as_single_selection, bool check_for_already_contained) { - if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) + if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) return; const GLVolume* volume = (*m_volumes)[volume_idx]; @@ -167,8 +166,7 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier; needs_reset |= is_any_modifier() && !volume->is_modifier; - if (!already_contained || needs_reset) - { + if (!already_contained || needs_reset) { wxGetApp().plater()->take_snapshot(_L("Selection-Add")); if (needs_reset) @@ -185,7 +183,7 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec { case Volume: { - if ((volume->volume_idx() >= 0) && (is_empty() || (volume->instance_idx() == get_instance_idx()))) + if (volume->volume_idx() >= 0 && (is_empty() || volume->instance_idx() == get_instance_idx())) do_add_volume(volume_idx); break; @@ -204,7 +202,7 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec void Selection::remove(unsigned int volume_idx) { - if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) + if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) return; if (!contains_volume(volume_idx)) @@ -333,10 +331,9 @@ void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) if (!m_valid) return; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == (int)object_idx) && (v->volume_idx() == (int)volume_idx)) + if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) do_remove_volume(i); } @@ -358,8 +355,7 @@ void Selection::add_volumes(EMode mode, const std::vector& volume_ clear(); m_mode = mode; - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (i < (unsigned int)m_volumes->size()) do_add_volume(i); } @@ -374,8 +370,7 @@ void Selection::remove_volumes(EMode mode, const std::vector& volu return; m_mode = mode; - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (i < (unsigned int)m_volumes->size()) do_remove_volume(i); } @@ -390,8 +385,7 @@ void Selection::add_all() return; unsigned int count = 0; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { if (!(*m_volumes)[i]->is_wipe_tower) ++count; } @@ -404,8 +398,7 @@ void Selection::add_all() m_mode = Instance; clear(); - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { if (!(*m_volumes)[i]->is_wipe_tower) do_add_volume(i); } @@ -455,8 +448,7 @@ void Selection::clear() if (m_list.empty()) return; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { (*m_volumes)[i]->selected = false; } @@ -522,16 +514,15 @@ bool Selection::is_single_full_instance() const return false; int object_idx = m_valid ? get_object_idx() : -1; - if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) return false; int instance_idx = (*m_volumes)[*m_list.begin()]->instance_idx(); std::set volumes_idxs; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { const GLVolume* v = (*m_volumes)[i]; - if ((object_idx != v->object_idx()) || (instance_idx != v->instance_idx())) + if (object_idx != v->object_idx() || instance_idx != v->instance_idx()) return false; int volume_idx = v->volume_idx(); @@ -544,8 +535,8 @@ bool Selection::is_single_full_instance() const bool Selection::is_from_single_object() const { - int idx = get_object_idx(); - return (0 <= idx) && (idx < 1000); + const int idx = get_object_idx(); + return 0 <= idx && idx < 1000; } bool Selection::is_sla_compliant() const @@ -553,8 +544,7 @@ bool Selection::is_sla_compliant() const if (m_mode == Volume) return false; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if ((*m_volumes)[i]->is_modifier) return false; } @@ -564,8 +554,7 @@ bool Selection::is_sla_compliant() const bool Selection::contains_all_volumes(const std::vector& volume_idxs) const { - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (m_list.find(i) == m_list.end()) return false; } @@ -575,8 +564,7 @@ bool Selection::contains_all_volumes(const std::vector& volume_idx bool Selection::contains_any_volume(const std::vector& volume_idxs) const { - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (m_list.find(i) != m_list.end()) return true; } @@ -588,8 +576,7 @@ bool Selection::matches(const std::vector& volume_idxs) const { unsigned int count = 0; - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (m_list.find(i) != m_list.end()) ++count; else @@ -614,8 +601,7 @@ int Selection::get_object_idx() const int Selection::get_instance_idx() const { - if (m_cache.content.size() == 1) - { + if (m_cache.content.size() == 1) { const InstanceIdxsList& idxs = m_cache.content.begin()->second; if (idxs.size() == 1) return *idxs.begin(); @@ -673,12 +659,11 @@ void Selection::translate(const Vec3d& displacement, bool local) EMode translation_type = m_mode; for (unsigned int i : m_list) { - if (m_mode == Volume || (*m_volumes)[i]->is_wipe_tower) - { + if (m_mode == Volume || (*m_volumes)[i]->is_wipe_tower) { if (local) (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); else { - Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); } } @@ -686,7 +671,7 @@ void Selection::translate(const Vec3d& displacement, bool local) if (is_from_fully_selected_instance(i)) (*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); else { - Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); translation_type = Volume; } @@ -714,18 +699,14 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ if (!is_wipe_tower()) { int rot_axis_max = 0; - if (rotation.isApprox(Vec3d::Zero())) - { - for (unsigned int i : m_list) - { + if (rotation.isApprox(Vec3d::Zero())) { + for (unsigned int i : m_list) { GLVolume &volume = *(*m_volumes)[i]; - if (m_mode == Instance) - { + if (m_mode == Instance) { volume.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); volume.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); } - else if (m_mode == Volume) - { + else if (m_mode == Volume) { volume.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); volume.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); } @@ -742,14 +723,14 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. std::vector object_instance_first(m_model->objects.size(), -1); auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { - int first_volume_idx = object_instance_first[volume.object_idx()]; + const int first_volume_idx = object_instance_first[volume.object_idx()]; if (rot_axis_max != 2 && first_volume_idx != -1) { // Generic rotation, but no rotation around the Z axis. // Always do a local rotation (do not consider the selection to be a rigid body). assert(is_approx(rotation.z(), 0.0)); const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; const Vec3d &rotation = first_volume.get_instance_rotation(); - double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); } else { @@ -759,7 +740,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); if (rot_axis_max == 2 && transformation_type.joint()) { // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. - double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); } volume.set_instance_rotation(new_rotation); @@ -767,19 +748,16 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } }; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { GLVolume &volume = *(*m_volumes)[i]; if (is_single_full_instance()) rotate_instance(volume, i); - else if (is_single_volume() || is_single_modifier()) - { + else if (is_single_volume() || is_single_modifier()) { if (transformation_type.independent()) volume.set_volume_rotation(volume.get_volume_rotation() + rotation); - else - { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + else { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); volume.set_volume_rotation(new_rotation); } } @@ -787,15 +765,13 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ { if (m_mode == Instance) rotate_instance(volume, i); - else if (m_mode == Volume) - { + else if (m_mode == Volume) { // extracts rotations from the composed transformation Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - if (transformation_type.joint()) - { - Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; - Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); + if (transformation_type.joint()) { + const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; + const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); volume.set_volume_offset(local_pivot + offset); } volume.set_volume_rotation(new_rotation); @@ -816,8 +792,8 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ // make sure the wipe tower rotates around its center, not origin // we can assume that only Z rotation changes - Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); - Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0, 0, 1)) * center_local; + const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); + const Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0.0, 0.0, 1.0)) * center_local; volume.set_volume_rotation(rotation); volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); } @@ -835,8 +811,7 @@ void Selection::flattening_rotate(const Vec3d& normal) if (!m_valid) return; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { // Normal transformed from the object coordinate space to the world coordinate space. const auto &voldata = m_cache.volumes_data[i]; Vec3d tnormal = (Geometry::assemble_transform( @@ -1781,18 +1756,16 @@ void Selection::render_synchronized_volumes() const float color[3] = { 1.0f, 1.0f, 0.0f }; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { const GLVolume* volume = (*m_volumes)[i]; int object_idx = volume->object_idx(); int volume_idx = volume->volume_idx(); - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { if (i == j) continue; const GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx)) + if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) continue; render_bounding_box(v->transformed_convex_hull_bounding_box(), color); @@ -1986,7 +1959,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); ::glBegin(GL_QUADS); - if ((camera_on_top && (type == 1)) || (!camera_on_top && (type == 2))) + if ((camera_on_top && type == 1) || (!camera_on_top && type == 2)) ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); else ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); @@ -1997,7 +1970,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co glsafe(::glEnd()); ::glBegin(GL_QUADS); - if ((camera_on_top && (type == 2)) || (!camera_on_top && (type == 1))) + if ((camera_on_top && type == 2) || (!camera_on_top && type == 1)) ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); else ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); @@ -2014,9 +1987,9 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co #ifndef NDEBUG static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) { - Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); - Vec3d axis = angle_axis.axis(); - double angle = angle_axis.angle(); + const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + const Vec3d axis = angle_axis.axis(); + const double angle = angle_axis.angle(); if (std::abs(angle) < 1e-8) return true; assert(std::abs(axis.x()) < 1e-8); @@ -2053,24 +2026,22 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ std::set done; // prevent processing volumes twice done.insert(m_list.begin(), m_list.end()); - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if (done.size() == m_volumes->size()) break; const GLVolume* volume = (*m_volumes)[i]; - int object_idx = volume->object_idx(); + const int object_idx = volume->object_idx(); if (object_idx >= 1000) continue; - int instance_idx = volume->instance_idx(); + const int instance_idx = volume->instance_idx(); const Vec3d& rotation = volume->get_instance_rotation(); const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); const Vec3d& mirror = volume->get_instance_mirror(); // Process unselected instances. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { if (done.size() == m_volumes->size()) break; @@ -2078,24 +2049,33 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ continue; GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->instance_idx() == instance_idx)) + if (v->object_idx() != object_idx || v->instance_idx() == instance_idx) continue; assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); switch (sync_rotation_type) { - case SYNC_ROTATION_NONE: + case SYNC_ROTATION_NONE: { +#if ENABLE_ALLOW_NEGATIVE_Z + // z only rotation -> synch instance z + // The X,Y rotations should be synchronized from start to end of the rotation. + assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); + v->set_instance_offset(Z, volume->get_instance_offset().z()); + break; +#else // z only rotation -> keep instance z // The X,Y rotations should be synchronized from start to end of the rotation. assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); break; +#endif // ENABLE_ALLOW_NEGATIVE_Z + } case SYNC_ROTATION_FULL: // rotation comes from place on face -> force given z - v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2))); + v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() }); break; case SYNC_ROTATION_GENERAL: // generic rotation -> update instance z with the delta of the rotation. - double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); - v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); + v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); break; } @@ -2113,27 +2093,25 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ void Selection::synchronize_unselected_volumes() { - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { const GLVolume* volume = (*m_volumes)[i]; - int object_idx = volume->object_idx(); + const int object_idx = volume->object_idx(); if (object_idx >= 1000) continue; - int volume_idx = volume->volume_idx(); + const int volume_idx = volume->volume_idx(); const Vec3d& offset = volume->get_volume_offset(); const Vec3d& rotation = volume->get_volume_rotation(); const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); const Vec3d& mirror = volume->get_volume_mirror(); // Process unselected volumes. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { if (j == i) continue; GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx)) + if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) continue; v->set_volume_offset(offset); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 8bb418baa..c28a6e867 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -129,7 +129,7 @@ private: TransformCache m_instance; public: - VolumeCache() {} + VolumeCache() = default; VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform); const Vec3d& get_volume_position() const { return m_volume.position; } From a91306032ca5d3a6e04d11d18bb24f5b2b39d21d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 5 May 2021 13:17:20 +0200 Subject: [PATCH 26/75] Project dirty state manager -> Fixed crash when loading/saving a 3mf file --- src/libslic3r/Technologies.hpp | 2 +- src/slic3r/GUI/ProjectDirtyStateManager.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index efb7ab486..1d4c63fb6 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -67,7 +67,7 @@ // Enable project dirty state manager #define ENABLE_PROJECT_DIRTY_STATE (1 && ENABLE_2_4_0_ALPHA0) // Enable project dirty state manager debug window -#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW (0 && ENABLE_PROJECT_DIRTY_STATE) +#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW (1 && ENABLE_PROJECT_DIRTY_STATE) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 95cfa042e..30d41f808 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -210,8 +210,7 @@ void ProjectDirtyStateManager::reset_after_save() if (&main_stack == &active_stack) { const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main); - assert(saveable_snapshot != nullptr); - m_last_save.main = saveable_snapshot->timestamp; + m_last_save.main = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0; } else { const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); From ff632a9ff242db41bfebf34aff1f53039bf888fc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 6 May 2021 14:04:07 +0200 Subject: [PATCH 27/75] Tech ENABLE_ALLOW_NEGATIVE_Z-> Disable sinking objects for SLA printer --- src/libslic3r/Technologies.hpp | 1 + src/slic3r/GUI/3DScene.cpp | 7 +++++++ src/slic3r/GUI/GLCanvas3D.cpp | 4 ++++ src/slic3r/GUI/Plater.cpp | 4 ++++ src/slic3r/GUI/Selection.cpp | 17 ++++++++++++----- 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 369996fba..316cff195 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -61,6 +61,7 @@ #define ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG (1 && ENABLE_2_4_0_ALPHA0) // Enable to push object instances under the bed #define ENABLE_ALLOW_NEGATIVE_Z (1 && ENABLE_2_4_0_ALPHA0) +#define DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA (1 && ENABLE_ALLOW_NEGATIVE_Z) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index c64c36ea8..00bad52ff 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -23,6 +23,9 @@ #include "libslic3r/Format/STL.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/AppConfig.hpp" +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA +#include "libslic3r/PresetBundle.hpp" +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA #include #include @@ -521,7 +524,11 @@ bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int( #if ENABLE_ALLOW_NEGATIVE_Z bool GLVolume::is_sinking() const { +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + if (is_modifier || GUI::wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) +#else if (is_modifier) +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA return false; const BoundingBoxf3& box = transformed_convex_hull_bounding_box(); return box.min(2) < -EPSILON && box.max(2) >= -EPSILON; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index cc8658fc2..c8e402a5d 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3571,7 +3571,11 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) ModelObject* m = m_model->objects[i.first]; #if ENABLE_ALLOW_NEGATIVE_Z double shift_z = m->get_instance_min_z(i.second); +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + if (m_process->current_printer_technology() == ptSLA || shift_z > 0.0) { +#else if (shift_z > 0.0) { +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA Vec3d shift(0.0, 0.0, -shift_z); #else Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 32ecef438..ab1d657e4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5816,7 +5816,11 @@ void Plater::changed_object(int obj_idx) // recenter and re - align to Z = 0 auto model_object = p->model.objects[obj_idx]; #if ENABLE_ALLOW_NEGATIVE_Z +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + model_object->ensure_on_bed(this->p->printer_technology != ptSLA); +#else model_object->ensure_on_bed(true); +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA #else model_object->ensure_on_bed(); #endif // ENABLE_ALLOW_NEGATIVE_Z diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 8d7878650..13512d534 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -12,6 +12,9 @@ #include "Plater.hpp" #include "libslic3r/Model.hpp" +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA +#include "libslic3r/PresetBundle.hpp" +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA #include @@ -893,10 +896,11 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type else if (m_mode == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH - -#if !ENABLE_ALLOW_NEGATIVE_Z - ensure_on_bed(); -#endif // !ENABLE_ALLOW_NEGATIVE_Z + +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) + ensure_on_bed(); +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA this->set_bounding_boxes_dirty(); } @@ -2059,7 +2063,10 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ // z only rotation -> synch instance z // The X,Y rotations should be synchronized from start to end of the rotation. assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); - v->set_instance_offset(Z, volume->get_instance_offset().z()); +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + v->set_instance_offset(Z, volume->get_instance_offset().z()); break; #else // z only rotation -> keep instance z From 96447de1d4391a89049394b0cfd8fa56d9d0faa7 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 7 May 2021 12:17:17 +0200 Subject: [PATCH 28/75] ConfigWizard:: Use wxTextCtrl instead of wxDoubleSpinCtrl for nozzle and filament diameters --- src/slic3r/GUI/ConfigWizard.cpp | 49 +++++++++++++++++++------ src/slic3r/GUI/ConfigWizard_private.hpp | 4 +- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index f4497b997..52df39fd5 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -33,6 +33,7 @@ #include "GUI_App.hpp" #include "GUI_Utils.hpp" #include "GUI_ObjectManipulation.hpp" +#include "Field.hpp" #include "DesktopIntegrationDialog.hpp" #include "slic3r/Config/Snapshot.hpp" #include "slic3r/Utils/PresetUpdater.hpp" @@ -1383,20 +1384,39 @@ void PageBedShape::apply_custom_config(DynamicPrintConfig &config) config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); } +static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) +{ + e.Skip(); + wxString str = ctrl->GetValue(); + // Replace the first occurence of comma in decimal number. + bool was_replace = str.Replace(",", ".", false) > 0; + double val = 0.0; + if (!str.ToCDouble(&val)) { + if (val == 0.0) + val = def_value; + ctrl->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + ctrl->SetFocus(); + } + else if (was_replace) + ctrl->SetValue(double_to_string(val)); +} + PageDiameters::PageDiameters(ConfigWizard *parent) : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1) - , spin_nozzle(new wxSpinCtrlDouble(this, wxID_ANY)) - , spin_filam(new wxSpinCtrlDouble(this, wxID_ANY)) + , diam_nozzle(new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord))) + , diam_filam (new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord))) { - spin_nozzle->SetDigits(2); - spin_nozzle->SetIncrement(0.1); auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value(); - spin_nozzle->SetValue(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); + wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); + diam_nozzle->SetValue(value); - spin_filam->SetDigits(2); - spin_filam->SetIncrement(0.25); auto *default_filam = print_config_def.get("filament_diameter")->get_default_value(); - spin_filam->SetValue(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); + value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); + diam_filam->SetValue(value); + + diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId()); + diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId()); append_text(_L("Enter the diameter of your printer's hot end nozzle.")); @@ -1405,7 +1425,7 @@ PageDiameters::PageDiameters(ConfigWizard *parent) auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm")); sizer_nozzle->AddGrowableCol(0, 1); sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL); - sizer_nozzle->Add(spin_nozzle); + sizer_nozzle->Add(diam_nozzle); sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL); append(sizer_nozzle); @@ -1419,16 +1439,21 @@ PageDiameters::PageDiameters(ConfigWizard *parent) auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm")); sizer_filam->AddGrowableCol(0, 1); sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); - sizer_filam->Add(spin_filam); + sizer_filam->Add(diam_filam); sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); append(sizer_filam); } void PageDiameters::apply_custom_config(DynamicPrintConfig &config) { - auto *opt_nozzle = new ConfigOptionFloats(1, spin_nozzle->GetValue()); + double val = 0.0; + diam_nozzle->GetValue().ToCDouble(&val); + auto *opt_nozzle = new ConfigOptionFloats(1, val); config.set_key_value("nozzle_diameter", opt_nozzle); - auto *opt_filam = new ConfigOptionFloats(1, spin_filam->GetValue()); + + val = 0.0; + diam_filam->GetValue().ToCDouble(&val); + auto * opt_filam = new ConfigOptionFloats(1, val); config.set_key_value("filament_diameter", opt_filam); auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 4e3f1538e..ea39e04ab 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -451,8 +451,8 @@ struct PageBedShape: ConfigWizardPage struct PageDiameters: ConfigWizardPage { - wxSpinCtrlDouble *spin_nozzle; - wxSpinCtrlDouble *spin_filam; + wxTextCtrl *diam_nozzle; + wxTextCtrl *diam_filam; PageDiameters(ConfigWizard *parent); virtual void apply_custom_config(DynamicPrintConfig &config); From ddf59a4a8cb88f635a8c321e36b125bb0c43815c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 30 Apr 2021 11:39:52 +0200 Subject: [PATCH 29/75] Tech ENABLE_SCROLLABLE_LEGEND -> 1st installment of scrollable legend --- src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/GCodeViewer.cpp | 310 +++++++++++++++++++-------------- src/slic3r/GUI/GLCanvas3D.cpp | 17 +- src/slic3r/GUI/GLCanvas3D.hpp | 4 + 4 files changed, 196 insertions(+), 137 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a28d23bcc..edb6e6bcb 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,6 +59,8 @@ #define ENABLE_EXTENDED_M73_LINES (1 && ENABLE_VALIDATE_CUSTOM_GCODE) // Enable a modified version of automatic downscale on load of objects too big #define ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG (1 && ENABLE_2_4_0_ALPHA0) +// Enable scrollable legend in preview +#define ENABLE_SCROLLABLE_LEGEND (1 && ENABLE_2_4_0_ALPHA0) // Enable visualization of start gcode as regular toolpaths #define ENABLE_START_GCODE_VISUALIZATION (1 && ENABLE_2_4_0_ALPHA0) // Enable visualization of seams in preview diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ca1617bc2..bb186622f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -4051,14 +4051,23 @@ void GCodeViewer::render_legend() const if (!m_legend_enabled) return; +#if ENABLE_SCROLLABLE_LEGEND + Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); +#endif // ENABLE_SCROLLABLE_LEGEND + ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); +#if ENABLE_SCROLLABLE_LEGEND + float child_height = 0.2222f * static_cast(cnv_size.get_height()); + imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); +#else imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); ImDrawList* draw_list = ImGui::GetWindowDrawList(); +#endif // ENABLE_SCROLLABLE_LEGEND enum class EItemType : unsigned char { @@ -4069,94 +4078,106 @@ void GCodeViewer::render_legend() const }; const PrintEstimatedTimeStatistics::Mode& time_mode = m_time_statistics.modes[static_cast(m_time_estimate_mode)]; +#if ENABLE_SCROLLABLE_LEGEND + bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || + (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); +#endif // ENABLE_SCROLLABLE_LEGEND float icon_size = ImGui::GetTextLineHeight(); float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); +#if ENABLE_SCROLLABLE_LEGEND + auto append_item = [this, icon_size, percent_bar_size, &imgui](EItemType type, const Color& color, const std::string& label, +#else auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui](EItemType type, const Color& color, const std::string& label, +#endif // ENABLE_SCROLLABLE_LEGEND bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f }, std::function callback = nullptr) { - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - ImVec2 pos = ImGui::GetCursorScreenPos(); - switch (type) { - default: - case EItemType::Rect: { - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); - break; - } - case EItemType::Circle: { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { - draw_list->AddCircleFilled(center, 0.5f * icon_size, - ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - float radius = 0.5f * icon_size; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - radius = 0.5f * icon_size * 0.01f * 33.0f; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - } - else - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - break; - } - case EItemType::Hexagon: { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); - break; - } - case EItemType::Line: { - draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); - break; - } - } - - // draw text - ImGui::Dummy({ icon_size, icon_size }); - ImGui::SameLine(); - if (callback != nullptr) { - if (ImGui::MenuItem(label.c_str())) - callback(); - else { - // show tooltip - if (ImGui::IsItemHovered()) { - if (!visible) - ImGui::PopStyleVar(); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); - ImGui::BeginTooltip(); - imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); - ImGui::EndTooltip(); - ImGui::PopStyleColor(); - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - - // to avoid the tooltip to change size when moving the mouse - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - } - } - - if (!time.empty()) { - ImGui::SameLine(offsets[0]); - imgui.text(time); - ImGui::SameLine(offsets[1]); - pos = ImGui::GetCursorScreenPos(); - float width = std::max(1.0f, percent_bar_size * percent / max_percent); - draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, - ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); - ImGui::Dummy({ percent_bar_size, icon_size }); - ImGui::SameLine(); - char buf[64]; - ::sprintf(buf, "%.1f%%", 100.0f * percent); - ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); - } +#if ENABLE_SCROLLABLE_LEGEND + ImDrawList* draw_list = ImGui::GetWindowDrawList(); +#endif // ENABLE_SCROLLABLE_LEGEND + ImVec2 pos = ImGui::GetCursorScreenPos(); + switch (type) { + default: + case EItemType::Rect: { + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + break; + } + case EItemType::Circle: { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { + draw_list->AddCircleFilled(center, 0.5f * icon_size, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + float radius = 0.5f * icon_size; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + radius = 0.5f * icon_size * 0.01f * 33.0f; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); } else - imgui.text(label); + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - if (!visible) - ImGui::PopStyleVar(); + break; + } + case EItemType::Hexagon: { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); + break; + } + case EItemType::Line: { + draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + break; + } + } + + // draw text + ImGui::Dummy({ icon_size, icon_size }); + ImGui::SameLine(); + if (callback != nullptr) { + if (ImGui::MenuItem(label.c_str())) + callback(); + else { + // show tooltip + if (ImGui::IsItemHovered()) { + if (!visible) + ImGui::PopStyleVar(); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); + + // to avoid the tooltip to change size when moving the mouse + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + } + + if (!time.empty()) { + ImGui::SameLine(offsets[0]); + imgui.text(time); + ImGui::SameLine(offsets[1]); + pos = ImGui::GetCursorScreenPos(); + float width = std::max(1.0f, percent_bar_size * percent / max_percent); + draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, + ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); + ImGui::Dummy({ percent_bar_size, icon_size }); + ImGui::SameLine(); + char buf[64]; + ::sprintf(buf, "%.1f%%", 100.0f * percent); + ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); + } + } + else + imgui.text(label); + + if (!visible) + ImGui::PopStyleVar(); }; auto append_range = [append_item](const Extrusions::Range& range, unsigned int decimals) { @@ -4254,7 +4275,7 @@ void GCodeViewer::render_legend() const return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); }; - auto role_time_and_percent = [ time_mode](ExtrusionRole role) { + auto role_time_and_percent = [time_mode](ExtrusionRole role) { auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair& item) { return role == item.first; }); return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); }; @@ -4341,57 +4362,65 @@ void GCodeViewer::render_legend() const } case EViewType::ColorPrint: { - const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; - if (m_extruders_count == 1) { // single extruder use case - std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); - const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); - } - else { - for (int i = items_cnt; i >= 0; --i) { - // create label for color change item - if (i == 0) { - append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first)); - break; - } - else if (i == items_cnt) { - append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); - continue; - } - append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); - } - } - } - else { // multi extruder use case - // shows only extruders actually used - for (unsigned char i : m_extruder_ids) { - std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); +#if ENABLE_SCROLLABLE_LEGEND + // add scrollable region + if (ImGui::BeginChild("color_prints", { -1.0f, child_height }, false)) { +#endif // ENABLE_SCROLLABLE_LEGEND + const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; + if (m_extruders_count == 1) { // single extruder use case + std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); + append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); } else { - for (int j = items_cnt; j >= 0; --j) { + for (int i = items_cnt; i >= 0; --i) { // create label for color change item - std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); - if (j == 0) { - label += " " + upto_label(cp_values.front().second.first); - append_item(EItemType::Rect, m_tool_colors[i], label); + if (i == 0) { + append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first)); break; } - else if (j == items_cnt) { - label += " " + above_label(cp_values[j - 1].second.second); - append_item(EItemType::Rect, cp_values[j - 1].first, label); + else if (i == items_cnt) { + append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); continue; } - - label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); - append_item(EItemType::Rect, cp_values[j - 1].first, label); + append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); } } } + else { // multi extruder use case + // shows only extruders actually used + for (unsigned char i : m_extruder_ids) { + std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); + const int items_cnt = static_cast(cp_values.size()); + if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode + append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); + } + else { + for (int j = items_cnt; j >= 0; --j) { + // create label for color change item + std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); + if (j == 0) { + label += " " + upto_label(cp_values.front().second.first); + append_item(EItemType::Rect, m_tool_colors[i], label); + break; + } + else if (j == items_cnt) { + label += " " + above_label(cp_values[j - 1].second.second); + append_item(EItemType::Rect, cp_values[j - 1].first, label); + continue; + } + + label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); + append_item(EItemType::Rect, cp_values[j - 1].first, label); + } + } + } + } +#if ENABLE_SCROLLABLE_LEGEND } + ImGui::EndChild(); +#endif // ENABLE_SCROLLABLE_LEGEND break; } @@ -4517,26 +4546,35 @@ void GCodeViewer::render_legend() const offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time") }, 2.0f * icon_size); ImGui::Spacing(); - append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration") }, offsets); - for (const PartialTime& item : partial_times) { - switch (item.type) - { - case PartialTime::EType::Print: { - append_print(item.color1, offsets, item.times); - break; - } - case PartialTime::EType::Pause: { - imgui.text(_u8L("Pause")); - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); - break; - } - case PartialTime::EType::ColorChange: { - append_color_change(item.color1, item.color2, offsets, item.times); - break; - } + append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration") }, offsets); +#if ENABLE_SCROLLABLE_LEGEND + // add scrollable region + if (ImGui::BeginChild("events", { -1.0f, child_height }, false)) { +#endif // ENABLE_SCROLLABLE_LEGEND + for (const PartialTime& item : partial_times) { + switch (item.type) + { + case PartialTime::EType::Print: { + append_print(item.color1, offsets, item.times); + break; + } + case PartialTime::EType::Pause: { + imgui.text(_u8L("Pause")); + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); + break; + } + case PartialTime::EType::ColorChange: { + append_color_change(item.color1, item.color2, offsets, item.times); + break; + } + } } +#if ENABLE_SCROLLABLE_LEGEND } + ImGui::EndChild(); +#endif // ENABLE_SCROLLABLE_LEGEND + } } @@ -4680,10 +4718,14 @@ void GCodeViewer::render_legend() const } // total estimated printing time section +#if ENABLE_SCROLLABLE_LEGEND + if (show_estimated_time) { +#else if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { ImGui::Spacing(); +#endif // ENABLE_SCROLLABLE_LEGEND ImGui::Spacing(); ImGui::PushStyleColor(ImGuiCol_Separator, { 1.0f, 1.0f, 1.0f, 1.0f }); ImGui::Separator(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 26c4a314c..d4f037998 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4780,8 +4780,16 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) if (m_canvas == nullptr && m_context == nullptr) return; +#if ENABLE_SCROLLABLE_LEGEND + const std::array new_size = { w, h }; + if (m_old_size == new_size) + return; + + m_old_size = new_size; +#endif // ENABLE_SCROLLABLE_LEGEND + auto *imgui = wxGetApp().imgui(); - imgui->set_display_size((float)w, (float)h); + imgui->set_display_size(static_cast(w), static_cast(h)); const float font_size = 1.5f * wxGetApp().em_unit(); #if ENABLE_RETINA_GL imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor()); @@ -4789,6 +4797,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f); #endif +#if ENABLE_SCROLLABLE_LEGEND + this->request_extra_frame(); +#endif // ENABLE_SCROLLABLE_LEGEND + // ensures that this canvas is current _set_current(); } @@ -4829,8 +4841,7 @@ void GLCanvas3D::_update_camera_zoom(double zoom) void GLCanvas3D::_refresh_if_shown_on_screen() { - if (_is_shown_on_screen()) - { + if (_is_shown_on_screen()) { const Size& cnv_size = get_canvas_size(); _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9e9a2501e..0e1bb7229 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -474,6 +474,10 @@ private: Model* m_model; BackgroundSlicingProcess *m_process; +#if ENABLE_SCROLLABLE_LEGEND + std::array m_old_size{ 0, 0 }; +#endif // ENABLE_SCROLLABLE_LEGEND + // Screen is only refreshed from the OnIdle handler if it is dirty. bool m_dirty; bool m_initialized; From 49503db65eea7336072d8603584e1d6bf996ef8c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Sat, 1 May 2021 12:24:24 +0200 Subject: [PATCH 30/75] Tech ENABLE_SCROLLABLE_LEGEND -> Set legend max height --- src/slic3r/GUI/GCodeViewer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index bb186622f..dead0c795 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -4061,7 +4061,9 @@ void GCodeViewer::render_legend() const ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); #if ENABLE_SCROLLABLE_LEGEND - float child_height = 0.2222f * static_cast(cnv_size.get_height()); + float max_height = 0.75f * static_cast(cnv_size.get_height()); + float child_height = 0.3333f * max_height; + ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); #else imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); From c602e655e05d8b7157af3319d4d7550695db6821 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 4 May 2021 14:40:37 +0200 Subject: [PATCH 31/75] Tech ENABLE_SCROLLABLE_LEGEND -> Fixed layout of scrollable sub panels --- src/slic3r/GUI/GCodeViewer.cpp | 164 ++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 76 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index dead0c795..ed1ae7af2 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -4052,7 +4052,7 @@ void GCodeViewer::render_legend() const return; #if ENABLE_SCROLLABLE_LEGEND - Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); #endif // ENABLE_SCROLLABLE_LEGEND ImGuiWrapper& imgui = *wxGetApp().imgui(); @@ -4061,8 +4061,8 @@ void GCodeViewer::render_legend() const ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); #if ENABLE_SCROLLABLE_LEGEND - float max_height = 0.75f * static_cast(cnv_size.get_height()); - float child_height = 0.3333f * max_height; + const float max_height = 0.75f * static_cast(cnv_size.get_height()); + const float child_height = 0.3333f * max_height; ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); #else @@ -4085,8 +4085,8 @@ void GCodeViewer::render_legend() const (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); #endif // ENABLE_SCROLLABLE_LEGEND - float icon_size = ImGui::GetTextLineHeight(); - float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); + const float icon_size = ImGui::GetTextLineHeight(); + const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); #if ENABLE_SCROLLABLE_LEGEND auto append_item = [this, icon_size, percent_bar_size, &imgui](EItemType type, const Color& color, const std::string& label, @@ -4114,7 +4114,7 @@ void GCodeViewer::render_legend() const if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - float radius = 0.5f * icon_size; + const float radius = 0.5f * icon_size; draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); radius = 0.5f * icon_size * 0.01f * 33.0f; draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); @@ -4165,7 +4165,7 @@ void GCodeViewer::render_legend() const imgui.text(time); ImGui::SameLine(offsets[1]); pos = ImGui::GetCursorScreenPos(); - float width = std::max(1.0f, percent_bar_size * percent / max_percent); + const float width = std::max(1.0f, percent_bar_size * percent / max_percent); draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); ImGui::Dummy({ percent_bar_size, icon_size }); @@ -4197,7 +4197,7 @@ void GCodeViewer::render_legend() const append_range_item(0, range.min, decimals); } else { - float step_size = range.step_size(); + const float step_size = range.step_size(); for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { append_range_item(i, range.min + static_cast(i) * step_size, decimals); } @@ -4246,8 +4246,8 @@ void GCodeViewer::render_legend() const if (lower_b == zs.end()) continue; - double current_z = *lower_b; - double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b); + const double current_z = *lower_b; + const double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b); // to avoid duplicate values, check adding values if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z)) @@ -4333,7 +4333,7 @@ void GCodeViewer::render_legend() const ExtrusionRole role = m_roles[i]; if (role >= erCount) continue; - bool visible = is_visible(role); + const bool visible = is_visible(role); append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], visible, times[i], percents[i], max_percent, offsets, [this, role, visible]() { Extrusions* extrusions = const_cast(&m_extrusions); @@ -4365,63 +4365,73 @@ void GCodeViewer::render_legend() const case EViewType::ColorPrint: { #if ENABLE_SCROLLABLE_LEGEND - // add scrollable region - if (ImGui::BeginChild("color_prints", { -1.0f, child_height }, false)) { + const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; + size_t total_items = 1; + for (unsigned char i : m_extruder_ids) { + total_items += color_print_ranges(i, custom_gcode_per_print_z).size(); + } + + const bool need_scrollable = static_cast(total_items) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + + // add scrollable region, if needed + if (need_scrollable) + ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); +#else + const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; #endif // ENABLE_SCROLLABLE_LEGEND - const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; - if (m_extruders_count == 1) { // single extruder use case - std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); + if (m_extruders_count == 1) { // single extruder use case + const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); + const int items_cnt = static_cast(cp_values.size()); + if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode + append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); + } + else { + for (int i = items_cnt; i >= 0; --i) { + // create label for color change item + if (i == 0) { + append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first)); + break; + } + else if (i == items_cnt) { + append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); + continue; + } + append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); + } + } + } + else { // multi extruder use case + // shows only extruders actually used + for (unsigned char i : m_extruder_ids) { + const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); + append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); } else { - for (int i = items_cnt; i >= 0; --i) { + for (int j = items_cnt; j >= 0; --j) { // create label for color change item - if (i == 0) { - append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first)); + std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); + if (j == 0) { + label += " " + upto_label(cp_values.front().second.first); + append_item(EItemType::Rect, m_tool_colors[i], label); break; } - else if (i == items_cnt) { - append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); + else if (j == items_cnt) { + label += " " + above_label(cp_values[j - 1].second.second); + append_item(EItemType::Rect, cp_values[j - 1].first, label); continue; } - append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); - } - } - } - else { // multi extruder use case - // shows only extruders actually used - for (unsigned char i : m_extruder_ids) { - std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); - const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); - } - else { - for (int j = items_cnt; j >= 0; --j) { - // create label for color change item - std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); - if (j == 0) { - label += " " + upto_label(cp_values.front().second.first); - append_item(EItemType::Rect, m_tool_colors[i], label); - break; - } - else if (j == items_cnt) { - label += " " + above_label(cp_values[j - 1].second.second); - append_item(EItemType::Rect, cp_values[j - 1].first, label); - continue; - } - label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); - append_item(EItemType::Rect, cp_values[j - 1].first, label); - } + label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); + append_item(EItemType::Rect, cp_values[j - 1].first, label); } } } -#if ENABLE_SCROLLABLE_LEGEND } - ImGui::EndChild(); +#if ENABLE_SCROLLABLE_LEGEND + if (need_scrollable) + ImGui::EndChild(); #endif // ENABLE_SCROLLABLE_LEGEND break; @@ -4548,35 +4558,37 @@ void GCodeViewer::render_legend() const offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time") }, 2.0f * icon_size); ImGui::Spacing(); - append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration") }, offsets); + append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration") }, offsets); #if ENABLE_SCROLLABLE_LEGEND + const bool need_scrollable = static_cast(partial_times.size()) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + if (need_scrollable) // add scrollable region - if (ImGui::BeginChild("events", { -1.0f, child_height }, false)) { + ImGui::BeginChild("events", { -1.0f, child_height }, false); #endif // ENABLE_SCROLLABLE_LEGEND - for (const PartialTime& item : partial_times) { - switch (item.type) - { - case PartialTime::EType::Print: { - append_print(item.color1, offsets, item.times); - break; - } - case PartialTime::EType::Pause: { - imgui.text(_u8L("Pause")); - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); - break; - } - case PartialTime::EType::ColorChange: { - append_color_change(item.color1, item.color2, offsets, item.times); - break; - } - } + for (const PartialTime& item : partial_times) { + switch (item.type) + { + case PartialTime::EType::Print: { + append_print(item.color1, offsets, item.times); + break; + } + case PartialTime::EType::Pause: { + imgui.text(_u8L("Pause")); + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); + break; + } + case PartialTime::EType::ColorChange: { + append_color_change(item.color1, item.color2, offsets, item.times); + break; + } } -#if ENABLE_SCROLLABLE_LEGEND } - ImGui::EndChild(); -#endif // ENABLE_SCROLLABLE_LEGEND +#if ENABLE_SCROLLABLE_LEGEND + if (need_scrollable) + ImGui::EndChild(); +#endif // ENABLE_SCROLLABLE_LEGEND } } From 56aa45fa1fabe7fbc3389f9881b109b34c39279f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 4 May 2021 14:50:53 +0200 Subject: [PATCH 32/75] Fixed typo --- src/slic3r/GUI/GCodeViewer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ed1ae7af2..c7cb3a5d9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -4114,7 +4114,7 @@ void GCodeViewer::render_legend() const if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - const float radius = 0.5f * icon_size; + float radius = 0.5f * icon_size; draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); radius = 0.5f * icon_size * 0.01f * 33.0f; draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); @@ -4565,6 +4565,7 @@ void GCodeViewer::render_legend() const // add scrollable region ImGui::BeginChild("events", { -1.0f, child_height }, false); #endif // ENABLE_SCROLLABLE_LEGEND + for (const PartialTime& item : partial_times) { switch (item.type) { From b9910669e82d79dfe5a4312c296b34ac5a107f4a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 21 Apr 2021 14:57:43 +0200 Subject: [PATCH 33/75] Fix of #2825 - Add the length of each filament used --- src/libslic3r/GCode.cpp | 43 +++- src/libslic3r/GCode/GCodeProcessor.cpp | 264 +++++++++++++++++-------- src/libslic3r/GCode/GCodeProcessor.hpp | 76 +++++-- src/libslic3r/Print.hpp | 2 +- src/slic3r/GUI/GCodeViewer.cpp | 221 ++++++++++++++++----- src/slic3r/GUI/GCodeViewer.hpp | 4 +- src/slic3r/GUI/GUI_Preview.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 13 +- xs/xsp/Print.xsp | 2 +- 9 files changed, 463 insertions(+), 164 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 01740ca7b..4368783a6 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -614,9 +614,44 @@ namespace DoExport { static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) { const GCodeProcessor::Result& result = processor.get_result(); - print_statistics.estimated_normal_print_time = get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time); + print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? - get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A"; + get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; + } + + static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector& extruders, PrintStatistics& print_statistics) + { + const GCodeProcessor::Result& 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.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"; + + // update filament statictics + double total_extruded_volume = 0.0; + double total_used_filament = 0.0; + double total_weight = 0.0; + double total_cost = 0.0; + for (auto volume : result.print_statistics.volumes_per_extruder) { + total_extruded_volume += volume.second; + + size_t extruder_id = volume.first; + auto extruder = std::find_if(extruders.begin(), extruders.end(), [extruder_id](const Extruder& extr) { return extr.id() == extruder_id; }); + if (extruder == extruders.end()) + continue; + + double s = PI * sqr(0.5* extruder->filament_diameter()); + double weight = volume.second * extruder->filament_density() * 0.001; + total_used_filament += volume.second/s; + total_weight += weight; + total_cost += weight * extruder->filament_cost() * 0.001; + } + + print_statistics.total_extruded_volume = total_extruded_volume; + print_statistics.total_used_filament = total_used_filament; + print_statistics.total_weight = total_weight; + print_statistics.total_cost = total_cost; + + print_statistics.filament_stats = result.print_statistics.volumes_per_extruder; } #if ENABLE_VALIDATE_CUSTOM_GCODE @@ -754,7 +789,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); m_processor.process_file(path_tmp, true, [print]() { print->throw_if_canceled(); }); - DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); +// DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); + DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics); #if ENABLE_GCODE_WINDOW if (result != nullptr) { *result = std::move(m_processor.extract_result()); @@ -957,7 +993,6 @@ namespace DoExport { dst.first += buf; ++ dst.second; }; - print_statistics.filament_stats.insert(std::pair{extruder.id(), (float)used_filament}); append(out_filament_used_mm, "%.2lf", used_filament); append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001); if (filament_weight > 0.) { diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 232a51239..917f84f40 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -186,6 +186,72 @@ void GCodeProcessor::TimeMachine::CustomGCodeTime::reset() times = std::vector>(); } +void GCodeProcessor::UsedFilaments::reset() +{ + color_change_cache = 0.0f; + volumes_per_color_change = std::vector(); + + tool_change_cache = 0.0f; + volumes_per_extruder.clear(); + + role_cache = 0.0f; + filaments_per_role.clear(); +} + +void GCodeProcessor::UsedFilaments::increase_caches(double extruded_volume) +{ + color_change_cache += extruded_volume; + tool_change_cache += extruded_volume; + role_cache += extruded_volume; +} + +void GCodeProcessor::UsedFilaments::process_color_change_cache() +{ + if (color_change_cache != 0.0f) { + volumes_per_color_change.push_back(color_change_cache); + color_change_cache = 0.0f; + } +} + +void GCodeProcessor::UsedFilaments::process_extruder_cache(GCodeProcessor* processor) +{ + size_t active_extruder_id = processor->m_extruder_id; + if (tool_change_cache != 0.0f) { + if (volumes_per_extruder.find(active_extruder_id) != volumes_per_extruder.end()) + volumes_per_extruder[active_extruder_id] += tool_change_cache; + else + volumes_per_extruder[active_extruder_id] = tool_change_cache; + tool_change_cache = 0.0f; + } +} + +void GCodeProcessor::UsedFilaments::process_role_cache(GCodeProcessor* processor) +{ + if (role_cache != 0.0f) { + std::pair filament = { 0.0f, 0.0f }; + + double s = PI * sqr(0.5 * processor->m_filament_diameters[processor->m_extruder_id]); + filament.first = role_cache/s * 0.001; + filament.second = role_cache * processor->m_filament_densities[processor->m_extruder_id] * 0.001; + + ExtrusionRole active_role = processor->m_extrusion_role; + if (filaments_per_role.find(active_role) != filaments_per_role.end()) { + filaments_per_role[active_role].first += filament.first; + filaments_per_role[active_role].second += filament.second; + } + else + filaments_per_role[active_role] = filament; + role_cache = 0.0f; + } +} + +void GCodeProcessor::UsedFilaments::process_caches(GCodeProcessor* processor) +{ + process_color_change_cache(); + process_extruder_cache(processor); + process_role_cache(processor); +} + void GCodeProcessor::TimeMachine::reset() { enabled = false; @@ -348,10 +414,10 @@ void GCodeProcessor::TimeProcessor::reset() machine_limits = MachineEnvelopeConfig(); filament_load_times = std::vector(); filament_unload_times = std::vector(); - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { machines[i].reset(); } - machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].enabled = true; + machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true; } #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER @@ -416,19 +482,19 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) size_t g1_lines_counter = 0; // keeps track of last exported pair #if ENABLE_EXTENDED_M73_LINES - std::array, static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported_main; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_main; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { last_exported_main[i] = { 0, time_in_minutes(machines[i].time) }; } // keeps track of last exported remaining time to next printer stop - std::array(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported_stop; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + std::array(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_stop; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { last_exported_stop[i] = time_in_minutes(machines[i].time); } #else - std::array, static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> last_exported; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { last_exported[i] = { 0, time_in_minutes(machines[i].time) }; } #endif // ENABLE_EXTENDED_M73_LINES @@ -451,7 +517,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) line = line.substr(1); if (export_remaining_time_enabled && (line == reserved_tag(ETags::First_Line_M73_Placeholder) || line == reserved_tag(ETags::Last_Line_M73_Placeholder))) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { #if ENABLE_EXTENDED_M73_LINES @@ -486,7 +552,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) else if (line == reserved_tag(ETags::Estimated_Printing_Time_Placeholder)) { #else if (export_remaining_time_enabled && (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag)) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { ret += format_line_M73(machine.line_m73_mask.c_str(), @@ -497,13 +563,13 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) } else if (line == Estimated_Printing_Time_Placeholder_Tag) { #endif // ENABLE_VALIDATE_CUSTOM_GCODE - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; - PrintEstimatedTimeStatistics::ETimeMode mode = static_cast(i); - if (mode == PrintEstimatedTimeStatistics::ETimeMode::Normal || machine.enabled) { + PrintEstimatedStatistics::ETimeMode mode = static_cast(i); + if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) { char buf[128]; sprintf(buf, "; estimated printing time (%s mode) = %s\n", - (mode == PrintEstimatedTimeStatistics::ETimeMode::Normal) ? "normal" : "silent", + (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent", get_time_dhms(machine.time).c_str()); ret += buf; } @@ -545,7 +611,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) unsigned int exported_lines_count = 0; #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER if (export_remaining_time_enabled) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { // export pair @@ -789,13 +855,13 @@ GCodeProcessor::GCodeProcessor() { reset(); #if ENABLE_EXTENDED_M73_LINES - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n"; #else - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n"; #endif // ENABLE_EXTENDED_M73_LINES } @@ -826,6 +892,11 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_filament_diameters[i] = static_cast(config.filament_diameter.values[i]); } + m_filament_densities.resize(config.filament_density.values.size()); + for (size_t i = 0; i < config.filament_density.values.size(); ++i) { + m_filament_densities[i] = static_cast(config.filament_density.values[i]); + } + if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) { m_time_processor.machine_limits = reinterpret_cast(config); if (m_flavor == gcfMarlinLegacy) { @@ -846,7 +917,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_time_processor.filament_unload_times[i] = static_cast(config.filament_unload_time.values[i]); } - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; @@ -896,6 +967,13 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } + const ConfigOptionFloats* filament_densities = config.option("filament_density"); + if (filament_densities != nullptr) { + for (double dens : filament_densities->values) { + m_filament_densities.push_back(static_cast(dens)); + } + } + m_result.extruders_count = config.option("nozzle_diameter")->values.size(); const ConfigOptionPoints* extruder_offset = config.option("extruder_offset"); @@ -1026,7 +1104,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values; } - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; @@ -1051,7 +1129,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) void GCodeProcessor::enable_stealth_time_estimator(bool enabled) { - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled = enabled; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled = enabled; } void GCodeProcessor::reset() @@ -1096,6 +1174,7 @@ void GCodeProcessor::reset() } m_filament_diameters = std::vector(Min_Extruder_Count, 1.75f); + m_filament_densities = std::vector(Min_Extruder_Count, 1.245f); m_extruded_last_z = 0.0f; #if ENABLE_START_GCODE_VISUALIZATION m_first_layer_height = 0.0f; @@ -1109,6 +1188,7 @@ void GCodeProcessor::reset() m_producers_enabled = false; m_time_processor.reset(); + m_used_filaments.reset(); m_result.reset(); m_result.id = ++s_result_id; @@ -1186,7 +1266,7 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr } // process the time blocks - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; machine.calculate_time(); @@ -1194,6 +1274,8 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache }); } + m_used_filaments.process_caches(this); + update_estimated_times_stats(); // post-process to add M73 lines into the gcode @@ -1216,20 +1298,20 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr #endif // ENABLE_GCODE_VIEWER_STATISTICS } -float GCodeProcessor::get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_time(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; } -std::string GCodeProcessor::get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::string GCodeProcessor::get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); } -std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const +std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const { std::vector>> ret; - if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { const TimeMachine& machine = m_time_processor.machines[static_cast(mode)]; float total_time = 0.0f; for (const auto& [type, time] : machine.gcode_time.times) { @@ -1241,10 +1323,10 @@ std::vector>> GCodeProcesso return ret; } -std::vector> GCodeProcessor::get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::vector> GCodeProcessor::get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const { std::vector> ret; - if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].moves_time.size(); ++i) { float time = m_time_processor.machines[static_cast(mode)].moves_time[i]; if (time > 0.0f) @@ -1254,10 +1336,10 @@ std::vector> GCodeProcessor::get_moves_time(PrintEst return ret; } -std::vector> GCodeProcessor::get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::vector> GCodeProcessor::get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const { std::vector> ret; - if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].roles_time.size(); ++i) { float time = m_time_processor.machines[static_cast(mode)].roles_time[i]; if (time > 0.0f) @@ -1267,9 +1349,9 @@ std::vector> GCodeProcessor::get_roles_time(Prin return ret; } -std::vector GCodeProcessor::get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::vector GCodeProcessor::get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].layers_time : std::vector(); } @@ -1461,6 +1543,7 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_VALIDATE_CUSTOM_GCODE // extrusion role tag if (boost::starts_with(comment, reserved_tag(ETags::Role))) { + m_used_filaments.process_role_cache(this); m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(reserved_tag(ETags::Role).length())); #if ENABLE_SEAMS_VISUALIZATION if (m_extrusion_role == erExternalPerimeter) @@ -1546,7 +1629,8 @@ void GCodeProcessor::process_tags(const std::string_view comment) extruder_id = static_cast(eid); } - m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview + if (extruder_id < m_extruder_colors.size()) + m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview ++m_cp_color.counter; if (m_cp_color.counter == UCHAR_MAX) m_cp_color.counter = 0; @@ -1557,6 +1641,7 @@ void GCodeProcessor::process_tags(const std::string_view comment) } process_custom_gcode_time(CustomGCode::ColorChange); + process_filaments(CustomGCode::ColorChange); return; } @@ -2194,6 +2279,9 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; float area_toolpath_cross_section = volume_extruded_filament / delta_xyz; + // save extruded volume to the cache + m_used_filaments.increase_caches(volume_extruded_filament); + // volume extruded filament / tool displacement = area toolpath cross section m_mm3_per_mm = area_toolpath_cross_section; #if ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -2254,7 +2342,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) assert(distance != 0.0f); float inv_distance = 1.0f / distance; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) continue; @@ -2264,8 +2352,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) std::vector& blocks = machine.blocks; curr.feedrate = (delta_pos[E] == 0.0f) ? - minimum_travel_feedrate(static_cast(i), m_feedrate) : - minimum_feedrate(static_cast(i), m_feedrate); + minimum_travel_feedrate(static_cast(i), m_feedrate) : + minimum_feedrate(static_cast(i), m_feedrate); TimeBlock block; block.move_type = type; @@ -2283,7 +2371,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); if (curr.abs_axis_feedrate[a] != 0.0f) { - float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } @@ -2300,13 +2388,13 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // calculates block acceleration float acceleration = - (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : + (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : (is_extrusion_only_move(delta_pos) ? - get_retract_acceleration(static_cast(i)) : - get_acceleration(static_cast(i))); + get_retract_acceleration(static_cast(i)) : + get_acceleration(static_cast(i))); for (unsigned char a = X; a <= E; ++a) { - float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) acceleration = axis_max_acceleration; } @@ -2317,7 +2405,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) curr.safe_feedrate = block.feedrate_profile.cruise; for (unsigned char a = X; a <= E; ++a) { - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); if (curr.abs_axis_feedrate[a] > axis_max_jerk) curr.safe_feedrate = std::min(curr.safe_feedrate, axis_max_jerk); } @@ -2365,7 +2453,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // axis reversal std::max(-v_exit, v_entry)); - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); if (jerk > axis_max_jerk) { v_factor *= axis_max_jerk / jerk; limited = true; @@ -2650,8 +2738,8 @@ void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration float factor = ((m_flavor != gcfRepRapSprinter && m_flavor != gcfRepRapFirmware) && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor); @@ -2678,8 +2766,8 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) // http://smoothieware.org/supported-g-codes float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, i, line.x() * factor); @@ -2699,27 +2787,27 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) { float value; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_value('S', value)) { // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware // It is also generated by PrusaSlicer to control acceleration per extrusion type // (perimeters, first layer etc) when 'Marlin (legacy)' flavor is used. - set_acceleration(static_cast(i), value); - set_travel_acceleration(static_cast(i), value); + set_acceleration(static_cast(i), value); + set_travel_acceleration(static_cast(i), value); if (line.has_value('T', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); } else { // New acceleration format, compatible with the upstream Marlin. if (line.has_value('P', value)) - set_acceleration(static_cast(i), value); + set_acceleration(static_cast(i), value); if (line.has_value('R', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); if (line.has_value('T', value)) // Interpret the T value as the travel acceleration in the new Marlin format. - set_travel_acceleration(static_cast(i), value); + set_travel_acceleration(static_cast(i), value); } } } @@ -2727,8 +2815,8 @@ void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_x()) { float max_jerk = line.x(); @@ -2761,7 +2849,7 @@ void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line) float value_t; if (line.has_value('S', value_s) && !line.has_value('T', value_t)) { value_s *= 0.01f; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { m_time_processor.machines[i].extrude_factor_override_percentage = value_s; } } @@ -2812,7 +2900,7 @@ void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M566(const GCodeReader::GCodeLine& line) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, line.x() * MMMIN_TO_MMSEC); @@ -2863,6 +2951,7 @@ void GCodeProcessor::process_T(const std::string_view command) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; else { unsigned char old_extruder_id = m_extruder_id; + process_filaments(CustomGCode::ToolChange); m_extruder_id = id; m_cp_color.current = m_extruder_colors[id]; // Specific to the MK3 MMU2: @@ -2920,7 +3009,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) #if ENABLE_EXTENDED_M73_LINES // stores stop time placeholders for later use if (type == EMoveType::Color_change || type == EMoveType::Pause_Print) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) continue; @@ -2931,7 +3020,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) #endif // ENABLE_EXTENDED_M73_LINES } -float GCodeProcessor::minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const +float GCodeProcessor::minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const { if (m_time_processor.machine_limits.machine_min_extruding_rate.empty()) return feedrate; @@ -2939,7 +3028,7 @@ float GCodeProcessor::minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode m return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, static_cast(mode))); } -float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const +float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const { if (m_time_processor.machine_limits.machine_min_travel_rate.empty()) return feedrate; @@ -2947,7 +3036,7 @@ float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETim return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast(mode))); } -float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -2959,7 +3048,7 @@ float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeM } } -float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -2971,7 +3060,7 @@ float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedTimeStatistics::ET } } -float GCodeProcessor::get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -2983,18 +3072,18 @@ float GCodeProcessor::get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode } } -float GCodeProcessor::get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, static_cast(mode)); } -float GCodeProcessor::get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const { size_t id = static_cast(mode); return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].acceleration : DEFAULT_ACCELERATION; } -void GCodeProcessor::set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value) +void GCodeProcessor::set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) { size_t id = static_cast(mode); if (id < m_time_processor.machines.size()) { @@ -3004,13 +3093,13 @@ void GCodeProcessor::set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mo } } -float GCodeProcessor::get_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const { size_t id = static_cast(mode); return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; } -void GCodeProcessor::set_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value) +void GCodeProcessor::set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) { size_t id = static_cast(mode); if (id < m_time_processor.machines.size()) { @@ -3038,7 +3127,7 @@ float GCodeProcessor::get_filament_unload_time(size_t extruder_id) void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) continue; @@ -3055,17 +3144,26 @@ void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) } } +void GCodeProcessor::process_filaments(CustomGCode::Type code) +{ + if (code == CustomGCode::ColorChange) + m_used_filaments.process_color_change_cache(); + + if (code == CustomGCode::ToolChange) + m_used_filaments.process_extruder_cache(this); +} + void GCodeProcessor::simulate_st_synchronize(float additional_time) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { m_time_processor.machines[i].simulate_st_synchronize(additional_time); } } void GCodeProcessor::update_estimated_times_stats() { - auto update_mode = [this](PrintEstimatedTimeStatistics::ETimeMode mode) { - PrintEstimatedTimeStatistics::Mode& data = m_result.time_statistics.modes[static_cast(mode)]; + auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) { + PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast(mode)]; data.time = get_time(mode); data.custom_gcode_times = get_custom_gcode_times(mode, true); data.moves_times = get_moves_time(mode); @@ -3073,11 +3171,15 @@ void GCodeProcessor::update_estimated_times_stats() data.layers_times = get_layers_time(mode); }; - update_mode(PrintEstimatedTimeStatistics::ETimeMode::Normal); - if (m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled) - update_mode(PrintEstimatedTimeStatistics::ETimeMode::Stealth); + update_mode(PrintEstimatedStatistics::ETimeMode::Normal); + if (m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled) + update_mode(PrintEstimatedStatistics::ETimeMode::Stealth); else - m_result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].reset(); + m_result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].reset(); + + m_result.print_statistics.volumes_per_color_change = m_used_filaments.volumes_per_color_change; + m_result.print_statistics.volumes_per_extruder = m_used_filaments.volumes_per_extruder; + m_result.print_statistics.used_filaments_per_role = m_used_filaments.filaments_per_role; } } /* namespace Slic3r */ diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 75ec1546b..8975255ec 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -36,7 +36,7 @@ namespace Slic3r { Count }; - struct PrintEstimatedTimeStatistics + struct PrintEstimatedStatistics { enum class ETimeMode : unsigned char { @@ -62,14 +62,21 @@ namespace Slic3r { } }; + std::vector volumes_per_color_change; + std::map volumes_per_extruder; + std::map> used_filaments_per_role; + std::array(ETimeMode::Count)> modes; - PrintEstimatedTimeStatistics() { reset(); } + PrintEstimatedStatistics() { reset(); } void reset() { for (auto m : modes) { m.reset(); } + volumes_per_color_change.clear(); + volumes_per_extruder.clear(); + used_filaments_per_role.clear(); } }; @@ -314,7 +321,7 @@ namespace Slic3r { // Additional load / unload times for a filament exchange sequence. std::vector filament_load_times; std::vector filament_unload_times; - std::array(PrintEstimatedTimeStatistics::ETimeMode::Count)> machines; + std::array(PrintEstimatedStatistics::ETimeMode::Count)> machines; void reset(); @@ -327,6 +334,30 @@ namespace Slic3r { #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER }; + struct UsedFilaments // filaments per ColorChange + { + double color_change_cache; + std::vector volumes_per_color_change; + + double tool_change_cache; + std::map volumes_per_extruder; + + double role_cache; + // ExtrusionRole : + std::map> filaments_per_role; + + void reset(); + + void increase_caches(double extruded_volume); + + void process_color_change_cache(); + void process_extruder_cache(GCodeProcessor* processor); + void process_role_cache(GCodeProcessor* processor); + void process_caches(GCodeProcessor* processor); + + friend class GCodeProcessor; + }; + public: #if !ENABLE_GCODE_LINES_ID_IN_H_SLIDER struct MoveVertex @@ -372,7 +403,7 @@ namespace Slic3r { SettingsIds settings_ids; size_t extruders_count; std::vector extruder_colors; - PrintEstimatedTimeStatistics time_statistics; + PrintEstimatedStatistics print_statistics; #if ENABLE_GCODE_VIEWER_STATISTICS int64_t time{ 0 }; @@ -519,6 +550,7 @@ namespace Slic3r { ExtruderColors m_extruder_colors; ExtruderTemps m_extruder_temps; std::vector m_filament_diameters; + std::vector m_filament_densities; float m_extruded_last_z; #if ENABLE_START_GCODE_VISUALIZATION float m_first_layer_height; // mm @@ -550,6 +582,7 @@ namespace Slic3r { bool m_producers_enabled; TimeProcessor m_time_processor; + UsedFilaments m_used_filaments; Result m_result; static unsigned int s_result_id; @@ -566,7 +599,7 @@ namespace Slic3r { void apply_config(const PrintConfig& config); void enable_stealth_time_estimator(bool enabled); bool is_stealth_time_estimator_enabled() const { - return m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled; + return m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled; } void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } void enable_producers(bool enabled) { m_producers_enabled = enabled; } @@ -579,13 +612,13 @@ namespace Slic3r { // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). void process_file(const std::string& filename, bool apply_postprocess, std::function cancel_callback = nullptr); - float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::vector>> get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const; + float get_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; + std::vector>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const; - std::vector> get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::vector> get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::vector get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; + std::vector> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::vector> get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::vector get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const; private: void apply_config(const DynamicPrintConfig& config); @@ -701,20 +734,21 @@ namespace Slic3r { void store_move_vertex(EMoveType type); - float minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const; - float minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const; - float get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; - float get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; - float get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; - float get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; - float get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; - void set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); - float get_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; - void set_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); + float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; + float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; + float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + float get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + void set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); + float get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + void set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); float get_filament_load_time(size_t extruder_id); float get_filament_unload_time(size_t extruder_id); void process_custom_gcode_time(CustomGCode::Type code); + void process_filaments(CustomGCode::Type code); // Simulates firmware st_synchronize() call void simulate_st_synchronize(float additional_time = 0.0f); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index b4241c91e..3fdc49db8 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -407,7 +407,7 @@ struct PrintStatistics double total_weight; double total_wipe_tower_cost; double total_wipe_tower_filament; - std::map filament_stats; + std::map filament_stats; // Config with the filled in print statistics. DynamicConfig config() const; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index c7cb3a5d9..233bdf1cd 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -17,6 +17,7 @@ #include "GLCanvas3D.hpp" #include "GLToolbar.hpp" #include "GUI_Preview.hpp" +#include "GUI_ObjectManipulation.hpp" #include #include @@ -687,13 +688,13 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& wxGetApp().plater()->set_bed_shape(bed_shape, texture, model, gcode_result.bed_shape.empty()); } - m_time_statistics = gcode_result.time_statistics; + m_print_statistics = gcode_result.print_statistics; - if (m_time_estimate_mode != PrintEstimatedTimeStatistics::ETimeMode::Normal) { - float time = m_time_statistics.modes[static_cast(m_time_estimate_mode)].time; + if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) { + float time = m_print_statistics.modes[static_cast(m_time_estimate_mode)].time; if (time == 0.0f || - short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time))) - m_time_estimate_mode = PrintEstimatedTimeStatistics::ETimeMode::Normal; + short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time))) + m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal; } } @@ -788,7 +789,7 @@ void GCodeViewer::reset() m_layers.reset(); m_layers_z_range = { 0, 0 }; m_roles = std::vector(); - m_time_statistics.reset(); + m_print_statistics.reset(); #if ENABLE_GCODE_WINDOW m_sequential_view.gcode_window.reset(); #endif // ENABLE_GCODE_WINDOW @@ -4079,7 +4080,7 @@ void GCodeViewer::render_legend() const Line }; - const PrintEstimatedTimeStatistics::Mode& time_mode = m_time_statistics.modes[static_cast(m_time_estimate_mode)]; + const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_time_estimate_mode)]; #if ENABLE_SCROLLABLE_LEGEND bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); @@ -4088,12 +4089,15 @@ void GCodeViewer::render_legend() const const float icon_size = ImGui::GetTextLineHeight(); const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); + bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + #if ENABLE_SCROLLABLE_LEGEND - auto append_item = [this, icon_size, percent_bar_size, &imgui](EItemType type, const Color& color, const std::string& label, + auto append_item = [this, icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label, #else - auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui](EItemType type, const Color& color, const std::string& label, + auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label, #endif // ENABLE_SCROLLABLE_LEGEND - bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f }, + bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f, 0.0f, 0.0f }, + double used_filament_m = 0.0, double used_filament_g = 0.0, std::function callback = nullptr) { if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); @@ -4173,10 +4177,26 @@ void GCodeViewer::render_legend() const char buf[64]; ::sprintf(buf, "%.1f%%", 100.0f * percent); ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); + ImGui::SameLine(offsets[2]); + ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); + imgui.text(buf); + ImGui::SameLine(offsets[3]); + ::sprintf(buf, "%.2f g", used_filament_g); + imgui.text(buf); } } - else + else { imgui.text(label); + if (used_filament_m > 0.0) { + char buf[64]; + ImGui::SameLine(offsets[0]); + ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); + imgui.text(buf); + ImGui::SameLine(offsets[1]); + ::sprintf(buf, "%.2f g", used_filament_g); + imgui.text(buf); + } + } if (!visible) ImGui::PopStyleVar(); @@ -4204,12 +4224,13 @@ void GCodeViewer::render_legend() const } }; - auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { - imgui.text(texts[0]); - ImGui::SameLine(offsets[0]); - imgui.text(texts[1]); - ImGui::SameLine(offsets[1]); - imgui.text(texts[2]); + auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { + size_t i = 0; + for (; i < offsets.size(); i++) { + imgui.text(texts[i]); + ImGui::SameLine(offsets[i]); + } + imgui.text(texts[i]); ImGui::Separator(); }; @@ -4222,11 +4243,12 @@ void GCodeViewer::render_legend() const }; auto calculate_offsets = [max_width](const std::vector& labels, const std::vector& times, - const std::array& titles, float extra_size = 0.0f) { + const std::array& titles, float extra_size = 0.0f) { const ImGuiStyle& style = ImGui::GetStyle(); - std::array ret = { 0.0f, 0.0f }; + std::array ret = { 0.0f, 0.0f, 0.0f, 0.0f }; ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x; - ret[1] = ret[0] + max_width(times, titles[1]) + style.ItemSpacing.x; + for (size_t i = 1; i < titles.size(); i++) + ret[i] = ret[i-1] + max_width(times, titles[i]) + style.ItemSpacing.x; return ret; }; @@ -4282,11 +4304,22 @@ void GCodeViewer::render_legend() const return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); }; + auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) { + auto it = m_print_statistics.used_filaments_per_role.find(role); + if (it == m_print_statistics.used_filaments_per_role.end()) + return std::make_pair(0.0, 0.0); + + double koef = imperial_units ? 1000.0 / ObjectManipulation::in_to_mm : 1.0; + return std::make_pair(it->second.first * koef, it->second.second); + }; + // data used to properly align items in columns when showing time - std::array offsets = { 0.0f, 0.0f }; + std::array offsets = { 0.0f, 0.0f, 0.0f, 0.0f }; std::vector labels; std::vector times; std::vector percents; + std::vector used_filaments_m; + std::vector used_filaments_g; float max_percent = 0.0f; if (m_view_type == EViewType::FeatureType) { @@ -4299,10 +4332,73 @@ void GCodeViewer::render_legend() const times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : ""); percents.push_back(percent); max_percent = std::max(max_percent, percent); + auto [used_filament_m, used_filament_g] = used_filament_per_role(role); + used_filaments_m.push_back(used_filament_m); + used_filaments_g.push_back(used_filament_g); } } - offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time") }, icon_size); + std::string longest_percentage_string; + for (double item : percents) { + char buffer[64]; + ::sprintf(buffer, "%.2f %%", item); + if (::strlen(buffer) > longest_percentage_string.length()) + longest_percentage_string = buffer; + } + longest_percentage_string += " "; + if (_u8L("Percentage").length() > longest_percentage_string.length()) + longest_percentage_string = _u8L("Percentage"); + + std::string longest_used_filament_string; + for (double item : used_filaments_m) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + + offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time"), longest_percentage_string, longest_used_filament_string }, icon_size); + } + + // get used filament (meters and grams) from used volume in respect to the active extruder + auto get_used_filament_from_volume = [imperial_units](double volume, int extruder_id) { + const std::vector& filament_presets = wxGetApp().preset_bundle->filament_presets; + const PresetCollection& filaments = wxGetApp().preset_bundle->filaments; + + double koef = imperial_units ? 1.0/ObjectManipulation::in_to_mm : 0.001; + + std::pair ret = { 0.0, 0.0 }; + if (const Preset* filament_preset = filaments.find_preset(filament_presets[extruder_id], false)) { + double filament_radius = 0.5 * filament_preset->config.opt_float("filament_diameter", 0); + double s = PI * sqr(filament_radius); + ret.first = volume / s * koef; + double filament_density = filament_preset->config.opt_float("filament_density", 0); + ret.second = volume * filament_density * 0.001; + } + return ret; + }; + + if (m_view_type == EViewType::Tool) { + // calculate used filaments data + for (size_t extruder_id : m_extruder_ids) { + if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end()) + continue; + double volume = m_print_statistics.volumes_per_extruder.at(extruder_id); + + auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id); + used_filaments_m.push_back(used_filament_m); + used_filaments_g.push_back(used_filament_g); + } + + std::string longest_used_filament_string; + for (double item : used_filaments_m) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + + offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size); } // extrusion paths section -> title @@ -4310,7 +4406,7 @@ void GCodeViewer::render_legend() const { case EViewType::FeatureType: { - append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage") }, offsets); + append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); break; } case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } @@ -4319,7 +4415,11 @@ void GCodeViewer::render_legend() const case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } - case EViewType::Tool: { imgui.title(_u8L("Tool")); break; } + case EViewType::Tool: + { + append_headers({ _u8L("Tool"), _u8L("Used filament") }, offsets); + break; + } case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } default: { break; } } @@ -4335,7 +4435,7 @@ void GCodeViewer::render_legend() const continue; const bool visible = is_visible(role); append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], - visible, times[i], percents[i], max_percent, offsets, [this, role, visible]() { + visible, times[i], percents[i], max_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { Extrusions* extrusions = const_cast(&m_extrusions); extrusions->role_visibility_flags = visible ? extrusions->role_visibility_flags & ~(1 << role) : extrusions->role_visibility_flags | (1 << role); // update buffers' render paths @@ -4357,8 +4457,11 @@ void GCodeViewer::render_legend() const case EViewType::Tool: { // shows only extruders actually used - for (unsigned char i : m_extruder_ids) { - append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1)); + size_t i = 0; + for (unsigned char extruder_id : m_extruder_ids) { + append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1), + true, "", 0.0f, 0.0f, offsets, used_filaments_m[i], used_filaments_g[i]); + i++; } break; } @@ -4458,10 +4561,11 @@ void GCodeViewer::render_legend() const Color color1; Color color2; Times times; + std::pair used_filament {0.0f, 0.0f}; }; using PartialTimes = std::vector; - auto generate_partial_times = [this](const TimesList& times) { + auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector& used_filaments) { PartialTimes items; std::vector custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; @@ -4471,6 +4575,7 @@ void GCodeViewer::render_legend() const last_color[i] = m_tool_colors[i]; } int last_extruder_id = 1; + int color_change_idx = 0; for (const auto& time_rec : times) { switch (time_rec.first) { @@ -4486,14 +4591,14 @@ void GCodeViewer::render_legend() const case CustomGCode::ColorChange: { auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder-1) }); items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); last_color[it->extruder - 1] = decode_color(it->color); last_extruder_id = it->extruder; custom_gcode_per_print_z.erase(it); } else - items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id -1) }); break; } @@ -4504,7 +4609,7 @@ void GCodeViewer::render_legend() const return items; }; - auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { + auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { imgui.text(_u8L("Color change")); ImGui::SameLine(); @@ -4523,7 +4628,7 @@ void GCodeViewer::render_legend() const imgui.text(short_time(get_time_dhms(times.second - times.first))); }; - auto append_print = [&imgui](const Color& color, const std::array& offsets, const Times& times) { + auto append_print = [&imgui, imperial_units](const Color& color, const std::array& offsets, const Times& times, std::pair used_filament) { imgui.text(_u8L("Print")); ImGui::SameLine(); @@ -4539,9 +4644,19 @@ void GCodeViewer::render_legend() const imgui.text(short_time(get_time_dhms(times.second))); ImGui::SameLine(offsets[1]); imgui.text(short_time(get_time_dhms(times.first))); + if (used_filament.first > 0.0f) { + char buffer[64]; + ImGui::SameLine(offsets[2]); + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first); + imgui.text(buffer); + + ImGui::SameLine(offsets[3]); + ::sprintf(buffer, "%.2f g", used_filament.second); + imgui.text(buffer); + } }; - PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times); + PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change); if (!partial_times.empty()) { labels.clear(); times.clear(); @@ -4555,10 +4670,22 @@ void GCodeViewer::render_legend() const } times.push_back(short_time(get_time_dhms(item.times.second))); } - offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time") }, 2.0f * icon_size); + + + std::string longest_used_filament_string; + for (const PartialTime& item : partial_times) { + if (item.used_filament.first > 0.0f) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + } + + offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size); ImGui::Spacing(); - append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration") }, offsets); + append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets); #if ENABLE_SCROLLABLE_LEGEND const bool need_scrollable = static_cast(partial_times.size()) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; if (need_scrollable) @@ -4570,7 +4697,7 @@ void GCodeViewer::render_legend() const switch (item.type) { case PartialTime::EType::Print: { - append_print(item.color1, offsets, item.times); + append_print(item.color1, offsets, item.times, item.used_filament); break; } case PartialTime::EType::Pause: { @@ -4750,12 +4877,12 @@ void GCodeViewer::render_legend() const ImGui::AlignTextToFramePadding(); switch (m_time_estimate_mode) { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: + case PrintEstimatedStatistics::ETimeMode::Normal: { imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:"); break; } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: + case PrintEstimatedStatistics::ETimeMode::Stealth: { imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); break; @@ -4765,18 +4892,18 @@ void GCodeViewer::render_legend() const ImGui::SameLine(); imgui.text(short_time(get_time_dhms(time_mode.time))); - auto show_mode_button = [this, &imgui](const wxString& label, PrintEstimatedTimeStatistics::ETimeMode mode) { + auto show_mode_button = [this, &imgui](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { bool show = false; - for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) { + for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) { if (i != static_cast(mode) && - short_time(get_time_dhms(m_time_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) { + short_time(get_time_dhms(m_print_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_print_statistics.modes[i].time))) { show = true; break; } } - if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { + if (show && m_print_statistics.modes[static_cast(mode)].roles_times.size() > 0) { if (imgui.button(label)) { - *const_cast(&m_time_estimate_mode) = mode; + *const_cast(&m_time_estimate_mode) = mode; wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); } @@ -4784,12 +4911,12 @@ void GCodeViewer::render_legend() const }; switch (m_time_estimate_mode) { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: { - show_mode_button(_L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); + case PrintEstimatedStatistics::ETimeMode::Normal: { + show_mode_button(_L("Show stealth mode"), PrintEstimatedStatistics::ETimeMode::Stealth); break; } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: { - show_mode_button(_L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); + case PrintEstimatedStatistics::ETimeMode::Stealth: { + show_mode_button(_L("Show normal mode"), PrintEstimatedStatistics::ETimeMode::Normal); break; } default : { assert(false); break; } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 2ccda6f5d..112c681d4 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -696,8 +696,8 @@ private: Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; - PrintEstimatedTimeStatistics m_time_statistics; - PrintEstimatedTimeStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedTimeStatistics::ETimeMode::Normal }; + PrintEstimatedStatistics m_print_statistics; + PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal }; #if ENABLE_GCODE_VIEWER_STATISTICS Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index ea289bb14..52223b3d4 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -643,7 +643,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee if (sla_print_technology) m_layers_slider->SetLayersTimes(plater->sla_print().print_statistics().layers_times); else { - auto print_mode_stat = m_gcode_result->time_statistics.modes.front(); + auto print_mode_stat = m_gcode_result->print_statistics.modes.front(); m_layers_slider->SetLayersTimes(print_mode_stat.layers_times, print_mode_stat.time); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index aebec14ee..13601396f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1172,10 +1172,10 @@ void Sidebar::update_sliced_info_sizer() new_label += format_wxstr(":\n - %1%\n - %2%", _L("objects"), _L("wipe tower")); wxString info_text = is_wipe_tower ? - wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / /*1000*/koef, - (ps.total_used_filament - ps.total_wipe_tower_filament) / /*1000*/koef, - ps.total_wipe_tower_filament / /*1000*/koef) : - wxString::Format("%.2f", ps.total_used_filament / /*1000*/koef); + wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / koef, + (ps.total_used_filament - ps.total_wipe_tower_filament) / koef, + ps.total_wipe_tower_filament / koef) : + wxString::Format("%.2f", ps.total_used_filament / koef); p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label); koef = imperial_units ? pow(ObjectManipulation::mm_to_in, 3) : 1.0f; @@ -1203,7 +1203,7 @@ void Sidebar::update_sliced_info_sizer() filament_weight = ps.total_weight; else { double filament_density = filament_preset->config.opt_float("filament_density", 0); - filament_weight = filament.second * filament_density * 2.4052f * 0.001; // assumes 1.75mm filament diameter; + filament_weight = filament.second * filament_density/* *2.4052f*/ * 0.001; // assumes 1.75mm filament diameter; new_label += "\n - " + format_wxstr(_L("Filament at extruder %1%"), filament.first + 1); info_text += wxString::Format("\n%.2f", filament_weight); @@ -1357,7 +1357,8 @@ void Sidebar::update_ui_from_settings() update_sliced_info_sizer(); // update Cut gizmo, if it's open p->plater->canvas3D()->update_gizmos_on_off_state(); - p->plater->canvas3D()->request_extra_frame(); + p->plater->set_current_canvas_as_dirty(); + p->plater->get_current_canvas3D()->request_extra_frame(); } std::vector& Sidebar::combos_filament() diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 44d58266b..8aef9e7a3 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -105,7 +105,7 @@ _constant() SV* filament_stats() %code%{ HV* hv = newHV(); - for (std::map::const_iterator it = THIS->print_statistics().filament_stats.begin(); it != THIS->print_statistics().filament_stats.end(); ++it) { + for (std::map::const_iterator it = THIS->print_statistics().filament_stats.begin(); it != THIS->print_statistics().filament_stats.end(); ++it) { // stringify extruder_id std::ostringstream ss; ss << it->first; From 4d2c2070f8c6f321094498f4540e2aef5ef3ad9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 7 May 2021 12:51:10 +0200 Subject: [PATCH 34/75] Added missing includes (GCC 9.3) --- src/slic3r/GUI/DesktopIntegrationDialog.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.cpp b/src/slic3r/GUI/DesktopIntegrationDialog.cpp index 8ac134f5f..a2f7c8933 100644 --- a/src/slic3r/GUI/DesktopIntegrationDialog.cpp +++ b/src/slic3r/GUI/DesktopIntegrationDialog.cpp @@ -8,6 +8,12 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Platform.hpp" +#include +#include + +#include +#include + namespace Slic3r { namespace GUI { From 389955966cce168b46dc985f32121658bb8cf853 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 7 May 2021 13:42:53 +0200 Subject: [PATCH 35/75] Disabled tech ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW --- src/libslic3r/Technologies.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 4cc5fbfec..99684e93e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -69,7 +69,7 @@ // Enable project dirty state manager #define ENABLE_PROJECT_DIRTY_STATE (1 && ENABLE_2_4_0_ALPHA0) // Enable project dirty state manager debug window -#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW (1 && ENABLE_PROJECT_DIRTY_STATE) +#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW (0 && ENABLE_PROJECT_DIRTY_STATE) #endif // _prusaslicer_technologies_h_ From f1cb529a7b190cc7a9ca9d84b721fa5e91612308 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 7 May 2021 14:17:17 +0200 Subject: [PATCH 36/75] Fixed warnings into ProjectDirtyStateManager --- src/slic3r/GUI/ProjectDirtyStateManager.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 30d41f808..d538b0cf7 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -133,13 +133,13 @@ void ProjectDirtyStateManager::DirtyState::Gizmos::add_used(const UndoRedo::Snap void ProjectDirtyStateManager::DirtyState::Gizmos::remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack) { const std::vector& snapshots = main_stack.snapshots(); - for (auto& [name, gizmo] : used) { - auto it = gizmo.modified_timestamps.begin(); - while (it != gizmo.modified_timestamps.end()) { + for (auto& item : used) { + auto it = item.second.modified_timestamps.begin(); + while (it != item.second.modified_timestamps.end()) { size_t timestamp = *it; auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [timestamp](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == timestamp; }); if (snapshot_it == snapshots.end()) - it = gizmo.modified_timestamps.erase(it); + it = item.second.modified_timestamps.erase(it); else ++it; } @@ -160,8 +160,8 @@ bool ProjectDirtyStateManager::DirtyState::Gizmos::any_used_modified() const // returns true if the given snapshot is contained in any of the gizmos caches bool ProjectDirtyStateManager::DirtyState::Gizmos::is_used_and_modified(const UndoRedo::Snapshot& snapshot) const { - for (auto& [name, gizmo] : used) { - for (size_t i : gizmo.modified_timestamps) { + for (const auto& item : used) { + for (size_t i : item.second.modified_timestamps) { if (i == snapshot.timestamp) return true; } @@ -366,7 +366,6 @@ void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, boost::starts_with(active_snapshot->name, _utf8("Load Project:"))) return; - size_t search_timestamp = 0; if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) { if (type == UpdateType::UndoRedoTo) { std::string topmost_redo; @@ -382,14 +381,12 @@ void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, } m_state.gizmos.current = false; m_last_save.gizmo = 0; - search_timestamp = m_last_save.main; } else if (boost::starts_with(active_snapshot->name, _utf8("Leaving"))) { if (m_state.gizmos.current) m_state.gizmos.add_used(*active_snapshot); m_state.gizmos.current = false; m_last_save.gizmo = 0; - search_timestamp = m_last_save.main; } const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos, m_last_save.main); From 62ad1904e2534a61a55bb69afb3f26a33470899f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 7 May 2021 14:46:10 +0200 Subject: [PATCH 37/75] Fixed warnings into DoExport --- src/libslic3r/GCode.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 4368783a6..d47d185a0 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -611,13 +611,13 @@ std::vector>> GCode::collec // free functions called by GCode::do_export() namespace DoExport { - static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) - { - const GCodeProcessor::Result& 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.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"; - } +// static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) +// { +// const GCodeProcessor::Result& 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.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"; +// } static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector& extruders, PrintStatistics& print_statistics) { From 89da02734e981b174dd76c07c8c79d925ce91a2d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 7 May 2021 15:08:07 +0200 Subject: [PATCH 38/75] ENABLE_ALLOW_NEGATIVE_Z -> Ensure objects on bed when switching to SLA printer --- src/slic3r/GUI/GLCanvas3D.cpp | 12 ++++++------ src/slic3r/GUI/Plater.cpp | 8 ++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c8e402a5d..f99947293 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1945,7 +1945,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh; - PrinterTechnology printer_technology = m_process->current_printer_technology(); + PrinterTechnology printer_technology = current_printer_technology(); int volume_idx_wipe_tower_old = -1; // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). @@ -3572,7 +3572,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) #if ENABLE_ALLOW_NEGATIVE_Z double shift_z = m->get_instance_min_z(i.second); #if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA - if (m_process->current_printer_technology() == ptSLA || shift_z > 0.0) { + if (current_printer_technology() == ptSLA || shift_z > 0.0) { #else if (shift_z > 0.0) { #endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA @@ -3919,7 +3919,7 @@ void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() { std::string new_tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (m_process->current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; m_main_toolbar.set_tooltip(get_main_toolbar_item_id("settings"), new_tooltip); @@ -4073,7 +4073,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) ArrangeSettings &settings_out = get_arrange_settings(); auto &appcfg = wxGetApp().app_config; - PrinterTechnology ptech = m_process->current_printer_technology(); + PrinterTechnology ptech = current_printer_technology(); bool settings_changed = false; float dist_min = 0.f; @@ -4640,7 +4640,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "settings"; item.icon_filename = "settings.svg"; item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (m_process->current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; item.sprite_id = 10; item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; @@ -4682,7 +4682,7 @@ bool GLCanvas3D::_init_main_toolbar() item.sprite_id = 12; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; item.visibility_callback = [this]()->bool { - bool res = m_process->current_printer_technology() == ptFFF; + bool res = current_printer_technology() == ptFFF; // turns off if changing printer technology if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ab1d657e4..9f23de421 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5796,6 +5796,14 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology) //FIXME for SLA synchronize //p->background_process.apply(Model)! +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + if (printer_technology == ptSLA) { + for (ModelObject* model_object : p->model.objects) { + model_object->ensure_on_bed(); + } + } +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export"); p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer"); From f11b9a5b6a9758309e2c656b9475c475873ae108 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 7 May 2021 16:45:37 +0200 Subject: [PATCH 39/75] DiffPresetDialog: Fixed update of the related presets after changing selection of the Printer preset --- src/slic3r/GUI/UnsavedChangesDialog.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 0a0a3dc60..dc2c56246 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -1694,6 +1694,9 @@ void DiffPresetDialog::update_compatibility(const std::string& preset_name, Pres technology_changed = old_printer_technology != new_printer_technology; } + // select preset + presets->select_preset_by_name(preset_name, false); + // Mark the print & filament enabled if they are compatible with the currently selected preset. // The following method should not discard changes of current print or filament presets on change of a printer profile, // if they are compatible with the current printer. From 5828decfc7f3776b000fc16e6401dc649884707c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 10 May 2021 09:32:24 +0200 Subject: [PATCH 40/75] Fixing multi-material printing after recent refactoring (d21b9aa to 1c6333e) --- src/libslic3r/PrintObject.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 8fefd4beb..2987b7a23 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1745,7 +1745,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) } // Make sure all layers contain layer region objects for all regions. for (size_t region_id = 0; region_id < m_region_volumes.size(); ++ region_id) - layer->add_region(&this->print()->get_print_region(region_id)); + layer->add_region(&this->printing_region(region_id)); prev = layer; } } @@ -1793,7 +1793,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) if (spiral_vase) { // Slice the bottom layers with SlicingMode::Regular. // This needs to be in sync with LayerRegion::make_perimeters() spiral_vase! - const PrintRegionConfig &config = this->print()->get_print_region(region_id).config(); + const PrintRegionConfig &config = this->printing_region(region_id).config(); slicing_mode_normal_below_layer = size_t(config.bottom_solid_layers.value); for (; slicing_mode_normal_below_layer < slice_zs.size() && slice_zs[slicing_mode_normal_below_layer] < config.bottom_solid_min_thickness - EPSILON; ++ slicing_mode_normal_below_layer); From a49d34c6f30df5d0d21a1ec6b3e932028c777334 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 May 2021 12:09:44 +0200 Subject: [PATCH 41/75] Fix of #5437 - Make it clear on the G-code slider that color change is not supported for sequential print --- src/slic3r/GUI/DoubleSlider.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 84a499d6f..655ef496b 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -575,7 +575,10 @@ void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_ else is_horizontal() ? y_draw = pt_beg.y - m_tick_icon_dim-2 : x_draw = pt_end.x + 3; - dc.DrawBitmap(*icon, x_draw, y_draw); + if (m_draw_mode == dmSequentialFffPrint) + dc.DrawBitmap(create_scaled_bitmap("colorchange_add", nullptr, 16, true), x_draw, y_draw); + else + dc.DrawBitmap(*icon, x_draw, y_draw); //update rect of the tick action icon m_rect_tick_action = wxRect(x_draw, y_draw, m_tick_icon_dim, m_tick_icon_dim); @@ -591,7 +594,7 @@ void Control::draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, const Selec dc.DrawLine(pt_beg, pt_end); //draw action icon - if (m_draw_mode == dmRegular) + if (m_draw_mode == dmRegular || m_draw_mode == dmSequentialFffPrint) draw_action_icon(dc, pt_beg, pt_end); } } @@ -1377,6 +1380,10 @@ wxString Control::get_tooltip(int tick/*=-1*/) if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist { + if (m_draw_mode == dmSequentialFffPrint) + return _L("The sequential print is on.\n" + "It's impossible to apply any custom G-code for objects printing sequentually.\n"); + // Show mode as a first string of tooltop tooltip = " " + _L("Print mode") + ": "; tooltip += (m_mode == SingleExtruder ? SingleExtruderMode : From 137dbbd19f387f08b22a94672957015b27359062 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 10 May 2021 13:06:13 +0200 Subject: [PATCH 42/75] Fixed crash into ProjectDirtyStateManager::update_from_undo_redo_stack() when switching language --- src/libslic3r/Technologies.hpp | 2 +- src/slic3r/GUI/ProjectDirtyStateManager.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 480ca0ee5..386728efb 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -49,7 +49,7 @@ #define ENABLE_RELOAD_FROM_DISK_FOR_3MF (1 && ENABLE_2_4_0_ALPHA0) // Removes obsolete warning texture code #define ENABLE_WARNING_TEXTURE_REMOVAL (1 && ENABLE_2_4_0_ALPHA0) -// Enable showing gcode line numbers in previeww horizontal slider +// Enable showing gcode line numbers in preview horizontal slider #define ENABLE_GCODE_LINES_ID_IN_H_SLIDER (1 && ENABLE_2_4_0_ALPHA0) // Enable validation of custom gcode against gcode processor reserved keywords #define ENABLE_VALIDATE_CUSTOM_GCODE (1 && ENABLE_2_4_0_ALPHA0) diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index d538b0cf7..9ff4821c2 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -180,6 +180,9 @@ void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type) return; const Plater* plater = wxGetApp().plater(); + if (plater == nullptr) + return; + const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main(); const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active(); From ca8a42c8b12ab4d212497223778ea79228ff91da Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 10 May 2021 14:45:17 +0200 Subject: [PATCH 43/75] Tech ENABLE_SPLITTED_VERTEX_BUFFER set as default --- src/libslic3r/Technologies.hpp | 4 +- src/slic3r/GUI/GCodeViewer.cpp | 1364 +------------------------------- src/slic3r/GUI/GCodeViewer.hpp | 56 -- src/slic3r/GUI/GLCanvas3D.cpp | 4 - 4 files changed, 2 insertions(+), 1426 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 386728efb..f51241acc 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -41,10 +41,8 @@ //==================== #define ENABLE_2_4_0_ALPHA0 1 -// Enable splitting of vertex buffers used to render toolpaths -#define ENABLE_SPLITTED_VERTEX_BUFFER (1 && ENABLE_2_4_0_ALPHA0) // Enable rendering only starting and final caps for toolpaths -#define ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS (1 && ENABLE_SPLITTED_VERTEX_BUFFER) +#define ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS (1 && ENABLE_2_4_0_ALPHA0) // Enable reload from disk command for 3mf files #define ENABLE_RELOAD_FROM_DISK_FOR_3MF (1 && ENABLE_2_4_0_ALPHA0) // Removes obsolete warning texture code diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 233bdf1cd..7ccf10795 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -84,7 +84,6 @@ static float round_to_nearest(float value, unsigned int decimals) return res; } -#if ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::VBuffer::reset() { // release gpu memory @@ -95,38 +94,16 @@ void GCodeViewer::VBuffer::reset() sizes.clear(); count = 0; } -#else -void GCodeViewer::VBuffer::reset() -{ - // release gpu memory - if (id > 0) { - glsafe(::glDeleteBuffers(1, &id)); - id = 0; - } - - count = 0; -} -#endif // ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::IBuffer::reset() { -#if ENABLE_SPLITTED_VERTEX_BUFFER // release gpu memory if (ibo > 0) { glsafe(::glDeleteBuffers(1, &ibo)); ibo = 0; } -#else - // release gpu memory - if (id > 0) { - glsafe(::glDeleteBuffers(1, &id)); - id = 0; - } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER -#if ENABLE_SPLITTED_VERTEX_BUFFER vbo = 0; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER count = 0; } @@ -149,17 +126,10 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const #endif // ENABLE_SEAMS_VISUALIZATION case EMoveType::Extrude: { // use rounding to reduce the number of generated paths -#if ENABLE_SPLITTED_VERTEX_BUFFER return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && move.position[2] <= sub_paths.front().first.position[2] && feedrate == move.feedrate && fan_speed == move.fan_speed && height == round_to_nearest(move.height, 2) && width == round_to_nearest(move.width, 2) && matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); -#else - return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && - move.position[2] <= first.position[2] && feedrate == move.feedrate && fan_speed == move.fan_speed && - height == round_to_nearest(move.height, 2) && width == round_to_nearest(move.width, 2) && - matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); -#endif // ENABLE_SPLITTED_VERTEX_BUFFER } case EMoveType::Travel: { return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; @@ -186,17 +156,10 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsi { Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; // use rounding to reduce the number of generated paths -#if ENABLE_SPLITTED_VERTEX_BUFFER paths.push_back({ move.type, move.extrusion_role, move.delta_extruder, round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed, move.temperature, move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); -#else - paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, - round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), - move.feedrate, move.fan_speed, move.temperature, - move.volumetric_rate(), move.extruder_id, move.cp_color_id }); -#endif // ENABLE_SPLITTED_VERTEX_BUFFER } GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const @@ -881,12 +844,10 @@ void GCodeViewer::render() const #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#if ENABLE_SPLITTED_VERTEX_BUFFER bool GCodeViewer::can_export_toolpaths() const { return has_data() && m_buffers[buffer_id(EMoveType::Extrude)].render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle; } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned int last) { @@ -894,12 +855,8 @@ void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned in for (const TBuffer& buffer : m_buffers) { if (buffer.visible) { for (const Path& path : buffer.paths) { -#if ENABLE_SPLITTED_VERTEX_BUFFER if (path.sub_paths.front().first.s_id <= id && id <= path.sub_paths.back().last.s_id) -#else - if (path.first.s_id <= id && id <= path.last.s_id) -#endif // ENABLE_SPLITTED_VERTEX_BUFFER - return true; + return true; } } } @@ -1021,20 +978,16 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const if (!t_buffer.has_data()) return; -#if ENABLE_SPLITTED_VERTEX_BUFFER if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) return; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER // collect color information to generate materials std::vector colors; for (const RenderPath& path : t_buffer.render_paths) { colors.push_back(path.color); } -#if ENABLE_SPLITTED_VERTEX_BUFFER std::sort(colors.begin(), colors.end()); colors.erase(std::unique(colors.begin(), colors.end()), colors.end()); -#endif // ENABLE_SPLITTED_VERTEX_BUFFER // save materials file boost::filesystem::path mat_filename(filename); @@ -1069,7 +1022,6 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str()); -#if ENABLE_SPLITTED_VERTEX_BUFFER const size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats(); std::vector out_vertices; @@ -1155,253 +1107,10 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const } ++i; } -#else - // get vertices data from vertex buffer on gpu - size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats(); - VertexBuffer vertices = VertexBuffer(t_buffer.vertices.count * floats_per_vertex); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, t_buffer.vertices.id)); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, t_buffer.vertices.data_size_bytes(), vertices.data())); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - // get indices data from index buffer on gpu - MultiIndexBuffer indices; - for (size_t i = 0; i < t_buffer.indices.size(); ++i) { - indices.push_back(IndexBuffer(t_buffer.indices[i].count)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, t_buffer.indices[i].id)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast(indices.back().size() * sizeof(unsigned int)), indices.back().data())); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - - auto get_vertex = [&vertices, floats_per_vertex](unsigned int id) { - // extract vertex from vector of floats - unsigned int base_id = id * floats_per_vertex; - return Vec3f(vertices[base_id + 0], vertices[base_id + 1], vertices[base_id + 2]); - }; - - struct Segment - { - Vec3f v1; - Vec3f v2; - Vec3f dir; - Vec3f right; - Vec3f up; - Vec3f rl_displacement; - Vec3f tb_displacement; - float length; - }; - - auto generate_segment = [get_vertex](unsigned int start_id, unsigned int end_id, float half_width, float half_height) { - auto local_basis = [](const Vec3f& dir) { - // calculate local basis (dir, right, up) on given segment - std::array ret; - ret[0] = dir.normalized(); - if (std::abs(ret[0][2]) < EPSILON) { - // segment parallel to XY plane - ret[1] = { ret[0][1], -ret[0][0], 0.0f }; - ret[2] = Vec3f::UnitZ(); - } - else if (std::abs(std::abs(ret[0].dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) { - // segment parallel to Z axis - ret[1] = Vec3f::UnitX(); - ret[2] = Vec3f::UnitY(); - } - else { - ret[0] = dir.normalized(); - ret[1] = ret[0].cross(Vec3f::UnitZ()).normalized(); - ret[2] = ret[1].cross(ret[0]); - } - return ret; - }; - - Vec3f v1 = get_vertex(start_id) - half_height * Vec3f::UnitZ(); - Vec3f v2 = get_vertex(end_id) - half_height * Vec3f::UnitZ(); - float length = (v2 - v1).norm(); - const auto&& [dir, right, up] = local_basis(v2 - v1); - return Segment({ v1, v2, dir, right, up, half_width * right, half_height * up, length }); - }; - - size_t out_vertices_count = 0; - unsigned int indices_per_segment = t_buffer.indices_per_segment(); - unsigned int start_vertex_offset = t_buffer.start_segment_vertex_offset(); - unsigned int end_vertex_offset = t_buffer.end_segment_vertex_offset(); - - size_t i = 0; - for (const RenderPath& render_path : t_buffer.render_paths) { - // get paths segments from buffer paths - const IndexBuffer& ibuffer = indices[render_path.ibuffer_id]; - const Path& path = t_buffer.paths[render_path.path_id]; - - float half_width = 0.5f * path.width; - // clamp height to avoid artifacts due to z-fighting when importing the obj file into blender and similar - float half_height = std::max(0.5f * path.height, 0.005f); - - // generates vertices/normals/triangles - std::vector out_vertices; - std::vector out_normals; - using Triangle = std::array; - std::vector out_triangles; - for (size_t j = 0; j < render_path.offsets.size(); ++j) { - unsigned int start = static_cast(render_path.offsets[j] / sizeof(unsigned int)); - unsigned int end = start + render_path.sizes[j]; - - for (size_t k = start; k < end; k += static_cast(indices_per_segment)) { - Segment curr = generate_segment(ibuffer[k + start_vertex_offset], ibuffer[k + end_vertex_offset], half_width, half_height); - if (k == start) { - // starting endpoint vertices/normals - out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right - out_vertices.push_back(curr.v1 + curr.tb_displacement); out_normals.push_back(curr.up); // top - out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left - out_vertices.push_back(curr.v1 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom - out_vertices_count += 4; - - // starting cap triangles - size_t base_id = out_vertices_count - 4 + 1; - out_triangles.push_back({ base_id + 0, base_id + 1, base_id + 2 }); - out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 3 }); - } - else { - // for the endpoint shared by the current and the previous segments - // we keep the top and bottom vertices of the previous vertices - // and add new left/right vertices for the current segment - out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right - out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left - out_vertices_count += 2; - - size_t first_vertex_id = k - static_cast(indices_per_segment); - Segment prev = generate_segment(ibuffer[first_vertex_id + start_vertex_offset], ibuffer[first_vertex_id + end_vertex_offset], half_width, half_height); - float disp = 0.0f; - float cos_dir = prev.dir.dot(curr.dir); - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - Vec3f med_dir = (prev.dir + curr.dir).normalized(); - disp = half_width * ::tan(::acos(std::clamp(curr.dir.dot(med_dir), -1.0f, 1.0f))); - } - - Vec3f disp_vec = disp * prev.dir; - - bool is_right_turn = prev.up.dot(prev.dir.cross(curr.dir)) <= 0.0f; - if (cos_dir < 0.7071068f) { - // if the angle between two consecutive segments is greater than 45 degrees - // we add a cap in the outside corner - // and displace the vertices in the inside corner to the same position, if possible - if (is_right_turn) { - // corner cap triangles (left) - size_t base_id = out_vertices_count - 6 + 1; - out_triangles.push_back({ base_id + 5, base_id + 2, base_id + 1 }); - out_triangles.push_back({ base_id + 5, base_id + 3, base_id + 2 }); - - // update right vertices - if (disp > 0.0f && disp < prev.length && disp < curr.length) { - base_id = out_vertices.size() - 6; - out_vertices[base_id + 0] -= disp_vec; - out_vertices[base_id + 4] = out_vertices[base_id + 0]; - } - } - else { - // corner cap triangles (right) - size_t base_id = out_vertices_count - 6 + 1; - out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 1 }); - out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 4 }); - - // update left vertices - if (disp > 0.0f && disp < prev.length && disp < curr.length) { - base_id = out_vertices.size() - 6; - out_vertices[base_id + 2] -= disp_vec; - out_vertices[base_id + 5] = out_vertices[base_id + 2]; - } - } - } - else { - // if the angle between two consecutive segments is lesser than 45 degrees - // displace the vertices to the same position - if (is_right_turn) { - size_t base_id = out_vertices.size() - 6; - // right - out_vertices[base_id + 0] -= disp_vec; - out_vertices[base_id + 4] = out_vertices[base_id + 0]; - // left - out_vertices[base_id + 2] += disp_vec; - out_vertices[base_id + 5] = out_vertices[base_id + 2]; - } - else { - size_t base_id = out_vertices.size() - 6; - // right - out_vertices[base_id + 0] += disp_vec; - out_vertices[base_id + 4] = out_vertices[base_id + 0]; - // left - out_vertices[base_id + 2] -= disp_vec; - out_vertices[base_id + 5] = out_vertices[base_id + 2]; - } - } - } - - // current second endpoint vertices/normals - out_vertices.push_back(curr.v2 + curr.rl_displacement); out_normals.push_back(curr.right); // right - out_vertices.push_back(curr.v2 + curr.tb_displacement); out_normals.push_back(curr.up); // top - out_vertices.push_back(curr.v2 - curr.rl_displacement); out_normals.push_back(-curr.right); // left - out_vertices.push_back(curr.v2 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom - out_vertices_count += 4; - - // sides triangles - if (k == start) { - size_t base_id = out_vertices_count - 8 + 1; - out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 5 }); - out_triangles.push_back({ base_id + 0, base_id + 5, base_id + 1 }); - out_triangles.push_back({ base_id + 1, base_id + 5, base_id + 6 }); - out_triangles.push_back({ base_id + 1, base_id + 6, base_id + 2 }); - out_triangles.push_back({ base_id + 2, base_id + 6, base_id + 7 }); - out_triangles.push_back({ base_id + 2, base_id + 7, base_id + 3 }); - out_triangles.push_back({ base_id + 3, base_id + 7, base_id + 4 }); - out_triangles.push_back({ base_id + 3, base_id + 4, base_id + 0 }); - } - else { - size_t base_id = out_vertices_count - 10 + 1; - out_triangles.push_back({ base_id + 4, base_id + 6, base_id + 7 }); - out_triangles.push_back({ base_id + 4, base_id + 7, base_id + 1 }); - out_triangles.push_back({ base_id + 1, base_id + 7, base_id + 8 }); - out_triangles.push_back({ base_id + 1, base_id + 8, base_id + 5 }); - out_triangles.push_back({ base_id + 5, base_id + 8, base_id + 9 }); - out_triangles.push_back({ base_id + 5, base_id + 9, base_id + 3 }); - out_triangles.push_back({ base_id + 3, base_id + 9, base_id + 6 }); - out_triangles.push_back({ base_id + 3, base_id + 6, base_id + 4 }); - } - - if (k + 2 == end) { - // ending cap triangles - size_t base_id = out_vertices_count - 4 + 1; - out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 1 }); - out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 2 }); - } - } - } - - // save to file - fprintf(fp, "\n# vertices path %zu\n", i + 1); - for (const Vec3f& v : out_vertices) { - fprintf(fp, "v %g %g %g\n", v[0], v[1], v[2]); - } - - fprintf(fp, "\n# normals path %zu\n", i + 1); - for (const Vec3f& n : out_normals) { - fprintf(fp, "vn %g %g %g\n", n[0], n[1], n[2]); - } - - fprintf(fp, "\n# material path %zu\n", i + 1); - fprintf(fp, "usemtl material_%zu\n", i + 1); - - fprintf(fp, "\n# triangles path %zu\n", i + 1); - for (const Triangle& t : out_triangles) { - fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", t[0], t[0], t[1], t[1], t[2], t[2]); - } - - ++ i; - } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER fclose(fp); } -#if ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { // max index buffer size, in bytes @@ -2265,667 +1974,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (progress_dialog != nullptr) progress_dialog->Destroy(); } -#else -void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) -{ -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); - m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessor::MoveVertex); - m_statistics.results_time = gcode_result.time; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // vertices data - m_moves_count = gcode_result.moves.size(); - if (m_moves_count == 0) - return; - - unsigned int progress_count = 0; - static const unsigned int progress_threshold = 1000; - wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ? - new wxProgressDialog(_L("Generating toolpaths"), "...", - 100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr; - - m_extruders_count = gcode_result.extruders_count; - - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; - if (wxGetApp().is_gcode_viewer()) - // for the gcode viewer we need all moves to correctly size the printbed - m_paths_bounding_box.merge(move.position.cast()); - else { - if (move.type == EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) - m_paths_bounding_box.merge(move.position.cast()); - } - } - - // max bounding box (account for tool marker) - m_max_bounding_box = m_paths_bounding_box; - m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ()); - - auto log_memory_usage = [this](const std::string& label, const std::vector& vertices, const std::vector& indices) { - int64_t vertices_size = 0; - for (size_t i = 0; i < vertices.size(); ++i) { - vertices_size += SLIC3R_STDVEC_MEMSIZE(vertices[i], float); - } - int64_t indices_size = 0; - for (size_t i = 0; i < indices.size(); ++i) { - for (size_t j = 0; j < indices[i].size(); ++j) { - indices_size += SLIC3R_STDVEC_MEMSIZE(indices[i][j], unsigned int); - } - } - log_memory_used(label, vertices_size + indices_size); - }; - - // format data into the buffers to be rendered as points - auto add_vertices_as_point = [](const GCodeProcessor::MoveVertex& curr, VertexBuffer& vertices) { - vertices.push_back(curr.position[0]); - vertices.push_back(curr.position[1]); - vertices.push_back(curr.position[2]); - }; - auto add_indices_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - buffer.add_path(curr, ibuffer_id, indices.size(), move_id); - indices.push_back(static_cast(indices.size())); - }; - - // format data into the buffers to be rendered as lines - auto add_vertices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, - VertexBuffer& vertices) { - // x component of the normal to the current segment (the normal is parallel to the XY plane) - float normal_x = (curr.position - prev.position).normalized()[1]; - - auto add_vertex = [&vertices, normal_x](const GCodeProcessor::MoveVertex& vertex) { - // add position - vertices.push_back(vertex.position[0]); - vertices.push_back(vertex.position[1]); - vertices.push_back(vertex.position[2]); - // add normal x component - vertices.push_back(normal_x); - }; - - // add previous vertex - add_vertex(prev); - // add current vertex - add_vertex(curr); - }; - auto add_indices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - // add starting index - indices.push_back(static_cast(indices.size())); - buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1); - buffer.paths.back().first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - if (last_path.first.i_id != last_path.last.i_id) { - // add previous index - indices.push_back(static_cast(indices.size())); - } - - // add current index - indices.push_back(static_cast(indices.size())); - last_path.last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; - }; - - // format data into the buffers to be rendered as solid - auto add_vertices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - VertexBuffer& vertices, size_t move_id) { - static Vec3f prev_dir; - static Vec3f prev_up; - static float prev_length; - auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { - // append position - vertices.push_back(position[0]); - vertices.push_back(position[1]); - vertices.push_back(position[2]); - // append normal - vertices.push_back(normal[0]); - vertices.push_back(normal[1]); - vertices.push_back(normal[2]); - }; - auto extract_position_at = [](const VertexBuffer& vertices, size_t id) { - return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]); - }; - auto update_position_at = [](VertexBuffer& vertices, size_t id, const Vec3f& position) { - vertices[id + 0] = position[0]; - vertices[id + 1] = position[1]; - vertices[id + 2] = position[2]; - }; - - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, 0, 0, move_id - 1); - buffer.paths.back().first.position = prev.position; - } - - unsigned int starting_vertices_size = static_cast(vertices.size() / buffer.vertices.vertex_size_floats()); - - Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); - Vec3f left = -right; - Vec3f up = right.cross(dir); - Vec3f down = -up; - - Path& last_path = buffer.paths.back(); - - float half_width = 0.5f * last_path.width; - float half_height = 0.5f * last_path.height; - - Vec3f prev_pos = prev.position - half_height * up; - Vec3f curr_pos = curr.position - half_height * up; - - float length = (curr_pos - prev_pos).norm(); - if (last_path.vertices_count() == 1) { - // 1st segment - - // vertices 1st endpoint - store_vertex(vertices, prev_pos + half_height * up, up); - store_vertex(vertices, prev_pos + half_width * right, right); - store_vertex(vertices, prev_pos + half_height * down, down); - store_vertex(vertices, prev_pos + half_width * left, left); - - // vertices 2nd endpoint - store_vertex(vertices, curr_pos + half_height * up, up); - store_vertex(vertices, curr_pos + half_width * right, right); - store_vertex(vertices, curr_pos + half_height * down, down); - store_vertex(vertices, curr_pos + half_width * left, left); - } - else { - // any other segment - float displacement = 0.0f; - float cos_dir = prev_dir.dot(dir); - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - Vec3f med_dir = (prev_dir + dir).normalized(); - displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); - } - - Vec3f displacement_vec = displacement * prev_dir; - bool can_displace = displacement > 0.0f && displacement < prev_length&& displacement < length; - - size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats(); - size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats(); - Vec3f prev_right_pos = extract_position_at(vertices, prev_right_id); - Vec3f prev_left_pos = extract_position_at(vertices, prev_left_id); - - bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; - // whether the angle between adjacent segments is greater than 45 degrees - bool is_sharp = cos_dir < 0.7071068f; - - bool right_displaced = false; - bool left_displaced = false; - - // displace the vertex (inner with respect to the corner) of the previous segment 2nd enpoint, if possible - if (can_displace) { - if (is_right_turn) { - prev_right_pos -= displacement_vec; - update_position_at(vertices, prev_right_id, prev_right_pos); - right_displaced = true; - } - else { - prev_left_pos -= displacement_vec; - update_position_at(vertices, prev_left_id, prev_left_pos); - left_displaced = true; - } - } - - if (!is_sharp) { - // displace the vertex (outer with respect to the corner) of the previous segment 2nd enpoint, if possible - if (can_displace) { - if (is_right_turn) { - prev_left_pos += displacement_vec; - update_position_at(vertices, prev_left_id, prev_left_pos); - left_displaced = true; - } - else { - prev_right_pos += displacement_vec; - update_position_at(vertices, prev_right_id, prev_right_pos); - right_displaced = true; - } - } - - // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) - // vertices position matches that of the previous segment 2nd endpoint, if displaced - store_vertex(vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); - store_vertex(vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); - } - else { - // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) - // the inner corner vertex position matches that of the previous segment 2nd endpoint, if displaced - if (is_right_turn) { - store_vertex(vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); - store_vertex(vertices, prev_pos + half_width * left, left); - } - else { - store_vertex(vertices, prev_pos + half_width * right, right); - store_vertex(vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); - } - } - - // vertices 2nd endpoint - store_vertex(vertices, curr_pos + half_height * up, up); - store_vertex(vertices, curr_pos + half_width * right, right); - store_vertex(vertices, curr_pos + half_height * down, down); - store_vertex(vertices, curr_pos + half_width * left, left); - } - - last_path.last = { 0, 0, move_id, curr.position }; - prev_dir = dir; - prev_up = up; - prev_length = length; - }; - auto add_indices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - size_t& buffer_vertices_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - static Vec3f prev_dir; - static Vec3f prev_up; - static float prev_length; - auto store_triangle = [](IndexBuffer& indices, unsigned int i1, unsigned int i2, unsigned int i3) { - indices.push_back(i1); - indices.push_back(i2); - indices.push_back(i3); - }; - auto append_dummy_cap = [store_triangle](IndexBuffer& indices, unsigned int id) { - store_triangle(indices, id, id, id); - store_triangle(indices, id, id, id); - }; - - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); - buffer.paths.back().first.position = prev.position; - } - - Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); - Vec3f up = right.cross(dir); - - Path& last_path = buffer.paths.back(); - - float half_width = 0.5f * last_path.width; - float half_height = 0.5f * last_path.height; - - Vec3f prev_pos = prev.position - half_height * up; - Vec3f curr_pos = curr.position - half_height * up; - - float length = (curr_pos - prev_pos).norm(); - if (last_path.vertices_count() == 1) { - // 1st segment - - // triangles starting cap - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 2, buffer_vertices_size + 1); - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 3, buffer_vertices_size + 2); - - // dummy triangles outer corner cap - append_dummy_cap(indices, buffer_vertices_size); - - // triangles sides - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 1, buffer_vertices_size + 4); - store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 5, buffer_vertices_size + 4); - store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 2, buffer_vertices_size + 5); - store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 6, buffer_vertices_size + 5); - store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 3, buffer_vertices_size + 6); - store_triangle(indices, buffer_vertices_size + 3, buffer_vertices_size + 7, buffer_vertices_size + 6); - store_triangle(indices, buffer_vertices_size + 3, buffer_vertices_size + 0, buffer_vertices_size + 7); - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 4, buffer_vertices_size + 7); - - // triangles ending cap - store_triangle(indices, buffer_vertices_size + 4, buffer_vertices_size + 6, buffer_vertices_size + 7); - store_triangle(indices, buffer_vertices_size + 4, buffer_vertices_size + 5, buffer_vertices_size + 6); - - buffer_vertices_size += 8; - } - else { - // any other segment - float displacement = 0.0f; - float cos_dir = prev_dir.dot(dir); - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - Vec3f med_dir = (prev_dir + dir).normalized(); - displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); - } - - Vec3f displacement_vec = displacement * prev_dir; - bool can_displace = displacement > 0.0f && displacement < prev_length && displacement < length; - - bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; - // whether the angle between adjacent segments is greater than 45 degrees - bool is_sharp = cos_dir < 0.7071068f; - - bool right_displaced = false; - bool left_displaced = false; - - if (!is_sharp) { - if (can_displace) { - if (is_right_turn) - left_displaced = true; - else - right_displaced = true; - } - } - - // triangles starting cap - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size - 2, buffer_vertices_size + 0); - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 1, buffer_vertices_size - 2); - - // triangles outer corner cap - if (is_right_turn) { - if (left_displaced) - // dummy triangles - append_dummy_cap(indices, buffer_vertices_size); - else { - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 1, buffer_vertices_size - 1); - store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size - 2, buffer_vertices_size - 1); - } - } - else { - if (right_displaced) - // dummy triangles - append_dummy_cap(indices, buffer_vertices_size); - else { - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size - 3, buffer_vertices_size + 0); - store_triangle(indices, buffer_vertices_size - 3, buffer_vertices_size - 2, buffer_vertices_size + 0); - } - } - - // triangles sides - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 0, buffer_vertices_size + 2); - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 3, buffer_vertices_size + 2); - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size - 2, buffer_vertices_size + 3); - store_triangle(indices, buffer_vertices_size - 2, buffer_vertices_size + 4, buffer_vertices_size + 3); - store_triangle(indices, buffer_vertices_size - 2, buffer_vertices_size + 1, buffer_vertices_size + 4); - store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 5, buffer_vertices_size + 4); - store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size - 4, buffer_vertices_size + 5); - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 2, buffer_vertices_size + 5); - - // triangles ending cap - store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 4, buffer_vertices_size + 5); - store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 3, buffer_vertices_size + 4); - - buffer_vertices_size += 6; - } - - last_path.last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; - prev_dir = dir; - prev_up = up; - prev_length = length; - }; - - wxBusyCursor busy; - - // to reduce the peak in memory usage, we split the generation of the vertex and index buffers in two steps. - // the data are deleted as soon as they are sent to the gpu. - std::vector vertices(m_buffers.size()); - std::vector indices(m_buffers.size()); - std::vector options_zs; - - // toolpaths data -> extract vertices from result - for (size_t i = 0; i < m_moves_count; ++i) { - // skip first vertex - if (i == 0) - continue; - - ++progress_count; - if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { - progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), - _L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog->Fit(); - progress_count = 0; - } - - const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; - const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; - - unsigned char id = buffer_id(curr.type); - TBuffer& buffer = m_buffers[id]; - VertexBuffer& buffer_vertices = vertices[id]; - - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { - add_vertices_as_point(curr, buffer_vertices); - break; - } - case TBuffer::ERenderPrimitiveType::Line: { - add_vertices_as_line(prev, curr, buffer_vertices); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { - add_vertices_as_solid(prev, curr, buffer, buffer_vertices, i); - break; - } - } - - if (curr.type == EMoveType::Pause_Print || curr.type == EMoveType::Custom_GCode) { - const float* const last_z = options_zs.empty() ? nullptr : &options_zs.back(); - if (last_z == nullptr || curr.position[2] < *last_z - EPSILON || *last_z + EPSILON < curr.position[2]) - options_zs.emplace_back(curr.position[2]); - } - } - - // move the wipe toolpaths half height up to render them on proper position - VertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)]; - for (size_t i = 2; i < wipe_vertices.size(); i += 3) { - wipe_vertices[i] += 0.5f * GCodeProcessor::Wipe_Height; - } - - log_memory_usage("Loaded G-code generated vertex buffers, ", vertices, indices); - - // toolpaths data -> send vertices data to gpu - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& buffer = m_buffers[i]; - - const VertexBuffer& buffer_vertices = vertices[i]; - buffer.vertices.count = buffer_vertices.size() / buffer.vertices.vertex_size_floats(); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_vertices_gpu_size += buffer_vertices.size() * sizeof(float); - m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast(buffer_vertices.size() * sizeof(float))); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (buffer.vertices.count > 0) { -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.vbuffers_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - glsafe(::glGenBuffers(1, &buffer.vertices.id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer_vertices.size() * sizeof(float), buffer_vertices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - } - - // dismiss vertices data, no more needed - std::vector().swap(vertices); - - // toolpaths data -> extract indices from result - // paths may have been filled while extracting vertices, - // so reset them, they will be filled again while extracting indices - for (TBuffer& buffer : m_buffers) { - buffer.paths.clear(); - } - - // max index buffer size - const size_t IBUFFER_THRESHOLD = 1024 * 1024 * 32; - - // variable used to keep track of the current size (in vertices) of the vertex buffer - std::vector curr_buffer_vertices_size(m_buffers.size(), 0); - for (size_t i = 0; i < m_moves_count; ++i) { - // skip first vertex - if (i == 0) - continue; - - ++progress_count; - if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { - progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), - _L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog->Fit(); - progress_count = 0; - } - - const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; - const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; - - unsigned char id = buffer_id(curr.type); - TBuffer& buffer = m_buffers[id]; - MultiIndexBuffer& buffer_indices = indices[id]; - if (buffer_indices.empty()) - buffer_indices.push_back(IndexBuffer()); - - // if adding the indices for the current segment exceeds the threshold size of the current index buffer - // create another index buffer, and move the current path indices into it - if (buffer_indices.back().size() >= IBUFFER_THRESHOLD - static_cast(buffer.indices_per_segment())) { - buffer_indices.push_back(IndexBuffer()); - if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) { - if (!(prev.type != curr.type || !buffer.paths.back().matches(curr))) { - Path& last_path = buffer.paths.back(); - size_t delta_id = last_path.last.i_id - last_path.first.i_id; - - // move indices of the last path from the previous into the new index buffer - IndexBuffer& src_buffer = buffer_indices[buffer_indices.size() - 2]; - IndexBuffer& dst_buffer = buffer_indices[buffer_indices.size() - 1]; - std::move(src_buffer.begin() + last_path.first.i_id, src_buffer.end(), std::back_inserter(dst_buffer)); - src_buffer.erase(src_buffer.begin() + last_path.first.i_id, src_buffer.end()); - - // updates path indices - last_path.first.b_id = buffer_indices.size() - 1; - last_path.first.i_id = 0; - last_path.last.b_id = buffer_indices.size() - 1; - last_path.last.i_id = delta_id; - } - } - } - - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { - add_indices_as_point(curr, buffer, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); - break; - } - case TBuffer::ERenderPrimitiveType::Line: { - add_indices_as_line(prev, curr, buffer, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { - add_indices_as_solid(prev, curr, buffer, curr_buffer_vertices_size[id], static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); - break; - } - } - } - - if (progress_dialog != nullptr) { - progress_dialog->Update(100, ""); - progress_dialog->Fit(); - } - - log_memory_usage("Loaded G-code generated indices buffers, ", vertices, indices); - - // toolpaths data -> send indices data to gpu - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& buffer = m_buffers[i]; - - for (size_t j = 0; j < indices[i].size(); ++j) { - const IndexBuffer& buffer_indices = indices[i][j]; - buffer.indices.push_back(IBuffer()); - IBuffer& ibuffer = buffer.indices.back(); - ibuffer.count = buffer_indices.size(); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_indices_gpu_size += ibuffer.count * sizeof(unsigned int); - m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast(ibuffer.count * sizeof(unsigned int))); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (ibuffer.count > 0) { -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.ibuffers_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - glsafe(::glGenBuffers(1, &ibuffer.id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer_indices.size() * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - } - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - for (const TBuffer& buffer : m_buffers) { - m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); - } - unsigned int travel_buffer_id = buffer_id(EMoveType::Travel); - const MultiIndexBuffer& travel_buffer_indices = indices[travel_buffer_id]; - for (size_t i = 0; i < travel_buffer_indices.size(); ++i) { - m_statistics.travel_segments_count += travel_buffer_indices[i].size() / m_buffers[travel_buffer_id].indices_per_segment(); - } - unsigned int wipe_buffer_id = buffer_id(EMoveType::Wipe); - const MultiIndexBuffer& wipe_buffer_indices = indices[wipe_buffer_id]; - for (size_t i = 0; i < wipe_buffer_indices.size(); ++i) { - m_statistics.wipe_segments_count += wipe_buffer_indices[i].size() / m_buffers[wipe_buffer_id].indices_per_segment(); - } - unsigned int extrude_buffer_id = buffer_id(EMoveType::Extrude); - const MultiIndexBuffer& extrude_buffer_indices = indices[extrude_buffer_id]; - for (size_t i = 0; i < extrude_buffer_indices.size(); ++i) { - m_statistics.extrude_segments_count += extrude_buffer_indices[i].size() / m_buffers[extrude_buffer_id].indices_per_segment(); - } -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // dismiss indices data, no more needed - std::vector().swap(indices); - - // layers zs / roles / extruder ids / cp color ids -> extract from result - size_t last_travel_s_id = 0; - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; - if (move.type == EMoveType::Extrude) { - // layers zs - const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back(); - double z = static_cast(move.position[2]); - if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) - m_layers.append(z, { last_travel_s_id, i }); - else - m_layers.get_endpoints().back().last = i; - // extruder ids - m_extruder_ids.emplace_back(move.extruder_id); - // roles - if (i > 0) - m_roles.emplace_back(move.extrusion_role); - } - else if (move.type == EMoveType::Travel) { - if (i - last_travel_s_id > 1 && !m_layers.empty()) - m_layers.get_endpoints().back().last = i; - - last_travel_s_id = i; - } - } - - // set layers z range - if (!m_layers.empty()) - m_layers_z_range = { 0, static_cast(m_layers.size() - 1) }; - - // change color of paths whose layer contains option points - if (!options_zs.empty()) { - TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - for (Path& path : extrude_buffer.paths) { - float z = path.first.position[2]; - if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end()) - path.cp_color_id = 255 - path.cp_color_id; - } - } - - // roles -> remove duplicates - std::sort(m_roles.begin(), m_roles.end()); - m_roles.erase(std::unique(m_roles.begin(), m_roles.end()), m_roles.end()); - m_roles.shrink_to_fit(); - - // extruder ids -> remove duplicates - std::sort(m_extruder_ids.begin(), m_extruder_ids.end()); - m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end()); - m_extruder_ids.shrink_to_fit(); - - log_memory_usage("Loaded G-code generated extrusion paths, ", vertices, indices); - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (progress_dialog != nullptr) - progress_dialog->Destroy(); -} -#endif // ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::load_shells(const Print& print, bool initialized) { @@ -2981,7 +2029,6 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) } } -#if ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const { #if ENABLE_GCODE_VIEWER_STATISTICS @@ -3444,261 +2491,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#else -void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const -{ -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - auto extrusion_color = [this](const Path& path) { - Color color; - switch (m_view_type) - { - case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } - case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; } - case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } - case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } - case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } - case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } - case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } - case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } - case EViewType::ColorPrint: { - if (path.cp_color_id >= static_cast(m_tool_colors.size())) { - color = { 0.5f, 0.5f, 0.5f }; -// // complementary color -// color = m_tool_colors[255 - path.cp_color_id]; -// color = { 1.0f - color[0], 1.0f - color[1], 1.0f - color[2] }; - } - else - color = m_tool_colors[path.cp_color_id]; - - break; - } - default: { color = { 1.0f, 1.0f, 1.0f }; break; } - } - - return color; - }; - - auto travel_color = [this](const Path& path) { - return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : - ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : - Travel_Colors[0] /* Move */); - }; - - auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) { - auto in_layers_range = [this, min_id, max_id](size_t id) { - return m_layers.get_endpoints_at(min_id).first <= id && id <= m_layers.get_endpoints_at(max_id).last; - }; - - return in_layers_range(path.first.s_id) || in_layers_range(path.last.s_id); - }; - - auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) { - auto is_in_z_range = [](const Path& path, double min_z, double max_z) { - auto in_z_range = [min_z, max_z](double z) { - return min_z - EPSILON < z&& z < max_z + EPSILON; - }; - - return in_z_range(path.first.position[2]) || in_z_range(path.last.position[2]); - }; - - const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)]; - if (path_id >= buffer.paths.size()) - return false; - - Path path = buffer.paths[path_id]; - size_t first = path_id; - size_t last = path_id; - - // check adjacent paths - while (first > 0 && path.first.position.isApprox(buffer.paths[first - 1].last.position)) { - --first; - path.first = buffer.paths[first].first; - } - while (last < buffer.paths.size() - 1 && path.last.position.isApprox(buffer.paths[last + 1].first.position)) { - ++last; - path.last = buffer.paths[last].last; - } - - size_t min_s_id = m_layers.get_endpoints_at(min_id).first; - size_t max_s_id = m_layers.get_endpoints_at(max_id).last; - - return (min_s_id <= path.first.s_id && path.first.s_id <= max_s_id) || - (min_s_id <= path.last.s_id && path.last.s_id <= max_s_id); - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - Statistics* statistics = const_cast(&m_statistics); - statistics->render_paths_size = 0; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1"; - - SequentialView::Endpoints global_endpoints = { m_moves_count , 0 }; - SequentialView::Endpoints top_layer_endpoints = global_endpoints; - SequentialView* sequential_view = const_cast(&m_sequential_view); - if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0; - if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count; - - // first pass: collect visible paths and update sequential view data - std::vector> paths; - for (size_t b = 0; b < m_buffers.size(); ++b) { - TBuffer& buffer = const_cast(m_buffers[b]); - // reset render paths - buffer.render_paths.clear(); - - if (!buffer.visible) - continue; - - for (size_t i = 0; i < buffer.paths.size(); ++i) { - const Path& path = buffer.paths[i]; - if (path.type == EMoveType::Travel) { - if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) - continue; - } - else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1])) - continue; - - if (path.type == EMoveType::Extrude && !is_visible(path)) - continue; - - // store valid path - paths.push_back({ &buffer, path.first.b_id, static_cast(i) }); - - global_endpoints.first = std::min(global_endpoints.first, path.first.s_id); - global_endpoints.last = std::max(global_endpoints.last, path.last.s_id); - - if (top_layer_only) { - if (path.type == EMoveType::Travel) { - if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) { - top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.first.s_id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.last.s_id); - } - } - else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) { - top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.first.s_id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.last.s_id); - } - } - } - } - - // update current sequential position - sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first; - sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last; - - // get the world position from gpu - bool found = false; - for (const TBuffer& buffer : m_buffers) { - // searches the path containing the current position - for (const Path& path : buffer.paths) { - if (path.contains(m_sequential_view.current.last)) { - unsigned int offset = static_cast(m_sequential_view.current.last - path.first.s_id); - if (offset > 0) { - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) - offset = 2 * offset - 1; - else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - unsigned int indices_count = buffer.indices_per_segment(); - offset = indices_count * (offset - 1) + (indices_count - 6); - } - } - offset += static_cast(path.first.i_id); - - // gets the index from the index buffer on gpu - unsigned int index = 0; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[path.first.b_id].id)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(unsigned int)), static_cast(sizeof(unsigned int)), static_cast(&index))); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // gets the position from the vertices buffer on gpu - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(sequential_view->current_position.data()))); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - found = true; - break; - } - } - if (found) - break; - } - - // second pass: filter paths by sequential data and collect them by color - RenderPath *render_path = nullptr; - for (const auto& [buffer, ibuffer_id, path_id] : paths) { - const Path& path = buffer->paths[path_id]; - if (m_sequential_view.current.last <= path.first.s_id || path.last.s_id <= m_sequential_view.current.first) - continue; - - Color color; - switch (path.type) - { - case EMoveType::Extrude: { - if (!top_layer_only || - m_sequential_view.current.last == global_endpoints.last || - is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) - color = extrusion_color(path); - else - color = { 0.25f, 0.25f, 0.25f }; - - break; - } - case EMoveType::Travel: { - if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1])) - color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); - else - color = { 0.25f, 0.25f, 0.25f }; - - break; - } - case EMoveType::Wipe: { color = Wipe_Color; break; } - default: { color = { 0.0f, 0.0f, 0.0f }; break; } - } - - RenderPath key{ color, static_cast(ibuffer_id), path_id }; - if (render_path == nullptr || ! RenderPathPropertyEqual()(*render_path, key)) - render_path = const_cast(&(*buffer->render_paths.emplace(key).first)); - unsigned int segments_count = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; - unsigned int size_in_indices = 0; - switch (buffer->render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = segments_count; break; } - case TBuffer::ERenderPrimitiveType::Line: - case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (segments_count - 1); break; } - } - render_path->sizes.push_back(size_in_indices); - - unsigned int delta_1st = 0; - if (path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= path.last.s_id) - delta_1st = m_sequential_view.current.first - path.first.s_id; - - if (buffer->render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) - delta_1st *= buffer->indices_per_segment(); - - render_path->offsets.push_back(static_cast((path.first.i_id + delta_1st) * sizeof(unsigned int))); - } - - // set sequential data to their final value - sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; - sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; - - wxGetApp().plater()->enable_preview_moves_slider(!paths.empty()); - -#if ENABLE_GCODE_VIEWER_STATISTICS - for (const TBuffer& buffer : m_buffers) { - statistics->render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); - for (const RenderPath& path : buffer.render_paths) { - statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); - statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); - } - } - statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS -} -#endif // ENABLE_SPLITTED_VERTEX_BUFFER - -#if ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::render_toolpaths() const { #if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS @@ -3875,155 +2668,6 @@ void GCodeViewer::render_toolpaths() const } #endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } -#else -void GCodeViewer::render_toolpaths() const -{ -#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - float point_size = 20.0f; -#else - float point_size = 0.8f; -#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - std::array light_intensity = { 0.25f, 0.70f, 0.75f, 0.75f }; - const Camera& camera = wxGetApp().plater()->get_camera(); - double zoom = camera.get_zoom(); - const std::array& viewport = camera.get_viewport(); - float near_plane_height = camera.get_type() == Camera::Perspective ? static_cast(viewport[3]) / (2.0f * static_cast(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) : - static_cast(viewport[3]) * 0.0005; - - auto set_uniform_color = [](const std::array& color, GLShaderProgram& shader) { - std::array color4 = { color[0], color[1], color[2], 1.0f }; - shader.set_uniform("uniform_color", color4); - }; - - auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color] - (const TBuffer& buffer, unsigned int ibuffer_id, EOptionsColors color_id, GLShaderProgram& shader) { - set_uniform_color(Options_Colors[static_cast(color_id)], shader); -#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - shader.set_uniform("use_fixed_screen_size", 1); -#else - shader.set_uniform("use_fixed_screen_size", 0); -#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - shader.set_uniform("zoom", zoom); - shader.set_uniform("percent_outline_radius", 0.0f); - shader.set_uniform("percent_center_radius", 0.33f); - shader.set_uniform("point_size", point_size); - shader.set_uniform("near_plane_height", near_plane_height); - - glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - glsafe(::glEnable(GL_POINT_SPRITE)); - - for (const RenderPath& path : buffer.render_paths) { - if (path.ibuffer_id == ibuffer_id) { - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++const_cast(&m_statistics)->gl_multi_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - - glsafe(::glDisable(GL_POINT_SPRITE)); - glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); - }; - - auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { - shader.set_uniform("light_intensity", light_intensity); - for (const RenderPath& path : buffer.render_paths) { - if (path.ibuffer_id == ibuffer_id) { - set_uniform_color(path.color, shader); - glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++const_cast(&m_statistics)->gl_multi_lines_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - }; - - auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { - for (const RenderPath& path : buffer.render_paths) { - if (path.ibuffer_id == ibuffer_id) { - set_uniform_color(path.color, shader); - glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++const_cast(&m_statistics)->gl_multi_triangles_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - }; - - auto line_width = [](double zoom) { - return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); - }; - - glsafe(::glLineWidth(static_cast(line_width(zoom)))); - - unsigned char begin_id = buffer_id(EMoveType::Retract); - unsigned char end_id = buffer_id(EMoveType::Count); - - for (unsigned char i = begin_id; i < end_id; ++i) { - const TBuffer& buffer = m_buffers[i]; - if (!buffer.visible || !buffer.has_data()) - continue; - - GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); - if (shader != nullptr) { - shader->start_using(); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_size())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - bool has_normals = buffer.vertices.normal_size_floats() > 0; - if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_size())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - } - - for (size_t j = 0; j < buffer.indices.size(); ++j) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[j].id)); - - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: - { - EOptionsColors color = EOptionsColors(0); - switch (buffer_type(i)) - { - case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } - case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; } - case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; } - case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; } - case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } - case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } - default: { assert(false); break; } - } - render_as_points(buffer, static_cast(j), color, *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Line: - { - render_as_lines(buffer, static_cast(j), *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: - { - render_as_triangles(buffer, static_cast(j), *shader); - break; - } - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - shader->stop_using(); - } - } -} -#endif // ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::render_shells() const { @@ -5047,15 +3691,9 @@ void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) } int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double); layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_endpoints(), Layers::Endpoints); -#if ENABLE_SPLITTED_VERTEX_BUFFER BOOST_LOG_TRIVIAL(trace) << label << "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");" << log_memory_info(); -#else - BOOST_LOG_TRIVIAL(trace) << label - << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) - << log_memory_info(); -#endif // ENABLE_SPLITTED_VERTEX_BUFFER } } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 112c681d4..3386e314e 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -23,17 +23,11 @@ namespace GUI { class GCodeViewer { -#if ENABLE_SPLITTED_VERTEX_BUFFER using IBufferType = unsigned short; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER using Color = std::array; using VertexBuffer = std::vector; -#if ENABLE_SPLITTED_VERTEX_BUFFER using MultiVertexBuffer = std::vector; using IndexBuffer = std::vector; -#else - using IndexBuffer = std::vector; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER using MultiIndexBuffer = std::vector; static const std::vector Extrusion_Role_Colors; @@ -69,24 +63,17 @@ class GCodeViewer }; EFormat format{ EFormat::Position }; -#if ENABLE_SPLITTED_VERTEX_BUFFER // vbos id std::vector vbos; // sizes of the buffers, in bytes, used in export to obj std::vector sizes; -#else - // vbo id - unsigned int id{ 0 }; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER // count of vertices, updated after data are sent to gpu size_t count{ 0 }; size_t data_size_bytes() const { return count * vertex_size_bytes(); } -#if ENABLE_SPLITTED_VERTEX_BUFFER // We set 65536 as max count of vertices inside a vertex buffer to allow // to use unsigned short in place of unsigned int for indices in the index buffer, to save memory size_t max_size_bytes() const { return 65536 * vertex_size_bytes(); } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER size_t vertex_size_floats() const { return position_size_floats() + normal_size_floats(); } size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); } @@ -116,15 +103,10 @@ class GCodeViewer // ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type struct IBuffer { -#if ENABLE_SPLITTED_VERTEX_BUFFER // id of the associated vertex buffer unsigned int vbo{ 0 }; // ibo id unsigned int ibo{ 0 }; -#else - // ibo id - unsigned int id{ 0 }; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER // count of indices, updated after data are sent to gpu size_t count{ 0 }; @@ -148,7 +130,6 @@ class GCodeViewer Vec3f position{ Vec3f::Zero() }; }; -#if ENABLE_SPLITTED_VERTEX_BUFFER struct Sub_Path { Endpoint first; @@ -158,14 +139,9 @@ class GCodeViewer return first.s_id <= s_id && s_id <= last.s_id; } }; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER EMoveType type{ EMoveType::Noop }; ExtrusionRole role{ erNone }; -#if !ENABLE_SPLITTED_VERTEX_BUFFER - Endpoint first; - Endpoint last; -#endif // !ENABLE_SPLITTED_VERTEX_BUFFER float delta_extruder{ 0.0f }; float height{ 0.0f }; float width{ 0.0f }; @@ -175,12 +151,9 @@ class GCodeViewer float volumetric_rate{ 0.0f }; unsigned char extruder_id{ 0 }; unsigned char cp_color_id{ 0 }; -#if ENABLE_SPLITTED_VERTEX_BUFFER std::vector sub_paths; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER bool matches(const GCodeProcessor::MoveVertex& move) const; -#if ENABLE_SPLITTED_VERTEX_BUFFER size_t vertices_count() const { return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1; } @@ -202,10 +175,6 @@ class GCodeViewer Endpoint endpoint = { b_id, i_id, s_id, move.position }; sub_paths.push_back({ endpoint , endpoint }); } -#else - size_t vertices_count() const { return last.s_id - first.s_id + 1; } - bool contains(size_t id) const { return first.s_id <= id && id <= last.s_id; } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; // Used to batch the indices needed to render the paths @@ -295,7 +264,6 @@ class GCodeViewer // s_id index of first vertex contained in this->vertices void add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id); -#if ENABLE_SPLITTED_VERTEX_BUFFER unsigned int max_vertices_per_segment() const { switch (render_primitive_type) { @@ -308,7 +276,6 @@ class GCodeViewer size_t max_vertices_per_segment_size_floats() const { return vertices.vertex_size_floats() * static_cast(max_vertices_per_segment()); } size_t max_vertices_per_segment_size_bytes() const { return max_vertices_per_segment_size_floats() * sizeof(float); } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER unsigned int indices_per_segment() const { switch (render_primitive_type) { @@ -322,9 +289,7 @@ class GCodeViewer default: { return 0; } } } -#if ENABLE_SPLITTED_VERTEX_BUFFER size_t indices_per_segment_size_bytes() const { return static_cast(indices_per_segment() * sizeof(IBufferType)); } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER #if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS unsigned int max_indices_per_segment() const { switch (render_primitive_type) @@ -338,24 +303,9 @@ class GCodeViewer size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); } #endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS -#if ENABLE_SPLITTED_VERTEX_BUFFER bool has_data() const { return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; } -#else - unsigned int start_segment_vertex_offset() const { return 0; } - unsigned int end_segment_vertex_offset() const { - switch (render_primitive_type) - { - case ERenderPrimitiveType::Point: { return 0; } - case ERenderPrimitiveType::Line: { return 1; } - case ERenderPrimitiveType::Triangle: { return 36; } // 1st vertex of 13th triangle - default: { return 0; } - } - } - - bool has_data() const { return vertices.id != 0 && !indices.empty() && indices.front().id != 0; } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; // helper to render shells @@ -434,11 +384,9 @@ class GCodeViewer size_t first{ 0 }; size_t last{ 0 }; -#if ENABLE_SPLITTED_VERTEX_BUFFER bool operator == (const Endpoints& other) const { return first == other.first && last == other.last; } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; private: @@ -464,7 +412,6 @@ class GCodeViewer double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; } Endpoints get_endpoints_at(unsigned int id) const { return (id < m_endpoints.size()) ? m_endpoints[id] : Endpoints(); } -#if ENABLE_SPLITTED_VERTEX_BUFFER bool operator != (const Layers& other) const { if (m_zs != other.m_zs) return true; @@ -473,7 +420,6 @@ class GCodeViewer return false; } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; #if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS @@ -722,9 +668,7 @@ public: void render() const; bool has_data() const { return !m_roles.empty(); } -#if ENABLE_SPLITTED_VERTEX_BUFFER bool can_export_toolpaths() const; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; } const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8873af245..064924c41 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3933,11 +3933,7 @@ void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() bool GLCanvas3D::has_toolpaths_to_export() const { -#if ENABLE_SPLITTED_VERTEX_BUFFER return m_gcode_viewer.can_export_toolpaths(); -#else - return m_gcode_viewer.has_data(); -#endif // ENABLE_SPLITTED_VERTEX_BUFFER } void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const From 426d2cd725eacaadbc41ee63d525d2450ccfc6f4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 10 May 2021 16:05:16 +0200 Subject: [PATCH 44/75] Tech ENABLE_WARNING_TEXTURE_REMOVAL set as default --- src/libslic3r/Technologies.hpp | 2 - src/slic3r/GUI/GLCanvas3D.cpp | 348 --------------------------------- src/slic3r/GUI/GLCanvas3D.hpp | 54 ----- 3 files changed, 404 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index f51241acc..a8cdbd77d 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -45,8 +45,6 @@ #define ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS (1 && ENABLE_2_4_0_ALPHA0) // Enable reload from disk command for 3mf files #define ENABLE_RELOAD_FROM_DISK_FOR_3MF (1 && ENABLE_2_4_0_ALPHA0) -// Removes obsolete warning texture code -#define ENABLE_WARNING_TEXTURE_REMOVAL (1 && ENABLE_2_4_0_ALPHA0) // Enable showing gcode line numbers in preview horizontal slider #define ENABLE_GCODE_LINES_ID_IN_H_SLIDER (1 && ENABLE_2_4_0_ALPHA0) // Enable validation of custom gcode against gcode processor reserved keywords diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 064924c41..1eb52e92e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -604,278 +604,6 @@ GLCanvas3D::Mouse::Mouse() { } -#if !ENABLE_WARNING_TEXTURE_REMOVAL -const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 120, 120, 120 };//{ 9, 91, 134 }; -const unsigned char GLCanvas3D::WarningTexture::Opacity = 255; - -GLCanvas3D::WarningTexture::WarningTexture() - : GUI::GLTexture() - , m_original_width(0) - , m_original_height(0) -{ -} - -void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool state, const GLCanvas3D& canvas) -{ - // Since we have NotificationsManager.hpp the warning textures are no loger needed. - // However i have left the infrastructure here and only commented the rendering. - // The plater warning / error notifications are added and closed from here. - - std::string text; - bool error = false; - switch (warning) { - case ObjectOutside: text = _u8L("An object outside the print area was detected."); break; - case ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = true; break; - case SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = true; break; - case SomethingNotShown: text = _u8L("Some objects are not visible."); break; - case ObjectClashed: - text = _u8L( "An object outside the print area was detected.\n" - "Resolve the current problem to continue slicing."); - error = true; - break; - } - BOOST_LOG_TRIVIAL(error) << state << " : " << text ; - auto ¬ification_manager = *wxGetApp().plater()->get_notification_manager(); - if (state) { - if(error) - notification_manager.push_plater_error_notification(text); - else - notification_manager.push_plater_warning_notification(text); - } else { - if (error) - notification_manager.close_plater_error_notification(text); - else - notification_manager.close_plater_warning_notification(text); - } - - /* - auto it = std::find(m_warnings.begin(), m_warnings.end(), warning); - - if (state) { - if (it != m_warnings.end()) // this warning is already set to be shown - return; - - m_warnings.push_back(warning); - std::sort(m_warnings.begin(), m_warnings.end()); - } - else { - if (it == m_warnings.end()) // deactivating something that is not active is an easy task - return; - - m_warnings.erase(it); - if (m_warnings.empty()) { // nothing remains to be shown - reset(); - m_msg_text = "";// save information for rescaling - return; - } - } - - // Look at the end of our vector and generate proper texture. - std::string text; - bool red_colored = false; - switch (m_warnings.back()) { - case ObjectOutside : text = L("An object outside the print area was detected"); break; - case ToolpathOutside : text = L("A toolpath outside the print area was detected"); break; - case SlaSupportsOutside : text = L("SLA supports outside the print area were detected"); break; - case SomethingNotShown : text = L("Some objects are not visible when editing supports"); break; - case ObjectClashed: { - text = L("An object outside the print area was detected\n" - "Resolve the current problem to continue slicing"); - red_colored = true; - break; - } - } - - generate(text, canvas, true, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...) - - // save information for rescaling - m_msg_text = text; - m_is_colored_red = red_colored; - */ -} - - -#ifdef __WXMSW__ -static bool is_font_cleartype(const wxFont &font) -{ - // Native font description: on MSW, it is a version number plus the content of LOGFONT, separated by semicolon. - wxString font_desc = font.GetNativeFontInfoDesc(); - // Find the quality field. - wxString sep(";"); - size_t startpos = 0; - for (size_t i = 0; i < 12; ++ i) - startpos = font_desc.find(sep, startpos + 1); - ++ startpos; - size_t endpos = font_desc.find(sep, startpos); - int quality = wxAtoi(font_desc(startpos, endpos - startpos)); - return quality == CLEARTYPE_QUALITY; -} - -// ClearType produces renders, which are difficult to convert into an alpha blended OpenGL texture. -// Therefore it is better to disable it, though Vojtech found out, that the font returned with ClearType -// disabled is signifcantly thicker than the default ClearType font. -// This function modifies the font provided. -static void msw_disable_cleartype(wxFont &font) -{ - // Native font description: on MSW, it is a version number plus the content of LOGFONT, separated by semicolon. - wxString font_desc = font.GetNativeFontInfoDesc(); - // Find the quality field. - wxString sep(";"); - size_t startpos_weight = 0; - for (size_t i = 0; i < 5; ++ i) - startpos_weight = font_desc.find(sep, startpos_weight + 1); - ++ startpos_weight; - size_t endpos_weight = font_desc.find(sep, startpos_weight); - // Parse the weight field. - unsigned int weight = wxAtoi(font_desc(startpos_weight, endpos_weight - startpos_weight)); - size_t startpos = endpos_weight; - for (size_t i = 0; i < 6; ++ i) - startpos = font_desc.find(sep, startpos + 1); - ++ startpos; - size_t endpos = font_desc.find(sep, startpos); - int quality = wxAtoi(font_desc(startpos, endpos - startpos)); - if (quality == CLEARTYPE_QUALITY) { - // Replace the weight with a smaller value to compensate the weight of non ClearType font. - wxString sweight = std::to_string(weight * 2 / 4); - size_t len_weight = endpos_weight - startpos_weight; - wxString squality = std::to_string(ANTIALIASED_QUALITY); - font_desc.replace(startpos_weight, len_weight, sweight); - font_desc.replace(startpos + sweight.size() - len_weight, endpos - startpos, squality); - font.SetNativeFontInfo(font_desc); - wxString font_desc2 = font.GetNativeFontInfoDesc(); - } - wxString font_desc2 = font.GetNativeFontInfoDesc(); -} -#endif /* __WXMSW__ */ - -bool GLCanvas3D::WarningTexture::generate(const std::string& msg_utf8, const GLCanvas3D& canvas, bool compress, bool red_colored/* = false*/) -{ - reset(); - - if (msg_utf8.empty()) - return false; - - wxString msg = _(msg_utf8); - - wxMemoryDC memDC; - -#ifdef __WXMSW__ - // set scaled application normal font as default font - wxFont font = wxGetApp().normal_font(); -#else - // select default font - const float scale = canvas.get_canvas_size().get_scale_factor(); -#if ENABLE_RETINA_GL - // For non-visible or non-created window getBackingScaleFactor function return 0.0 value. - // And using of the zero scale causes a crash, when we trying to draw text to the (0,0) rectangle - // https://github.com/prusa3d/PrusaSlicer/issues/3916 - if (scale <= 0.0f) - return false; -#endif - wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scale(scale); -#endif - - font.MakeLarger(); - font.MakeBold(); - memDC.SetFont(font); - - // calculates texture size - wxCoord w, h; - memDC.GetMultiLineTextExtent(msg, &w, &h); - - m_original_width = (int)w; - m_original_height = (int)h; - m_width = (int)next_highest_power_of_2((uint32_t)w); - m_height = (int)next_highest_power_of_2((uint32_t)h); - - // generates bitmap - wxBitmap bitmap(m_width, m_height); - - memDC.SelectObject(bitmap); - memDC.SetBackground(wxBrush(*wxBLACK)); - memDC.Clear(); - - // draw message - memDC.SetTextForeground(*wxRED); - memDC.DrawLabel(msg, wxRect(0,0, m_original_width, m_original_height), wxALIGN_CENTER); - - memDC.SelectObject(wxNullBitmap); - - // Convert the bitmap into a linear data ready to be loaded into the GPU. - wxImage image = bitmap.ConvertToImage(); - - // prepare buffer - std::vector data(4 * m_width * m_height, 0); - const unsigned char *src = image.GetData(); - for (int h = 0; h < m_height; ++h) { - unsigned char* dst = data.data() + 4 * h * m_width; - for (int w = 0; w < m_width; ++w) { - *dst++ = 255; - if (red_colored) { - *dst++ = 72; // 204 - *dst++ = 65; // 204 - } else { - *dst++ = 255; - *dst++ = 255; - } - *dst++ = (unsigned char)std::min(255, *src); - src += 3; - } - } - - // sends buffer to gpu - glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - glsafe(::glGenTextures(1, &m_id)); - glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); - if (compress && GLEW_EXT_texture_compression_s3tc) - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - else - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - - return true; -} - -void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const -{ - if (m_warnings.empty()) - return; - - if (m_id > 0 && m_original_width > 0 && m_original_height > 0 && m_width > 0 && m_height > 0) { - const Size& cnv_size = canvas.get_canvas_size(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - float left = (-0.5f * (float)m_original_width) * inv_zoom; - float top = (-0.5f * (float)cnv_size.get_height() + (float)m_original_height + 2.0f) * inv_zoom; - float right = left + (float)m_original_width * inv_zoom; - float bottom = top - (float)m_original_height * inv_zoom; - - float uv_left = 0.0f; - float uv_top = 0.0f; - float uv_right = (float)m_original_width / (float)m_width; - float uv_bottom = (float)m_original_height / (float)m_height; - - GLTexture::Quad_UVs uvs; - uvs.left_top = { uv_left, uv_top }; - uvs.left_bottom = { uv_left, uv_bottom }; - uvs.right_bottom = { uv_right, uv_bottom }; - uvs.right_top = { uv_right, uv_top }; - - GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs); - } -} - -void GLCanvas3D::WarningTexture::msw_rescale(const GLCanvas3D& canvas) -{ - if (m_msg_text.empty()) - return; - - generate(m_msg_text, canvas, true, m_is_colored_red); -} -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL - void GLCanvas3D::Labels::render(const std::vector& sorted_instances) const { if (!m_enabled || !is_shown()) @@ -1294,11 +1022,7 @@ void GLCanvas3D::reset_volumes() m_volumes.clear(); m_dirty = true; -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification(EWarning::ObjectOutside, false); -#else - _set_warning_texture(WarningTexture::ObjectOutside, false); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL } int GLCanvas3D::check_volumes_outside_state() const @@ -1352,19 +1076,11 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject if (visible && !mo) toggle_sla_auxiliaries_visibility(true, mo, instance_idx); -#if ENABLE_WARNING_TEXTURE_REMOVAL if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1)) _set_warning_notification(EWarning::SomethingNotShown, true); if (!mo && visible) _set_warning_notification(EWarning::SomethingNotShown, false); -#else - if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1)) - _set_warning_texture(WarningTexture::SomethingNotShown, true); - - if (!mo && visible) - _set_warning_texture(WarningTexture::SomethingNotShown, false); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL } void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx) @@ -2241,31 +1957,18 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re bool fullyOut = false; const bool contained_min_one = m_volumes.check_outside_state(m_config, partlyOut, fullyOut); -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification(EWarning::ObjectClashed, partlyOut); _set_warning_notification(EWarning::ObjectOutside, fullyOut); if (printer_technology != ptSLA || !contained_min_one) _set_warning_notification(EWarning::SlaSupportsOutside, false); -#else - _set_warning_texture(WarningTexture::ObjectClashed, partlyOut); - _set_warning_texture(WarningTexture::ObjectOutside, fullyOut); - if(printer_technology != ptSLA || !contained_min_one) - _set_warning_texture(WarningTexture::SlaSupportsOutside, false); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, contained_min_one && !m_model->objects.empty() && !partlyOut)); } else { -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification(EWarning::ObjectOutside, false); _set_warning_notification(EWarning::ObjectClashed, false); _set_warning_notification(EWarning::SlaSupportsOutside, false); -#else - _set_warning_texture(WarningTexture::ObjectOutside, false); - _set_warning_texture(WarningTexture::ObjectClashed, false); - _set_warning_texture(WarningTexture::SlaSupportsOutside, false); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); } @@ -2308,11 +2011,7 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) if (wxGetApp().is_editor()) { m_gcode_viewer.update_shells_color_by_extruder(m_config); -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification_if_needed(EWarning::ToolpathOutside); -#else - _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL } } @@ -2339,11 +2038,7 @@ void GLCanvas3D::load_sla_preview() this->reset_volumes(); _load_sla_shells(); _update_sla_shells_outside_state(); -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification_if_needed(EWarning::SlaSupportsOutside); -#else - _show_warning_texture_if_needed(WarningTexture::SlaSupportsOutside); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL } } @@ -2364,11 +2059,7 @@ void GLCanvas3D::load_preview(const std::vector& str_tool_colors, c _load_print_object_toolpaths(*object, str_tool_colors, color_print_values); _update_toolpath_volumes_outside_state(); -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification_if_needed(EWarning::ToolpathOutside); -#else - _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL } void GLCanvas3D::bind_event_handlers() @@ -3916,9 +3607,6 @@ void GLCanvas3D::set_cursor(ECursorType type) void GLCanvas3D::msw_rescale() { -#if !ENABLE_WARNING_TEXTURE_REMOVAL - m_warning_texture.msw_rescale(*this); -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL } void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() @@ -5270,9 +4958,6 @@ void GLCanvas3D::_render_overlays() const _check_and_update_toolbar_icon_scale(); _render_gizmos_overlay(); -#if !ENABLE_WARNING_TEXTURE_REMOVAL - _render_warning_texture(); -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL // main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed // to correctly place them @@ -5310,13 +4995,6 @@ void GLCanvas3D::_render_overlays() const glsafe(::glPopMatrix()); } -#if !ENABLE_WARNING_TEXTURE_REMOVAL -void GLCanvas3D::_render_warning_texture() const -{ - m_warning_texture.render(*this); -} -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL - void GLCanvas3D::_render_volumes_for_picking() const { static const GLfloat INV_255 = 1.0f / 255.0f; @@ -6313,7 +5991,6 @@ void GLCanvas3D::_update_sla_shells_outside_state() } } -#if ENABLE_WARNING_TEXTURE_REMOVAL void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) { _set_current(); @@ -6330,24 +6007,6 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) } _set_warning_notification(warning, show); } -#else -void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning) -{ - _set_current(); - bool show = false; - if (!m_volumes.empty()) - show = _is_any_volume_outside(); - else { - if (wxGetApp().is_editor()) { - BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); - const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); - if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) - show = !test_volume.contains(paths_volume); - } - } - _set_warning_texture(warning, show); -} -#endif // ENABLE_WARNING_TEXTURE_REMOVAL std::vector GLCanvas3D::_parse_colors(const std::vector& colors) { @@ -6371,7 +6030,6 @@ std::vector GLCanvas3D::_parse_colors(const std::vector& col return output; } -#if ENABLE_WARNING_TEXTURE_REMOVAL void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) { enum ErrorType{ @@ -6417,12 +6075,6 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) break; } } -#else -void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state) -{ - m_warning_texture.activate(warning, state, *this); -} -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL bool GLCanvas3D::_is_any_volume_outside() const { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 10457d877..f51f53f62 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -295,7 +295,6 @@ class GLCanvas3D bool matches(double z) const { return this->z == z; } }; -#if ENABLE_WARNING_TEXTURE_REMOVAL enum class EWarning { ObjectOutside, ToolpathOutside, @@ -303,46 +302,6 @@ class GLCanvas3D SomethingNotShown, ObjectClashed }; -#else - class WarningTexture : public GUI::GLTexture - { - public: - WarningTexture(); - - enum Warning { - ObjectOutside, - ToolpathOutside, - SlaSupportsOutside, - SomethingNotShown, - ObjectClashed - }; - - // Sets a warning of the given type to be active/inactive. If several warnings are active simultaneously, - // only the last one is shown (decided by the order in the enum above). - void activate(WarningTexture::Warning warning, bool state, const GLCanvas3D& canvas); - void render(const GLCanvas3D& canvas) const; - - // function used to get an information for rescaling of the warning - void msw_rescale(const GLCanvas3D& canvas); - - private: - static const unsigned char Background_Color[3]; - static const unsigned char Opacity; - - int m_original_width; - int m_original_height; - - // information for rescaling of the warning legend - std::string m_msg_text = ""; - bool m_is_colored_red{false}; - - // Information about which warnings are currently active. - std::vector m_warnings; - - // Generates the texture with given text. - bool generate(const std::string& msg, const GLCanvas3D& canvas, bool compress, bool red_colored = false); - }; -#endif // ENABLE_WARNING_TEXTURE_REMOVAL #if ENABLE_RENDER_STATISTICS class RenderStats @@ -440,9 +399,6 @@ private: std::unique_ptr m_retina_helper; #endif bool m_in_render; -#if !ENABLE_WARNING_TEXTURE_REMOVAL - WarningTexture m_warning_texture; -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL wxTimer m_timer; LayersEditing m_layers_editing; Mouse m_mouse; @@ -814,9 +770,6 @@ private: #endif // ENABLE_RENDER_SELECTION_CENTER void _check_and_update_toolbar_icon_scale() const; void _render_overlays() const; -#if !ENABLE_WARNING_TEXTURE_REMOVAL - void _render_warning_texture() const; -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL void _render_volumes_for_picking() const; void _render_current_gizmo() const; void _render_gizmos_overlay() const; @@ -869,17 +822,10 @@ private: void _load_sla_shells(); void _update_toolpath_volumes_outside_state(); void _update_sla_shells_outside_state(); -#if ENABLE_WARNING_TEXTURE_REMOVAL void _set_warning_notification_if_needed(EWarning warning); // generates a warning notification containing the given message void _set_warning_notification(EWarning warning, bool state); -#else - void _show_warning_texture_if_needed(WarningTexture::Warning warning); - - // generates a warning texture containing the given message - void _set_warning_texture(WarningTexture::Warning warning, bool state); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL bool _is_any_volume_outside() const; From b87c03fc0937b27431833b4de0ebb013d9b49fa8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 May 2021 17:57:17 +0200 Subject: [PATCH 45/75] Linux specific: Fixed ObjectDataViewModel::GetColumnType() When "string" type was returned, strange editing TextControl was appeared. + Added check of the selection for ObjectList::toggle_printable_state() function --- src/slic3r/GUI/GUI_ObjectList.cpp | 2 ++ src/slic3r/GUI/ObjectDataViewModel.cpp | 9 +++++++++ src/slic3r/GUI/ObjectDataViewModel.hpp | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 28053971f..c969b95da 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3909,6 +3909,8 @@ void ObjectList::toggle_printable_state() { wxDataViewItemArray sels; GetSelections(sels); + if (sels.IsEmpty()) + return; wxDataViewItem frst_item = sels[0]; diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 49c75f9f2..297d8e3c9 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -1180,6 +1180,15 @@ int ObjectDataViewModel::GetExtruderNumber(const wxDataViewItem& item) const return atoi(node->m_extruder.c_str()); } +wxString ObjectDataViewModel::GetColumnType(unsigned int col) const +{ + if (col == colName || col == colExtruder) + return wxT("DataViewBitmapText"); + if (col == colPrint || col == colEditing) + return wxT("DataViewBitmap"); + return wxT("string"); +} + void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const { wxASSERT(item.IsOk()); diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 1dd41bb10..93e092fc2 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -316,7 +316,7 @@ public: // helper methods to change the model unsigned int GetColumnCount() const override { return 3;} - wxString GetColumnType(unsigned int col) const override{ return wxT("string"); } + wxString GetColumnType(unsigned int col) const override; void GetValue( wxVariant &variant, const wxDataViewItem &item, From d701b24bc0f2efaec8e64855531134927dc4aff4 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 28 Apr 2021 15:32:43 +0200 Subject: [PATCH 46/75] Fix of crash when notification text + hypertext wont fit line length --- src/slic3r/GUI/NotificationManager.cpp | 98 +++++++++++++++----------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 7f61ad7f3..489b72293 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -254,7 +254,7 @@ void NotificationManager::PopNotification::count_spaces() void NotificationManager::PopNotification::count_lines() { - std::string text = m_text1 + " " + m_hypertext; + std::string text = m_text1; size_t last_end = 0; m_lines_count = 0; @@ -302,6 +302,14 @@ void NotificationManager::PopNotification::count_lines() } m_lines_count++; } + // hypertext calculation + if (!m_hypertext.empty()) { + int prev_end = m_endlines.size() > 1 ? m_endlines[m_endlines.size() - 2] : 0; + if (ImGui::CalcTextSize((text.substr(prev_end, last_end - prev_end) + m_hypertext).c_str()).x > m_window_width - m_window_width_offset) { + m_endlines.push_back(last_end); + m_lines_count++; + } + } } void NotificationManager::PopNotification::init() @@ -312,7 +320,7 @@ void NotificationManager::PopNotification::init() count_spaces(); count_lines(); - + if (m_lines_count == 3) m_multiline = true; m_notification_start = GLCanvas3D::timestamp_now(); @@ -342,39 +350,46 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons int last_end = 0; float starting_y = m_line_height/2; float shift_y = m_line_height; + std::string line; + for (size_t i = 0; i < m_lines_count; i++) { - std::string line = m_text1.substr(last_end , m_endlines[i] - last_end); - if(i < m_lines_count - 1) - last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(starting_y + i * shift_y); - imgui.text(line.c_str()); + if (m_text1.size() > m_endlines[i]) { + line = m_text1.substr(last_end, m_endlines[i] - last_end); + if (i < m_lines_count - 1) + last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(starting_y + i * shift_y); + imgui.text(line.c_str()); + } } //hyperlink text - if (!m_hypertext.empty()) - { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext); + if (!m_hypertext.empty()) { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext); } } else { // line1 - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + if (m_text1.size() >= m_endlines[0]) { + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + } // line2 - std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) - { - line = line.substr(0, line.length() - 6); - line += ".."; - }else - line += " "; - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2); - imgui.text(line.c_str()); - // "More" hypertext - render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true); + if (m_text1.size() > m_endlines[0]) { + std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) { + line = line.substr(0, line.length() - 6); + line += ".."; + } else + line += " "; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2); + imgui.text(line.c_str()); + // "More" hypertext + render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true); + } + } } else { //text 1 @@ -382,9 +397,11 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons float cursor_x = x_offset; if(m_lines_count > 1) { // line1 - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + if (m_text1.length() >= m_endlines[0]) { + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + } // line2 if (m_text1.length() > m_endlines[0]) { std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); @@ -401,8 +418,7 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x; } //hyperlink text - if (!m_hypertext.empty()) - { + if (!m_hypertext.empty()) { render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext); } @@ -712,15 +728,17 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper& float starting_y = m_line_height / 2;//10; float shift_y = m_line_height;// -m_line_height / 20; for (size_t i = 0; i < m_lines_count; i++) { - std::string line = m_text1.substr(last_end, m_endlines[i] - last_end); - if (i < m_lines_count - 1) - last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(starting_y + i * shift_y); - imgui.text(line.c_str()); - //hyperlink text - if ( i == 0 ) { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(0, last_end).c_str()).x + ImGui::CalcTextSize(" ").x, starting_y, _u8L("Open Folder.")); + if (m_text1.size() >= m_endlines[i]) { + std::string line = m_text1.substr(last_end, m_endlines[i] - last_end); + if (i < m_lines_count - 1) + last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(starting_y + i * shift_y); + imgui.text(line.c_str()); + //hyperlink text + if ( i == 0 ) { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x + ImGui::CalcTextSize(" ").x, starting_y, _u8L("Open Folder.")); + } } } From fabaee10a8fca9676801f2892be382ab4cbce93e Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 7 May 2021 16:30:04 +0200 Subject: [PATCH 47/75] Additional controls in NotificationManager --- src/slic3r/GUI/NotificationManager.cpp | 41 ++++++++++++++------------ 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 489b72293..1b29b7f76 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -353,12 +353,14 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons std::string line; for (size_t i = 0; i < m_lines_count; i++) { - if (m_text1.size() > m_endlines[i]) { + line.clear(); + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(starting_y + i * shift_y); + if (m_endlines.size() > i && m_text1.size() >= m_endlines[i]) { line = m_text1.substr(last_end, m_endlines[i] - last_end); - if (i < m_lines_count - 1) - last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(starting_y + i * shift_y); + last_end = m_endlines[i]; + if (m_text1.size() > m_endlines[i]) + last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); imgui.text(line.c_str()); } } @@ -376,20 +378,20 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); } // line2 - if (m_text1.size() > m_endlines[0]) { - std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + std::string line; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2); + if (m_text1.size() >= m_endlines[1]) { + line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) { line = line.substr(0, line.length() - 6); line += ".."; } else line += " "; - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2); imgui.text(line.c_str()); - // "More" hypertext - render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true); } - + // "More" hypertext + render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true); } } else { //text 1 @@ -397,17 +399,17 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons float cursor_x = x_offset; if(m_lines_count > 1) { // line1 - if (m_text1.length() >= m_endlines[0]) { + if (m_text1.length() >= m_endlines[0]) { // could be equal than substr takes whole string ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); } // line2 - if (m_text1.length() > m_endlines[0]) { + ImGui::SetCursorPosX(x_offset); + cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; + ImGui::SetCursorPosY(cursor_y); + if (m_text1.length() > m_endlines[0]) { // must be greater otherwise theres nothing to show and m_text1[m_endlines[0]] is beyond last letter std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(cursor_y); imgui.text(line.c_str()); cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; } @@ -730,8 +732,9 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper& for (size_t i = 0; i < m_lines_count; i++) { if (m_text1.size() >= m_endlines[i]) { std::string line = m_text1.substr(last_end, m_endlines[i] - last_end); - if (i < m_lines_count - 1) - last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); + last_end = m_endlines[i]; + if (m_text1.size() > m_endlines[i]) + last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(starting_y + i * shift_y); imgui.text(line.c_str()); From 13b0757b8b4131bebaf58171ab21892ceccc80de Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 11 May 2021 10:28:04 +0200 Subject: [PATCH 48/75] Tech ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS set as default --- src/libslic3r/Technologies.hpp | 2 - src/slic3r/GUI/GCodeViewer.cpp | 120 --------------------------------- src/slic3r/GUI/GCodeViewer.hpp | 24 ------- 3 files changed, 146 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a8cdbd77d..e00af1851 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -41,8 +41,6 @@ //==================== #define ENABLE_2_4_0_ALPHA0 1 -// Enable rendering only starting and final caps for toolpaths -#define ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS (1 && ENABLE_2_4_0_ALPHA0) // Enable reload from disk command for 3mf files #define ENABLE_RELOAD_FROM_DISK_FOR_3MF (1 && ENABLE_2_4_0_ALPHA0) // Enable showing gcode line numbers in preview horizontal slider diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 7ccf10795..3287a99e9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -185,7 +185,6 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con return ret; } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS GCodeViewer::SequentialRangeCap::~SequentialRangeCap() { if (ibo > 0) glsafe(::glDeleteBuffers(1, &ibo)); @@ -200,7 +199,6 @@ void GCodeViewer::SequentialRangeCap::reset() { vbo = 0; color = { 0.0f, 0.0f, 0.0f }; } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS void GCodeViewer::SequentialView::Marker::init() { @@ -1241,13 +1239,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position }; }; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS auto add_indices_as_solid = [&](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, const GCodeProcessor::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { -#else - auto add_indices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS static Vec3f prev_dir; static Vec3f prev_up; static float sq_prev_length; @@ -1260,7 +1253,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) store_triangle(indices, id, id, id); store_triangle(indices, id, id, id); }; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS auto convert_vertices_offset = [](size_t vbuffer_size, const std::array& v_offsets) { std::array ret = { static_cast(static_cast(vbuffer_size) + v_offsets[0]), @@ -1292,32 +1284,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) store_triangle(indices, v_offsets[4], v_offsets[6], v_offsets[7]); store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]); }; -#else - auto append_stem_triangles = [&](IndexBuffer& indices, size_t vbuffer_size, const std::array& v_offsets) { - std::array v_ids; - for (size_t i = 0; i < v_ids.size(); ++i) { - v_ids[i] = static_cast(static_cast(vbuffer_size) + v_offsets[i]); - } - - // triangles starting cap - store_triangle(indices, v_ids[0], v_ids[2], v_ids[1]); - store_triangle(indices, v_ids[0], v_ids[3], v_ids[2]); - - // triangles sides - store_triangle(indices, v_ids[0], v_ids[1], v_ids[4]); - store_triangle(indices, v_ids[1], v_ids[5], v_ids[4]); - store_triangle(indices, v_ids[1], v_ids[2], v_ids[5]); - store_triangle(indices, v_ids[2], v_ids[6], v_ids[5]); - store_triangle(indices, v_ids[2], v_ids[3], v_ids[6]); - store_triangle(indices, v_ids[3], v_ids[7], v_ids[6]); - store_triangle(indices, v_ids[3], v_ids[0], v_ids[7]); - store_triangle(indices, v_ids[0], v_ids[4], v_ids[7]); - - // triangles ending cap - store_triangle(indices, v_ids[4], v_ids[6], v_ids[7]); - store_triangle(indices, v_ids[4], v_ids[5], v_ids[6]); - }; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); @@ -1331,30 +1297,20 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) Vec3f up = right.cross(dir); float sq_length = (curr.position - prev.position).squaredNorm(); -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS const std::array first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); const std::array non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); bool is_first_segment = (last_path.vertices_count() == 1); if (is_first_segment || vbuffer_size == 0) { -#else - if (last_path.vertices_count() == 1 || vbuffer_size == 0) { -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // 1st segment or restart into a new vertex buffer // =============================================== -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (is_first_segment) // starting cap triangles append_starting_cap_triangles(indices, first_seg_v_offsets); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // dummy triangles outer corner cap append_dummy_cap(indices, vbuffer_size); // stem triangles -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS append_stem_triangles(indices, first_seg_v_offsets); -#else - append_stem_triangles(indices, vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS vbuffer_size += 8; } @@ -1408,20 +1364,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } // stem triangles -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS append_stem_triangles(indices, non_first_seg_v_offsets); -#else - append_stem_triangles(indices, vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS vbuffer_size += 6; } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (next != nullptr && (curr.type != next->type || !last_path.matches(*next))) // ending cap triangles append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; prev_dir = dir; @@ -1761,11 +1711,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) continue; const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS const GCodeProcessor::MoveVertex* next = nullptr; if (i < m_moves_count - 1) next = &gcode_result.moves[i + 1]; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS ++progress_count; if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { @@ -1789,11 +1737,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // if adding the indices for the current segment exceeds the threshold size of the current index buffer // create another index buffer -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - t_buffer.max_indices_per_segment_size_bytes()) { -#else - if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - t_buffer.indices_per_segment_size_bytes()) { -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS i_multibuffer.push_back(IndexBuffer()); vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) { @@ -1832,11 +1776,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) break; } case TBuffer::ERenderPrimitiveType::Triangle: { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, i); -#else - add_indices_as_solid(prev, curr, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, i); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS break; } } @@ -1888,7 +1828,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) auto update_segments_count = [&](EMoveType type, int64_t& count) { unsigned int id = buffer_id(type); const MultiIndexBuffer& buffers = indices[id]; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS int64_t indices_count = 0; for (const IndexBuffer& buffer : buffers) { indices_count += buffer.size(); @@ -1898,11 +1837,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) indices_count -= static_cast(12 * t_buffer.paths.size()); // remove the starting + ending caps = 4 triangles count += indices_count / t_buffer.indices_per_segment(); -#else - for (const IndexBuffer& buffer : buffers) { - count += buffer.size() / m_buffers[id].indices_per_segment(); - } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS }; update_segments_count(EMoveType::Travel, m_statistics.travel_segments_count); @@ -2119,11 +2053,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count; // first pass: collect visible paths and update sequential view data -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS std::vector> paths; -#else - std::vector> paths; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS for (size_t b = 0; b < m_buffers.size(); ++b) { TBuffer& buffer = const_cast(m_buffers[b]); // reset render paths @@ -2146,11 +2076,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool // store valid path for (size_t j = 0; j < path.sub_paths.size(); ++j) { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS paths.push_back({ static_cast(b), path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); -#else - paths.push_back({ &buffer, path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id); @@ -2190,13 +2116,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool offset = 2 * offset - 1; else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { unsigned int indices_count = buffer.indices_per_segment(); -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS offset = indices_count * (offset - 1) + (indices_count - 2); if (sub_path_id == 0) offset += 6; // add 2 triangles for starting cap -#else - offset = indices_count * (offset - 1) + (indices_count - 6); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } } offset += static_cast(sub_path.first.i_id); @@ -2225,14 +2147,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool // second pass: filter paths by sequential data and collect them by color RenderPath* render_path = nullptr; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); const Path& path = buffer.paths[path_id]; -#else - for (const auto& [buffer, ibuffer_id, path_id, sub_path_id] : paths) { - const Path& path = buffer->paths[path_id]; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; if (m_sequential_view.current.last < sub_path.first.s_id || sub_path.last.s_id < m_sequential_view.current.first) continue; @@ -2271,7 +2188,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool default: { color = { 0.0f, 0.0f, 0.0f }; break; } } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS RenderPath key{ tbuffer_id, color, static_cast(ibuffer_id), path_id }; if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) render_path = const_cast(&(*buffer.render_paths.emplace(key).first)); @@ -2279,40 +2195,22 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool unsigned int delta_1st = 0; if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id) delta_1st = static_cast(m_sequential_view.current.first - sub_path.first.s_id); -#else - RenderPath key{ color, static_cast(ibuffer_id), path_id }; - if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) - render_path = const_cast(&(*buffer->render_paths.emplace(key).first)); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS unsigned int size_in_indices = 0; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS switch (buffer.render_primitive_type) -#else - switch (buffer->render_primitive_type) -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS { case TBuffer::ERenderPrimitiveType::Point: { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS size_in_indices = buffer.indices_per_segment(); -#else - size_in_indices = buffer->indices_per_segment(); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS break; } case TBuffer::ERenderPrimitiveType::Line: case TBuffer::ERenderPrimitiveType::Triangle: { unsigned int segments_count = std::min(m_sequential_view.current.last, sub_path.last.s_id) - std::max(m_sequential_view.current.first, sub_path.first.s_id); -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS size_in_indices = buffer.indices_per_segment() * segments_count; -#else - size_in_indices = buffer->indices_per_segment() * segments_count; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS break; } } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (size_in_indices == 0) continue; @@ -2324,17 +2222,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool if (delta_1st > 0) size_in_indices -= 6; // remove 2 triangles for corner cap } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS render_path->sizes.push_back(size_in_indices); -#if !ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS - unsigned int delta_1st = 0; - if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id) - delta_1st = m_sequential_view.current.first - sub_path.first.s_id; -#endif // !ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS - -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { delta_1st *= buffer.indices_per_segment(); if (delta_1st > 0) { @@ -2343,10 +2233,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool delta_1st += 6; // skip 2 triangles for starting cap } } -#else - if (buffer->render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) - delta_1st *= buffer->indices_per_segment(); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS render_path->offsets.push_back(static_cast((sub_path.first.i_id + delta_1st) * sizeof(IBufferType))); @@ -2365,7 +2251,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // updates sequential range caps std::array* sequential_range_caps = const_cast*>(&m_sequential_range_caps); (*sequential_range_caps)[0].reset(); @@ -2476,7 +2361,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool break; } } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS wxGetApp().plater()->enable_preview_moves_slider(!paths.empty()); @@ -2627,7 +2511,6 @@ void GCodeViewer::render_toolpaths() const } } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS auto render_sequential_range_cap = [set_uniform_color](const SequentialRangeCap& cap) { GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str()); if (shader != nullptr) { @@ -2666,7 +2549,6 @@ void GCodeViewer::render_toolpaths() const if (m_sequential_range_caps[i].is_renderable()) render_sequential_range_cap(m_sequential_range_caps[i]); } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } void GCodeViewer::render_shells() const @@ -3642,9 +3524,7 @@ void GCodeViewer::render_statistics() const add_counter(std::string("Multi GL_POINTS:"), m_statistics.gl_multi_points_calls_count); add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } if (ImGui::CollapsingHeader("CPU memory")) { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 3386e314e..931f01b68 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -180,10 +180,8 @@ class GCodeViewer // Used to batch the indices needed to render the paths struct RenderPath { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // Index of the parent tbuffer unsigned char tbuffer_id; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // Render path property Color color; // Index of the buffer in TBuffer::indices @@ -193,7 +191,6 @@ class GCodeViewer unsigned int path_id; std::vector sizes; std::vector offsets; // use size_t because we need an unsigned integer whose size matches pointer's size (used in the call glMultiDrawElements()) -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS bool contains(size_t offset) const { for (size_t i = 0; i < offsets.size(); ++i) { if (offsets[i] <= offset && offset <= offsets[i] + static_cast(sizes[i] * sizeof(IBufferType))) @@ -201,7 +198,6 @@ class GCodeViewer } return false; } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS }; // // for unordered_set implementation of render_paths // struct RenderPathPropertyHash { @@ -213,10 +209,8 @@ class GCodeViewer // }; struct RenderPathPropertyLower { bool operator() (const RenderPath &l, const RenderPath &r) const { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (l.tbuffer_id < r.tbuffer_id) return true; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS for (int i = 0; i < 3; ++i) { if (l.color[i] < r.color[i]) return true; @@ -228,11 +222,7 @@ class GCodeViewer }; struct RenderPathPropertyEqual { bool operator() (const RenderPath &l, const RenderPath &r) const { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS return l.tbuffer_id == r.tbuffer_id && l.ibuffer_id == r.ibuffer_id && l.color == r.color; -#else - return l.color == r.color && l.ibuffer_id == r.ibuffer_id; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } }; @@ -281,16 +271,11 @@ class GCodeViewer { case ERenderPrimitiveType::Point: { return 1; } case ERenderPrimitiveType::Line: { return 2; } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS case ERenderPrimitiveType::Triangle: { return 30; } // 3 indices x 10 triangles -#else - case ERenderPrimitiveType::Triangle: { return 42; } // 3 indices x 14 triangles -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS default: { return 0; } } } size_t indices_per_segment_size_bytes() const { return static_cast(indices_per_segment() * sizeof(IBufferType)); } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS unsigned int max_indices_per_segment() const { switch (render_primitive_type) { @@ -301,7 +286,6 @@ class GCodeViewer } } size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS bool has_data() const { return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; @@ -422,7 +406,6 @@ class GCodeViewer } }; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // used to render the toolpath caps of the current sequential range // (i.e. when sliding on the horizontal slider) struct SequentialRangeCap @@ -437,7 +420,6 @@ class GCodeViewer void reset(); size_t indices_count() const { return 6; } }; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS #if ENABLE_GCODE_VIEWER_STATISTICS struct Statistics @@ -454,9 +436,7 @@ class GCodeViewer int64_t gl_multi_points_calls_count{ 0 }; int64_t gl_multi_lines_calls_count{ 0 }; int64_t gl_multi_triangles_calls_count{ 0 }; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS int64_t gl_triangles_calls_count{ 0 }; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // memory int64_t results_size{ 0 }; int64_t total_vertices_gpu_size{ 0 }; @@ -493,9 +473,7 @@ class GCodeViewer gl_multi_points_calls_count = 0; gl_multi_lines_calls_count = 0; gl_multi_triangles_calls_count = 0; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS gl_triangles_calls_count = 0; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } void reset_sizes() { @@ -649,9 +627,7 @@ private: #endif // ENABLE_GCODE_VIEWER_STATISTICS std::array m_detected_point_sizes = { 0.0f, 0.0f }; GCodeProcessor::Result::SettingsIds m_settings_ids; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS std::array m_sequential_range_caps; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS public: GCodeViewer(); From ab886e037bf577db28ec98828b6282dc57431f2d Mon Sep 17 00:00:00 2001 From: Oleksandra Yushchenko Date: Tue, 11 May 2021 11:02:12 +0200 Subject: [PATCH 49/75] Implementation for #6216 * Implementation for #6216 - Make number keys select extruder when object treeview has focus + deleted unused extruder_selection() + Fixed notification after splitting of the solid object * Follow up https://github.com/prusa3d/PrusaSlicer/commit/85a10268b9eabfa8aac4c7b1361136b40b75f9f0 - OSX implementation + Added shortcuts description to the "Keyboard Shortcuts" dialog * Workaround to use "+/-" and numbers shortcuts on Linux + Fixed build on Linux * OSX specific: fixed a work of keyboard accelerators from numbers on NumPad keyboard * KBShortcutsDialog: fixed shortcuts for "Preferences" and "Show/Hide 3Dconnexion devices settings dialog, if enabled" under osx and "Set Printable/Unprintable" and "Set extruder" under Linux + OSX specific: Added minimize of the application on "Cmd+M" * Hot-fix for https://github.com/prusa3d/PrusaSlicer/commit/6efeb9d6b433f49aaca8ee42ed02d824c452eb8e * Removed Linux specific workaround --- src/slic3r/GUI/GLCanvas3D.cpp | 10 ++++ src/slic3r/GUI/GUI_ObjectList.cpp | 88 +++++++++++++++------------- src/slic3r/GUI/GUI_ObjectList.hpp | 1 - src/slic3r/GUI/KBShortcutsDialog.cpp | 23 ++++++++ src/slic3r/GUI/Plater.cpp | 5 +- src/slic3r/GUI/Search.cpp | 6 ++ src/slic3r/GUI/Search.hpp | 2 +- 7 files changed, 89 insertions(+), 46 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1eb52e92e..e77a6a324 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2205,9 +2205,19 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #ifdef _WIN32 if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "1") { #endif //_WIN32 +#ifdef __APPLE__ + // On OSX use Cmd+Shift+M to "Show/Hide 3Dconnexion devices settings dialog" + if ((evt.GetModifiers() & shiftMask) != 0) { +#endif // __APPLE__ Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller(); controller.show_settings_dialog(!controller.is_settings_dialog_shown()); m_dirty = true; +#ifdef __APPLE__ + } + else + // and Cmd+M to minimize application + wxGetApp().mainframe->Iconize(); +#endif // __APPLE__ #ifdef _WIN32 } #endif //_WIN32 diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c969b95da..f27623b09 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -146,18 +146,28 @@ ObjectList::ObjectList(wxWindow* parent) : // Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this); { // Accelerators - wxAcceleratorEntry entries[10]; - entries[0].Set(wxACCEL_CTRL, (int) 'C', wxID_COPY); - entries[1].Set(wxACCEL_CTRL, (int) 'X', wxID_CUT); - entries[2].Set(wxACCEL_CTRL, (int) 'V', wxID_PASTE); - entries[3].Set(wxACCEL_CTRL, (int) 'A', wxID_SELECTALL); - entries[4].Set(wxACCEL_CTRL, (int) 'Z', wxID_UNDO); - entries[5].Set(wxACCEL_CTRL, (int) 'Y', wxID_REDO); + wxAcceleratorEntry entries[33]; + entries[0].Set(wxACCEL_CTRL, (int)'C', wxID_COPY); + entries[1].Set(wxACCEL_CTRL, (int)'X', wxID_CUT); + entries[2].Set(wxACCEL_CTRL, (int)'V', wxID_PASTE); + entries[3].Set(wxACCEL_CTRL, (int)'A', wxID_SELECTALL); + entries[4].Set(wxACCEL_CTRL, (int)'Z', wxID_UNDO); + entries[5].Set(wxACCEL_CTRL, (int)'Y', wxID_REDO); entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE); - entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE); - entries[8].Set(wxACCEL_NORMAL, int('+'), wxID_ADD); - entries[9].Set(wxACCEL_NORMAL, int('-'), wxID_REMOVE); - wxAcceleratorTable accel(10, entries); + entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE); + entries[8].Set(wxACCEL_NORMAL, int('+'), wxID_ADD); + entries[9].Set(wxACCEL_NORMAL, WXK_NUMPAD_ADD, wxID_ADD); + entries[10].Set(wxACCEL_NORMAL, int('-'), wxID_REMOVE); + entries[11].Set(wxACCEL_NORMAL, WXK_NUMPAD_SUBTRACT, wxID_REMOVE); + entries[12].Set(wxACCEL_NORMAL, int('p'), wxID_PRINT); + + int numbers_cnt = 1; + for (auto char_number : { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }) { + entries[12 + numbers_cnt].Set(wxACCEL_NORMAL, int(char_number), wxID_LAST + numbers_cnt); + entries[22 + numbers_cnt].Set(wxACCEL_NORMAL, WXK_NUMPAD0 + numbers_cnt - 1, wxID_LAST + numbers_cnt); + numbers_cnt++; + } + wxAcceleratorTable accel(33, entries); SetAcceleratorTable(accel); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY); @@ -168,6 +178,13 @@ ObjectList::ObjectList(wxWindow* parent) : this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo(); }, wxID_REDO); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->increase_instances(); }, wxID_ADD); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->decrease_instances(); }, wxID_REMOVE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->toggle_printable_state(); }, wxID_PRINT); + + for (int i = 0; i < 10; i++) + this->Bind(wxEVT_MENU, [this, i](wxCommandEvent &evt) { + if (extruders_count() > 1 && i <= extruders_count()) + this->set_extruder_for_selected_items(i); + }, wxID_LAST+i+1); } #else //__WXOSX__ Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX @@ -1034,6 +1051,20 @@ void ObjectList::key_event(wxKeyEvent& event) increase_instances(); else if (event.GetUnicodeKey() == '-') decrease_instances(); + else if (event.GetUnicodeKey() == 'p') + toggle_printable_state(); + else if (extruders_count() > 1) { + std::vector numbers = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; + wxChar key_char = event.GetUnicodeKey(); + if (std::find(numbers.begin(), numbers.end(), key_char) != numbers.end()) { + long extruder_number; + if (wxNumberFormatter::FromString(wxString(key_char), &extruder_number) && + extruders_count() >= extruder_number) + set_extruder_for_selected_items(int(extruder_number)); + } + else + event.Skip(); + } else event.Skip(); } @@ -3790,33 +3821,6 @@ void ObjectList::OnEditingDone(wxDataViewEvent &event) plater->set_current_canvas_as_dirty(); } -void ObjectList::extruder_selection() -{ - wxArrayString choices; - choices.Add(_(L("default"))); - for (int i = 1; i <= extruders_count(); ++i) - choices.Add(wxString::Format("%d", i)); - - const wxString& selected_extruder = wxGetSingleChoice(_(L("Select extruder number:")), - _(L("This extruder will be set for selected items")), - choices, 0, this); - if (selected_extruder.IsEmpty()) - return; - - const int extruder_num = selected_extruder == _(L("default")) ? 0 : atoi(selected_extruder.c_str()); - -// /* Another variant for an extruder selection */ -// extruder_num = wxGetNumberFromUser(_(L("Attention!!! \n" -// "It's a possibile to set an extruder number \n" -// "for whole Object(s) and/or object Part(s), \n" -// "not for an Instance. ")), -// _(L("Enter extruder number:")), -// _(L("This extruder will be set for selected items")), -// 1, 1, 5, this); - - set_extruder_for_selected_items(extruder_num); -} - void ObjectList::set_extruder_for_selected_items(const int extruder) const { wxDataViewItemArray sels; @@ -3923,10 +3927,10 @@ void ObjectList::toggle_printable_state() int inst_idx = type == itObject ? 0 : m_objects_model->GetInstanceIdByItem(frst_item); bool printable = !object(obj_idx)->instances[inst_idx]->printable; - const wxString snapshot_text = sels.Count() > 1 ? (printable ? _L("Set Printable group") : _L("Set Unprintable group")) : - object(obj_idx)->instances.size() == 1 ? from_u8((boost::format("%1% %2%") - % (printable ? _L("Set Printable") : _L("Set Unprintable")) - % object(obj_idx)->name).str()) : + const wxString snapshot_text = sels.Count() > 1 ? + (printable ? _L("Set Printable group") : _L("Set Unprintable group")) : + object(obj_idx)->instances.size() == 1 ? + format_wxstr("%1% %2%", (printable ? _L("Set Printable") : _L("Set Unprintable")), from_u8(object(obj_idx)->name)) : (printable ? _L("Set Printable Instance") : _L("Set Unprintable Instance")); take_snapshot(snapshot_text); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 8bcfec11c..b378b00af 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -390,7 +390,6 @@ private: void OnEditingStarted(wxDataViewEvent &event); #endif /* __WXMSW__ */ void OnEditingDone(wxDataViewEvent &event); - void extruder_selection(); }; diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 1c451672c..d766ff5fd 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -109,7 +109,11 @@ void KBShortcutsDialog::fill_shortcuts() { "0-6", L("Camera view") }, { "E", L("Show/Hide object/instance labels") }, // Configuration +#ifdef __APPLE__ + { ctrl + ",", L("Preferences") }, +#else { ctrl + "P", L("Preferences") }, +#endif // Help { "?", L("Show keyboard shortcuts list") } }; @@ -149,8 +153,13 @@ void KBShortcutsDialog::fill_shortcuts() { "Shift+Tab", L("Collapse/Expand the sidebar") }, #ifdef _WIN32 { ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog, if enabled") }, +#else +#ifdef __APPLE__ + { ctrl + "Shift+M", L("Show/Hide 3Dconnexion devices settings dialog") }, + { ctrl + "M", L("Minimize application") }, #else { ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog") }, +#endif // __APPLE__ #endif // _WIN32 #if ENABLE_RENDER_PICKING_PASS // Don't localize debugging texts. @@ -171,6 +180,20 @@ void KBShortcutsDialog::fill_shortcuts() }; m_full_shortcuts.push_back({ { _L("Gizmos"), _L("The following shortcuts are applicable when the specified gizmo is active") }, gizmos_shortcuts }); + + Shortcuts object_list_shortcuts = { +#if defined (__linux__) + { alt + "P", L("Set selected items as Ptrintable/Unprintable") }, + { alt + "0", L("Set default extruder for the selected items") }, + { alt + "1-9", L("Set extruder number for the selected items") }, +#else + { "P", L("Set selected items as Ptrintable/Unprintable") }, + { "0", L("Set default extruder for the selected items") }, + { "1-9", L("Set extruder number for the selected items") }, +#endif + }; + + m_full_shortcuts.push_back({ { _L("Objects List"), "" }, object_list_shortcuts }); } else { Shortcuts commands_shortcuts = { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 24d32b116..f7fbb6395 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2797,10 +2797,11 @@ void Plater::priv::split_object() Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one solid part.")); else { - if (current_model_object->volumes.size() != new_objects.size()) + // If we splited object which is contain some parts/modifiers then all non-solid parts (modifiers) were deleted + if (current_model_object->volumes.size() > 1 && current_model_object->volumes.size() != new_objects.size()) notification_manager->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotification, - _u8L("All non-solid parts (modifiers) was deleted")); + _u8L("All non-solid parts (modifiers) were deleted")); Plater::TakeSnapshot snapshot(q, _L("Split to Objects")); diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 9ff4e82a1..21373cc89 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -7,6 +7,7 @@ #include #include "wx/dataview.h" +#include "wx/numformatter.h" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/PresetBundle.hpp" @@ -45,6 +46,11 @@ static char marker_by_type(Preset::Type type, PrinterTechnology pt) } } +std::string Option::opt_key() const +{ + return boost::nowide::narrow(key).substr(2); +} + void FoundOption::get_marked_label_and_tooltip(const char** label_, const char** tooltip_) const { *label_ = marked_label.c_str(); diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index a9ced75dd..a942a89f8 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -53,7 +53,7 @@ struct Option { std::wstring category; std::wstring category_local; - std::string opt_key() const { return boost::nowide::narrow(key).substr(2); } + std::string opt_key() const; }; struct FoundOption { From b875fd2755651e5affa0c6ab1b7ed7cfcc884af3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 11 May 2021 15:01:33 +0200 Subject: [PATCH 50/75] Fixed project dirty state after changing language --- src/slic3r/GUI/ProjectDirtyStateManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 9ff4821c2..67feed4f3 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -64,7 +64,7 @@ static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, con return true; else if (snapshot.name == _utf8("Reset Project")) return true; - else if (boost::starts_with(snapshot.name, _utf8("Load Project:"))) + else if (boost::starts_with(snapshot.name, _utf8("Load Project"))) return true; else if (boost::starts_with(snapshot.name, _utf8("Selection"))) return true; @@ -366,7 +366,7 @@ void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); if (active_snapshot->name == _utf8("New Project") || active_snapshot->name == _utf8("Reset Project") || - boost::starts_with(active_snapshot->name, _utf8("Load Project:"))) + boost::starts_with(active_snapshot->name, _utf8("Load Project"))) return; if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) { From de1d36cc9aa3890208b2e9bfcc30606972539959 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 May 2021 18:02:18 +0200 Subject: [PATCH 51/75] Probably fix for #6270 - Segfault during startup in prusa-slicer-git 2.3.0.r24.gd06aa6069-1 --- src/slic3r/GUI/MainFrame.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 8c54e10f8..e7065cb13 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -544,9 +544,12 @@ void MainFrame::init_tabpanel() m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxBookCtrlEvent& e) { #if ENABLE_VALIDATE_CUSTOM_GCODE - Tab* old_tab = dynamic_cast(m_tabpanel->GetPage(e.GetOldSelection())); - if (old_tab) - old_tab->validate_custom_gcodes(); + if (int old_selection = e.GetOldSelection(); + old_selection != wxNOT_FOUND && old_selection < m_tabpanel->GetPageCount()) { + Tab* old_tab = dynamic_cast(m_tabpanel->GetPage(old_selection)); + if (old_tab) + old_tab->validate_custom_gcodes(); + } #endif // ENABLE_VALIDATE_CUSTOM_GCODE wxWindow* panel = m_tabpanel->GetCurrentPage(); From ebe762f17710eeb33b6ca91b271791379ae8f39f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 12 May 2021 11:21:18 +0200 Subject: [PATCH 52/75] Add estimated printing time for first layer in legend --- src/slic3r/GUI/GCodeViewer.cpp | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 3287a99e9..ce4c18a53 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3395,27 +3395,27 @@ void GCodeViewer::render_legend() const ImGui::Spacing(); #endif // ENABLE_SCROLLABLE_LEGEND ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Separator, { 1.0f, 1.0f, 1.0f, 1.0f }); - ImGui::Separator(); - ImGui::PopStyleColor(); - ImGui::Spacing(); - - ImGui::AlignTextToFramePadding(); + std::string time_title = _u8L("Estimated printing times"); switch (m_time_estimate_mode) { - case PrintEstimatedStatistics::ETimeMode::Normal: - { - imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:"); - break; + case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]:"; break; } + case PrintEstimatedStatistics::ETimeMode::Stealth: { time_title += " [" + _u8L("Stealth mode") + "]:"; break; } + default: { assert(false); break; } } - case PrintEstimatedStatistics::ETimeMode::Stealth: - { - imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); - break; - } - default : { assert(false); break; } - } - ImGui::SameLine(); + + imgui.title(time_title); + + std::string first_str = _u8L("First layer"); + std::string total_str = _u8L("Total"); + + float max_len = 10.0f + ImGui::GetStyle().ItemSpacing.x + std::max(ImGui::CalcTextSize(first_str.c_str()).x, ImGui::CalcTextSize(total_str.c_str()).x); + + imgui.text(first_str + ":"); + ImGui::SameLine(max_len); + imgui.text(short_time(get_time_dhms(time_mode.layers_times.front()))); + + imgui.text(total_str + ":"); + ImGui::SameLine(max_len); imgui.text(short_time(get_time_dhms(time_mode.time))); auto show_mode_button = [this, &imgui](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { From 82da1f8fc122ad1ac9cd87d4bdaaa61a9dd11d8f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 12 May 2021 11:39:39 +0200 Subject: [PATCH 53/75] Code cleaning: Delete workaround code --- src/slic3r/GUI/KBShortcutsDialog.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index d766ff5fd..e72975629 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -182,15 +182,9 @@ void KBShortcutsDialog::fill_shortcuts() m_full_shortcuts.push_back({ { _L("Gizmos"), _L("The following shortcuts are applicable when the specified gizmo is active") }, gizmos_shortcuts }); Shortcuts object_list_shortcuts = { -#if defined (__linux__) - { alt + "P", L("Set selected items as Ptrintable/Unprintable") }, - { alt + "0", L("Set default extruder for the selected items") }, - { alt + "1-9", L("Set extruder number for the selected items") }, -#else { "P", L("Set selected items as Ptrintable/Unprintable") }, { "0", L("Set default extruder for the selected items") }, { "1-9", L("Set extruder number for the selected items") }, -#endif }; m_full_shortcuts.push_back({ { _L("Objects List"), "" }, object_list_shortcuts }); From 3006213d3b3f2e24d51f21db02451729a3998aca Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 12 May 2021 14:25:13 +0200 Subject: [PATCH 54/75] Removed obsolete member variable from Canvas3D::Slope --- src/slic3r/GUI/GLCanvas3D.cpp | 2 -- src/slic3r/GUI/GLCanvas3D.hpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e77a6a324..3829ab0d1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -787,8 +787,6 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas ImGui::PopStyleVar(2); } -float GLCanvas3D::Slope::s_window_width; - wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index f51f53f62..46de9b12e 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -357,7 +357,6 @@ class GLCanvas3D { bool m_enabled{ false }; GLVolumeCollection& m_volumes; - static float s_window_width; public: Slope(GLVolumeCollection& volumes) : m_volumes(volumes) {} @@ -368,7 +367,6 @@ class GLCanvas3D void set_normal_angle(float angle_in_deg) const { m_volumes.set_slope_normal_z(-::cos(Geometry::deg2rad(90.0f - angle_in_deg))); } - static float get_window_width() { return s_window_width; }; }; class RenderTimer : public wxTimer { From 4be1d622591a8b7b68b467abeb7787a7b63cf533 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 12 May 2021 15:53:58 +0200 Subject: [PATCH 55/75] Fixed update of the "Supports" in frequently used parameters when Vase mode is selected --- src/slic3r/GUI/ConfigManipulation.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index d55758538..c5633b8fa 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -91,6 +91,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK)); DynamicPrintConfig new_conf = *config; auto answer = dialog.ShowModal(); + bool support = true; if (!is_global_config || answer == wxID_YES) { new_conf.set_key_value("perimeters", new ConfigOptionInt(1)); new_conf.set_key_value("top_solid_layers", new ConfigOptionInt(0)); @@ -100,13 +101,17 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(true)); new_conf.set_key_value("thin_walls", new ConfigOptionBool(false)); fill_density = 0; + support = false; } else { new_conf.set_key_value("spiral_vase", new ConfigOptionBool(false)); } apply(config, &new_conf); - if (cb_value_change) + if (cb_value_change) { cb_value_change("fill_density", fill_density); + if (!support) + cb_value_change("support_material", false); + } } if (config->opt_bool("wipe_tower") && config->opt_bool("support_material") && From 707ce9d3d46777af1d484cd744fbf51e5101d025 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 12 May 2021 17:04:36 +0200 Subject: [PATCH 56/75] Added a missing include (gcc) --- src/slic3r/GUI/GUI_ObjectList.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index f27623b09..0b6235425 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -21,6 +21,7 @@ #include #include +#include #include "slic3r/Utils/FixModelByWin10.hpp" From 044634d7d1ab2571a5a409e99d000324d79f8a57 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 May 2021 09:47:44 +0200 Subject: [PATCH 57/75] Fixed "Extruder sequence", when extruder changes are per mm --- src/slic3r/GUI/DoubleSlider.cpp | 6 +++--- src/slic3r/GUI/DoubleSlider.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 655ef496b..a10acb2e3 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -326,10 +326,10 @@ double Control::get_double_value(const SelectedSlider& selection) return m_values[selection == ssLower ? m_lower_value : m_higher_value]; } -int Control::get_tick_from_value(double value) +int Control::get_tick_from_value(double value, bool force_lower_bound/* = false*/) { std::vector::iterator it; - if (m_is_wipe_tower) + if (m_is_wipe_tower && !force_lower_bound) it = std::find_if(m_values.begin(), m_values.end(), [value](const double & val) { return fabs(value - val) <= epsilon(); }); else @@ -2395,7 +2395,7 @@ void Control::edit_extruder_sequence() extruder = 0; if (m_extruders_sequence.is_mm_intervals) { value += m_extruders_sequence.interval_by_mm; - tick = get_tick_from_value(value); + tick = get_tick_from_value(value, true); if (tick < 0) break; } diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 439b03a50..400265885 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -322,7 +322,7 @@ private: wxSize get_size() const; void get_size(int* w, int* h) const; double get_double_value(const SelectedSlider& selection); - int get_tick_from_value(double value); + int get_tick_from_value(double value, bool force_lower_bound = false); wxString get_tooltip(int tick = -1); int get_edited_tick_for_position(wxPoint pos, Type type = ColorChange); From d54548367a946f4c1c388a91fca61f1589533fa2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 13 May 2021 10:59:13 +0200 Subject: [PATCH 58/75] Fixed imgui out of synch with mouse after switching between preview and 3D view --- src/slic3r/GUI/GLCanvas3D.cpp | 8 -------- src/slic3r/GUI/GLCanvas3D.hpp | 4 ---- 2 files changed, 12 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3829ab0d1..ea3bec131 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4527,14 +4527,6 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) if (m_canvas == nullptr && m_context == nullptr) return; -#if ENABLE_SCROLLABLE_LEGEND - const std::array new_size = { w, h }; - if (m_old_size == new_size) - return; - - m_old_size = new_size; -#endif // ENABLE_SCROLLABLE_LEGEND - auto *imgui = wxGetApp().imgui(); imgui->set_display_size(static_cast(w), static_cast(h)); const float font_size = 1.5f * wxGetApp().em_unit(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 46de9b12e..a5c010d95 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -425,10 +425,6 @@ private: Model* m_model; BackgroundSlicingProcess *m_process; -#if ENABLE_SCROLLABLE_LEGEND - std::array m_old_size{ 0, 0 }; -#endif // ENABLE_SCROLLABLE_LEGEND - // Screen is only refreshed from the OnIdle handler if it is dirty. bool m_dirty; bool m_initialized; From e0dd7edb21984b2800ff06d1c05b2bb8a655e2d3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 13 May 2021 14:56:35 +0200 Subject: [PATCH 59/75] Removed obsolete method from GLVolumeCollection --- src/slic3r/GUI/3DScene.cpp | 22 ---------------------- src/slic3r/GUI/3DScene.hpp | 2 -- 2 files changed, 24 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 00bad52ff..462594f0f 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1071,28 +1071,6 @@ std::string GLVolumeCollection::log_memory_info() const return " (GLVolumeCollection RAM: " + format_memsize_MB(this->cpu_memory_used()) + " GPU: " + format_memsize_MB(this->gpu_memory_used()) + " Both: " + format_memsize_MB(this->gpu_memory_used()) + ")"; } -bool can_export_to_obj(const GLVolume& volume) -{ - if (!volume.is_active || !volume.is_extrusion_path) - return false; - - bool has_triangles = !volume.indexed_vertex_array.triangle_indices.empty() || (std::min(volume.indexed_vertex_array.triangle_indices_size, volume.tverts_range.second - volume.tverts_range.first) > 0); - bool has_quads = !volume.indexed_vertex_array.quad_indices.empty() || (std::min(volume.indexed_vertex_array.quad_indices_size, volume.qverts_range.second - volume.qverts_range.first) > 0); - - return has_triangles || has_quads; -} - -bool GLVolumeCollection::has_toolpaths_to_export() const -{ - for (const GLVolume* volume : this->volumes) - { - if (can_export_to_obj(*volume)) - return true; - } - - return false; -} - // caller is responsible for supplying NO lines with zero length static void thick_lines_to_indexed_vertex_array( const Lines &lines, diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 2d46a4524..db64bdab5 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -590,8 +590,6 @@ public: // Return CPU, GPU and total memory log line. std::string log_memory_info() const; - bool has_toolpaths_to_export() const; - private: GLVolumeCollection(const GLVolumeCollection &other); GLVolumeCollection& operator=(const GLVolumeCollection &); From 221af991c2474a8f382042b667269e3346dab25e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 14 May 2021 11:52:33 +0200 Subject: [PATCH 60/75] Fixed update of PrintRegions after removing an object. The bug has been introduced during recent PrintRegion refactoring. --- src/libslic3r/PrintApply.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index f0e262fdc..c906a0041 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -372,6 +372,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ std::set model_object_status; // 1) Synchronize model objects. + bool print_regions_reshuffled = false; if (model.id() != m_model.id()) { // Kill everything, initialize from scratch. // Stop background processing. @@ -383,6 +384,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ delete object; } m_objects.clear(); + print_regions_reshuffled = true; m_model.assign_copy(model); for (const ModelObject *model_object : m_model.objects) model_object_status.emplace(model_object->id(), ModelObjectStatus::New); @@ -462,6 +464,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } for (ModelObject *model_object : model_objects_old) delete model_object; + print_regions_reshuffled = true; } } } @@ -595,7 +598,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } // 4) Generate PrintObjects from ModelObjects and their instances. - bool print_regions_reshuffled = false; { PrintObjectPtrs print_objects_new; print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); From 2b9e41e6955f2bdf5a1a498bb96f6173a4780ee1 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 14 May 2021 14:57:41 +0200 Subject: [PATCH 61/75] Fix of a special case in auto color print feature, which crashed with single layer PrintObjects. Fixes Segmentation fault (GLib-GObject null pointer) #6516 --- src/slic3r/GUI/GUI_Preview.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 52223b3d4..769ab3f59 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -661,31 +661,32 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee // if it's sign, than object have not to be a too height double height = object->height(); coord_t longer_side = std::max(object_x, object_y); - if (height / longer_side > 0.3) + auto num_layers = int(object->layers().size()); + if (height / longer_side > 0.3 || num_layers < 2) continue; const ExPolygons& bottom = object->get_layer(0)->lslices; double bottom_area = area(bottom); // at least 30% of object's height have to be a solid - size_t i; - for (i = 1; i < size_t(0.3 * object->layers().size()); i++) { + int i; + for (i = 1; i < int(0.3 * num_layers); ++ i) { double cur_area = area(object->get_layer(i)->lslices); if (cur_area != bottom_area && fabs(cur_area - bottom_area) > scale_(scale_(1))) break; } - if (i < size_t(0.3 * object->layers().size())) + if (i < size_t(0.3 * num_layers)) continue; // bottom layer have to be a biggest, so control relation between bottom layer and object size double prev_area = area(object->get_layer(i)->lslices); - for ( i++; i < object->layers().size(); i++) { + for ( i++; i < num_layers; i++) { double cur_area = area(object->get_layer(i)->lslices); if (cur_area > prev_area && prev_area - cur_area > scale_(scale_(1))) break; prev_area = cur_area; } - if (i < object->layers().size()) + if (i < num_layers) continue; double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); From ca14ea4c33a0cb368e7bf4dc1576c80900882e24 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 14 May 2021 15:02:54 +0200 Subject: [PATCH 62/75] Tech ENABLE_ALLOW_NEGATIVE_Z -> ModelObject::convex_hull_2d() and sequential_print_horizontal_clearance_valid() modified to take in account for sinking instances --- src/libslic3r/Model.cpp | 10 ++++++++-- src/libslic3r/Print.cpp | 12 +++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index a02eb738b..2a5f73645 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -901,13 +901,19 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const for (const stl_facet &facet : stl.facet_start) for (size_t j = 0; j < 3; ++ j) { Vec3d p = trafo * facet.vertex[j].cast(); - pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); +#if ENABLE_ALLOW_NEGATIVE_Z + if (p.z() >= 0.0) +#endif // ENABLE_ALLOW_NEGATIVE_Z + pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } } else { // Using the shared vertices should be a bit quicker than using the STL faces. for (size_t i = 0; i < its.vertices.size(); ++ i) { Vec3d p = trafo * its.vertices[i].cast(); - pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); +#if ENABLE_ALLOW_NEGATIVE_Z + if (p.z() >= 0.0) +#endif // ENABLE_ALLOW_NEGATIVE_Z + pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } } } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 538ee6009..99119f566 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -371,6 +371,15 @@ static inline bool sequential_print_horizontal_clearance_valid(const Print &prin // FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2) // which causes that the warning will be showed after arrangement with the // appropriate object distance. Even if I set this to jtMiter the warning still shows up. +#if ENABLE_ALLOW_NEGATIVE_Z + it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id, + offset(print_object->model_object()->convex_hull_2d( + Geometry::assemble_transform({ 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror())), + // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects + // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. + float(scale_(0.5 * print.config().extruder_clearance_radius.value - EPSILON)), + jtRound, float(scale_(0.1))).front()); +#else it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id, offset(print_object->model_object()->convex_hull_2d( Geometry::assemble_transform(Vec3d::Zero(), model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror())), @@ -378,7 +387,8 @@ static inline bool sequential_print_horizontal_clearance_valid(const Print &prin // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. float(scale_(0.5 * print.config().extruder_clearance_radius.value - EPSILON)), jtRound, float(scale_(0.1))).front()); - } +#endif // ENABLE_ALLOW_NEGATIVE_Z + } // Make a copy, so it may be rotated for instances. Polygon convex_hull0 = it_convex_hull->second; double z_diff = Geometry::rotation_diff_z(model_instance0->get_rotation(), print_object->instances().front().model_instance->get_rotation()); From c37d18f046bfe5d868968815235e75b17e5c6f04 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 17 May 2021 12:53:05 +0200 Subject: [PATCH 63/75] Follow-up of ca14ea4c33a0cb368e7bf4dc1576c80900882e24 -> Fixed arrange with sinking objects --- src/libslic3r/Model.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 2a5f73645..83c03eef0 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1906,8 +1906,13 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const Vec3d rotation = get_rotation(); rotation.z() = 0.; Transform3d trafo_instance = +#if ENABLE_ALLOW_NEGATIVE_Z + Geometry::assemble_transform(get_offset().z() * Vec3d::UnitZ(), rotation, + get_scaling_factor(), get_mirror()); +#else Geometry::assemble_transform(Vec3d::Zero(), rotation, get_scaling_factor(), get_mirror()); +#endif // ENABLE_ALLOW_NEGATIVE_Z Polygon p = get_object()->convex_hull_2d(trafo_instance); From 0605813e683ed06ba114d9a48e8697ba22864b21 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 17 May 2021 13:02:38 +0200 Subject: [PATCH 64/75] Faster ModelObject::convex_hull_2d() by using ModelVolume 3D convex hulls --- src/libslic3r/Model.cpp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 83c03eef0..8d73782a4 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -893,6 +893,30 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const Points pts; for (const ModelVolume *v : this->volumes) if (v->is_model_part()) { +#if ENABLE_ALLOW_NEGATIVE_Z + const Transform3d trafo = trafo_instance * v->get_matrix(); + const TriangleMesh& hull_3d = v->get_convex_hull(); + const indexed_triangle_set& its = hull_3d.its; + if (its.vertices.empty()) { + // Using the STL faces. + const stl_file& stl = hull_3d.stl; + for (const stl_facet& facet : stl.facet_start) { + for (size_t j = 0; j < 3; ++j) { + const Vec3d p = trafo * facet.vertex[j].cast(); + if (p.z() >= 0.0) + pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); + } + } + } + else { + // Using the shared vertices should be a bit quicker than using the STL faces. + for (size_t i = 0; i < its.vertices.size(); ++i) { + const Vec3d p = trafo * its.vertices[i].cast(); + if (p.z() >= 0.0) + pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); + } + } +#else Transform3d trafo = trafo_instance * v->get_matrix(); const indexed_triangle_set &its = v->mesh().its; if (its.vertices.empty()) { @@ -901,21 +925,16 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const for (const stl_facet &facet : stl.facet_start) for (size_t j = 0; j < 3; ++ j) { Vec3d p = trafo * facet.vertex[j].cast(); -#if ENABLE_ALLOW_NEGATIVE_Z - if (p.z() >= 0.0) -#endif // ENABLE_ALLOW_NEGATIVE_Z pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } } else { // Using the shared vertices should be a bit quicker than using the STL faces. for (size_t i = 0; i < its.vertices.size(); ++ i) { Vec3d p = trafo * its.vertices[i].cast(); -#if ENABLE_ALLOW_NEGATIVE_Z - if (p.z() >= 0.0) -#endif // ENABLE_ALLOW_NEGATIVE_Z pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } } +#endif // ENABLE_ALLOW_NEGATIVE_Z } std::sort(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) < b(0) || (a(0) == b(0) && a(1) < b(1)); }); pts.erase(std::unique(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) == b(0) && a(1) == b(1); }), pts.end()); From 68d2427a347d0d8b0df0f42465289f1b662e23b1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 17 May 2021 14:54:47 +0200 Subject: [PATCH 65/75] Fix marching squares test crash in debug builds --- src/libslic3r/SLA/RasterBase.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index bbb83b5a0..1cd688ccb 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -72,7 +72,8 @@ public: size_t width_px = 0; size_t height_px = 0; - Resolution(size_t w = 0, size_t h = 0) : width_px(w), height_px(h) {} + Resolution() = default; + Resolution(size_t w, size_t h) : width_px(w), height_px(h) {} size_t pixels() const { return width_px * height_px; } }; @@ -81,7 +82,8 @@ public: double w_mm = 1.; double h_mm = 1.; - PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0) + PixelDim() = default; + PixelDim(double px_width_mm, double px_height_mm) : w_mm(px_width_mm), h_mm(px_height_mm) {} }; From 308d6b78096ac7b53b4698d9c6bc698fa434282e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 17 May 2021 20:25:59 +0200 Subject: [PATCH 66/75] WIP: Reworked slicing 1) Slicing code moved to TriangleMeshSlicer.cpp,hpp from TriangleMesh.cpp,hpp 2) Refactored to use as little as possible of admesh. --- src/admesh/stl.h | 6 +- src/clipper/clipper.cpp | 71 +- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Model.cpp | 1 + src/libslic3r/PrintObject.cpp | 13 +- src/libslic3r/SLA/Hollowing.cpp | 5 +- src/libslic3r/SLA/Pad.cpp | 4 +- src/libslic3r/SLA/SupportTree.cpp | 8 +- src/libslic3r/SLAPrintSteps.cpp | 10 +- src/libslic3r/SVG.cpp | 6 - src/libslic3r/SVG.hpp | 3 +- src/libslic3r/TriangleMesh.cpp | 1561 +++---------------- src/libslic3r/TriangleMesh.hpp | 193 +-- src/libslic3r/TriangleMeshSlicer.cpp | 1363 ++++++++++++++++ src/libslic3r/TriangleMeshSlicer.hpp | 141 ++ src/slic3r/GUI/BackgroundSlicingProcess.hpp | 2 + src/slic3r/GUI/MeshUtils.cpp | 6 +- src/slic3r/GUI/MeshUtils.hpp | 4 +- src/slic3r/Utils/FixModelByWin10.cpp | 1 + tests/fff_print/test_trianglemesh.cpp | 1 + tests/libslic3r/test_marchingsquares.cpp | 3 +- tests/sla_print/sla_print_tests.cpp | 7 +- tests/sla_print/sla_test_utils.cpp | 7 +- xs/xsp/TriangleMesh.xsp | 4 +- 24 files changed, 1828 insertions(+), 1594 deletions(-) create mode 100644 src/libslic3r/TriangleMeshSlicer.cpp create mode 100644 src/libslic3r/TriangleMeshSlicer.hpp diff --git a/src/admesh/stl.h b/src/admesh/stl.h index e0f2865f0..f7b8937ad 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -135,7 +135,7 @@ struct stl_file { std::vector facet_start; std::vector neighbors_start; // Statistics - stl_stats stats; + stl_stats stats; }; struct indexed_triangle_set @@ -149,9 +149,9 @@ struct indexed_triangle_set } std::vector indices; - std::vector vertices; + std::vector vertices; //FIXME add normals once we get rid of the stl_file from TriangleMesh completely. - //std::vector normals + //std::vector normals }; extern bool stl_open(stl_file *stl, const char *file); diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 0285d9167..e46a0797c 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -105,6 +105,15 @@ struct OutRec { //------------------------------------------------------------------------------ +inline IntPoint IntPoint2d(cInt x, cInt y) +{ + return IntPoint(x, y +#ifdef CLIPPERLIB_USE_XYZ + , 0 +#endif // CLIPPERLIB_USE_XYZ + ); +} + inline cInt Round(double val) { return static_cast((val < 0) ? (val - 0.5) : (val + 0.5)); @@ -2243,7 +2252,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) while (maxIt != m_Maxima.end() && *maxIt < e->Curr.x()) { if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.y())); + AddOutPt(horzEdge, IntPoint2d(*maxIt, horzEdge->Bot.y())); ++maxIt; } } @@ -2252,7 +2261,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.x()) { if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.y())); + AddOutPt(horzEdge, IntPoint2d(*maxRit, horzEdge->Bot.y())); ++maxRit; } } @@ -2297,12 +2306,12 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) if(dir == dLeftToRight) { - IntPoint Pt = IntPoint(e->Curr.x(), horzEdge->Curr.y()); + IntPoint Pt = IntPoint2d(e->Curr.x(), horzEdge->Curr.y()); IntersectEdges(horzEdge, e, Pt); } else { - IntPoint Pt = IntPoint(e->Curr.x(), horzEdge->Curr.y()); + IntPoint Pt = IntPoint2d(e->Curr.x(), horzEdge->Curr.y()); IntersectEdges( e, horzEdge, Pt); } TEdge* eNext = (dir == dLeftToRight) ? e->NextInAEL : e->PrevInAEL; @@ -3372,14 +3381,14 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType //if this path's lowest pt is lower than all the others then update m_lowest if (endType != etClosedPolygon) return; if (m_lowest.x() < 0) - m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + m_lowest = IntPoint2d(m_polyNodes.ChildCount() - 1, k); else { IntPoint ip = m_polyNodes.Childs[(int)m_lowest.x()]->Contour[(int)m_lowest.y()]; if (newNode->Contour[k].y() > ip.y() || (newNode->Contour[k].y() == ip.y() && newNode->Contour[k].x() < ip.x())) - m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + m_lowest = IntPoint2d(m_polyNodes.ChildCount() - 1, k); } } //------------------------------------------------------------------------------ @@ -3427,10 +3436,10 @@ void ClipperOffset::Execute(Paths& solution, double delta) { IntRect r = clpr.GetBounds(); Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); + outer[0] = IntPoint2d(r.left - 10, r.bottom + 10); + outer[1] = IntPoint2d(r.right + 10, r.bottom + 10); + outer[2] = IntPoint2d(r.right + 10, r.top - 10); + outer[3] = IntPoint2d(r.left - 10, r.top - 10); clpr.AddPath(outer, ptSubject, true); clpr.ReverseSolution(true); @@ -3457,10 +3466,10 @@ void ClipperOffset::Execute(PolyTree& solution, double delta) { IntRect r = clpr.GetBounds(); Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); + outer[0] = IntPoint2d(r.left - 10, r.bottom + 10); + outer[1] = IntPoint2d(r.right + 10, r.bottom + 10); + outer[2] = IntPoint2d(r.right + 10, r.top - 10); + outer[3] = IntPoint2d(r.left - 10, r.top - 10); clpr.AddPath(outer, ptSubject, true); clpr.ReverseSolution(true); @@ -3536,7 +3545,7 @@ void ClipperOffset::DoOffset(double delta) double X = 1.0, Y = 0.0; for (cInt j = 1; j <= steps; j++) { - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[0].x() + X * delta), Round(m_srcPoly[0].y() + Y * delta))); double X2 = X; @@ -3549,7 +3558,7 @@ void ClipperOffset::DoOffset(double delta) double X = -1.0, Y = -1.0; for (int j = 0; j < 4; ++j) { - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[0].x() + X * delta), Round(m_srcPoly[0].y() + Y * delta))); if (X < 0) X = 1; @@ -3604,9 +3613,9 @@ void ClipperOffset::DoOffset(double delta) if (node.m_endtype == etOpenButt) { int j = len - 1; - pt1 = IntPoint(Round(m_srcPoly[j].x() + m_normals[j].x() * delta), Round(m_srcPoly[j].y() + m_normals[j].y() * delta)); + pt1 = IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * delta), Round(m_srcPoly[j].y() + m_normals[j].y() * delta)); m_destPoly.push_back(pt1); - pt1 = IntPoint(Round(m_srcPoly[j].x() - m_normals[j].x() * delta), Round(m_srcPoly[j].y() - m_normals[j].y() * delta)); + pt1 = IntPoint2d(Round(m_srcPoly[j].x() - m_normals[j].x() * delta), Round(m_srcPoly[j].y() - m_normals[j].y() * delta)); m_destPoly.push_back(pt1); } else @@ -3631,9 +3640,9 @@ void ClipperOffset::DoOffset(double delta) if (node.m_endtype == etOpenButt) { - pt1 = IntPoint(Round(m_srcPoly[0].x() - m_normals[0].x() * delta), Round(m_srcPoly[0].y() - m_normals[0].y() * delta)); + pt1 = IntPoint2d(Round(m_srcPoly[0].x() - m_normals[0].x() * delta), Round(m_srcPoly[0].y() - m_normals[0].y() * delta)); m_destPoly.push_back(pt1); - pt1 = IntPoint(Round(m_srcPoly[0].x() + m_normals[0].x() * delta), Round(m_srcPoly[0].y() + m_normals[0].y() * delta)); + pt1 = IntPoint2d(Round(m_srcPoly[0].x() + m_normals[0].x() * delta), Round(m_srcPoly[0].y() + m_normals[0].y() * delta)); m_destPoly.push_back(pt1); } else @@ -3661,7 +3670,7 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) double cosA = (m_normals[k].x() * m_normals[j].x() + m_normals[j].y() * m_normals[k].y() ); if (cosA > 0) // angle => 0 degrees { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), + m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta))); return; } @@ -3672,10 +3681,10 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) if (m_sinA * m_delta < 0) { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), + m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta))); m_destPoly.push_back(m_srcPoly[j]); - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), + m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta))); } else @@ -3699,10 +3708,10 @@ void ClipperOffset::DoSquare(int j, int k) { double dx = std::tan(std::atan2(m_sinA, m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y()) / 4); - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[j].x() + m_delta * (m_normals[k].x() - m_normals[k].y() * dx)), Round(m_srcPoly[j].y() + m_delta * (m_normals[k].y() + m_normals[k].x() * dx)))); - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[j].x() + m_delta * (m_normals[j].x() + m_normals[j].y() * dx)), Round(m_srcPoly[j].y() + m_delta * (m_normals[j].y() - m_normals[j].x() * dx)))); } @@ -3711,7 +3720,7 @@ void ClipperOffset::DoSquare(int j, int k) void ClipperOffset::DoMiter(int j, int k, double r) { double q = m_delta / r; - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q), + m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q), Round(m_srcPoly[j].y() + (m_normals[k].y() + m_normals[j].y()) * q))); } //------------------------------------------------------------------------------ @@ -3725,14 +3734,14 @@ void ClipperOffset::DoRound(int j, int k) double X = m_normals[k].x(), Y = m_normals[k].y(), X2; for (int i = 0; i < steps; ++i) { - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[j].x() + X * m_delta), Round(m_srcPoly[j].y() + Y * m_delta))); X2 = X; X = X * m_cos - m_sin * Y; Y = X2 * m_sin + Y * m_cos; } - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta))); } @@ -4001,7 +4010,7 @@ void Minkowski(const Path& poly, const Path& path, Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].x() + poly[j].x(), path[i].y() + poly[j].y())); + p.push_back(IntPoint2d(path[i].x() + poly[j].x(), path[i].y() + poly[j].y())); pp.push_back(p); } else @@ -4010,7 +4019,7 @@ void Minkowski(const Path& poly, const Path& path, Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].x() - poly[j].x(), path[i].y() - poly[j].y())); + p.push_back(IntPoint2d(path[i].x() - poly[j].x(), path[i].y() - poly[j].y())); pp.push_back(p); } @@ -4045,7 +4054,7 @@ void TranslatePath(const Path& input, Path& output, const IntPoint& delta) //precondition: input != output output.resize(input.size()); for (size_t i = 0; i < input.size(); ++i) - output[i] = IntPoint(input[i].x() + delta.x(), input[i].y() + delta.y()); + output[i] = IntPoint2d(input[i].x() + delta.x(), input[i].y() + delta.y()); } //------------------------------------------------------------------------------ diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 0cf67597a..d98d0e10f 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -198,6 +198,8 @@ add_library(libslic3r STATIC Tesselate.hpp TriangleMesh.cpp TriangleMesh.hpp + TriangleMeshSlicer.cpp + TriangleMeshSlicer.hpp TriangulateWall.hpp TriangulateWall.cpp utils.cpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 8d73782a4..4ddcbac39 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -3,6 +3,7 @@ #include "ModelArrange.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" +#include "TriangleMeshSlicer.hpp" #include "TriangleSelector.hpp" #include "Format/AMF.hpp" diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 46069849b..b77b13345 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -10,6 +10,7 @@ #include "Surface.hpp" #include "Slicing.hpp" #include "Tesselate.hpp" +#include "TriangleMeshSlicer.hpp" #include "Utils.hpp" #include "Fill/FillAdaptive.hpp" #include "Format/STL.hpp" @@ -2221,9 +2222,8 @@ std::vector PrintObject::slice_volumes( auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); // TriangleMeshSlicer needs shared vertices, also this calls the repair() function. mesh.require_shared_vertices(); - TriangleMeshSlicer mslicer; - mslicer.init(&mesh, callback); - mslicer.slice(z, mode, slicing_mode_normal_below_layer, mode_below, float(m_config.slice_closing_radius.value), &layers, callback); + MeshSlicingParamsExtended params { { mode, slicing_mode_normal_below_layer, mode_below }, float(m_config.slice_closing_radius.value) }; + slice_mesh(mesh, z, params, layers, callback); m_print->throw_if_canceled(); } } @@ -2245,13 +2245,14 @@ std::vector PrintObject::slice_volume(const std::vector &z, S // apply XY shift mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); // perform actual slicing - TriangleMeshSlicer mslicer; const Print *print = this->print(); auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); // TriangleMeshSlicer needs the shared vertices. mesh.require_shared_vertices(); - mslicer.init(&mesh, callback); - mslicer.slice(z, mode, float(m_config.slice_closing_radius.value), &layers, callback); + MeshSlicingParamsExtended params; + params.mode = mode; + params.closing_radius = float(m_config.slice_closing_radius.value); + slice_mesh(mesh, z, params, layers, callback); m_print->throw_if_canceled(); } } diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index b38784521..f55a10178 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -296,10 +297,8 @@ void cut_drainholes(std::vector & obj_slices, mesh.require_shared_vertices(); - TriangleMeshSlicer slicer(&mesh); - std::vector hole_slices; - slicer.slice(slicegrid, SlicingMode::Regular, closing_radius, &hole_slices, thr); + slice_mesh(mesh, slicegrid, closing_radius, hole_slices, thr); if (obj_slices.size() != hole_slices.size()) BOOST_LOG_TRIVIAL(warning) diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index e11914a1c..658ecf6d8 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "ConcaveHull.hpp" @@ -476,10 +477,9 @@ void pad_blueprint(const TriangleMesh & mesh, ThrowOnCancel thrfn) { if (mesh.empty()) return; - TriangleMeshSlicer slicer(&mesh); auto out = reserve_vector(heights.size()); - slicer.slice(heights, SlicingMode::Regular, 0.f, &out, thrfn); + slice_mesh(mesh, heights, out, thrfn); size_t count = 0; for(auto& o : out) count += o.size(); diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index 1bb4cfab7..45e90a704 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -44,9 +45,7 @@ std::vector SupportTree::slice( if (!sup_mesh.empty()) { slices.emplace_back(); - - TriangleMeshSlicer sup_slicer(&sup_mesh); - sup_slicer.slice(grid, SlicingMode::Regular, cr, &slices.back(), ctl().cancelfn); + slice_mesh(sup_mesh, grid, cr, slices.back(), ctl().cancelfn); } if (!pad_mesh.empty()) { @@ -59,8 +58,7 @@ std::vector SupportTree::slice( auto padgrid = reserve_vector(size_t(cap > 0 ? cap : 0)); std::copy(grid.begin(), maxzit, std::back_inserter(padgrid)); - TriangleMeshSlicer pad_slicer(&pad_mesh); - pad_slicer.slice(padgrid, SlicingMode::Regular, cr, &slices.back(), ctl().cancelfn); + slice_mesh(pad_mesh, padgrid, cr, slices.back(), ctl().cancelfn); } size_t len = grid.size(); diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 108159b89..d2c3c06d2 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -3,6 +3,7 @@ #include #include #include +#include // Need the cylinder method for the the drainholes in hollowing step #include @@ -198,7 +199,7 @@ static std::vector create_exclude_mask( std::vector exclude_mask(its.indices.size(), false); std::vector< std::vector > neighbor_index = - create_neighbor_index(its); + create_vertex_faces_index(its); auto exclude_neighbors = [&neighbor_index, &exclude_mask](const Vec3i &face) { @@ -470,13 +471,11 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) for(auto it = slindex_it; it != po.m_slice_index.end(); ++it) po.m_model_height_levels.emplace_back(it->slice_level()); - TriangleMeshSlicer slicer(&mesh); - po.m_model_slices.clear(); float closing_r = float(po.config().slice_closing_radius.value); auto thr = [this]() { m_print->throw_if_canceled(); }; auto &slice_grid = po.m_model_height_levels; - slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr); + slice_mesh(mesh, slice_grid, closing_r, po.m_model_slices, thr); sla::Interior *interior = po.m_hollowing_data ? po.m_hollowing_data->interior.get() : @@ -486,9 +485,8 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) TriangleMesh interiormesh = sla::get_mesh(*interior); interiormesh.repaired = false; interiormesh.repair(true); - TriangleMeshSlicer interior_slicer(&interiormesh); std::vector interior_slices; - interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr); + slice_mesh(interiormesh, slice_grid, closing_r, interior_slices, thr); sla::ccr::for_each(size_t(0), interior_slices.size(), [&po, &interior_slices] (size_t i) { diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 6bc334eec..6a743b1eb 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -83,12 +83,6 @@ void SVG::draw(const Lines &lines, std::string stroke, coordf_t stroke_width) this->draw(l, stroke, stroke_width); } -void SVG::draw(const IntersectionLines &lines, std::string stroke) -{ - for (const IntersectionLine &il : lines) - this->draw((Line)il, stroke); -} - void SVG::draw(const ExPolygon &expolygon, std::string fill, const float fill_opacity) { this->fill = fill; diff --git a/src/libslic3r/SVG.hpp b/src/libslic3r/SVG.hpp index da9dca093..314fae9b9 100644 --- a/src/libslic3r/SVG.hpp +++ b/src/libslic3r/SVG.hpp @@ -43,8 +43,7 @@ public: void draw(const Line &line, std::string stroke = "black", coordf_t stroke_width = 0); void draw(const ThickLine &line, const std::string &fill, const std::string &stroke, coordf_t stroke_width = 0); void draw(const Lines &lines, std::string stroke = "black", coordf_t stroke_width = 0); - void draw(const IntersectionLines &lines, std::string stroke = "black"); - + void draw(const ExPolygon &expolygon, std::string fill = "grey", const float fill_opacity=1.f); void draw_outline(const ExPolygon &polygon, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0); void draw(const ExPolygons &expolygons, std::string fill = "grey", const float fill_opacity=1.f); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 7b5c40466..9ce2d3a7f 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1,46 +1,28 @@ #include "Exception.hpp" #include "TriangleMesh.hpp" +#include "TriangleMeshSlicer.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" -#include "Tesselate.hpp" + #include #include #include + #include #include #include -#include #include -#include #include #include -#include #include #include -#include - #include #include -// for SLIC3R_DEBUG_SLICE_PROCESSING -#include "libslic3r.h" - -#if 0 - #define DEBUG - #define _DEBUG - #undef NDEBUG - #define SLIC3R_DEBUG -// #define SLIC3R_TRIANGLEMESH_DEBUG -#endif - #include -#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING) -#include "SVG.hpp" -#endif - namespace Slic3r { TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector &facets) : repaired(false) @@ -53,7 +35,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector &fac stl.stats.original_num_facets = stl.stats.number_of_facets; stl_allocate(&stl); - for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) { + for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) { stl_facet facet; facet.vertex[0] = points[facets[i](0)].cast(); facet.vertex[1] = points[facets[i](1)].cast(); @@ -104,23 +86,23 @@ TriangleMesh::TriangleMesh(const indexed_triangle_set &M) : repaired(false) void TriangleMesh::repair(bool update_shared_vertices) { if (this->repaired) { - if (update_shared_vertices) - this->require_shared_vertices(); - return; + if (update_shared_vertices) + this->require_shared_vertices(); + return; } // admesh fails when repairing empty meshes if (this->stl.stats.number_of_facets == 0) - return; + return; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started"; // checking exact #ifdef SLIC3R_TRACE_REPAIR - BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; + BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; #endif /* SLIC3R_TRACE_REPAIR */ - assert(stl_validate(&this->stl)); - stl_check_facets_exact(&stl); + assert(stl_validate(&this->stl)); + stl_check_facets_exact(&stl); assert(stl_validate(&this->stl)); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); @@ -128,17 +110,17 @@ void TriangleMesh::repair(bool update_shared_vertices) // checking nearby //int last_edges_fixed = 0; - float tolerance = (float)stl.stats.shortest_edge; - float increment = (float)stl.stats.bounding_diameter / 10000.0f; + float tolerance = (float)stl.stats.shortest_edge; + float increment = (float)stl.stats.bounding_diameter / 10000.0f; int iterations = 2; if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { for (int i = 0; i < iterations; i++) { if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); #ifdef SLIC3R_TRACE_REPAIR - BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_nearby"; + BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_nearby"; #endif /* SLIC3R_TRACE_REPAIR */ - stl_check_facets_nearby(&stl, tolerance); + stl_check_facets_nearby(&stl, tolerance); //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed); //last_edges_fixed = stl.stats.edges_fixed; tolerance += increment; @@ -155,7 +137,7 @@ void TriangleMesh::repair(bool update_shared_vertices) BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets"; #endif /* SLIC3R_TRACE_REPAIR */ stl_remove_unconnected_facets(&stl); - assert(stl_validate(&this->stl)); + assert(stl_validate(&this->stl)); } // fill_holes @@ -207,7 +189,7 @@ void TriangleMesh::repair(bool update_shared_vertices) // and it is risky to generate such a structure once the meshes are shared. Do it now. this->its.clear(); if (update_shared_vertices) - this->require_shared_vertices(); + this->require_shared_vertices(); } float TriangleMesh::volume() @@ -273,18 +255,18 @@ void TriangleMesh::WriteOBJFile(const char* output_file) const void TriangleMesh::scale(float factor) { stl_scale(&(this->stl), factor); - for (stl_vertex& v : this->its.vertices) - v *= factor; + for (stl_vertex& v : this->its.vertices) + v *= factor; } void TriangleMesh::scale(const Vec3d &versor) { stl_scale_versor(&this->stl, versor.cast()); - for (stl_vertex& v : this->its.vertices) { - v.x() *= versor.x(); - v.y() *= versor.y(); - v.z() *= versor.z(); - } + for (stl_vertex& v : this->its.vertices) { + v.x() *= versor.x(); + v.y() *= versor.y(); + v.z() *= versor.z(); + } } void TriangleMesh::translate(float x, float y, float z) @@ -292,9 +274,9 @@ void TriangleMesh::translate(float x, float y, float z) if (x == 0.f && y == 0.f && z == 0.f) return; stl_translate_relative(&(this->stl), x, y, z); - stl_vertex shift(x, y, z); - for (stl_vertex& v : this->its.vertices) - v += shift; + stl_vertex shift(x, y, z); + for (stl_vertex& v : this->its.vertices) + v += shift; } void TriangleMesh::translate(const Vec3f &displacement) @@ -339,15 +321,15 @@ void TriangleMesh::mirror(const Axis &axis) if (axis == X) { stl_mirror_yz(&this->stl); for (stl_vertex &v : this->its.vertices) - v(0) *= -1.0; + v(0) *= -1.0; } else if (axis == Y) { stl_mirror_xz(&this->stl); for (stl_vertex &v : this->its.vertices) - v(1) *= -1.0; + v(1) *= -1.0; } else if (axis == Z) { stl_mirror_xy(&this->stl); for (stl_vertex &v : this->its.vertices) - v(2) *= -1.0; + v(2) *= -1.0; } } @@ -355,8 +337,8 @@ void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) { stl_transform(&stl, t); its_transform(its, t); - if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) { - // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. + if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) { + // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. // As for the assert: the repair function would fix the normals, reversing would // break them again. The caller should provide a mesh that does not need repair. // The repair call is left here so things don't break more than they were. @@ -365,7 +347,7 @@ void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) stl_reverse_all_facets(&stl); this->its.clear(); this->require_shared_vertices(); - } + } } void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) @@ -522,7 +504,7 @@ ExPolygons TriangleMesh::horizontal_projection() const auto delta = scaled(0.01); std::vector deltas { delta, delta, delta }; paths.reserve(this->stl.stats.number_of_facets); - for (const stl_facet &facet : this->stl.facet_start) { + for (const stl_facet &facet : this->stl.facet_start) { p.points[0] = Point::new_scale(facet.vertex[0](0), facet.vertex[0](1)); p.points[1] = Point::new_scale(facet.vertex[1](0), facet.vertex[1](1)); p.points[2] = Point::new_scale(facet.vertex[2](0), facet.vertex[2](1)); @@ -560,12 +542,12 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) c BoundingBoxf3 bbox; if (this->its.vertices.empty()) { // Using the STL faces. - for (const stl_facet &facet : this->stl.facet_start) + for (const stl_facet &facet : this->stl.facet_start) for (size_t j = 0; j < 3; ++ j) bbox.merge(trafo * facet.vertex[j].cast()); } else { // Using the shared vertices should be a bit quicker than using the STL faces. - for (const stl_vertex &v : this->its.vertices) + for (const stl_vertex &v : this->its.vertices) bbox.merge(trafo * v.cast()); } return bbox; @@ -576,29 +558,29 @@ TriangleMesh TriangleMesh::convex_hull_3d() const // The qhull call: orgQhull::Qhull qhull; qhull.disableOutputStream(); // we want qhull to be quiet - std::vector src_vertices; - try + std::vector src_vertices; + try { - if (this->has_shared_vertices()) { + if (this->has_shared_vertices()) { #if REALfloat - qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt"); + qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt"); #else - src_vertices.reserve(this->its.vertices.size() * 3); - // We will now fill the vector with input points for computation: - for (const stl_vertex &v : this->its.vertices) - for (int i = 0; i < 3; ++ i) - src_vertices.emplace_back(v(i)); - qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); + src_vertices.reserve(this->its.vertices.size() * 3); + // We will now fill the vector with input points for computation: + for (const stl_vertex &v : this->its.vertices) + for (int i = 0; i < 3; ++ i) + src_vertices.emplace_back(v(i)); + qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); #endif - } else { - src_vertices.reserve(this->stl.facet_start.size() * 9); - // We will now fill the vector with input points for computation: - for (const stl_facet &f : this->stl.facet_start) - for (int i = 0; i < 3; ++ i) - for (int j = 0; j < 3; ++ j) - src_vertices.emplace_back(f.vertex[i](j)); - qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); - } + } else { + src_vertices.reserve(this->stl.facet_start.size() * 9); + // We will now fill the vector with input points for computation: + for (const stl_facet &f : this->stl.facet_start) + for (int i = 0; i < 3; ++ i) + for (int j = 0; j < 3; ++ j) + src_vertices.emplace_back(f.vertex[i](j)); + qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); + } } catch (...) { @@ -633,9 +615,8 @@ std::vector TriangleMesh::slice(const std::vector &z) { // convert doubles to floats std::vector z_f(z.begin(), z.end()); - TriangleMeshSlicer mslicer(this); std::vector layers; - mslicer.slice(z_f, SlicingMode::Regular, 0.0004f, &layers, [](){}); + slice_mesh(*this, z_f, 0.0004f, layers); return layers; } @@ -655,49 +636,61 @@ void TriangleMesh::require_shared_vertices() size_t TriangleMesh::memsize() const { - size_t memsize = 8 + this->stl.memsize() + this->its.memsize(); - return memsize; + size_t memsize = 8 + this->stl.memsize() + this->its.memsize(); + return memsize; } // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released. size_t TriangleMesh::release_optional() { - size_t memsize_released = sizeof(stl_neighbors) * this->stl.neighbors_start.size() + this->its.memsize(); - // The indexed triangle set may be recalculated using the stl_generate_shared_vertices() function. - this->its.clear(); - // The neighbors structure may be recalculated using the stl_check_facets_exact() function. - this->stl.neighbors_start.clear(); - return memsize_released; + size_t memsize_released = sizeof(stl_neighbors) * this->stl.neighbors_start.size() + this->its.memsize(); + // The indexed triangle set may be recalculated using the stl_generate_shared_vertices() function. + this->its.clear(); + // The neighbors structure may be recalculated using the stl_check_facets_exact() function. + this->stl.neighbors_start.clear(); + return memsize_released; } // Restore optional data possibly released by release_optional(). void TriangleMesh::restore_optional() { - if (! this->stl.facet_start.empty()) { - // Save the old stats before calling stl_check_faces_exact, as it may modify the statistics. - stl_stats stats = this->stl.stats; - if (this->stl.neighbors_start.empty()) { - stl_reallocate(&this->stl); - stl_check_facets_exact(&this->stl); - } - if (this->its.vertices.empty()) - stl_generate_shared_vertices(&this->stl, this->its); - // Restore the old statistics. - this->stl.stats = stats; - } + if (! this->stl.facet_start.empty()) { + // Save the old stats before calling stl_check_faces_exact, as it may modify the statistics. + stl_stats stats = this->stl.stats; + if (this->stl.neighbors_start.empty()) { + stl_reallocate(&this->stl); + stl_check_facets_exact(&this->stl); + } + if (this->its.vertices.empty()) + stl_generate_shared_vertices(&this->stl, this->its); + // Restore the old statistics. + this->stl.stats = stats; + } } -void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel) +std::vector> create_vertex_faces_index(const indexed_triangle_set &its) { - mesh = _mesh; - if (! mesh->has_shared_vertices()) - throw Slic3r::InvalidArgument("TriangleMeshSlicer was passed a mesh without shared vertices."); + std::vector> index; - throw_on_cancel(); - facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); - v_scaled_shared.assign(_mesh->its.vertices.size(), stl_vertex()); - for (size_t i = 0; i < v_scaled_shared.size(); ++ i) - this->v_scaled_shared[i] = _mesh->its.vertices[i] / float(SCALING_FACTOR); + if (! its.vertices.empty()) { + size_t res = its.indices.size() / its.vertices.size(); + index.assign(its.vertices.size(), reserve_vector(res)); + for (size_t fi = 0; fi < its.indices.size(); ++fi) { + auto &face = its.indices[fi]; + index[face(0)].emplace_back(fi); + index[face(1)].emplace_back(fi); + index[face(2)].emplace_back(fi); + } + } + + return index; +} + +// Map from a facet edge to a neighbor face index or -1 if no neighbor exists. +template +static inline std::vector create_face_neighbors_index_impl(const indexed_triangle_set &its, ThrowOnCancelCallback throw_on_cancel) +{ + std::vector out(its.indices.size() * 3, -1); // Create a mapping from triangle edge into face. struct EdgeToFace { @@ -713,12 +706,12 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac bool operator<(const EdgeToFace &other) const { return vertex_low < other.vertex_low || (vertex_low == other.vertex_low && vertex_high < other.vertex_high); } }; std::vector edges_map; - edges_map.assign(this->mesh->stl.stats.number_of_facets * 3, EdgeToFace()); - for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) + edges_map.assign(its.indices.size() * 3, EdgeToFace()); + for (uint32_t facet_idx = 0; facet_idx < its.indices.size(); ++ facet_idx) for (int i = 0; i < 3; ++ i) { - EdgeToFace &e2f = edges_map[facet_idx*3+i]; - e2f.vertex_low = this->mesh->its.indices[facet_idx][i]; - e2f.vertex_high = this->mesh->its.indices[facet_idx][(i + 1) % 3]; + EdgeToFace &e2f = edges_map[facet_idx * 3 + i]; + e2f.vertex_low = its.indices[facet_idx][i]; + e2f.vertex_high = its.indices[facet_idx][(i + 1) % 3]; e2f.face = facet_idx; // 1 based indexing, to be always strictly positive. e2f.face_edge = i + 1; @@ -761,10 +754,10 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac } } // Assign an edge index to the 1st face. - this->facets_edges[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges; + out[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges; if (found) { EdgeToFace &edge_j = edges_map[j]; - this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges; + out[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges; // Mark the edge as connected. edge_j.face = -1; } @@ -772,1179 +765,75 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac if ((i & 0x0ffff) == 0) throw_on_cancel(); } -} - - -void TriangleMeshSlicer::set_up_direction(const Vec3f& up) -{ - m_quaternion.setFromTwoVectors(up, Vec3f::UnitZ()); - m_use_quaternion = true; -} - -void TriangleMeshSlicer::slice( - const std::vector &z, - SlicingMode mode, size_t alternate_mode_first_n_layers, SlicingMode alternate_mode, - std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const -{ - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice"; - - /* - This method gets called with a list of unscaled Z coordinates and outputs - a vector pointer having the same number of items as the original list. - Each item is a vector of polygons created by slicing our mesh at the - given heights. - - This method should basically combine the behavior of the existing - Perl methods defined in lib/Slic3r/TriangleMesh.pm: - - - analyze(): this creates the 'facets_edges' and the 'edges_facets' - tables (we don't need the 'edges' table) - - - slice_facet(): this has to be done for each facet. It generates - intersection lines with each plane identified by the Z list. - The get_layer_range() binary search used to identify the Z range - of the facet is already ported to C++ (see Object.xsp) - - - make_loops(): this has to be done for each layer. It creates polygons - from the lines generated by the previous step. - - At the end, we free the tables generated by analyze() as we don't - need them anymore. - - NOTE: this method accepts a vector of floats because the mesh coordinate - type is float. - */ - - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::_slice_do"; - std::vector lines(z.size()); - { - boost::mutex lines_mutex; - tbb::parallel_for( - tbb::blocked_range(0,this->mesh->stl.stats.number_of_facets), - [&lines, &lines_mutex, &z, throw_on_cancel, this](const tbb::blocked_range& range) { - for (int facet_idx = range.begin(); facet_idx < range.end(); ++ facet_idx) { - if ((facet_idx & 0x0ffff) == 0) - throw_on_cancel(); - this->_slice_do(facet_idx, &lines, &lines_mutex, z); - } - } - ); - } - throw_on_cancel(); - - // v_scaled_shared could be freed here - - // build loops - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::_make_loops_do"; - layers->resize(z.size()); - tbb::parallel_for( - tbb::blocked_range(0, z.size()), - [&lines, &layers, mode, alternate_mode_first_n_layers, alternate_mode, throw_on_cancel, this](const tbb::blocked_range& range) { - for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx) { - if ((line_idx & 0x0ffff) == 0) - throw_on_cancel(); - - Polygons &polygons = (*layers)[line_idx]; - this->make_loops(lines[line_idx], &polygons); - - auto this_mode = line_idx < alternate_mode_first_n_layers ? alternate_mode : mode; - if (! polygons.empty()) { - if (this_mode == SlicingMode::Positive) { - // Reorient all loops to be CCW. - for (Polygon& p : polygons) - p.make_counter_clockwise(); - } else if (this_mode == SlicingMode::PositiveLargestContour) { - // Keep just the largest polygon, make it CCW. - double max_area = 0.; - Polygon* max_area_polygon = nullptr; - for (Polygon& p : polygons) { - double a = p.area(); - if (std::abs(a) > std::abs(max_area)) { - max_area = a; - max_area_polygon = &p; - } - } - assert(max_area_polygon != nullptr); - if (max_area < 0.) - max_area_polygon->reverse(); - Polygon p(std::move(*max_area_polygon)); - polygons.clear(); - polygons.emplace_back(std::move(p)); - } - } - } - } - ); - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice finished"; - -#ifdef SLIC3R_DEBUG - { - static int iRun = 0; - for (size_t i = 0; i < z.size(); ++ i) { - Polygons &polygons = (*layers)[i]; - ExPolygons expolygons = union_ex(polygons, true); - SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), expolygons); - { - BoundingBox bbox; - for (const IntersectionLine &l : lines[i]) { - bbox.merge(l.a); - bbox.merge(l.b); - } - SVG svg(debug_out_path("slice_loops_%d_%d.svg", iRun, i).c_str(), bbox); - svg.draw(expolygons); - for (const IntersectionLine &l : lines[i]) - svg.draw(l, "red", 0); - svg.draw_outline(expolygons, "black", "blue", 0); - svg.Close(); - } -#if 0 -//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. - for (Polygon &poly : polygons) { - for (size_t i = 1; i < poly.points.size(); ++ i) - assert(poly.points[i-1] != poly.points[i]); - assert(poly.points.front() != poly.points.back()); - } -#endif - } - ++ iRun; - } -#endif -} - -void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, - const std::vector &z) const -{ - const stl_facet &facet = m_use_quaternion ? (this->mesh->stl.facet_start.data() + facet_idx)->rotated(m_quaternion) : *(this->mesh->stl.facet_start.data() + facet_idx); - - // find facet extents - const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2))); - const float max_z = fmaxf(facet.vertex[0](2), fmaxf(facet.vertex[1](2), facet.vertex[2](2))); - - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, - facet.vertex[0](0), facet.vertex[0](1), facet.vertex[0](2), - facet.vertex[1](0), facet.vertex[1](1), facet.vertex[1](2), - facet.vertex[2](0), facet.vertex[2](1), facet.vertex[2](2)); - printf("z: min = %.2f, max = %.2f\n", min_z, max_z); - #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ - - // find layer extents - std::vector::const_iterator min_layer, max_layer; - min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z - max_layer = std::upper_bound(min_layer, z.end(), max_z); // first layer whose slice_z is > max_z - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); - #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ - - for (std::vector::const_iterator it = min_layer; it != max_layer; ++ it) { - std::vector::size_type layer_idx = it - z.begin(); - IntersectionLine il; - if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) { - boost::lock_guard l(*lines_mutex); - if (il.edge_type == feHorizontal) { - // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume. - } else - (*lines)[layer_idx].emplace_back(il); - } - } -} - -void TriangleMeshSlicer::slice( - const std::vector &z, SlicingMode mode, size_t alternate_mode_first_n_layers, SlicingMode alternate_mode, const float closing_radius, - std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const -{ - std::vector layers_p; - this->slice(z, - (mode == SlicingMode::PositiveLargestContour) ? SlicingMode::Positive : mode, - alternate_mode_first_n_layers, - (alternate_mode == SlicingMode::PositiveLargestContour) ? SlicingMode::Positive : alternate_mode, - &layers_p, throw_on_cancel); - - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start"; - layers->resize(z.size()); - tbb::parallel_for( - tbb::blocked_range(0, z.size()), - [&layers_p, mode, alternate_mode_first_n_layers, alternate_mode, closing_radius, layers, throw_on_cancel, this] - (const tbb::blocked_range& range) { - for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { -#ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("Layer %zu (slice_z = %.2f):\n", layer_id, z[layer_id]); -#endif - throw_on_cancel(); - ExPolygons &expolygons = (*layers)[layer_id]; - this->make_expolygons(layers_p[layer_id], closing_radius, &expolygons); - const auto this_mode = layer_id < alternate_mode_first_n_layers ? alternate_mode : mode; - if (this_mode == SlicingMode::PositiveLargestContour) - keep_largest_contour_only(expolygons); - } - }); - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end"; -} - -// Return true, if the facet has been sliced and line_out has been filled. -TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( - float slice_z, const stl_facet &facet, const int facet_idx, - const float min_z, const float max_z, - IntersectionLine *line_out) const -{ - IntersectionPoint points[3]; - size_t num_points = 0; - size_t point_on_layer = size_t(-1); - - // Reorder vertices so that the first one is the one with lowest Z. - // This is needed to get all intersection lines in a consistent order - // (external on the right of the line) - const stl_triangle_vertex_indices &vertices = this->mesh->its.indices[facet_idx]; - int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0); - - // These are used only if the cut plane is tilted: - stl_vertex rotated_a; - stl_vertex rotated_b; - - for (int j = i; j - i < 3; ++j) { // loop through facet edges - int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; - int a_id = vertices[j % 3]; - int b_id = vertices[(j+1) % 3]; - - const stl_vertex *a; - const stl_vertex *b; - if (m_use_quaternion) { - rotated_a = m_quaternion * this->v_scaled_shared[a_id]; - rotated_b = m_quaternion * this->v_scaled_shared[b_id]; - a = &rotated_a; - b = &rotated_b; - } - else { - a = &this->v_scaled_shared[a_id]; - b = &this->v_scaled_shared[b_id]; - } - - // Is edge or face aligned with the cutting plane? - if (a->z() == slice_z && b->z() == slice_z) { - // Edge is horizontal and belongs to the current layer. - // The following rotation of the three vertices may not be efficient, but this branch happens rarely. - const stl_vertex &v0 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[0]]) : this->v_scaled_shared[vertices[0]]; - const stl_vertex &v1 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[1]]) : this->v_scaled_shared[vertices[1]]; - const stl_vertex &v2 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[2]]) : this->v_scaled_shared[vertices[2]]; - const stl_normal &normal = facet.normal; - // We may ignore this edge for slicing purposes, but we may still use it for object cutting. - FacetSliceType result = Slicing; - if (min_z == max_z) { - // All three vertices are aligned with slice_z. - line_out->edge_type = feHorizontal; - result = Cutting; - if (normal.z() < 0) { - // If normal points downwards this is a bottom horizontal facet so we reverse its point order. - std::swap(a, b); - std::swap(a_id, b_id); - } - } else { - // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane. - // Is the third vertex below the cutting plane? - bool third_below = v0.z() < slice_z || v1.z() < slice_z || v2.z() < slice_z; - // Two vertices on the cutting plane, the third vertex is below the plane. Consider the edge to be part of the slice - // only if it is the upper edge. - // (the bottom most edge resp. vertex of a triangle is not owned by the triangle, but the top most edge resp. vertex is part of the triangle - // in respect to the cutting plane). - result = third_below ? Slicing : Cutting; - if (third_below) { - line_out->edge_type = feTop; - std::swap(a, b); - std::swap(a_id, b_id); - } else - line_out->edge_type = feBottom; - } - line_out->a.x() = a->x(); - line_out->a.y() = a->y(); - line_out->b.x() = b->x(); - line_out->b.y() = b->y(); - line_out->a_id = a_id; - line_out->b_id = b_id; - assert(line_out->a != line_out->b); - return result; - } - - if (a->z() == slice_z) { - // Only point a alings with the cutting plane. - if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { - point_on_layer = num_points; - IntersectionPoint &point = points[num_points ++]; - point.x() = a->x(); - point.y() = a->y(); - point.point_id = a_id; - } - } else if (b->z() == slice_z) { - // Only point b alings with the cutting plane. - if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { - point_on_layer = num_points; - IntersectionPoint &point = points[num_points ++]; - point.x() = b->x(); - point.y() = b->y(); - point.point_id = b_id; - } - } else if ((a->z() < slice_z && b->z() > slice_z) || (b->z() < slice_z && a->z() > slice_z)) { - // A general case. The face edge intersects the cutting plane. Calculate the intersection point. - assert(a_id != b_id); - // Sort the edge to give a consistent answer. - if (a_id > b_id) { - std::swap(a_id, b_id); - std::swap(a, b); - } - IntersectionPoint &point = points[num_points]; - double t = (double(slice_z) - double(b->z())) / (double(a->z()) - double(b->z())); - if (t <= 0.) { - if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { - point.x() = a->x(); - point.y() = a->y(); - point_on_layer = num_points ++; - point.point_id = a_id; - } - } else if (t >= 1.) { - if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { - point.x() = b->x(); - point.y() = b->y(); - point_on_layer = num_points ++; - point.point_id = b_id; - } - } else { - point.x() = coord_t(floor(double(b->x()) + (double(a->x()) - double(b->x())) * t + 0.5)); - point.y() = coord_t(floor(double(b->y()) + (double(a->y()) - double(b->y())) * t + 0.5)); - point.edge_id = edge_id; - ++ num_points; - } - } - } - - // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only. - assert(num_points < 3); - if (num_points == 2) { - line_out->edge_type = feGeneral; - line_out->a = (Point)points[1]; - line_out->b = (Point)points[0]; - line_out->a_id = points[1].point_id; - line_out->b_id = points[0].point_id; - line_out->edge_a_id = points[1].edge_id; - line_out->edge_b_id = points[0].edge_id; - // Not a zero lenght edge. - //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. - //assert(line_out->a != line_out->b); - // The plane cuts at least one edge in a general position. - assert(line_out->a_id == -1 || line_out->b_id == -1); - assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1); - // General slicing position, use the segment for both slicing and object cutting. -#if 0 - if (line_out->a_id != -1 && line_out->b_id != -1) { - // Solving a degenerate case, where both the intersections snapped to an edge. - // Correctly classify the face as below or above based on the position of the 3rd point. - int i = vertices[0]; - if (i == line_out->a_id || i == line_out->b_id) - i = vertices[1]; - if (i == line_out->a_id || i == line_out->b_id) - i = vertices[2]; - assert(i != line_out->a_id && i != line_out->b_id); - line_out->edge_type = ((m_use_quaternion ? - (m_quaternion * this->v_scaled_shared[i]).z() - : this->v_scaled_shared[i].z()) < slice_z) ? feTop : feBottom; - } -#endif - return Slicing; - } - return NoSlice; -} - -#if 0 -//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing -// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces. -// So the following code makes only sense now to handle degenerate meshes with more than two faces -// sharing a single edge. -static inline void remove_tangent_edges(std::vector &lines) -{ - std::vector by_vertex_pair; - by_vertex_pair.reserve(lines.size()); - for (IntersectionLine& line : lines) - if (line.edge_type != feGeneral && line.a_id != -1) - // This is a face edge. Check whether there is its neighbor stored in lines. - by_vertex_pair.emplace_back(&line); - auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) { - // Sort vertices of l1, l2 lexicographically - int l1a = l1->a_id; - int l1b = l1->b_id; - int l2a = l2->a_id; - int l2b = l2->b_id; - if (l1a > l1b) - std::swap(l1a, l1b); - if (l2a > l2b) - std::swap(l2a, l2b); - // Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored. - return l1a < l2a || (l1a == l2a && l1b < l2b); - }; - std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted); - for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) { - IntersectionLine &l1 = **line; - if (! l1.skip()) { - // Iterate as long as line and line2 edges share the same end points. - for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) { - // Lines must share the end points. - assert(! edges_lower_sorted(*line, *line2)); - assert(! edges_lower_sorted(*line2, *line)); - IntersectionLine &l2 = **line2; - if (l2.skip()) - continue; - if (l1.a_id == l2.a_id) { - assert(l1.b_id == l2.b_id); - l2.set_skip(); - // If they are both oriented upwards or downwards (like a 'V'), - // then we can remove both edges from this layer since it won't - // affect the sliced shape. - // If one of them is oriented upwards and the other is oriented - // downwards, let's only keep one of them (it doesn't matter which - // one since all 'top' lines were reversed at slicing). - if (l1.edge_type == l2.edge_type) { - l1.set_skip(); - break; - } - } else { - assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id); - // If this edge joins two horizontal facets, remove both of them. - if (l1.edge_type == feHorizontal && l2.edge_type == feHorizontal) { - l1.set_skip(); - l2.set_skip(); - break; - } - } - } - } - } -} -#endif - -struct OpenPolyline { - OpenPolyline() {}; - OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : - start(start), end(end), points(std::move(points)), consumed(false) { this->length = Slic3r::length(this->points); } - void reverse() { - std::swap(start, end); - std::reverse(points.begin(), points.end()); - } - IntersectionReference start; - IntersectionReference end; - Points points; - double length; - bool consumed; -}; - -// called by TriangleMeshSlicer::make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity. -// Only connects segments crossing triangles of the same orientation. -static void chain_lines_by_triangle_connectivity(std::vector &lines, Polygons &loops, std::vector &open_polylines) -{ - // Build a map of lines by edge_a_id and a_id. - std::vector by_edge_a_id; - std::vector by_a_id; - by_edge_a_id.reserve(lines.size()); - by_a_id.reserve(lines.size()); - for (IntersectionLine &line : lines) { - if (! line.skip()) { - if (line.edge_a_id != -1) - by_edge_a_id.emplace_back(&line); - if (line.a_id != -1) - by_a_id.emplace_back(&line); - } - } - auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; - auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; - std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); - std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); - // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. - IntersectionLines::iterator it_line_seed = lines.begin(); - for (;;) { - // take first spare line and start a new loop - IntersectionLine *first_line = nullptr; - for (; it_line_seed != lines.end(); ++ it_line_seed) - if (it_line_seed->is_seed_candidate()) { - //if (! it_line_seed->skip()) { - first_line = &(*it_line_seed ++); - break; - } - if (first_line == nullptr) - break; - first_line->set_skip(); - Points loop_pts; - loop_pts.emplace_back(first_line->a); - IntersectionLine *last_line = first_line; - - /* - printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, - first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); - */ - - IntersectionLine key; - for (;;) { - // find a line starting where last one finishes - IntersectionLine* next_line = nullptr; - if (last_line->edge_b_id != -1) { - key.edge_a_id = last_line->edge_b_id; - auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); - if (it_begin != by_edge_a_id.end()) { - auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); - for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip()) { - next_line = *it_line; - break; - } - } - } - if (next_line == nullptr && last_line->b_id != -1) { - key.a_id = last_line->b_id; - auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); - if (it_begin != by_a_id.end()) { - auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); - for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip()) { - next_line = *it_line; - break; - } - } - } - if (next_line == nullptr) { - // Check whether we closed this loop. - if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || - (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { - // The current loop is complete. Add it to the output. - loops.emplace_back(std::move(loop_pts)); - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); - #endif - } else { - // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. - loop_pts.emplace_back(last_line->b); - open_polylines.emplace_back(OpenPolyline( - IntersectionReference(first_line->a_id, first_line->edge_a_id), - IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); - } - break; - } - /* - printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, - next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); - */ - loop_pts.emplace_back(next_line->a); - last_line = next_line; - next_line->set_skip(); - } - } -} - -std::vector open_polylines_sorted(std::vector &open_polylines, bool update_lengths) -{ - std::vector out; - out.reserve(open_polylines.size()); - for (OpenPolyline &opl : open_polylines) - if (! opl.consumed) { - if (update_lengths) - opl.length = Slic3r::length(opl.points); - out.emplace_back(&opl); - } - std::sort(out.begin(), out.end(), [](const OpenPolyline *lhs, const OpenPolyline *rhs){ return lhs->length > rhs->length; }); return out; } -// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices. -// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. -static void chain_open_polylines_exact(std::vector &open_polylines, Polygons &loops, bool try_connect_reversed) +std::vector create_face_neighbors_index(const indexed_triangle_set &its) { - // Store the end points of open_polylines into vectors sorted - struct OpenPolylineEnd { - OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} - OpenPolyline *polyline; - // Is it the start or end point? - bool start; - const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; } - // Return a unique ID for the intersection point. - // Return a positive id for a point, or a negative id for an edge. - int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; } - bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } - }; - auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); }; - std::vector by_id; - by_id.reserve(2 * open_polylines.size()); - for (OpenPolyline &opl : open_polylines) { - if (opl.start.point_id != -1 || opl.start.edge_id != -1) - by_id.emplace_back(OpenPolylineEnd(&opl, true)); - if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1)) - by_id.emplace_back(OpenPolylineEnd(&opl, false)); - } - std::sort(by_id.begin(), by_id.end(), by_id_lower); - // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute). - auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector::iterator { - for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); - it != by_id.end() && it->id() == end.id(); ++ it) - if (*it == end) - return it; - return by_id.end(); - }; - // Try to connect the loops. - std::vector sorted_by_length = open_polylines_sorted(open_polylines, false); - for (OpenPolyline *opl : sorted_by_length) { - if (opl->consumed) - continue; - opl->consumed = true; - OpenPolylineEnd end(opl, false); - for (;;) { - // find a line starting where last one finishes - auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); - for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start) - if (! it_next_start->polyline->consumed) - goto found; - // The current loop could not be closed. Unmark the segment. - opl->consumed = false; - break; - found: - // Attach this polyline to the end of the initial polyline. - if (it_next_start->start) { - auto it = it_next_start->polyline->points.begin(); - std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl->points)); - } else { - auto it = it_next_start->polyline->points.rbegin(); - std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl->points)); - } - opl->length += it_next_start->polyline->length; - // Mark the next polyline as consumed. - it_next_start->polyline->points.clear(); - it_next_start->polyline->length = 0.; - it_next_start->polyline->consumed = true; - if (try_connect_reversed) { - // Running in a mode, where the polylines may be connected by mixing their orientations. - // Update the end point lookup structure after the end point of the current polyline was extended. - auto it_end = find_polyline_end(end); - auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start)); - // Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag. - std::swap(opl->end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end); - // Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions. - std::swap(*it_end, *it_next_end); - } - // Check whether we closed this loop. - if ((opl->start.edge_id != -1 && opl->start.edge_id == opl->end.edge_id) || - (opl->start.point_id != -1 && opl->start.point_id == opl->end.point_id)) { - // The current loop is complete. Add it to the output. - //assert(opl->points.front().point_id == opl->points.back().point_id); - //assert(opl->points.front().edge_id == opl->points.back().edge_id); - // Remove the duplicate last point. - opl->points.pop_back(); - if (opl->points.size() >= 3) { - if (try_connect_reversed && area(opl->points) < 0) - // The closed polygon is patched from pieces with messed up orientation, therefore - // the orientation of the patched up polygon is not known. - // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. - std::reverse(opl->points.begin(), opl->points.end()); - loops.emplace_back(std::move(opl->points)); - } - opl->points.clear(); - break; - } - // Continue with the current loop. - } - } + return create_face_neighbors_index_impl(its, [](){}); } -// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices, -// possibly closing small gaps. -// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. -static void chain_open_polylines_close_gaps(std::vector &open_polylines, Polygons &loops, double max_gap, bool try_connect_reversed) +std::vector create_face_neighbors_index(const indexed_triangle_set &its, std::function throw_on_cancel_callback) { - const coord_t max_gap_scaled = (coord_t)scale_(max_gap); - - // Sort the open polylines by their length, so the new loops will be seeded from longer chains. - // Update the polyline lengths, return only not yet consumed polylines. - std::vector sorted_by_length = open_polylines_sorted(open_polylines, true); - - // Store the end points of open_polylines into ClosestPointInRadiusLookup. - struct OpenPolylineEnd { - OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} - OpenPolyline *polyline; - // Is it the start or end point? - bool start; - const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); } - bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } - }; - struct OpenPolylineEndAccessor { - const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); } - }; - typedef ClosestPointInRadiusLookup ClosestPointLookupType; - ClosestPointLookupType closest_end_point_lookup(max_gap_scaled); - for (OpenPolyline *opl : sorted_by_length) { - closest_end_point_lookup.insert(OpenPolylineEnd(opl, true)); - if (try_connect_reversed) - closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); - } - // Try to connect the loops. - for (OpenPolyline *opl : sorted_by_length) { - if (opl->consumed) - continue; - OpenPolylineEnd end(opl, false); - if (try_connect_reversed) - // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it. - closest_end_point_lookup.erase(end); - opl->consumed = true; - size_t n_segments_joined = 1; - for (;;) { - // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). - std::pair next_start_and_dist = closest_end_point_lookup.find(end.point()); - const OpenPolylineEnd *next_start = next_start_and_dist.first; - // Check whether we closed this loop. - double current_loop_closing_distance2 = (opl->points.back() - opl->points.front()).cast().squaredNorm(); - bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled); - if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) { - // Heuristics to decide, whether to close the loop, or connect another polyline. - // One should avoid closing loops shorter than max_gap_scaled. - loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl->points); - } - if (loop_closed) { - // Remove the start point of the current polyline from the lookup. - // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail. - opl->consumed = false; - closest_end_point_lookup.erase(OpenPolylineEnd(opl, true)); - if (current_loop_closing_distance2 == 0.) { - // Remove the duplicate last point. - opl->points.pop_back(); - } else { - // The end points are different, keep both of them. - } - if (opl->points.size() >= 3) { - if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0) - // The closed polygon is patched from pieces with messed up orientation, therefore - // the orientation of the patched up polygon is not known. - // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. - std::reverse(opl->points.begin(), opl->points.end()); - loops.emplace_back(std::move(opl->points)); - } - opl->points.clear(); - opl->consumed = true; - break; - } - if (next_start == nullptr) { - // The current loop could not be closed. Unmark the segment. - opl->consumed = false; - if (try_connect_reversed) - // Re-insert the end point. - closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); - break; - } - // Attach this polyline to the end of the initial polyline. - if (next_start->start) { - auto it = next_start->polyline->points.begin(); - if (*it == opl->points.back()) - ++ it; - std::copy(it, next_start->polyline->points.end(), back_inserter(opl->points)); - } else { - auto it = next_start->polyline->points.rbegin(); - if (*it == opl->points.back()) - ++ it; - std::copy(it, next_start->polyline->points.rend(), back_inserter(opl->points)); - } - ++ n_segments_joined; - // Remove the end points of the consumed polyline segment from the lookup. - OpenPolyline *opl2 = next_start->polyline; - closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true)); - if (try_connect_reversed) - closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false)); - opl2->points.clear(); - opl2->consumed = true; - // Continue with the current loop. - } - } + return create_face_neighbors_index_impl(its, throw_on_cancel_callback); } -void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const +int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit) { -#if 0 -//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. -//#ifdef _DEBUG - for (const Line &l : lines) - assert(l.a != l.b); -#endif /* _DEBUG */ - - // There should be no tangent edges, as the horizontal triangles are ignored and if two triangles touch at a cutting plane, - // only the bottom triangle is considered to be cutting the plane. -// remove_tangent_edges(lines); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - BoundingBox bbox_svg; - { - static int iRun = 0; - for (const Line &line : lines) { - bbox_svg.merge(line.a); - bbox_svg.merge(line.b); - } - SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-raw_lines-%d.svg", iRun ++).c_str(), bbox_svg); - for (const Line &line : lines) - svg.draw(line); - svg.Close(); + int last = 0; + for (int i = 0; i < int(its.indices.size()); ++ i) { + const stl_triangle_vertex_indices &face = its.indices[i]; + if (face(0) != face(1) && face(0) != face(2) && face(1) != face(2)) { + if (last < i) + its.indices[last] = its.indices[i]; + ++ last; } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - - std::vector open_polylines; - chain_lines_by_triangle_connectivity(lines, *loops, open_polylines); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - static int iRun = 0; - SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-%d.svg", iRun ++).c_str(), bbox_svg); - svg.draw(union_ex(*loops)); - for (const OpenPolyline &pl : open_polylines) - svg.draw(Polyline(pl.points), "red"); - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - - // Now process the open polylines. - // Do it in two rounds, first try to connect in the same direction only, - // then try to connect the open polylines in reversed order as well. - chain_open_polylines_exact(open_polylines, *loops, false); - chain_open_polylines_exact(open_polylines, *loops, true); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - static int iRun = 0; - SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg); - svg.draw(union_ex(*loops)); - for (const OpenPolyline &pl : open_polylines) { - if (pl.points.empty()) - continue; - svg.draw(Polyline(pl.points), "red"); - svg.draw(pl.points.front(), "blue"); - svg.draw(pl.points.back(), "blue"); - } - svg.Close(); } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - - // Try to close gaps. - // Do it in two rounds, first try to connect in the same direction only, - // then try to connect the open polylines in reversed order as well. -#if 0 - for (double max_gap : { EPSILON, 0.001, 0.1, 1., 2. }) { - chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); - chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); + int removed = int(its.indices.size()) - last; + if (removed) { + its.indices.erase(its.indices.begin() + last, its.indices.end()); + // Optionally shrink the vertices. + if (shrink_to_fit) + its.indices.shrink_to_fit(); } -#else - const double max_gap = 2.; //mm - chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); - chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); -#endif - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - static int iRun = 0; - SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg); - svg.draw(union_ex(*loops)); - for (const OpenPolyline &pl : open_polylines) { - if (pl.points.empty()) - continue; - svg.draw(Polyline(pl.points), "red"); - svg.draw(pl.points.front(), "blue"); - svg.draw(pl.points.back(), "blue"); - } - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + return removed; } -// Only used to cut the mesh into two halves. -void TriangleMeshSlicer::make_expolygons_simple(std::vector &lines, ExPolygons* slices) const +int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit) { - assert(slices->empty()); - - Polygons loops; - this->make_loops(lines, &loops); - - Polygons holes; - for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) { - if (loop->area() >= 0.) { - ExPolygon ex; - ex.contour = *loop; - slices->emplace_back(ex); - } else { - holes.emplace_back(*loop); + // First used to mark referenced vertices, later used for mapping old vertex index to a new one. + std::vector vertex_map(its.vertices.size(), 0); + // Mark referenced vertices. + for (const stl_triangle_vertex_indices &face : its.indices) + for (int i = 0; i < 3; ++ i) + vertex_map[face(i)] = 1; + // Compactify vertices, update map from old vertex index to a new one. + int last = 0; + for (int i = 0; i < int(vertex_map.size()); ++ i) + if (vertex_map[i]) { + if (last < i) + its.vertices[last] = its.vertices[i]; + vertex_map[i] = last ++; } + int removed = int(its.vertices.size()) - last; + if (removed) { + its.vertices.erase(its.vertices.begin() + last, its.vertices.end()); + // Update faces with the new vertex indices. + for (stl_triangle_vertex_indices &face : its.indices) + for (int i = 0; i < 3; ++ i) + face(i) = vertex_map[face(i)]; + // Optionally shrink the vertices. + if (shrink_to_fit) + its.vertices.shrink_to_fit(); } - - // If there are holes, then there should also be outer contours. - assert(holes.empty() || ! slices->empty()); - if (slices->empty()) - return; - - // Assign holes to outer contours. - for (Polygons::const_iterator hole = holes.begin(); hole != holes.end(); ++ hole) { - // Find an outer contour to a hole. - int slice_idx = -1; - double current_contour_area = std::numeric_limits::max(); - for (ExPolygons::iterator slice = slices->begin(); slice != slices->end(); ++ slice) { - if (slice->contour.contains(hole->points.front())) { - double area = slice->contour.area(); - if (area < current_contour_area) { - slice_idx = slice - slices->begin(); - current_contour_area = area; - } - } - } - // assert(slice_idx != -1); - if (slice_idx == -1) - // Ignore this hole. - continue; - assert(current_contour_area < std::numeric_limits::max() && current_contour_area >= -hole->area()); - (*slices)[slice_idx].holes.emplace_back(std::move(*hole)); - } - -#if 0 - // If the input mesh is not valid, the holes may intersect with the external contour. - // Rather subtract them from the outer contour. - Polygons poly; - for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { - if (it_slice->holes.empty()) { - poly.emplace_back(std::move(it_slice->contour)); - } else { - Polygons contours; - contours.emplace_back(std::move(it_slice->contour)); - for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it) - it->reverse(); - polygons_append(poly, diff(contours, it_slice->holes)); - } - } - // If the input mesh is not valid, the input contours may intersect. - *slices = union_ex(poly); -#endif - -#if 0 - // If the input mesh is not valid, the holes may intersect with the external contour. - // Rather subtract them from the outer contour. - ExPolygons poly; - for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { - Polygons contours; - contours.emplace_back(std::move(it_slice->contour)); - for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it) - it->reverse(); - expolygons_append(poly, diff_ex(contours, it_slice->holes)); - } - // If the input mesh is not valid, the input contours may intersect. - *slices = std::move(poly); -#endif + return removed; } -void TriangleMeshSlicer::make_expolygons(const Polygons &loops, const float closing_radius, ExPolygons* slices) const +void its_shrink_to_fit(indexed_triangle_set &its) { - /* - Input loops are not suitable for evenodd nor nonzero fill types, as we might get - two consecutive concentric loops having the same winding order - and we have to - respect such order. In that case, evenodd would create wrong inversions, and nonzero - would ignore holes inside two concentric contours. - So we're ordering loops and collapse consecutive concentric loops having the same - winding order. - TODO: find a faster algorithm for this, maybe with some sort of binary search. - If we computed a "nesting tree" we could also just remove the consecutive loops - having the same winding order, and remove the extra one(s) so that we could just - supply everything to offset() instead of performing several union/diff calls. - - we sort by area assuming that the outermost loops have larger area; - the previous sorting method, based on $b->contains($a->[0]), failed to nest - loops correctly in some edge cases when original model had overlapping facets - */ - - /* The following lines are commented out because they can generate wrong polygons, - see for example issue #661 */ - - //std::vector area; - //std::vector sorted_area; // vector of indices - //for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) { - // area.emplace_back(loop->area()); - // sorted_area.emplace_back(loop - loops.begin()); - //} - // - //// outer first - //std::sort(sorted_area.begin(), sorted_area.end(), - // [&area](size_t a, size_t b) { return std::abs(area[a]) > std::abs(area[b]); }); - - //// we don't perform a safety offset now because it might reverse cw loops - //Polygons p_slices; - //for (std::vector::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) { - // /* we rely on the already computed area to determine the winding order - // of the loops, since the Orientation() function provided by Clipper - // would do the same, thus repeating the calculation */ - // Polygons::const_iterator loop = loops.begin() + *loop_idx; - // if (area[*loop_idx] > +EPSILON) - // p_slices.emplace_back(*loop); - // else if (area[*loop_idx] < -EPSILON) - // //FIXME This is arbitrary and possibly very slow. - // // If the hole is inside a polygon, then there is no need to diff. - // // If the hole intersects a polygon boundary, then diff it, but then - // // there is no guarantee of an ordering of the loops. - // // Maybe we can test for the intersection before running the expensive diff algorithm? - // p_slices = diff(p_slices, *loop); - //} - - // Perform a safety offset to merge very close facets (TODO: find test case for this) - // 0.0499 comes from https://github.com/slic3r/Slic3r/issues/959 -// double safety_offset = scale_(0.0499); - // 0.0001 is set to satisfy GH #520, #1029, #1364 - double safety_offset = scale_(closing_radius); - - /* The following line is commented out because it can generate wrong polygons, - see for example issue #661 */ - //ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); - - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - size_t holes_count = 0; - for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++ e) - holes_count += e->holes.size(); - printf("%zu surface(s) having %zu holes detected from %zu polylines\n", - ex_slices.size(), holes_count, loops.size()); - #endif - - // append to the supplied collection - if (safety_offset > 0) - expolygons_append(*slices, offset2_ex(union_ex(loops), +safety_offset, -safety_offset)); - else - expolygons_append(*slices, union_ex(loops)); -} - -void TriangleMeshSlicer::make_expolygons(std::vector &lines, const float closing_radius, ExPolygons* slices) const -{ - Polygons pp; - this->make_loops(lines, &pp); - this->make_expolygons(pp, closing_radius, slices); -} - -void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) const -{ - IntersectionLines upper_lines, lower_lines; - - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object"; - float scaled_z = scale_(z); - for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) { - const stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; - - // find facet extents - float min_z = std::min(facet->vertex[0](2), std::min(facet->vertex[1](2), facet->vertex[2](2))); - float max_z = std::max(facet->vertex[0](2), std::max(facet->vertex[1](2), facet->vertex[2](2))); - - // intersect facet with cutting plane - IntersectionLine line; - if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line) != TriangleMeshSlicer::NoSlice) { - // Save intersection lines for generating correct triangulations. - if (line.edge_type == feTop) { - lower_lines.emplace_back(line); - } else if (line.edge_type == feBottom) { - upper_lines.emplace_back(line); - } else if (line.edge_type != feHorizontal) { - lower_lines.emplace_back(line); - upper_lines.emplace_back(line); - } - } - - if (min_z > z || (min_z == z && max_z > z)) { - // facet is above the cut plane and does not belong to it - if (upper != nullptr) - stl_add_facet(&upper->stl, facet); - } else if (max_z < z || (max_z == z && min_z < z)) { - // facet is below the cut plane and does not belong to it - if (lower != nullptr) - stl_add_facet(&lower->stl, facet); - } else if (min_z < z && max_z > z) { - // Facet is cut by the slicing plane. - - // look for the vertex on whose side of the slicing plane there are no other vertices - int isolated_vertex; - if ( (facet->vertex[0](2) > z) == (facet->vertex[1](2) > z) ) { - isolated_vertex = 2; - } else if ( (facet->vertex[1](2) > z) == (facet->vertex[2](2) > z) ) { - isolated_vertex = 0; - } else { - isolated_vertex = 1; - } - - // get vertices starting from the isolated one - const stl_vertex &v0 = facet->vertex[isolated_vertex]; - const stl_vertex &v1 = facet->vertex[(isolated_vertex+1) % 3]; - const stl_vertex &v2 = facet->vertex[(isolated_vertex+2) % 3]; - - // intersect v0-v1 and v2-v0 with cutting plane and make new vertices - stl_vertex v0v1, v2v0; - v0v1(0) = v1(0) + (v0(0) - v1(0)) * (z - v1(2)) / (v0(2) - v1(2)); - v0v1(1) = v1(1) + (v0(1) - v1(1)) * (z - v1(2)) / (v0(2) - v1(2)); - v0v1(2) = z; - v2v0(0) = v2(0) + (v0(0) - v2(0)) * (z - v2(2)) / (v0(2) - v2(2)); - v2v0(1) = v2(1) + (v0(1) - v2(1)) * (z - v2(2)) / (v0(2) - v2(2)); - v2v0(2) = z; - - // build the triangular facet - stl_facet triangle; - triangle.normal = facet->normal; - triangle.vertex[0] = v0; - triangle.vertex[1] = v0v1; - triangle.vertex[2] = v2v0; - - // build the facets forming a quadrilateral on the other side - stl_facet quadrilateral[2]; - quadrilateral[0].normal = facet->normal; - quadrilateral[0].vertex[0] = v1; - quadrilateral[0].vertex[1] = v2; - quadrilateral[0].vertex[2] = v0v1; - quadrilateral[1].normal = facet->normal; - quadrilateral[1].vertex[0] = v2; - quadrilateral[1].vertex[1] = v2v0; - quadrilateral[1].vertex[2] = v0v1; - - if (v0(2) > z) { - if (upper != nullptr) - stl_add_facet(&upper->stl, &triangle); - if (lower != nullptr) { - stl_add_facet(&lower->stl, &quadrilateral[0]); - stl_add_facet(&lower->stl, &quadrilateral[1]); - } - } else { - if (upper != nullptr) { - stl_add_facet(&upper->stl, &quadrilateral[0]); - stl_add_facet(&upper->stl, &quadrilateral[1]); - } - if (lower != nullptr) - stl_add_facet(&lower->stl, &triangle); - } - } - } - - if (upper != nullptr) { - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part"; - ExPolygons section; - this->make_expolygons_simple(upper_lines, §ion); - Pointf3s triangles = triangulate_expolygons_3d(section, z, true); - stl_facet facet; - facet.normal = stl_normal(0, 0, -1.f); - for (size_t i = 0; i < triangles.size(); ) { - for (size_t j = 0; j < 3; ++ j) - facet.vertex[j] = triangles[i ++].cast(); - stl_add_facet(&upper->stl, &facet); - } - } - - if (lower != nullptr) { - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part"; - ExPolygons section; - this->make_expolygons_simple(lower_lines, §ion); - Pointf3s triangles = triangulate_expolygons_3d(section, z, false); - stl_facet facet; - facet.normal = stl_normal(0, 0, -1.f); - for (size_t i = 0; i < triangles.size(); ) { - for (size_t j = 0; j < 3; ++ j) - facet.vertex[j] = triangles[i ++].cast(); - stl_add_facet(&lower->stl, &facet); - } - } - - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - updating object sizes"; - stl_get_size(&upper->stl); - stl_get_size(&lower->stl); + its.indices.shrink_to_fit(); + its.vertices.shrink_to_fit(); } // Generate the vertex list for a cube solid of arbitrary size in X/Y/Z. @@ -1962,8 +851,8 @@ TriangleMesh make_cube(double x, double y, double z) {1, 7, 6}, {1, 6, 2}, {2, 6, 5}, {2, 5, 3}, {4, 0, 3}, {4, 3, 5} }); - mesh.repair(); - return mesh; + mesh.repair(); + return mesh; } // Generate the mesh for a cylinder and return it, using @@ -1971,13 +860,13 @@ TriangleMesh make_cube(double x, double y, double z) // Default is 360 sides, angle fa is in radians. TriangleMesh make_cylinder(double r, double h, double fa) { - size_t n_steps = (size_t)ceil(2. * PI / fa); - double angle_step = 2. * PI / n_steps; + size_t n_steps = (size_t)ceil(2. * PI / fa); + double angle_step = 2. * PI / n_steps; - Pointf3s vertices; - std::vector facets; - vertices.reserve(2 * n_steps + 2); - facets.reserve(4 * n_steps); + Pointf3s vertices; + std::vector facets; + vertices.reserve(2 * n_steps + 2); + facets.reserve(4 * n_steps); // 2 special vertices, top and bottom center, rest are relative to this vertices.emplace_back(Vec3d(0.0, 0.0, 0.0)); @@ -1987,29 +876,29 @@ TriangleMesh make_cylinder(double r, double h, double fa) // circle, generate four points and four facets (2 for the wall, 2 for the // top and bottom. // Special case: Last line shares 2 vertices with the first line. - Vec2d p = Eigen::Rotation2Dd(0.) * Eigen::Vector2d(0, r); - vertices.emplace_back(Vec3d(p(0), p(1), 0.)); - vertices.emplace_back(Vec3d(p(0), p(1), h)); - for (size_t i = 1; i < n_steps; ++i) { + Vec2d p = Eigen::Rotation2Dd(0.) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(p(0), p(1), 0.)); + vertices.emplace_back(Vec3d(p(0), p(1), h)); + for (size_t i = 1; i < n_steps; ++i) { p = Eigen::Rotation2Dd(angle_step * i) * Eigen::Vector2d(0, r); vertices.emplace_back(Vec3d(p(0), p(1), 0.)); vertices.emplace_back(Vec3d(p(0), p(1), h)); int id = (int)vertices.size() - 1; facets.emplace_back( 0, id - 1, id - 3); // top facets.emplace_back(id, 1, id - 2); // bottom - facets.emplace_back(id, id - 2, id - 3); // upper-right of side + facets.emplace_back(id, id - 2, id - 3); // upper-right of side facets.emplace_back(id, id - 3, id - 1); // bottom-left of side } // Connect the last set of vertices with the first. - int id = (int)vertices.size() - 1; + int id = (int)vertices.size() - 1; facets.emplace_back( 0, 2, id - 1); facets.emplace_back( 3, 1, id); - facets.emplace_back(id, 2, 3); + facets.emplace_back(id, 2, 3); facets.emplace_back(id, id - 1, 2); - TriangleMesh mesh(std::move(vertices), std::move(facets)); - mesh.repair(); - return mesh; + TriangleMesh mesh(std::move(vertices), std::move(facets)); + mesh.repair(); + return mesh; } // Generates mesh for a sphere centered about the origin, using the generated angle @@ -2018,74 +907,56 @@ TriangleMesh make_cylinder(double r, double h, double fa) //FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html TriangleMesh make_sphere(double radius, double fa) { - int sectorCount = int(ceil(2. * M_PI / fa)); - int stackCount = int(ceil(M_PI / fa)); - float sectorStep = float(2. * M_PI / sectorCount); - float stackStep = float(M_PI / stackCount); + int sectorCount = int(ceil(2. * M_PI / fa)); + int stackCount = int(ceil(M_PI / fa)); + float sectorStep = float(2. * M_PI / sectorCount); + float stackStep = float(M_PI / stackCount); - Pointf3s vertices; - vertices.reserve((stackCount - 1) * sectorCount + 2); - for (int i = 0; i <= stackCount; ++ i) { - // from pi/2 to -pi/2 - double stackAngle = 0.5 * M_PI - stackStep * i; - double xy = radius * cos(stackAngle); - double z = radius * sin(stackAngle); - if (i == 0 || i == stackCount) - vertices.emplace_back(Vec3d(xy, 0., z)); - else - for (int j = 0; j < sectorCount; ++ j) { - // from 0 to 2pi - double sectorAngle = sectorStep * j; - vertices.emplace_back(Vec3d(xy * cos(sectorAngle), xy * sin(sectorAngle), z)); - } - } - - std::vector facets; - facets.reserve(2 * (stackCount - 1) * sectorCount); - for (int i = 0; i < stackCount; ++ i) { - // Beginning of current stack. - int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); - int k1_first = k1; - // Beginning of next stack. - int k2 = (i == 0) ? 1 : (k1 + sectorCount); - int k2_first = k2; - for (int j = 0; j < sectorCount; ++ j) { - // 2 triangles per sector excluding first and last stacks - int k1_next = k1; - int k2_next = k2; - if (i != 0) { - k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); - facets.emplace_back(k1, k2, k1_next); - } - if (i + 1 != stackCount) { - k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); - facets.emplace_back(k1_next, k2, k2_next); - } - k1 = k1_next; - k2 = k2_next; - } - } - TriangleMesh mesh(std::move(vertices), std::move(facets)); - mesh.repair(); - return mesh; -} - -std::vector > create_neighbor_index(const indexed_triangle_set &its) -{ - if (its.vertices.empty()) return {}; - - size_t res = its.indices.size() / its.vertices.size(); - std::vector< std::vector > index(its.vertices.size(), - reserve_vector(res)); - - for (size_t fi = 0; fi < its.indices.size(); ++fi) { - auto &face = its.indices[fi]; - index[face(0)].emplace_back(fi); - index[face(1)].emplace_back(fi); - index[face(2)].emplace_back(fi); + Pointf3s vertices; + vertices.reserve((stackCount - 1) * sectorCount + 2); + for (int i = 0; i <= stackCount; ++ i) { + // from pi/2 to -pi/2 + double stackAngle = 0.5 * M_PI - stackStep * i; + double xy = radius * cos(stackAngle); + double z = radius * sin(stackAngle); + if (i == 0 || i == stackCount) + vertices.emplace_back(Vec3d(xy, 0., z)); + else + for (int j = 0; j < sectorCount; ++ j) { + // from 0 to 2pi + double sectorAngle = sectorStep * j; + vertices.emplace_back(Vec3d(xy * cos(sectorAngle), xy * sin(sectorAngle), z)); + } } - return index; + std::vector facets; + facets.reserve(2 * (stackCount - 1) * sectorCount); + for (int i = 0; i < stackCount; ++ i) { + // Beginning of current stack. + int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); + int k1_first = k1; + // Beginning of next stack. + int k2 = (i == 0) ? 1 : (k1 + sectorCount); + int k2_first = k2; + for (int j = 0; j < sectorCount; ++ j) { + // 2 triangles per sector excluding first and last stacks + int k1_next = k1; + int k2_next = k2; + if (i != 0) { + k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); + facets.emplace_back(k1, k2, k1_next); + } + if (i + 1 != stackCount) { + k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); + facets.emplace_back(k1_next, k2, k2_next); + } + k1 = k1_next; + k2 = k2_next; + } + } + TriangleMesh mesh(std::move(vertices), std::move(facets)); + mesh.repair(); + return mesh; } } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index e6f6dc84b..dd1a76156 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include "BoundingBox.hpp" #include "Line.hpp" #include "Point.hpp" @@ -24,7 +23,7 @@ public: TriangleMesh() : repaired(false) {} TriangleMesh(const Pointf3s &points, const std::vector &facets); explicit TriangleMesh(const indexed_triangle_set &M); - void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; } + void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; } bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); } bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); } bool write_binary(const char* output_file) { return stl_write_binary(&this->stl, output_file, ""); } @@ -47,7 +46,7 @@ public: void mirror_y() { this->mirror(Y); } void mirror_z() { this->mirror(Z); } void transform(const Transform3d& t, bool fix_left_handed = false); - void transform(const Matrix3d& t, bool fix_left_handed = false); + void transform(const Matrix3d& t, bool fix_left_handed = false); void align_to_origin(); void rotate(double angle, Point* center); TriangleMeshPtrs split() const; @@ -62,7 +61,7 @@ public: // Return the size of the mesh in coordinates. Vec3d size() const { return stl.stats.size.cast(); } /// Return the center of the related bounding box. - Vec3d center() const { return this->bounding_box().center(); } + Vec3d center() const { return this->bounding_box().center(); } // Returns the convex hull of this TriangleMesh TriangleMesh convex_hull_3d() const; // Slice this mesh at the provided Z levels and return the vector @@ -78,8 +77,8 @@ public: size_t memsize() const; // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released. size_t release_optional(); - // Restore optional data possibly released by release_optional(). - void restore_optional(); + // Restore optional data possibly released by release_optional(). + void restore_optional(); stl_file stl; indexed_triangle_set its; @@ -92,160 +91,16 @@ private: // Create an index of faces belonging to each vertex. The returned vector can // be indexed with vertex indices and contains a list of face indices for each // vertex. -std::vector< std::vector > -create_neighbor_index(const indexed_triangle_set &its); +std::vector> create_vertex_faces_index(const indexed_triangle_set &its); -enum FacetEdgeType { - // A general case, the cutting plane intersect a face at two different edges. - feGeneral, - // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. - feTop, - // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. - feBottom, - // All three vertices of a face are aligned with the cutting plane. - feHorizontal -}; - -class IntersectionReference -{ -public: - IntersectionReference() : point_id(-1), edge_id(-1) {} - IntersectionReference(int point_id, int edge_id) : point_id(point_id), edge_id(edge_id) {} - // Where is this intersection point located? On mesh vertex or mesh edge? - // Only one of the following will be set, the other will remain set to -1. - // Index of the mesh vertex. - int point_id; - // Index of the mesh edge. - int edge_id; -}; - -class IntersectionPoint : public Point, public IntersectionReference -{ -public: - IntersectionPoint() {} - IntersectionPoint(int point_id, int edge_id, const Point &pt) : IntersectionReference(point_id, edge_id), Point(pt) {} - IntersectionPoint(const IntersectionReference &ir, const Point &pt) : IntersectionReference(ir), Point(pt) {} - // Inherits coord_t x, y -}; - -class IntersectionLine : public Line -{ -public: - IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feGeneral), flags(0) {} - - bool skip() const { return (this->flags & SKIP) != 0; } - void set_skip() { this->flags |= SKIP; } - - bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); } - void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; } - - // Inherits Point a, b - // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. - // Vertex indices of the line end points. - int a_id; - int b_id; - // Source mesh edges of the line end points. - int edge_a_id; - int edge_b_id; - // feGeneral, feTop, feBottom, feHorizontal - FacetEdgeType edge_type; - // Used by TriangleMeshSlicer::slice() to skip duplicate edges. - enum { - // Triangle edge added, because it has no neighbor. - EDGE0_NO_NEIGHBOR = 0x001, - EDGE1_NO_NEIGHBOR = 0x002, - EDGE2_NO_NEIGHBOR = 0x004, - // Triangle edge added, because it makes a fold with another horizontal edge. - EDGE0_FOLD = 0x010, - EDGE1_FOLD = 0x020, - EDGE2_FOLD = 0x040, - // The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds). - NO_SEED = 0x100, - SKIP = 0x200, - }; - uint32_t flags; -}; -typedef std::vector IntersectionLines; -typedef std::vector IntersectionLinePtrs; - -enum class SlicingMode : uint32_t { - // Regular slicing, maintain all contours and their orientation. - Regular, - // Maintain all contours, orient all contours CCW, therefore all holes are being closed. - Positive, - // Orient all contours CCW and keep only the contour with the largest area. - // This mode is useful for slicing complex objects in vase mode. - PositiveLargestContour, -}; - -class TriangleMeshSlicer -{ -public: - typedef std::function throw_on_cancel_callback_type; - TriangleMeshSlicer() : mesh(nullptr) {} - TriangleMeshSlicer(const TriangleMesh* mesh) { this->init(mesh, [](){}); } - void init(const TriangleMesh *mesh, throw_on_cancel_callback_type throw_on_cancel); - void slice( - const std::vector &z, SlicingMode mode, size_t alternate_mode_first_n_layers, SlicingMode alternate_mode, - std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const; - void slice(const std::vector &z, SlicingMode mode, std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const - { return this->slice(z, mode, 0, mode, layers, throw_on_cancel); } - void slice( - const std::vector &z, SlicingMode mode, size_t alternate_mode_first_n_layers, SlicingMode alternate_mode, const float closing_radius, - std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const; - void slice(const std::vector &z, SlicingMode mode, const float closing_radius, - std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const - { this->slice(z, mode, 0, mode, closing_radius, layers, throw_on_cancel); } - enum FacetSliceType { - NoSlice = 0, - Slicing = 1, - Cutting = 2 - }; - FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, - const float min_z, const float max_z, IntersectionLine *line_out) const; - void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; - void set_up_direction(const Vec3f& up); - -private: - const TriangleMesh *mesh; - // Map from a facet to an edge index. - std::vector facets_edges; - // Scaled copy of this->mesh->stl.v_shared - std::vector v_scaled_shared; - // Quaternion that will be used to rotate every facet before the slicing - Eigen::Quaternion m_quaternion; - // Whether or not the above quaterion should be used - bool m_use_quaternion = false; - - void _slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const; - void make_loops(std::vector &lines, Polygons* loops) const; - void make_expolygons(const Polygons &loops, const float closing_radius, ExPolygons* slices) const; - void make_expolygons_simple(std::vector &lines, ExPolygons* slices) const; - void make_expolygons(std::vector &lines, const float closing_radius, ExPolygons* slices) const; -}; - -inline void slice_mesh( - const TriangleMesh & mesh, - const std::vector & z, - std::vector & layers, - TriangleMeshSlicer::throw_on_cancel_callback_type thr = nullptr) -{ - if (mesh.empty()) return; - TriangleMeshSlicer slicer(&mesh); - slicer.slice(z, SlicingMode::Regular, &layers, thr); -} - -inline void slice_mesh( - const TriangleMesh & mesh, - const std::vector & z, - std::vector & layers, - float closing_radius, - TriangleMeshSlicer::throw_on_cancel_callback_type thr = nullptr) -{ - if (mesh.empty()) return; - TriangleMeshSlicer slicer(&mesh); - slicer.slice(z, SlicingMode::Regular, closing_radius, &layers, thr); -} +// Map from a facet edge to a neighbor face index or -1 if no neighbor exists. +std::vector create_face_neighbors_index(const indexed_triangle_set &its); +std::vector create_face_neighbors_index(const indexed_triangle_set &its, std::function throw_on_cancel_callback); +// Remove degenerate faces, return number of faces removed. +int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit = true); +// Remove vertices, which none of the faces references. Return number of freed vertices. +int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit = true); +void its_shrink_to_fit(indexed_triangle_set &its); TriangleMesh make_cube(double x, double y, double z); @@ -259,21 +114,21 @@ TriangleMesh make_sphere(double rho, double fa=(2*PI/360)); // Serialization through the Cereal library #include namespace cereal { - template struct specialize {}; - template void load(Archive &archive, Slic3r::TriangleMesh &mesh) { + template struct specialize {}; + template void load(Archive &archive, Slic3r::TriangleMesh &mesh) { stl_file &stl = mesh.stl; stl.stats.type = inmemory; - archive(stl.stats.number_of_facets, stl.stats.original_num_facets); + archive(stl.stats.number_of_facets, stl.stats.original_num_facets); stl_allocate(&stl); - archive.loadBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); + archive.loadBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); stl_get_size(&stl); mesh.repair(); - } - template void save(Archive &archive, const Slic3r::TriangleMesh &mesh) { - const stl_file& stl = mesh.stl; - archive(stl.stats.number_of_facets, stl.stats.original_num_facets); - archive.saveBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); - } + } + template void save(Archive &archive, const Slic3r::TriangleMesh &mesh) { + const stl_file& stl = mesh.stl; + archive(stl.stats.number_of_facets, stl.stats.original_num_facets); + archive.saveBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); + } } #endif diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp new file mode 100644 index 000000000..657a59b62 --- /dev/null +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -0,0 +1,1363 @@ +#include "ClipperUtils.hpp" +#include "Geometry.hpp" +#include "Tesselate.hpp" +#include "TriangleMesh.hpp" +#include "TriangleMeshSlicer.hpp" + +#include +#include +#include +#include +#include + +#include + +#include + +#if 0 + #define DEBUG + #define _DEBUG + #undef NDEBUG + #define SLIC3R_DEBUG +// #define SLIC3R_TRIANGLEMESH_DEBUG +#endif + +#include + +#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING) +#include "SVG.hpp" +#endif + +namespace Slic3r { + +class IntersectionReference +{ +public: + IntersectionReference() = default; + IntersectionReference(int point_id, int edge_id) : point_id(point_id), edge_id(edge_id) {} + // Where is this intersection point located? On mesh vertex or mesh edge? + // Only one of the following will be set, the other will remain set to -1. + // Index of the mesh vertex. + int point_id { -1 }; + // Index of the mesh edge. + int edge_id { -1 }; +}; + +class IntersectionPoint : public Point, public IntersectionReference +{ +public: + IntersectionPoint() = default; + IntersectionPoint(int point_id, int edge_id, const Point &pt) : IntersectionReference(point_id, edge_id), Point(pt) {} + IntersectionPoint(const IntersectionReference &ir, const Point &pt) : IntersectionReference(ir), Point(pt) {} + // Inherits coord_t x, y +}; + +class IntersectionLine : public Line +{ +public: + IntersectionLine() = default; + + bool skip() const { return (this->flags & SKIP) != 0; } + void set_skip() { this->flags |= SKIP; } + + bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); } + void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; } + + // Inherits Point a, b + // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. + // Vertex indices of the line end points. + int a_id { -1 }; + int b_id { -1 }; + // Source mesh edges of the line end points. + int edge_a_id { -1 }; + int edge_b_id { -1 }; + + enum class FacetEdgeType { + // A general case, the cutting plane intersect a face at two different edges. + General, + // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. + Top, + // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. + Bottom, + // All three vertices of a face are aligned with the cutting plane. + Horizontal + }; + + // feGeneral, feTop, feBottom, feHorizontal + FacetEdgeType edge_type { FacetEdgeType::General }; + // Used by TriangleMeshSlicer::slice() to skip duplicate edges. + enum { + // Triangle edge added, because it has no neighbor. + EDGE0_NO_NEIGHBOR = 0x001, + EDGE1_NO_NEIGHBOR = 0x002, + EDGE2_NO_NEIGHBOR = 0x004, + // Triangle edge added, because it makes a fold with another horizontal edge. + EDGE0_FOLD = 0x010, + EDGE1_FOLD = 0x020, + EDGE2_FOLD = 0x040, + // The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds). + NO_SEED = 0x100, + SKIP = 0x200, + }; + uint32_t flags { 0 }; +}; + +using IntersectionLines = std::vector; + +enum class FacetSliceType { + NoSlice = 0, + Slicing = 1, + Cutting = 2 +}; + +// Return true, if the facet has been sliced and line_out has been filled. +static FacetSliceType slice_facet( + float slice_z, const stl_vertex *vertices, const stl_triangle_vertex_indices &indices, const int *edge_neighbor, + const int idx_vertex_lowest, const bool horizontal, IntersectionLine *line_out) +{ + IntersectionPoint points[3]; + size_t num_points = 0; + auto point_on_layer = size_t(-1); + + // Reorder vertices so that the first one is the one with lowest Z. + // This is needed to get all intersection lines in a consistent order + // (external on the right of the line) + for (int j = 0; j < 3; ++ j) { // loop through facet edges + int edge_id; + const stl_vertex *a, *b; + int a_id, b_id; + { + int k = (idx_vertex_lowest + j) % 3; + int l = (k + 1) % 3; + edge_id = edge_neighbor[k]; + a_id = indices[k]; + a = vertices + k; + b_id = indices[l]; + b = vertices + l; + } + + // Is edge or face aligned with the cutting plane? + if (a->z() == slice_z && b->z() == slice_z) { + // Edge is horizontal and belongs to the current layer. + // The following rotation of the three vertices may not be efficient, but this branch happens rarely. + const stl_vertex &v0 = vertices[0]; + const stl_vertex &v1 = vertices[1]; + const stl_vertex &v2 = vertices[2]; + // We may ignore this edge for slicing purposes, but we may still use it for object cutting. + FacetSliceType result = FacetSliceType::Slicing; + if (horizontal) { + // All three vertices are aligned with slice_z. + line_out->edge_type = IntersectionLine::FacetEdgeType::Horizontal; + result = FacetSliceType::Cutting; + double normal = (v1.x() - v0.x()) * (v2.y() - v1.y()) - (v1.y() - v0.y()) * (v2.x() - v1.x()); + if (normal < 0) { + // If normal points downwards this is a bottom horizontal facet so we reverse its point order. + std::swap(a, b); + std::swap(a_id, b_id); + } + } else { + // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane. + // Is the third vertex below the cutting plane? + bool third_below = v0.z() < slice_z || v1.z() < slice_z || v2.z() < slice_z; + // Two vertices on the cutting plane, the third vertex is below the plane. Consider the edge to be part of the slice + // only if it is the upper edge. + // (the bottom most edge resp. vertex of a triangle is not owned by the triangle, but the top most edge resp. vertex is part of the triangle + // in respect to the cutting plane). + result = third_below ? FacetSliceType::Slicing : FacetSliceType::Cutting; + if (third_below) { + line_out->edge_type = IntersectionLine::FacetEdgeType::Top; + std::swap(a, b); + std::swap(a_id, b_id); + } else + line_out->edge_type = IntersectionLine::FacetEdgeType::Bottom; + } + line_out->a.x() = a->x(); + line_out->a.y() = a->y(); + line_out->b.x() = b->x(); + line_out->b.y() = b->y(); + line_out->a_id = a_id; + line_out->b_id = b_id; + assert(line_out->a != line_out->b); + return result; + } + + if (a->z() == slice_z) { + // Only point a alings with the cutting plane. + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { + point_on_layer = num_points; + IntersectionPoint &point = points[num_points ++]; + point.x() = a->x(); + point.y() = a->y(); + point.point_id = a_id; + } + } else if (b->z() == slice_z) { + // Only point b alings with the cutting plane. + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { + point_on_layer = num_points; + IntersectionPoint &point = points[num_points ++]; + point.x() = b->x(); + point.y() = b->y(); + point.point_id = b_id; + } + } else if ((a->z() < slice_z && b->z() > slice_z) || (b->z() < slice_z && a->z() > slice_z)) { + // A general case. The face edge intersects the cutting plane. Calculate the intersection point. + assert(a_id != b_id); + // Sort the edge to give a consistent answer. + if (a_id > b_id) { + std::swap(a_id, b_id); + std::swap(a, b); + } + IntersectionPoint &point = points[num_points]; + double t = (double(slice_z) - double(b->z())) / (double(a->z()) - double(b->z())); + if (t <= 0.) { + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { + point.x() = a->x(); + point.y() = a->y(); + point_on_layer = num_points ++; + point.point_id = a_id; + } + } else if (t >= 1.) { + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { + point.x() = b->x(); + point.y() = b->y(); + point_on_layer = num_points ++; + point.point_id = b_id; + } + } else { + point.x() = coord_t(floor(double(b->x()) + (double(a->x()) - double(b->x())) * t + 0.5)); + point.y() = coord_t(floor(double(b->y()) + (double(a->y()) - double(b->y())) * t + 0.5)); + point.edge_id = edge_id; + ++ num_points; + } + } + } + + // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only. + assert(num_points < 3); + if (num_points == 2) { + line_out->edge_type = IntersectionLine::FacetEdgeType::General; + line_out->a = (Point)points[1]; + line_out->b = (Point)points[0]; + line_out->a_id = points[1].point_id; + line_out->b_id = points[0].point_id; + line_out->edge_a_id = points[1].edge_id; + line_out->edge_b_id = points[0].edge_id; + // Not a zero lenght edge. + //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. + //assert(line_out->a != line_out->b); + // The plane cuts at least one edge in a general position. + assert(line_out->a_id == -1 || line_out->b_id == -1); + assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1); + // General slicing position, use the segment for both slicing and object cutting. +#if 0 + if (line_out->a_id != -1 && line_out->b_id != -1) { + // Solving a degenerate case, where both the intersections snapped to an edge. + // Correctly classify the face as below or above based on the position of the 3rd point. + int i = indices[0]; + if (i == line_out->a_id || i == line_out->b_id) + i = indices[1]; + if (i == line_out->a_id || i == line_out->b_id) + i = indices[2]; + assert(i != line_out->a_id && i != line_out->b_id); + line_out->edge_type = ((m_use_quaternion ? + (m_quaternion * this->v_scaled_shared[i]).z() + : this->v_scaled_shared[i].z()) < slice_z) ? IntersectionLine::FacetEdgeType::Top : IntersectionLine::FacetEdgeType::Bottom; + } +#endif + return FacetSliceType::Slicing; + } + return FacetSliceType::NoSlice; +} + +static void slice_facet_at_zs( + const stl_triangle_vertex_indices &indices, + const std::vector &v_scaled_shared, + const int *facet_neighbors, + const Eigen::Quaternion *quaternion, + std::vector *lines, + boost::mutex *lines_mutex, + const std::vector &scaled_zs) +{ + stl_vertex vertices[3] { v_scaled_shared[indices(0)], v_scaled_shared[indices(1)], v_scaled_shared[indices(2)] }; + if (quaternion) + for (int i = 0; i < 3; ++ i) + vertices[i] = *quaternion * vertices[i]; + + // find facet extents + const float min_z = fminf(vertices[0].z(), fminf(vertices[1].z(), vertices[2].z())); + const float max_z = fmaxf(vertices[0].z(), fmaxf(vertices[1].z(), vertices[2].z())); + + // find layer extents + auto min_layer = std::lower_bound(scaled_zs.begin(), scaled_zs.end(), min_z); // first layer whose slice_z is >= min_z + auto max_layer = std::upper_bound(min_layer, scaled_zs.end(), max_z); // first layer whose slice_z is > max_z + + for (auto it = min_layer; it != max_layer; ++ it) { + IntersectionLine il; + int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); + if (slice_facet(*it, vertices, indices, facet_neighbors, idx_vertex_lowest, min_z == max_z, &il) == FacetSliceType::Slicing && + il.edge_type != IntersectionLine::FacetEdgeType::Horizontal) { + // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume. + boost::lock_guard l(*lines_mutex); + (*lines)[it - scaled_zs.begin()].emplace_back(il); + } + } +} + +#if 0 +//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing +// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces. +// So the following code makes only sense now to handle degenerate meshes with more than two faces +// sharing a single edge. +static inline void remove_tangent_edges(std::vector &lines) +{ + std::vector by_vertex_pair; + by_vertex_pair.reserve(lines.size()); + for (IntersectionLine& line : lines) + if (line.edge_type != IntersectionLine::FacetEdgeType::General && line.a_id != -1) + // This is a face edge. Check whether there is its neighbor stored in lines. + by_vertex_pair.emplace_back(&line); + auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) { + // Sort vertices of l1, l2 lexicographically + int l1a = l1->a_id; + int l1b = l1->b_id; + int l2a = l2->a_id; + int l2b = l2->b_id; + if (l1a > l1b) + std::swap(l1a, l1b); + if (l2a > l2b) + std::swap(l2a, l2b); + // Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored. + return l1a < l2a || (l1a == l2a && l1b < l2b); + }; + std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted); + for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) { + IntersectionLine &l1 = **line; + if (! l1.skip()) { + // Iterate as long as line and line2 edges share the same end points. + for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) { + // Lines must share the end points. + assert(! edges_lower_sorted(*line, *line2)); + assert(! edges_lower_sorted(*line2, *line)); + IntersectionLine &l2 = **line2; + if (l2.skip()) + continue; + if (l1.a_id == l2.a_id) { + assert(l1.b_id == l2.b_id); + l2.set_skip(); + // If they are both oriented upwards or downwards (like a 'V'), + // then we can remove both edges from this layer since it won't + // affect the sliced shape. + // If one of them is oriented upwards and the other is oriented + // downwards, let's only keep one of them (it doesn't matter which + // one since all 'top' lines were reversed at slicing). + if (l1.edge_type == l2.edge_type) { + l1.set_skip(); + break; + } + } else { + assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id); + // If this edge joins two horizontal facets, remove both of them. + if (l1.edge_type == IntersectionLine::FacetEdgeType::Horizontal && l2.edge_type == IntersectionLine::FacetEdgeType::Horizontal) { + l1.set_skip(); + l2.set_skip(); + break; + } + } + } + } + } +} +#endif + +struct OpenPolyline { + OpenPolyline() = default; + OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : + start(start), end(end), points(std::move(points)), consumed(false) { this->length = Slic3r::length(this->points); } + void reverse() { + std::swap(start, end); + std::reverse(points.begin(), points.end()); + } + IntersectionReference start; + IntersectionReference end; + Points points; + double length; + bool consumed; +}; + +// called by TriangleMeshSlicer::make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity. +// Only connects segments crossing triangles of the same orientation. +static void chain_lines_by_triangle_connectivity(std::vector &lines, Polygons &loops, std::vector &open_polylines) +{ + // Build a map of lines by edge_a_id and a_id. + std::vector by_edge_a_id; + std::vector by_a_id; + by_edge_a_id.reserve(lines.size()); + by_a_id.reserve(lines.size()); + for (IntersectionLine &line : lines) { + if (! line.skip()) { + if (line.edge_a_id != -1) + by_edge_a_id.emplace_back(&line); + if (line.a_id != -1) + by_a_id.emplace_back(&line); + } + } + auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; + auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; + std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); + std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); + // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. + IntersectionLines::iterator it_line_seed = lines.begin(); + for (;;) { + // take first spare line and start a new loop + IntersectionLine *first_line = nullptr; + for (; it_line_seed != lines.end(); ++ it_line_seed) + if (it_line_seed->is_seed_candidate()) { + //if (! it_line_seed->skip()) { + first_line = &(*it_line_seed ++); + break; + } + if (first_line == nullptr) + break; + first_line->set_skip(); + Points loop_pts; + loop_pts.emplace_back(first_line->a); + IntersectionLine *last_line = first_line; + + /* + printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, + first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); + */ + + IntersectionLine key; + for (;;) { + // find a line starting where last one finishes + IntersectionLine* next_line = nullptr; + if (last_line->edge_b_id != -1) { + key.edge_a_id = last_line->edge_b_id; + auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); + if (it_begin != by_edge_a_id.end()) { + auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); + for (auto it_line = it_begin; it_line != it_end; ++ it_line) + if (! (*it_line)->skip()) { + next_line = *it_line; + break; + } + } + } + if (next_line == nullptr && last_line->b_id != -1) { + key.a_id = last_line->b_id; + auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); + if (it_begin != by_a_id.end()) { + auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); + for (auto it_line = it_begin; it_line != it_end; ++ it_line) + if (! (*it_line)->skip()) { + next_line = *it_line; + break; + } + } + } + if (next_line == nullptr) { + // Check whether we closed this loop. + if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || + (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { + // The current loop is complete. Add it to the output. + loops.emplace_back(std::move(loop_pts)); + #ifdef SLIC3R_TRIANGLEMESH_DEBUG + printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); + #endif + } else { + // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. + loop_pts.emplace_back(last_line->b); + open_polylines.emplace_back(OpenPolyline( + IntersectionReference(first_line->a_id, first_line->edge_a_id), + IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); + } + break; + } + /* + printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, + next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); + */ + loop_pts.emplace_back(next_line->a); + last_line = next_line; + next_line->set_skip(); + } + } +} + +std::vector open_polylines_sorted(std::vector &open_polylines, bool update_lengths) +{ + std::vector out; + out.reserve(open_polylines.size()); + for (OpenPolyline &opl : open_polylines) + if (! opl.consumed) { + if (update_lengths) + opl.length = Slic3r::length(opl.points); + out.emplace_back(&opl); + } + std::sort(out.begin(), out.end(), [](const OpenPolyline *lhs, const OpenPolyline *rhs){ return lhs->length > rhs->length; }); + return out; +} + +// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices. +// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. +static void chain_open_polylines_exact(std::vector &open_polylines, Polygons &loops, bool try_connect_reversed) +{ + // Store the end points of open_polylines into vectors sorted + struct OpenPolylineEnd { + OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} + OpenPolyline *polyline; + // Is it the start or end point? + bool start; + const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; } + // Return a unique ID for the intersection point. + // Return a positive id for a point, or a negative id for an edge. + int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; } + bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } + }; + auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); }; + std::vector by_id; + by_id.reserve(2 * open_polylines.size()); + for (OpenPolyline &opl : open_polylines) { + if (opl.start.point_id != -1 || opl.start.edge_id != -1) + by_id.emplace_back(OpenPolylineEnd(&opl, true)); + if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1)) + by_id.emplace_back(OpenPolylineEnd(&opl, false)); + } + std::sort(by_id.begin(), by_id.end(), by_id_lower); + // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute). + auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector::iterator { + for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + it != by_id.end() && it->id() == end.id(); ++ it) + if (*it == end) + return it; + return by_id.end(); + }; + // Try to connect the loops. + std::vector sorted_by_length = open_polylines_sorted(open_polylines, false); + for (OpenPolyline *opl : sorted_by_length) { + if (opl->consumed) + continue; + opl->consumed = true; + OpenPolylineEnd end(opl, false); + for (;;) { + // find a line starting where last one finishes + auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start) + if (! it_next_start->polyline->consumed) + goto found; + // The current loop could not be closed. Unmark the segment. + opl->consumed = false; + break; + found: + // Attach this polyline to the end of the initial polyline. + if (it_next_start->start) { + auto it = it_next_start->polyline->points.begin(); + std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl->points)); + } else { + auto it = it_next_start->polyline->points.rbegin(); + std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl->points)); + } + opl->length += it_next_start->polyline->length; + // Mark the next polyline as consumed. + it_next_start->polyline->points.clear(); + it_next_start->polyline->length = 0.; + it_next_start->polyline->consumed = true; + if (try_connect_reversed) { + // Running in a mode, where the polylines may be connected by mixing their orientations. + // Update the end point lookup structure after the end point of the current polyline was extended. + auto it_end = find_polyline_end(end); + auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start)); + // Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag. + std::swap(opl->end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end); + // Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions. + std::swap(*it_end, *it_next_end); + } + // Check whether we closed this loop. + if ((opl->start.edge_id != -1 && opl->start.edge_id == opl->end.edge_id) || + (opl->start.point_id != -1 && opl->start.point_id == opl->end.point_id)) { + // The current loop is complete. Add it to the output. + //assert(opl->points.front().point_id == opl->points.back().point_id); + //assert(opl->points.front().edge_id == opl->points.back().edge_id); + // Remove the duplicate last point. + opl->points.pop_back(); + if (opl->points.size() >= 3) { + if (try_connect_reversed && area(opl->points) < 0) + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + std::reverse(opl->points.begin(), opl->points.end()); + loops.emplace_back(std::move(opl->points)); + } + opl->points.clear(); + break; + } + // Continue with the current loop. + } + } +} + +// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices, +// possibly closing small gaps. +// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. +static void chain_open_polylines_close_gaps(std::vector &open_polylines, Polygons &loops, double max_gap, bool try_connect_reversed) +{ + const coord_t max_gap_scaled = (coord_t)scale_(max_gap); + + // Sort the open polylines by their length, so the new loops will be seeded from longer chains. + // Update the polyline lengths, return only not yet consumed polylines. + std::vector sorted_by_length = open_polylines_sorted(open_polylines, true); + + // Store the end points of open_polylines into ClosestPointInRadiusLookup. + struct OpenPolylineEnd { + OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} + OpenPolyline *polyline; + // Is it the start or end point? + bool start; + const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); } + bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } + }; + struct OpenPolylineEndAccessor { + const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + ClosestPointLookupType closest_end_point_lookup(max_gap_scaled); + for (OpenPolyline *opl : sorted_by_length) { + closest_end_point_lookup.insert(OpenPolylineEnd(opl, true)); + if (try_connect_reversed) + closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); + } + // Try to connect the loops. + for (OpenPolyline *opl : sorted_by_length) { + if (opl->consumed) + continue; + OpenPolylineEnd end(opl, false); + if (try_connect_reversed) + // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it. + closest_end_point_lookup.erase(end); + opl->consumed = true; + size_t n_segments_joined = 1; + for (;;) { + // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). + std::pair next_start_and_dist = closest_end_point_lookup.find(end.point()); + const OpenPolylineEnd *next_start = next_start_and_dist.first; + // Check whether we closed this loop. + double current_loop_closing_distance2 = (opl->points.back() - opl->points.front()).cast().squaredNorm(); + bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled); + if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) { + // Heuristics to decide, whether to close the loop, or connect another polyline. + // One should avoid closing loops shorter than max_gap_scaled. + loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl->points); + } + if (loop_closed) { + // Remove the start point of the current polyline from the lookup. + // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail. + opl->consumed = false; + closest_end_point_lookup.erase(OpenPolylineEnd(opl, true)); + if (current_loop_closing_distance2 == 0.) { + // Remove the duplicate last point. + opl->points.pop_back(); + } else { + // The end points are different, keep both of them. + } + if (opl->points.size() >= 3) { + if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0) + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + std::reverse(opl->points.begin(), opl->points.end()); + loops.emplace_back(std::move(opl->points)); + } + opl->points.clear(); + opl->consumed = true; + break; + } + if (next_start == nullptr) { + // The current loop could not be closed. Unmark the segment. + opl->consumed = false; + if (try_connect_reversed) + // Re-insert the end point. + closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); + break; + } + // Attach this polyline to the end of the initial polyline. + if (next_start->start) { + auto it = next_start->polyline->points.begin(); + if (*it == opl->points.back()) + ++ it; + std::copy(it, next_start->polyline->points.end(), back_inserter(opl->points)); + } else { + auto it = next_start->polyline->points.rbegin(); + if (*it == opl->points.back()) + ++ it; + std::copy(it, next_start->polyline->points.rend(), back_inserter(opl->points)); + } + ++ n_segments_joined; + // Remove the end points of the consumed polyline segment from the lookup. + OpenPolyline *opl2 = next_start->polyline; + closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true)); + if (try_connect_reversed) + closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false)); + opl2->points.clear(); + opl2->consumed = true; + // Continue with the current loop. + } + } +} + +static void make_loops(std::vector &lines, Polygons* loops) +{ +#if 0 +//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. +//#ifdef _DEBUG + for (const Line &l : lines) + assert(l.a != l.b); +#endif /* _DEBUG */ + + // There should be no tangent edges, as the horizontal triangles are ignored and if two triangles touch at a cutting plane, + // only the bottom triangle is considered to be cutting the plane. +// remove_tangent_edges(lines); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + BoundingBox bbox_svg; + { + static int iRun = 0; + for (const Line &line : lines) { + bbox_svg.merge(line.a); + bbox_svg.merge(line.b); + } + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-raw_lines-%d.svg", iRun ++).c_str(), bbox_svg); + for (const Line &line : lines) + svg.draw(line); + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + std::vector open_polylines; + chain_lines_by_triangle_connectivity(lines, *loops, open_polylines); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-%d.svg", iRun ++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) + svg.draw(Polyline(pl.points), "red"); + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + // Now process the open polylines. + // Do it in two rounds, first try to connect in the same direction only, + // then try to connect the open polylines in reversed order as well. + chain_open_polylines_exact(open_polylines, *loops, false); + chain_open_polylines_exact(open_polylines, *loops, true); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) { + if (pl.points.empty()) + continue; + svg.draw(Polyline(pl.points), "red"); + svg.draw(pl.points.front(), "blue"); + svg.draw(pl.points.back(), "blue"); + } + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + // Try to close gaps. + // Do it in two rounds, first try to connect in the same direction only, + // then try to connect the open polylines in reversed order as well. +#if 0 + for (double max_gap : { EPSILON, 0.001, 0.1, 1., 2. }) { + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); + } +#else + const double max_gap = 2.; //mm + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); +#endif + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) { + if (pl.points.empty()) + continue; + svg.draw(Polyline(pl.points), "red"); + svg.draw(pl.points.front(), "blue"); + svg.draw(pl.points.back(), "blue"); + } + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ +} + +// Used to cut the mesh into two halves. +static ExPolygons make_expolygons_simple(std::vector &lines) +{ + ExPolygons slices; + Polygons holes; + + { + Polygons loops; + make_loops(lines, &loops); + for (Polygon &loop : loops) + if (loop.area() >= 0.) + slices.emplace_back(std::move(loop)); + else + holes.emplace_back(std::move(loop)); + } + + // If there are holes, then there should also be outer contours. + assert(holes.empty() || ! slices.empty()); + if (! slices.empty()) + { + // Assign holes to outer contours. + for (Polygon &hole : holes) { + // Find an outer contour to a hole. + int slice_idx = -1; + double current_contour_area = std::numeric_limits::max(); + for (ExPolygon &slice : slices) + if (slice.contour.contains(hole.points.front())) { + double area = slice.contour.area(); + if (area < current_contour_area) { + slice_idx = &slice - slices.data(); + current_contour_area = area; + } + } + // assert(slice_idx != -1); + if (slice_idx == -1) + // Ignore this hole. + continue; + assert(current_contour_area < std::numeric_limits::max() && current_contour_area >= -hole.area()); + slices[slice_idx].holes.emplace_back(std::move(hole)); + } + +#if 0 + // If the input mesh is not valid, the holes may intersect with the external contour. + // Rather subtract them from the outer contour. + Polygons poly; + for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { + if (it_slice->holes.empty()) { + poly.emplace_back(std::move(it_slice->contour)); + } else { + Polygons contours; + contours.emplace_back(std::move(it_slice->contour)); + for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it) + it->reverse(); + polygons_append(poly, diff(contours, it_slice->holes)); + } + } + // If the input mesh is not valid, the input contours may intersect. + *slices = union_ex(poly); +#endif + +#if 0 + // If the input mesh is not valid, the holes may intersect with the external contour. + // Rather subtract them from the outer contour. + ExPolygons poly; + for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { + Polygons contours; + contours.emplace_back(std::move(it_slice->contour)); + for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it) + it->reverse(); + expolygons_append(poly, diff_ex(contours, it_slice->holes)); + } + // If the input mesh is not valid, the input contours may intersect. + *slices = std::move(poly); +#endif + } + + return slices; +} + +static void make_expolygons(const Polygons &loops, const float closing_radius, const float extra_offset, ExPolygons* slices) +{ + /* + Input loops are not suitable for evenodd nor nonzero fill types, as we might get + two consecutive concentric loops having the same winding order - and we have to + respect such order. In that case, evenodd would create wrong inversions, and nonzero + would ignore holes inside two concentric contours. + So we're ordering loops and collapse consecutive concentric loops having the same + winding order. + TODO: find a faster algorithm for this, maybe with some sort of binary search. + If we computed a "nesting tree" we could also just remove the consecutive loops + having the same winding order, and remove the extra one(s) so that we could just + supply everything to offset() instead of performing several union/diff calls. + + we sort by area assuming that the outermost loops have larger area; + the previous sorting method, based on $b->contains($a->[0]), failed to nest + loops correctly in some edge cases when original model had overlapping facets + */ + + /* The following lines are commented out because they can generate wrong polygons, + see for example issue #661 */ + + //std::vector area; + //std::vector sorted_area; // vector of indices + //for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) { + // area.emplace_back(loop->area()); + // sorted_area.emplace_back(loop - loops.begin()); + //} + // + //// outer first + //std::sort(sorted_area.begin(), sorted_area.end(), + // [&area](size_t a, size_t b) { return std::abs(area[a]) > std::abs(area[b]); }); + + //// we don't perform a safety offset now because it might reverse cw loops + //Polygons p_slices; + //for (std::vector::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) { + // /* we rely on the already computed area to determine the winding order + // of the loops, since the Orientation() function provided by Clipper + // would do the same, thus repeating the calculation */ + // Polygons::const_iterator loop = loops.begin() + *loop_idx; + // if (area[*loop_idx] > +EPSILON) + // p_slices.emplace_back(*loop); + // else if (area[*loop_idx] < -EPSILON) + // //FIXME This is arbitrary and possibly very slow. + // // If the hole is inside a polygon, then there is no need to diff. + // // If the hole intersects a polygon boundary, then diff it, but then + // // there is no guarantee of an ordering of the loops. + // // Maybe we can test for the intersection before running the expensive diff algorithm? + // p_slices = diff(p_slices, *loop); + //} + + // Perform a safety offset to merge very close facets (TODO: find test case for this) + // 0.0499 comes from https://github.com/slic3r/Slic3r/issues/959 +// double safety_offset = scale_(0.0499); + // 0.0001 is set to satisfy GH #520, #1029, #1364 + assert(closing_radius >= 0); + assert(extra_offset >= 0); + double offset_out = + scale_(closing_radius + extra_offset); + double offset_in = - scale_(closing_radius); + + /* The following line is commented out because it can generate wrong polygons, + see for example issue #661 */ + //ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); + + #ifdef SLIC3R_TRIANGLEMESH_DEBUG + size_t holes_count = 0; + for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++ e) + holes_count += e->holes.size(); + printf("%zu surface(s) having %zu holes detected from %zu polylines\n", + ex_slices.size(), holes_count, loops.size()); + #endif + + // append to the supplied collection + expolygons_append(*slices, + offset_out > 0 && offset_in < 0 ? offset2_ex(union_ex(loops), offset_out, offset_in) : + offset_out > 0 ? offset_ex(union_ex(loops), offset_out) : + offset_in < 0 ? offset_ex(union_ex(loops), offset_in) : + union_ex(loops)); +} + +/* +static void make_expolygons(std::vector &lines, const float closing_radius, ExPolygons* slices) +{ + Polygons pp; + make_loops(lines, &pp); + Slic3r::make_expolygons(pp, closing_radius, 0.f, slices); +} +*/ + +void TriangleMeshSlicer::init(const TriangleMesh *mesh, throw_on_cancel_callback_type throw_on_cancel) +{ + if (! mesh->has_shared_vertices()) + throw Slic3r::InvalidArgument("TriangleMeshSlicer was passed a mesh without shared vertices."); + + this->init(&mesh->its, throw_on_cancel); +} + +void TriangleMeshSlicer::init(const indexed_triangle_set *its, throw_on_cancel_callback_type throw_on_cancel) +{ + m_its = its; + facets_edges = create_face_neighbors_index(*its, throw_on_cancel); + v_scaled_shared.assign(its->vertices.size(), stl_vertex()); + for (size_t i = 0; i < v_scaled_shared.size(); ++ i) + this->v_scaled_shared[i] = its->vertices[i] / float(SCALING_FACTOR); +} + +void TriangleMeshSlicer::set_up_direction(const Vec3f& up) +{ + m_quaternion.setFromTwoVectors(up, Vec3f::UnitZ()); + m_use_quaternion = true; +} + +void TriangleMeshSlicer::slice( + const std::vector &z, + const MeshSlicingParams ¶ms, + std::vector *layers, + throw_on_cancel_callback_type throw_on_cancel) const +{ + BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice"; + + /* + This method gets called with a list of unscaled Z coordinates and outputs + a vector pointer having the same number of items as the original list. + Each item is a vector of polygons created by slicing our mesh at the + given heights. + + This method should basically combine the behavior of the existing + Perl methods defined in lib/Slic3r/TriangleMesh.pm: + + - analyze(): this creates the 'facets_edges' and the 'edges_facets' + tables (we don't need the 'edges' table) + + - slice_facet(): this has to be done for each facet. It generates + intersection lines with each plane identified by the Z list. + The get_layer_range() binary search used to identify the Z range + of the facet is already ported to C++ (see Object.xsp) + + - make_loops(): this has to be done for each layer. It creates polygons + from the lines generated by the previous step. + + At the end, we free the tables generated by analyze() as we don't + need them anymore. + + NOTE: this method accepts a vector of floats because the mesh coordinate + type is float. + */ + + BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice_facet_at_zs"; + std::vector lines(z.size()); + { + std::vector scaled_z(z); + for (float &z : scaled_z) + z = scaled(z); + boost::mutex lines_mutex; + tbb::parallel_for( + tbb::blocked_range(0, int(m_its->indices.size())), + [&lines, &lines_mutex, &scaled_z, throw_on_cancel, this](const tbb::blocked_range& range) { + const Eigen::Quaternion *rotation = m_use_quaternion ? &m_quaternion : nullptr; + for (int facet_idx = range.begin(); facet_idx < range.end(); ++ facet_idx) { + if ((facet_idx & 0x0ffff) == 0) + throw_on_cancel(); + slice_facet_at_zs(m_its->indices[facet_idx], this->v_scaled_shared, this->facets_edges.data() + facet_idx * 3, rotation, &lines, &lines_mutex, scaled_z); + } + } + ); + } + throw_on_cancel(); + + // v_scaled_shared could be freed here + + // build loops + BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::_make_loops_do"; + layers->resize(z.size()); + tbb::parallel_for( + tbb::blocked_range(0, z.size()), + [&lines, &layers, ¶ms, throw_on_cancel](const tbb::blocked_range& range) { + for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx) { + if ((line_idx & 0x0ffff) == 0) + throw_on_cancel(); + + Polygons &polygons = (*layers)[line_idx]; + make_loops(lines[line_idx], &polygons); + + auto this_mode = line_idx < params.slicing_mode_normal_below_layer ? params.mode_below : params.mode; + if (! polygons.empty()) { + if (this_mode == SlicingMode::Positive) { + // Reorient all loops to be CCW. + for (Polygon& p : polygons) + p.make_counter_clockwise(); + } else if (this_mode == SlicingMode::PositiveLargestContour) { + // Keep just the largest polygon, make it CCW. + double max_area = 0.; + Polygon* max_area_polygon = nullptr; + for (Polygon& p : polygons) { + double a = p.area(); + if (std::abs(a) > std::abs(max_area)) { + max_area = a; + max_area_polygon = &p; + } + } + assert(max_area_polygon != nullptr); + if (max_area < 0.) + max_area_polygon->reverse(); + Polygon p(std::move(*max_area_polygon)); + polygons.clear(); + polygons.emplace_back(std::move(p)); + } + } + } + } + ); + BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice finished"; + +#ifdef SLIC3R_DEBUG + { + static int iRun = 0; + for (size_t i = 0; i < z.size(); ++ i) { + Polygons &polygons = (*layers)[i]; + ExPolygons expolygons = union_ex(polygons, true); + SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), expolygons); + { + BoundingBox bbox; + for (const IntersectionLine &l : lines[i]) { + bbox.merge(l.a); + bbox.merge(l.b); + } + SVG svg(debug_out_path("slice_loops_%d_%d.svg", iRun, i).c_str(), bbox); + svg.draw(expolygons); + for (const IntersectionLine &l : lines[i]) + svg.draw(l, "red", 0); + svg.draw_outline(expolygons, "black", "blue", 0); + svg.Close(); + } +#if 0 +//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. + for (Polygon &poly : polygons) { + for (size_t i = 1; i < poly.points.size(); ++ i) + assert(poly.points[i-1] != poly.points[i]); + assert(poly.points.front() != poly.points.back()); + } +#endif + } + ++ iRun; + } +#endif +} + +void TriangleMeshSlicer::slice( + // Where to slice. + const std::vector &z, + const MeshSlicingParamsExtended ¶ms, + std::vector *layers, + throw_on_cancel_callback_type throw_on_cancel) const +{ + std::vector layers_p; + { + MeshSlicingParams slicing_params(params); + if (params.mode == SlicingMode::PositiveLargestContour) + slicing_params.mode = SlicingMode::Positive; + if (params.mode_below == SlicingMode::PositiveLargestContour) + slicing_params.mode_below = SlicingMode::Positive; + this->slice(z, slicing_params, &layers_p, throw_on_cancel); + } + + BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start"; + layers->resize(z.size()); + tbb::parallel_for( + tbb::blocked_range(0, z.size()), + [&layers_p, ¶ms, layers, throw_on_cancel] + (const tbb::blocked_range& range) { + for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { +#ifdef SLIC3R_TRIANGLEMESH_DEBUG + printf("Layer %zu (slice_z = %.2f):\n", layer_id, z[layer_id]); +#endif + throw_on_cancel(); + ExPolygons &expolygons = (*layers)[layer_id]; + Slic3r::make_expolygons(layers_p[layer_id], params.closing_radius, params.extra_offset, &expolygons); + //FIXME simplify + const auto this_mode = layer_id < params.slicing_mode_normal_below_layer ? params.mode_below : params.mode; + if (this_mode == SlicingMode::PositiveLargestContour) + keep_largest_contour_only(expolygons); + } + }); + BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end"; +} + +static void triangulate_slice(indexed_triangle_set &its, IntersectionLines &lines, const std::vector &slice_vertices, float z) +{ +// BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating the cut"; + + ExPolygons section = make_expolygons_simple(lines); + Pointf3s triangles = triangulate_expolygons_3d(section, z, true); + std::vector> map_vertex_to_index; + map_vertex_to_index.reserve(slice_vertices.size()); + for (int i : slice_vertices) + map_vertex_to_index.emplace_back(to_2d(its.vertices[i]), i); + std::sort(map_vertex_to_index.begin(), map_vertex_to_index.end(), + [](const std::pair &l, const std::pair &r) { + return l.first.x() < r.first.x() || (l.first.x() == r.first.x() && l.first.y() < r.first.y()); }); + size_t idx_vertex_new_first = its.vertices.size(); + for (size_t i = 0; i < triangles.size(); ) { + stl_triangle_vertex_indices facet; + for (size_t j = 0; j < 3; ++ j) { + Vec3f v = triangles[i++].cast(); + auto it = lower_bound_by_predicate(map_vertex_to_index.begin(), map_vertex_to_index.end(), + [&v](const std::pair &l) { return l.first.x() < v.x() || (l.first.x() == v.x() && l.first.y() < v.y()); }); + int idx = -1; + if (it != map_vertex_to_index.end() && it->first.x() == v.x() && it->first.y() == v.y()) + idx = it->second; + else { + // Try to find the vertex in the list of newly added vertices. Those vertices are not matched on the cut and they shall be rare. + for (size_t k = idx_vertex_new_first; k < its.vertices.size(); ++ k) + if (its.vertices[k] == v) { + idx = int(k); + break; + } + if (idx == -1) { + idx = int(its.vertices.size()); + its.vertices.emplace_back(v); + } + } + facet(j) = idx; + } + its.indices.emplace_back(facet); + } + + its_compactify_vertices(its); + its_remove_degenerate_faces(its); +} + +void TriangleMeshSlicer::cut(float z, indexed_triangle_set *upper, indexed_triangle_set *lower) const +{ + assert(upper || lower); + if (upper == nullptr && lower == nullptr) + return; + + if (upper) { + upper->clear(); + upper->vertices = m_its->vertices; + upper->indices.reserve(m_its->indices.size()); + } + if (lower) { + lower->clear(); + lower->vertices = m_its->vertices; + lower->indices.reserve(m_its->indices.size()); + } + + BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object"; + const auto scaled_z = scaled(z); + + // To triangulate the caps after slicing. + IntersectionLines upper_lines, lower_lines; + std::vector upper_slice_vertices, lower_slice_vertices; + + for (int facet_idx = 0; facet_idx < int(m_its->indices.size()); ++ facet_idx) { + const stl_triangle_vertex_indices &facet = m_its->indices[facet_idx]; + Vec3f vertices[3] { m_its->vertices[facet(0)], m_its->vertices[facet(1)], m_its->vertices[facet(2)] }; + stl_vertex vertices_scaled[3]{ this->v_scaled_shared[facet[0]], this->v_scaled_shared[facet[1]], this->v_scaled_shared[facet[2]] }; + float min_z = std::min(vertices[0].z(), std::min(vertices[1].z(), vertices[2].z())); + float max_z = std::max(vertices[0].z(), std::max(vertices[1].z(), vertices[2].z())); + + // intersect facet with cutting plane + IntersectionLine line; + int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); + FacetSliceType slice_type = slice_facet(scaled_z, vertices_scaled, m_its->indices[facet_idx], this->facets_edges.data() + facet_idx * 3, idx_vertex_lowest, min_z == max_z, &line); + if (slice_type != FacetSliceType::NoSlice) { + // Save intersection lines for generating correct triangulations. + if (line.edge_type == IntersectionLine::FacetEdgeType::Top) { + lower_lines.emplace_back(line); + lower_slice_vertices.emplace_back(line.a_id); + lower_slice_vertices.emplace_back(line.b_id); + } else if (line.edge_type == IntersectionLine::FacetEdgeType::Bottom) { + upper_lines.emplace_back(line); + upper_slice_vertices.emplace_back(line.a_id); + upper_slice_vertices.emplace_back(line.b_id); + } else if (line.edge_type == IntersectionLine::FacetEdgeType::General) { + lower_lines.emplace_back(line); + upper_lines.emplace_back(line); + } + } + + if (min_z > z || (min_z == z && max_z > z)) { + // facet is above the cut plane and does not belong to it + if (upper != nullptr) + upper->indices.emplace_back(facet); + } else if (max_z < z || (max_z == z && min_z < z)) { + // facet is below the cut plane and does not belong to it + if (lower != nullptr) + lower->indices.emplace_back(facet); + } else if (min_z < z && max_z > z) { + // Facet is cut by the slicing plane. + assert(slice_type == FacetSliceType::Slicing); + assert(line.edge_type == IntersectionLine::FacetEdgeType::General); + + // look for the vertex on whose side of the slicing plane there are no other vertices + int isolated_vertex = + (vertices[0].z() > z) == (vertices[1].z() > z) ? 2 : + (vertices[1].z() > z) == (vertices[2].z() > z) ? 0 : 1; + + // get vertices starting from the isolated one + int iv = isolated_vertex; + const stl_vertex &v0 = vertices[iv]; + const int iv0 = facet[iv]; + if (++ iv == 3) + iv = 0; + const stl_vertex &v1 = vertices[iv]; + const int iv1 = facet[iv]; + if (++ iv == 3) + iv = 0; + const stl_vertex &v2 = vertices[iv]; + const int iv2 = facet[iv]; + + // intersect v0-v1 and v2-v0 with cutting plane and make new vertices + auto new_vertex = [upper, lower, &upper_slice_vertices, &lower_slice_vertices](const Vec3f &a, const int ia, const Vec3f &b, const int ib, float z, float t) { + int iupper, ilower; + if (t <= 0.f) + iupper = ilower = ia; + else if (t >= 1.f) + iupper = ilower = ib; + else { + const stl_vertex c = Vec3f(lerp(a.x(), b.x(), t), lerp(a.y(), b.y(), t), z); + if (c == a) + iupper = ilower = ia; + else if (c == b) + iupper = ilower = ib; + else { + // Insert a new vertex into upper / lower. + if (upper) { + iupper = int(upper->vertices.size()); + upper->vertices.emplace_back(c); + upper_slice_vertices.emplace_back(iupper); + } + if (lower) { + ilower = int(lower->vertices.size()); + lower->vertices.emplace_back(c); + lower_slice_vertices.emplace_back(ilower); + } + } + } + return std::make_pair(iupper, ilower); + }; + auto [iv0v1_upper, iv0v1_lower] = new_vertex(v1, iv1, v0, iv0, z, (z - v1.z()) / (v0.z() - v1.z())); + auto [iv2v0_upper, iv2v0_lower] = new_vertex(v2, iv2, v0, iv0, z, (z - v2.z()) / (v0.z() - v2.z())); + + if (v0(2) > z) { + if (upper != nullptr) + upper->indices.emplace_back(iv0, iv0v1_upper, iv2v0_upper); + if (lower != nullptr) { + lower->indices.emplace_back(iv1, iv2, iv0v1_lower); + lower->indices.emplace_back(iv2, iv2v0_lower, iv0v1_lower); + } + } else { + if (upper != nullptr) { + upper->indices.emplace_back(iv1, iv2, iv0v1_upper); + upper->indices.emplace_back(iv2, iv2v0_upper, iv0v1_upper); + } + if (lower != nullptr) + lower->indices.emplace_back(iv0, iv0v1_lower, iv2v0_lower); + } + } + } + + if (upper != nullptr) + triangulate_slice(*upper, upper_lines, upper_slice_vertices, z); + + if (lower != nullptr) + triangulate_slice(*lower, lower_lines, lower_slice_vertices, z); +} + +void TriangleMeshSlicer::cut(float z, TriangleMesh *upper_mesh, TriangleMesh *lower_mesh) const +{ + indexed_triangle_set upper, lower; + this->cut(z, upper_mesh ? &upper : nullptr, lower_mesh ? &lower : nullptr); + if (upper_mesh) + *upper_mesh = TriangleMesh(upper); + if (lower_mesh) + *lower_mesh = TriangleMesh(lower); +} + +} diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp new file mode 100644 index 000000000..258ffa04c --- /dev/null +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -0,0 +1,141 @@ +#ifndef slic3r_TriangleMeshSlicer_hpp_ +#define slic3r_TriangleMeshSlicer_hpp_ + +#include "libslic3r.h" +#include +#include +#include +#include +#include "BoundingBox.hpp" +#include "Line.hpp" +#include "Point.hpp" +#include "Polygon.hpp" +#include "ExPolygon.hpp" + +namespace Slic3r { + +class TriangleMesh; + +enum class SlicingMode : uint32_t { + // Regular slicing, maintain all contours and their orientation. + Regular, + // Maintain all contours, orient all contours CCW, therefore all holes are being closed. + Positive, + // Orient all contours CCW and keep only the contour with the largest area. + // This mode is useful for slicing complex objects in vase mode. + PositiveLargestContour, +}; + +struct MeshSlicingParams +{ + SlicingMode mode { SlicingMode::Regular }; + // For vase mode: below this layer a different slicing mode will be used to produce a single contour. + // 0 = ignore. + size_t slicing_mode_normal_below_layer { 0 }; + // Mode to apply below slicing_mode_normal_below_layer. Ignored if slicing_mode_nromal_below_layer == 0. + SlicingMode mode_below { SlicingMode::Regular }; +}; + +struct MeshSlicingParamsExtended : public MeshSlicingParams +{ + // Morphological closing operation when creating output expolygons. + float closing_radius { 0 }; + // Positive offset applied when creating output expolygons. + float extra_offset { 0 }; + // Resolution for contour simplification. + // 0 = don't simplify. + double resolution { 0 }; + // Transformation of the object owning the ModelVolume. +// Transform3d object_trafo; +}; + +class TriangleMeshSlicer +{ +public: + using throw_on_cancel_callback_type = std::function; + TriangleMeshSlicer() = default; + TriangleMeshSlicer(const TriangleMesh *mesh) { this->init(mesh, []{}); } + TriangleMeshSlicer(const indexed_triangle_set *its) { this->init(its, []{}); } + void init(const TriangleMesh *mesh, throw_on_cancel_callback_type throw_on_cancel); + void init(const indexed_triangle_set *its, throw_on_cancel_callback_type); + + void slice( + const std::vector &z, + const MeshSlicingParams ¶ms, + std::vector *layers, + throw_on_cancel_callback_type throw_on_cancel = []{}) const; + + void slice( + // Where to slice. + const std::vector &z, + const MeshSlicingParamsExtended ¶ms, + std::vector *layers, + throw_on_cancel_callback_type throw_on_cancel = []{}) const; + + void cut(float z, indexed_triangle_set *upper, indexed_triangle_set *lower) const; + void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; + + void set_up_direction(const Vec3f& up); + +private: + const indexed_triangle_set *m_its { nullptr }; +// const TriangleMesh *mesh { nullptr }; + // Map from a facet to an edge index. + std::vector facets_edges; + // Scaled copy of this->mesh->stl.v_shared + std::vector v_scaled_shared; + // Quaternion that will be used to rotate every facet before the slicing + Eigen::Quaternion m_quaternion; + // Whether or not the above quaterion should be used + bool m_use_quaternion = false; +}; + +inline void slice_mesh( + const TriangleMesh &mesh, + const std::vector &z, + std::vector &layers, + TriangleMeshSlicer::throw_on_cancel_callback_type thr = []{}) +{ + if (! mesh.empty()) { + TriangleMeshSlicer slicer(&mesh); + slicer.slice(z, MeshSlicingParams{}, &layers, thr); + } +} + +inline void slice_mesh( + const TriangleMesh &mesh, + const std::vector &z, + const MeshSlicingParamsExtended ¶ms, + std::vector &layers, + TriangleMeshSlicer::throw_on_cancel_callback_type thr = []{}) +{ + if (! mesh.empty()) { + TriangleMeshSlicer slicer(&mesh); + slicer.slice(z, params, &layers, thr); + } +} + +inline void slice_mesh( + const TriangleMesh &mesh, + const std::vector &z, + float closing_radius, + std::vector &layers, + TriangleMeshSlicer::throw_on_cancel_callback_type thr = []{}) +{ + MeshSlicingParamsExtended params; + params.closing_radius = closing_radius; + slice_mesh(mesh, z, params, layers); +} + +inline void slice_mesh( + const TriangleMesh &mesh, + const std::vector &z, + std::vector &layers, + TriangleMeshSlicer::throw_on_cancel_callback_type thr = []{}) +{ + slice_mesh(mesh, z, MeshSlicingParamsExtended{}, layers); +} + +} + +#endif // slic3r_TriangleMeshSlicer_hpp_ diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index dc24ec0ad..12bf6fe02 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include "libslic3r/PrintBase.hpp" diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index f9ccfd0d6..2f575b504 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -2,6 +2,7 @@ #include "libslic3r/Tesselate.hpp" #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/ClipperUtils.hpp" #include "slic3r/GUI/Camera.hpp" @@ -83,16 +84,17 @@ void MeshClipper::recalculate_triangles() // Now do the cutting std::vector list_of_expolys; m_tms->set_up_direction(up.cast()); - m_tms->slice(std::vector{height_mesh}, SlicingMode::Regular, 0.f, &list_of_expolys, [](){}); + m_tms->slice(std::vector{height_mesh}, MeshSlicingParamsExtended{}, &list_of_expolys); if (m_negative_mesh && !m_negative_mesh->empty()) { TriangleMeshSlicer negative_tms{m_negative_mesh}; negative_tms.set_up_direction(up.cast()); std::vector neg_polys; - negative_tms.slice(std::vector{height_mesh}, SlicingMode::Regular, 0.f, &neg_polys, [](){}); + negative_tms.slice(std::vector{height_mesh}, MeshSlicingParamsExtended{}, &neg_polys); list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front()); } + m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.); // Rotate the cut into world coords: diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 09caf199b..8a430e322 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -3,6 +3,7 @@ #include "libslic3r/Point.hpp" #include "libslic3r/Geometry.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/SLA/IndexedMesh.hpp" #include "admesh/stl.h" @@ -12,9 +13,6 @@ namespace Slic3r { -class TriangleMesh; -class TriangleMeshSlicer; - namespace GUI { struct Camera; diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 7933a1d69..dead35267 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "libslic3r/Model.hpp" #include "libslic3r/Print.hpp" diff --git a/tests/fff_print/test_trianglemesh.cpp b/tests/fff_print/test_trianglemesh.cpp index 233b0e515..a066215c2 100644 --- a/tests/fff_print/test_trianglemesh.cpp +++ b/tests/fff_print/test_trianglemesh.cpp @@ -1,6 +1,7 @@ #include #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/Point.hpp" #include "libslic3r/Config.hpp" #include "libslic3r/Model.hpp" diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index 1a4b1fb72..66a0060fe 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -320,7 +321,7 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { bb = mesh.bounding_box(); std::vector layers; - slice_mesh(mesh, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh), layers, 0.f, []{}); + slice_mesh(mesh, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh), layers); sla::RasterBase::Resolution res{2560, 1440}; double disp_w = 120.96; diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 1f98463cc..75748dd34 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -6,6 +6,7 @@ #include "sla_test_utils.hpp" +#include #include #include @@ -48,9 +49,7 @@ TEST_CASE("Support point generator should be deterministic if seeded", sla::SupportPointGenerator::Config autogencfg; autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm); sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}}; - - TriangleMeshSlicer slicer{&mesh}; - + auto bb = mesh.bounding_box(); double zmin = bb.min.z(); double zmax = bb.max.z(); @@ -59,7 +58,7 @@ TEST_CASE("Support point generator should be deterministic if seeded", auto slicegrid = grid(float(gnd), float(zmax), layer_h); std::vector slices; - slicer.slice(slicegrid, SlicingMode::Regular, CLOSING_RADIUS, &slices, []{}); + slice_mesh(mesh, slicegrid, CLOSING_RADIUS, slices); point_gen.seed(0); point_gen.execute(slices, slicegrid); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 1ec890beb..fdf77466b 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -1,4 +1,5 @@ #include "sla_test_utils.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/SLA/AGGRaster.hpp" void test_support_model_collision(const std::string &obj_filename, @@ -94,8 +95,6 @@ void test_supports(const std::string &obj_filename, mesh.require_shared_vertices(); } - TriangleMeshSlicer slicer{&mesh}; - auto bb = mesh.bounding_box(); double zmin = bb.min.z(); double zmax = bb.max.z(); @@ -103,7 +102,7 @@ void test_supports(const std::string &obj_filename, auto layer_h = 0.05f; out.slicegrid = grid(float(gnd), float(zmax), layer_h); - slicer.slice(out.slicegrid, SlicingMode::Regular, CLOSING_RADIUS, &out.model_slices, []{}); + slice_mesh(mesh, out.slicegrid, CLOSING_RADIUS, out.model_slices); sla::cut_drainholes(out.model_slices, out.slicegrid, CLOSING_RADIUS, drainholes, []{}); // Create the special index-triangle mesh with spatial indexing which @@ -470,7 +469,7 @@ sla::SupportPoints calc_support_pts( std::vector slices; auto bb = cast(mesh.bounding_box()); std::vector heights = grid(bb.min.z(), bb.max.z(), 0.1f); - slice_mesh(mesh, heights, slices, CLOSING_RADIUS, [] {}); + slice_mesh(mesh, heights, CLOSING_RADIUS, slices); // Prepare the support point calculator sla::IndexedMesh emesh{mesh}; diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index 230f8b2a5..377bf7b5e 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -3,6 +3,7 @@ %{ #include #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" %} %name{Slic3r::TriangleMesh} class TriangleMesh { @@ -181,8 +182,7 @@ TriangleMesh::slice(z) std::vector z_f = cast(z); std::vector layers; - TriangleMeshSlicer mslicer(THIS); - mslicer.slice(z_f, SlicingMode::Regular, 0.049f, &layers, [](){}); + slice_mesh(*THIS, z_f, 0.049f, layers); AV* layers_av = newAV(); size_t len = layers.size(); From a5d5ceb30dfa0233c847c001c49a439057e3ffed Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 17 May 2021 21:11:29 +0200 Subject: [PATCH 67/75] DoubleSlider: Fixed ruler for sequential print of the object with different heights --- src/slic3r/GUI/DoubleSlider.cpp | 25 ++++++++++++++++++++----- src/slic3r/GUI/DoubleSlider.hpp | 5 ++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index a10acb2e3..0c30b0cb4 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -254,7 +254,7 @@ void Control::SetMaxValue(const int max_value) void Control::SetSliderValues(const std::vector& values) { m_values = values; - m_ruler.count = std::count(m_values.begin(), m_values.end(), m_values.front()); + m_ruler.init(m_values); } void Control::draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos) @@ -1023,8 +1023,23 @@ void Control::draw_colored_band(wxDC& dc) } } +void Control::Ruler::init(const std::vector& values) +{ + max_values.clear(); + max_values.reserve(std::count(values.begin(), values.end(), values.front())); + + auto it = std::find(values.begin() + 1, values.end(), values.front()); + while (it != values.end()) { + max_values.push_back(*(it - 1)); + it = std::find(it + 1, values.end(), values.front()); + } + max_values.push_back(*(it - 1)); +} + void Control::Ruler::update(wxWindow* win, const std::vector& values, double scroll_step) { + if (values.empty()) + return; int DPI = GUI::get_dpi_for_window(win); int pixels_per_sm = lround((double)(DPI) * 5.0/25.4); @@ -1035,7 +1050,7 @@ void Control::Ruler::update(wxWindow* win, const std::vector& values, do int pow = -2; int step = 0; - auto end_it = count == 1 ? values.end() : values.begin() + lround(values.size() / count); + auto end_it = std::find(values.begin() + 1, values.end(), values.front()); while (pow < 3) { for (int istep : {1, 2, 5}) { @@ -1099,7 +1114,7 @@ void Control::draw_ruler(wxDC& dc) double short_tick = std::nan(""); int tick = 0; double value = 0.0; - int sequence = 0; + size_t sequence = 0; int prev_y_pos = -1; wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2; @@ -1107,7 +1122,7 @@ void Control::draw_ruler(wxDC& dc) while (tick <= m_max_value) { value += m_ruler.long_step; - if (value > m_values.back() && sequence < m_ruler.count) { + if (value > m_ruler.max_values[sequence] && sequence < m_ruler.count()) { value = m_ruler.long_step; for (; tick < values_size; tick++) if (m_values[tick] < value) @@ -1140,7 +1155,7 @@ void Control::draw_ruler(wxDC& dc) draw_short_ticks(dc, short_tick, tick); - if (value == m_values.back() && sequence < m_ruler.count) { + if (value == m_ruler.max_values[sequence] && sequence < m_ruler.count()) { value = 0.0; sequence++; tick++; diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 400265885..c1766f83d 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -424,10 +424,13 @@ private: struct Ruler { double long_step; double short_step; - int count { 1 }; // > 1 for sequential print + std::vector max_values;// max value for each object/instance in sequence print + // > 1 for sequential print + void init(const std::vector& values); void update(wxWindow* win, const std::vector& values, double scroll_step); bool is_ok() { return long_step > 0 && short_step > 0; } + size_t count() { return max_values.size(); } } m_ruler; }; From 6c47b1583456588a909180217a6cc343f1e27dfd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 18 May 2021 12:32:37 +0200 Subject: [PATCH 68/75] Follow-up of a5d5ceb30dfa0233c847c001c49a439057e3ffed - Fixed run on Windows --- src/slic3r/GUI/DoubleSlider.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 0c30b0cb4..6d41e3c82 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1084,6 +1084,8 @@ void Control::Ruler::update(wxWindow* win, const std::vector& values, do void Control::draw_ruler(wxDC& dc) { + if (m_values.empty()) + return; m_ruler.update(this->GetParent(), m_values, get_scroll_step()); int height, width; From 1256aebd889222971825faf917ddc20e22b15b56 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 18 May 2021 12:58:14 +0200 Subject: [PATCH 69/75] Fix of some warnings --- src/slic3r/GUI/GUI_ObjectList.cpp | 5 +++-- src/slic3r/GUI/GUI_Preview.cpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 0b6235425..c371da2a7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2327,6 +2327,7 @@ void ObjectList::update_info_items(size_t obj_idx) should_show = printer_technology() == ptFFF && ! model_object->layer_height_profile.empty(); break; + default: break; } if (! shows && should_show) { @@ -2509,8 +2510,8 @@ void ObjectList::select_object_item(bool is_msr_gizmo) { if (wxDataViewItem item = GetSelection()) { ItemType type = m_objects_model->GetItemType(item); - bool is_volume_item = type == itVolume || type == itSettings && m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume; - if (is_msr_gizmo && is_volume_item || type == itObject) + bool is_volume_item = type == itVolume || (type == itSettings && m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume); + if ((is_msr_gizmo && is_volume_item) || type == itObject) return; if (wxDataViewItem obj_item = m_objects_model->GetTopParent(item)) { diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 769ab3f59..c5124cd63 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -675,7 +675,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee if (cur_area != bottom_area && fabs(cur_area - bottom_area) > scale_(scale_(1))) break; } - if (i < size_t(0.3 * num_layers)) + if (i < int(0.3 * num_layers)) continue; // bottom layer have to be a biggest, so control relation between bottom layer and object size diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index e7065cb13..989056bfc 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -545,7 +545,7 @@ void MainFrame::init_tabpanel() m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxBookCtrlEvent& e) { #if ENABLE_VALIDATE_CUSTOM_GCODE if (int old_selection = e.GetOldSelection(); - old_selection != wxNOT_FOUND && old_selection < m_tabpanel->GetPageCount()) { + old_selection != wxNOT_FOUND && old_selection < static_cast(m_tabpanel->GetPageCount())) { Tab* old_tab = dynamic_cast(m_tabpanel->GetPage(old_selection)); if (old_tab) old_tab->validate_custom_gcodes(); From 70b4915f9c6a91fef72410cb912b75e1b886b16e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 18 May 2021 15:05:23 +0200 Subject: [PATCH 70/75] TriangleMeshSlicer: Got rid of admesh! --- src/libslic3r/Model.cpp | 32 +- src/libslic3r/Print.hpp | 14 +- src/libslic3r/PrintObject.cpp | 28 +- src/libslic3r/SLA/Hollowing.cpp | 5 +- src/libslic3r/SLA/Pad.cpp | 4 +- src/libslic3r/SLA/SupportTree.cpp | 6 +- src/libslic3r/SLAPrintSteps.cpp | 6 +- src/libslic3r/TriangleMesh.cpp | 20 +- src/libslic3r/TriangleMesh.hpp | 8 +- src/libslic3r/TriangleMeshSlicer.cpp | 553 +++++++++++++---------- src/libslic3r/TriangleMeshSlicer.hpp | 142 ++---- src/slic3r/GUI/MeshUtils.cpp | 21 +- src/slic3r/GUI/MeshUtils.hpp | 2 - tests/fff_print/test_trianglemesh.cpp | 22 +- tests/libslic3r/test_marchingsquares.cpp | 4 +- tests/sla_print/sla_print_tests.cpp | 4 +- tests/sla_print/sla_test_utils.cpp | 7 +- xs/xsp/TriangleMesh.xsp | 17 +- 18 files changed, 443 insertions(+), 452 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 4ddcbac39..55eb6b995 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1213,32 +1213,32 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b } else if (! volume->mesh().empty()) { - TriangleMesh upper_mesh, lower_mesh; - // Transform the mesh by the combined transformation matrix. // Flip the triangles in case the composite transformation is left handed. TriangleMesh mesh(volume->mesh()); mesh.transform(instance_matrix * volume_matrix, true); volume->reset_mesh(); - - mesh.require_shared_vertices(); - - // Perform cut - TriangleMeshSlicer tms(&mesh); - tms.cut(float(z), &upper_mesh, &lower_mesh); - // Reset volume transformation except for offset const Vec3d offset = volume->get_offset(); volume->set_transformation(Geometry::Transformation()); volume->set_offset(offset); - if (keep_upper) { - upper_mesh.repair(); - upper_mesh.reset_repair_stats(); - } - if (keep_lower) { - lower_mesh.repair(); - lower_mesh.reset_repair_stats(); + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + { + indexed_triangle_set upper_its, lower_its; + mesh.require_shared_vertices(); + cut_mesh(mesh.its, float(z), &upper_its, &lower_its); + if (keep_upper) { + upper_mesh = TriangleMesh(upper_its); + upper_mesh.repair(); + upper_mesh.reset_repair_stats(); + } + if (keep_lower) { + lower_mesh = TriangleMesh(lower_its); + lower_mesh.repair(); + lower_mesh.reset_repair_stats(); + } } if (keep_upper && upper_mesh.facets_count() > 0) { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 3fdc49db8..edd8e19f1 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -8,6 +8,7 @@ #include "Flow.hpp" #include "Point.hpp" #include "Slicing.hpp" +#include "TriangleMeshSlicer.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" #include "GCode/ThumbnailData.hpp" @@ -24,7 +25,6 @@ class Print; class PrintObject; class ModelObject; class GCode; -enum class SlicingMode : uint32_t; class Layer; class SupportLayer; @@ -345,18 +345,18 @@ private: // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; - std::vector slice_region(size_t region_id, const std::vector &z, SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below) const; - std::vector slice_region(size_t region_id, const std::vector &z, SlicingMode mode) const + std::vector slice_region(size_t region_id, const std::vector &z, MeshSlicingParams::SlicingMode mode, size_t slicing_mode_normal_below_layer, MeshSlicingParams::SlicingMode mode_below) const; + std::vector slice_region(size_t region_id, const std::vector &z, MeshSlicingParams::SlicingMode mode) const { return this->slice_region(region_id, z, mode, 0, mode); } std::vector slice_modifiers(size_t region_id, const std::vector &z) const; std::vector slice_volumes( const std::vector &z, - SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below, + MeshSlicingParams::SlicingMode mode, size_t slicing_mode_normal_below_layer, MeshSlicingParams::SlicingMode mode_below, const std::vector &volumes) const; - std::vector slice_volumes(const std::vector &z, SlicingMode mode, const std::vector &volumes) const + std::vector slice_volumes(const std::vector &z, MeshSlicingParams::SlicingMode mode, const std::vector &volumes) const { return this->slice_volumes(z, mode, 0, mode, volumes); } - std::vector slice_volume(const std::vector &z, SlicingMode mode, const ModelVolume &volume) const; - std::vector slice_volume(const std::vector &z, const std::vector &ranges, SlicingMode mode, const ModelVolume &volume) const; + std::vector slice_volume(const std::vector &z, MeshSlicingParams::SlicingMode mode, const ModelVolume &volume) const; + std::vector slice_volume(const std::vector &z, const std::vector &ranges, MeshSlicingParams::SlicingMode mode, const ModelVolume &volume) const; }; struct WipeTowerData diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b77b13345..20c45f3cb 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1799,7 +1799,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) bool clipped = false; bool upscaled = false; bool spiral_vase = this->print()->config().spiral_vase; - auto slicing_mode = spiral_vase ? SlicingMode::PositiveLargestContour : SlicingMode::Regular; + auto slicing_mode = spiral_vase ? MeshSlicingParams::SlicingMode::PositiveLargestContour : MeshSlicingParams::SlicingMode::Regular; if (! has_z_ranges && (! m_config.clip_multipart_objects.value || all_volumes_single_region >= 0)) { // Cheap path: Slice regions without mutual clipping. // The cheap path is possible if no clipping is allowed or if slicing volumes of just a single region. @@ -1815,7 +1815,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) for (; slicing_mode_normal_below_layer < slice_zs.size() && slice_zs[slicing_mode_normal_below_layer] < config.bottom_solid_min_thickness - EPSILON; ++ slicing_mode_normal_below_layer); } - std::vector expolygons_by_layer = this->slice_region(region_id, slice_zs, slicing_mode, slicing_mode_normal_below_layer, SlicingMode::Regular); + std::vector expolygons_by_layer = this->slice_region(region_id, slice_zs, slicing_mode, slicing_mode_normal_below_layer, MeshSlicingParams::SlicingMode::Regular); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " start"; for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) @@ -2060,7 +2060,7 @@ end: } // To be used only if there are no layer span specific configurations applied, which would lead to z ranges being generated for this region. -std::vector PrintObject::slice_region(size_t region_id, const std::vector &z, SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below) const +std::vector PrintObject::slice_region(size_t region_id, const std::vector &z, MeshSlicingParams::SlicingMode mode, size_t slicing_mode_normal_below_layer, MeshSlicingParams::SlicingMode mode_below) const { std::vector volumes; if (region_id < m_region_volumes.size()) { @@ -2120,7 +2120,7 @@ std::vector PrintObject::slice_modifiers(size_t region_id, const std if (volume->is_modifier()) volumes.emplace_back(volume); } - out = this->slice_volumes(slice_zs, SlicingMode::Regular, volumes); + out = this->slice_volumes(slice_zs, MeshSlicingParams::SlicingMode::Regular, volumes); } else { // Some modifier in this region was split to layer spans. std::vector merge; @@ -2138,7 +2138,7 @@ std::vector PrintObject::slice_modifiers(size_t region_id, const std for (; j < volumes_and_ranges.volumes.size() && volume_id == volumes_and_ranges.volumes[j].volume_idx; ++ j) ranges.emplace_back(volumes_and_ranges.volumes[j].layer_height_range); // slicing in parallel - std::vector this_slices = this->slice_volume(slice_zs, ranges, SlicingMode::Regular, *model_volume); + std::vector this_slices = this->slice_volume(slice_zs, ranges, MeshSlicingParams::SlicingMode::Regular, *model_volume); // Variable this_slices could be empty if no value of slice_zs is within any of the ranges of this volume. if (out.empty()) { out = std::move(this_slices); @@ -2179,7 +2179,7 @@ std::vector PrintObject::slice_support_volumes(const ModelVolumeType zs.reserve(this->layers().size()); for (const Layer *l : this->layers()) zs.emplace_back((float)l->slice_z); - return this->slice_volumes(zs, SlicingMode::Regular, volumes); + return this->slice_volumes(zs, MeshSlicingParams::SlicingMode::Regular, volumes); } //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. @@ -2194,7 +2194,7 @@ static void fix_mesh_connectivity(TriangleMesh &mesh) std::vector PrintObject::slice_volumes( const std::vector &z, - SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below, + MeshSlicingParams::SlicingMode mode, size_t slicing_mode_normal_below_layer, MeshSlicingParams::SlicingMode mode_below, const std::vector &volumes) const { std::vector layers; @@ -2219,18 +2219,17 @@ std::vector PrintObject::slice_volumes( mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); // perform actual slicing const Print *print = this->print(); - auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); // TriangleMeshSlicer needs shared vertices, also this calls the repair() function. mesh.require_shared_vertices(); - MeshSlicingParamsExtended params { { mode, slicing_mode_normal_below_layer, mode_below }, float(m_config.slice_closing_radius.value) }; - slice_mesh(mesh, z, params, layers, callback); + MeshSlicingParamsEx params { { mode, slicing_mode_normal_below_layer, mode_below }, float(m_config.slice_closing_radius.value) }; + layers = slice_mesh_ex(mesh.its, z, params, [print]() { print->throw_if_canceled(); }); m_print->throw_if_canceled(); } } return layers; } -std::vector PrintObject::slice_volume(const std::vector &z, SlicingMode mode, const ModelVolume &volume) const +std::vector PrintObject::slice_volume(const std::vector &z, MeshSlicingParams::SlicingMode mode, const ModelVolume &volume) const { std::vector layers; if (! z.empty()) { @@ -2246,13 +2245,12 @@ std::vector PrintObject::slice_volume(const std::vector &z, S mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); // perform actual slicing const Print *print = this->print(); - auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); // TriangleMeshSlicer needs the shared vertices. mesh.require_shared_vertices(); - MeshSlicingParamsExtended params; + MeshSlicingParamsEx params; params.mode = mode; params.closing_radius = float(m_config.slice_closing_radius.value); - slice_mesh(mesh, z, params, layers, callback); + layers = slice_mesh_ex(mesh.its, z, params, [print](){ print->throw_if_canceled(); }); m_print->throw_if_canceled(); } } @@ -2260,7 +2258,7 @@ std::vector PrintObject::slice_volume(const std::vector &z, S } // Filter the zs not inside the ranges. The ranges are closed at the bottom and open at the top, they are sorted lexicographically and non overlapping. -std::vector PrintObject::slice_volume(const std::vector &z, const std::vector &ranges, SlicingMode mode, const ModelVolume &volume) const +std::vector PrintObject::slice_volume(const std::vector &z, const std::vector &ranges, MeshSlicingParams::SlicingMode mode, const ModelVolume &volume) const { std::vector out; if (! z.empty() && ! ranges.empty()) { diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index f55a10178..1d3016bde 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -296,9 +296,8 @@ void cut_drainholes(std::vector & obj_slices, if (mesh.empty()) return; mesh.require_shared_vertices(); - - std::vector hole_slices; - slice_mesh(mesh, slicegrid, closing_radius, hole_slices, thr); + + std::vector hole_slices = slice_mesh_ex(mesh.its, slicegrid, closing_radius, thr); if (obj_slices.size() != hole_slices.size()) BOOST_LOG_TRIVIAL(warning) diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index 658ecf6d8..bf2b4cf54 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -478,8 +478,8 @@ void pad_blueprint(const TriangleMesh & mesh, { if (mesh.empty()) return; - auto out = reserve_vector(heights.size()); - slice_mesh(mesh, heights, out, thrfn); + assert(mesh.has_shared_vertices()); + std::vector out = slice_mesh_ex(mesh.its, heights, thrfn); size_t count = 0; for(auto& o : out) count += o.size(); diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index 45e90a704..14a4dc360 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -45,7 +45,8 @@ std::vector SupportTree::slice( if (!sup_mesh.empty()) { slices.emplace_back(); - slice_mesh(sup_mesh, grid, cr, slices.back(), ctl().cancelfn); + assert(sup_mesh.has_shared_vertices()); + slices.back() = slice_mesh_ex(sup_mesh.its, grid, cr, ctl().cancelfn); } if (!pad_mesh.empty()) { @@ -58,7 +59,8 @@ std::vector SupportTree::slice( auto padgrid = reserve_vector(size_t(cap > 0 ? cap : 0)); std::copy(grid.begin(), maxzit, std::back_inserter(padgrid)); - slice_mesh(pad_mesh, padgrid, cr, slices.back(), ctl().cancelfn); + assert(pad_mesh.has_shared_vertices()); + slices.back() = slice_mesh_ex(pad_mesh.its, padgrid, cr, ctl().cancelfn); } size_t len = grid.size(); diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index d2c3c06d2..46064c55b 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -475,7 +475,8 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) float closing_r = float(po.config().slice_closing_radius.value); auto thr = [this]() { m_print->throw_if_canceled(); }; auto &slice_grid = po.m_model_height_levels; - slice_mesh(mesh, slice_grid, closing_r, po.m_model_slices, thr); + assert(mesh.has_shared_vertices()); + po.m_model_slices = slice_mesh_ex(mesh.its, slice_grid, closing_r, thr); sla::Interior *interior = po.m_hollowing_data ? po.m_hollowing_data->interior.get() : @@ -485,8 +486,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) TriangleMesh interiormesh = sla::get_mesh(*interior); interiormesh.repaired = false; interiormesh.repair(true); - std::vector interior_slices; - slice_mesh(interiormesh, slice_grid, closing_r, interior_slices, thr); + std::vector interior_slices = slice_mesh_ex(interiormesh.its, slice_grid, closing_r, thr); sla::ccr::for_each(size_t(0), interior_slices.size(), [&po, &interior_slices] (size_t i) { diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 9ce2d3a7f..2c35c76a2 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -615,9 +615,8 @@ std::vector TriangleMesh::slice(const std::vector &z) { // convert doubles to floats std::vector z_f(z.begin(), z.end()); - std::vector layers; - slice_mesh(*this, z_f, 0.0004f, layers); - return layers; + assert(this->has_shared_vertices()); + return slice_mesh_ex(this->its, z_f, 0.0004f); } void TriangleMesh::require_shared_vertices() @@ -686,11 +685,12 @@ std::vector> create_vertex_faces_index(const indexed_triangl return index; } -// Map from a facet edge to a neighbor face index or -1 if no neighbor exists. +// Map from a face edge to a unique edge identifier or -1 if no neighbor exists. +// Two neighbor faces share a unique edge identifier even if they are flipped. template -static inline std::vector create_face_neighbors_index_impl(const indexed_triangle_set &its, ThrowOnCancelCallback throw_on_cancel) +static inline std::vector create_face_neighbors_index_impl(const indexed_triangle_set &its, ThrowOnCancelCallback throw_on_cancel) { - std::vector out(its.indices.size() * 3, -1); + std::vector out(its.indices.size(), Vec3i(-1, -1, -1)); // Create a mapping from triangle edge into face. struct EdgeToFace { @@ -754,10 +754,10 @@ static inline std::vector create_face_neighbors_index_impl(const indexed_tr } } // Assign an edge index to the 1st face. - out[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges; + out[edge_i.face](std::abs(edge_i.face_edge) - 1) = num_edges; if (found) { EdgeToFace &edge_j = edges_map[j]; - out[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges; + out[edge_j.face](std::abs(edge_j.face_edge) - 1) = num_edges; // Mark the edge as connected. edge_j.face = -1; } @@ -769,12 +769,12 @@ static inline std::vector create_face_neighbors_index_impl(const indexed_tr return out; } -std::vector create_face_neighbors_index(const indexed_triangle_set &its) +std::vector create_face_neighbors_index(const indexed_triangle_set &its) { return create_face_neighbors_index_impl(its, [](){}); } -std::vector create_face_neighbors_index(const indexed_triangle_set &its, std::function throw_on_cancel_callback) +std::vector create_face_neighbors_index(const indexed_triangle_set &its, std::function throw_on_cancel_callback) { return create_face_neighbors_index_impl(its, throw_on_cancel_callback); } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index dd1a76156..2be822350 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -93,9 +93,11 @@ private: // vertex. std::vector> create_vertex_faces_index(const indexed_triangle_set &its); -// Map from a facet edge to a neighbor face index or -1 if no neighbor exists. -std::vector create_face_neighbors_index(const indexed_triangle_set &its); -std::vector create_face_neighbors_index(const indexed_triangle_set &its, std::function throw_on_cancel_callback); +// Map from a face edge to a unique edge identifier or -1 if no neighbor exists. +// Two neighbor faces share a unique edge identifier even if they are flipped. +// Used for chaining slice lines into polygons. +std::vector create_face_neighbors_index(const indexed_triangle_set &its); +std::vector create_face_neighbors_index(const indexed_triangle_set &its, std::function throw_on_cancel_callback); // Remove degenerate faces, return number of faces removed. int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit = true); // Remove vertices, which none of the faces references. Return number of freed vertices. diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 657a59b62..06061b034 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -85,7 +85,7 @@ public: // feGeneral, feTop, feBottom, feHorizontal FacetEdgeType edge_type { FacetEdgeType::General }; - // Used by TriangleMeshSlicer::slice() to skip duplicate edges. + // Used to skip duplicate edges. enum { // Triangle edge added, because it has no neighbor. EDGE0_NO_NEIGHBOR = 0x001, @@ -112,8 +112,15 @@ enum class FacetSliceType { // Return true, if the facet has been sliced and line_out has been filled. static FacetSliceType slice_facet( - float slice_z, const stl_vertex *vertices, const stl_triangle_vertex_indices &indices, const int *edge_neighbor, - const int idx_vertex_lowest, const bool horizontal, IntersectionLine *line_out) + // Z height of the slice in XY plane. Scaled or unscaled (same as vertices[].z()). + float slice_z, + // 3 vertices of the triangle, XY scaled. Z scaled or unscaled (same as slice_z). + const stl_vertex *vertices, + const stl_triangle_vertex_indices &indices, + const Vec3i &edge_neighbor, + const int idx_vertex_lowest, + const bool horizontal, + IntersectionLine &line_out) { IntersectionPoint points[3]; size_t num_points = 0; @@ -129,7 +136,7 @@ static FacetSliceType slice_facet( { int k = (idx_vertex_lowest + j) % 3; int l = (k + 1) % 3; - edge_id = edge_neighbor[k]; + edge_id = edge_neighbor(k); a_id = indices[k]; a = vertices + k; b_id = indices[l]; @@ -147,7 +154,7 @@ static FacetSliceType slice_facet( FacetSliceType result = FacetSliceType::Slicing; if (horizontal) { // All three vertices are aligned with slice_z. - line_out->edge_type = IntersectionLine::FacetEdgeType::Horizontal; + line_out.edge_type = IntersectionLine::FacetEdgeType::Horizontal; result = FacetSliceType::Cutting; double normal = (v1.x() - v0.x()) * (v2.y() - v1.y()) - (v1.y() - v0.y()) * (v2.x() - v1.x()); if (normal < 0) { @@ -165,19 +172,19 @@ static FacetSliceType slice_facet( // in respect to the cutting plane). result = third_below ? FacetSliceType::Slicing : FacetSliceType::Cutting; if (third_below) { - line_out->edge_type = IntersectionLine::FacetEdgeType::Top; + line_out.edge_type = IntersectionLine::FacetEdgeType::Top; std::swap(a, b); std::swap(a_id, b_id); } else - line_out->edge_type = IntersectionLine::FacetEdgeType::Bottom; + line_out.edge_type = IntersectionLine::FacetEdgeType::Bottom; } - line_out->a.x() = a->x(); - line_out->a.y() = a->y(); - line_out->b.x() = b->x(); - line_out->b.y() = b->y(); - line_out->a_id = a_id; - line_out->b_id = b_id; - assert(line_out->a != line_out->b); + line_out.a.x() = a->x(); + line_out.a.y() = a->y(); + line_out.b.x() = b->x(); + line_out.b.y() = b->y(); + line_out.a_id = a_id; + line_out.b_id = b_id; + assert(line_out.a != line_out.b); return result; } @@ -235,31 +242,31 @@ static FacetSliceType slice_facet( // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only. assert(num_points < 3); if (num_points == 2) { - line_out->edge_type = IntersectionLine::FacetEdgeType::General; - line_out->a = (Point)points[1]; - line_out->b = (Point)points[0]; - line_out->a_id = points[1].point_id; - line_out->b_id = points[0].point_id; - line_out->edge_a_id = points[1].edge_id; - line_out->edge_b_id = points[0].edge_id; + line_out.edge_type = IntersectionLine::FacetEdgeType::General; + line_out.a = static_cast(points[1]); + line_out.b = static_cast(points[0]); + line_out.a_id = points[1].point_id; + line_out.b_id = points[0].point_id; + line_out.edge_a_id = points[1].edge_id; + line_out.edge_b_id = points[0].edge_id; // Not a zero lenght edge. //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. - //assert(line_out->a != line_out->b); + //assert(line_out.a != line_out.b); // The plane cuts at least one edge in a general position. - assert(line_out->a_id == -1 || line_out->b_id == -1); - assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1); + assert(line_out.a_id == -1 || line_out.b_id == -1); + assert(line_out.edge_a_id != -1 || line_out.edge_b_id != -1); // General slicing position, use the segment for both slicing and object cutting. #if 0 - if (line_out->a_id != -1 && line_out->b_id != -1) { + if (line_out.a_id != -1 && line_out.b_id != -1) { // Solving a degenerate case, where both the intersections snapped to an edge. // Correctly classify the face as below or above based on the position of the 3rd point. int i = indices[0]; - if (i == line_out->a_id || i == line_out->b_id) + if (i == line_out.a_id || i == line_out.b_id) i = indices[1]; - if (i == line_out->a_id || i == line_out->b_id) + if (i == line_out.a_id || i == line_out.b_id) i = indices[2]; - assert(i != line_out->a_id && i != line_out->b_id); - line_out->edge_type = ((m_use_quaternion ? + assert(i != line_out.a_id && i != line_out.b_id); + line_out.edge_type = ((m_use_quaternion ? (m_quaternion * this->v_scaled_shared[i]).z() : this->v_scaled_shared[i].z()) < slice_z) ? IntersectionLine::FacetEdgeType::Top : IntersectionLine::FacetEdgeType::Bottom; } @@ -269,40 +276,64 @@ static FacetSliceType slice_facet( return FacetSliceType::NoSlice; } -static void slice_facet_at_zs( +template +void slice_facet_at_zs( + // Scaled or unscaled vertices. transform_vertex_fn may scale zs. + const std::vector &mesh_vertices, + const TransformVertex &transform_vertex_fn, const stl_triangle_vertex_indices &indices, - const std::vector &v_scaled_shared, - const int *facet_neighbors, - const Eigen::Quaternion *quaternion, - std::vector *lines, - boost::mutex *lines_mutex, - const std::vector &scaled_zs) + const Vec3i &facet_neighbors, + // Scaled or unscaled zs. If vertices have their zs scaled or transform_vertex_fn scales them, then zs have to be scaled as well. + const std::vector &zs, + std::vector &lines, + boost::mutex &lines_mutex) { - stl_vertex vertices[3] { v_scaled_shared[indices(0)], v_scaled_shared[indices(1)], v_scaled_shared[indices(2)] }; - if (quaternion) - for (int i = 0; i < 3; ++ i) - vertices[i] = *quaternion * vertices[i]; + stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) }; // find facet extents const float min_z = fminf(vertices[0].z(), fminf(vertices[1].z(), vertices[2].z())); const float max_z = fmaxf(vertices[0].z(), fmaxf(vertices[1].z(), vertices[2].z())); // find layer extents - auto min_layer = std::lower_bound(scaled_zs.begin(), scaled_zs.end(), min_z); // first layer whose slice_z is >= min_z - auto max_layer = std::upper_bound(min_layer, scaled_zs.end(), max_z); // first layer whose slice_z is > max_z + auto min_layer = std::lower_bound(zs.begin(), zs.end(), min_z); // first layer whose slice_z is >= min_z + auto max_layer = std::upper_bound(min_layer, zs.end(), max_z); // first layer whose slice_z is > max_z for (auto it = min_layer; it != max_layer; ++ it) { IntersectionLine il; int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); - if (slice_facet(*it, vertices, indices, facet_neighbors, idx_vertex_lowest, min_z == max_z, &il) == FacetSliceType::Slicing && + if (slice_facet(*it, vertices, indices, facet_neighbors, idx_vertex_lowest, min_z == max_z, il) == FacetSliceType::Slicing && il.edge_type != IntersectionLine::FacetEdgeType::Horizontal) { // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume. - boost::lock_guard l(*lines_mutex); - (*lines)[it - scaled_zs.begin()].emplace_back(il); + boost::lock_guard l(lines_mutex); + lines[it - zs.begin()].emplace_back(il); } } } +template +inline std::vector slice_make_lines( + const std::vector &vertices, + const TransformVertex &transform_vertex_fn, + const std::vector &indices, + const std::vector &face_neighbors, + const std::vector &zs, + const ThrowOnCancel throw_on_cancel_fn) +{ + std::vector lines(zs.size(), IntersectionLines()); + boost::mutex lines_mutex; + tbb::parallel_for( + tbb::blocked_range(0, int(indices.size())), + [&vertices, &transform_vertex_fn, &indices, &face_neighbors, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range &range) { + for (int face_idx = range.begin(); face_idx < range.end(); ++ face_idx) { + if ((face_idx & 0x0ffff) == 0) + throw_on_cancel_fn(); + slice_facet_at_zs(vertices, transform_vertex_fn, indices[face_idx], face_neighbors[face_idx], zs, lines, lines_mutex); + } + } + ); + return lines; +} + #if 0 //FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing // and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces. @@ -384,9 +415,9 @@ struct OpenPolyline { bool consumed; }; -// called by TriangleMeshSlicer::make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity. +// called by make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity. // Only connects segments crossing triangles of the same orientation. -static void chain_lines_by_triangle_connectivity(std::vector &lines, Polygons &loops, std::vector &open_polylines) +static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polygons &loops, std::vector &open_polylines) { // Build a map of lines by edge_a_id and a_id. std::vector by_edge_a_id; @@ -501,7 +532,7 @@ std::vector open_polylines_sorted(std::vector &open return out; } -// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices. +// called by make_loops() to connect remaining open polylines across shared triangle edges and vertices. // Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. static void chain_open_polylines_exact(std::vector &open_polylines, Polygons &loops, bool try_connect_reversed) { @@ -599,7 +630,7 @@ static void chain_open_polylines_exact(std::vector &open_polylines } } -// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices, +// called by make_loops() to connect remaining open polylines across shared triangle edges and vertices, // possibly closing small gaps. // Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. static void chain_open_polylines_close_gaps(std::vector &open_polylines, Polygons &loops, double max_gap, bool try_connect_reversed) @@ -707,8 +738,11 @@ static void chain_open_polylines_close_gaps(std::vector &open_poly } } -static void make_loops(std::vector &lines, Polygons* loops) +static Polygons make_loops( + // Lines will have their flags modified. + IntersectionLines &lines) { + Polygons loops; #if 0 //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. //#ifdef _DEBUG @@ -736,7 +770,7 @@ static void make_loops(std::vector &lines, Polygons* loops) #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ std::vector open_polylines; - chain_lines_by_triangle_connectivity(lines, *loops, open_polylines); + chain_lines_by_triangle_connectivity(lines, loops, open_polylines); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -752,8 +786,8 @@ static void make_loops(std::vector &lines, Polygons* loops) // Now process the open polylines. // Do it in two rounds, first try to connect in the same direction only, // then try to connect the open polylines in reversed order as well. - chain_open_polylines_exact(open_polylines, *loops, false); - chain_open_polylines_exact(open_polylines, *loops, true); + chain_open_polylines_exact(open_polylines, loops, false); + chain_open_polylines_exact(open_polylines, loops, true); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -781,8 +815,8 @@ static void make_loops(std::vector &lines, Polygons* loops) } #else const double max_gap = 2.; //mm - chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); - chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); + chain_open_polylines_close_gaps(open_polylines, loops, max_gap, false); + chain_open_polylines_close_gaps(open_polylines, loops, max_gap, true); #endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -800,6 +834,60 @@ static void make_loops(std::vector &lines, Polygons* loops) svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + return loops; +} + +template +static std::vector make_loops( + // Lines will have their flags modified. + std::vector &lines, + const MeshSlicingParams ¶ms, + ThrowOnCancel throw_on_cancel) +{ + std::vector layers; + layers.resize(lines.size()); + tbb::parallel_for( + tbb::blocked_range(0, lines.size()), + [&lines, &layers, ¶ms, throw_on_cancel](const tbb::blocked_range &range) { + for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx) { + if ((line_idx & 0x0ffff) == 0) + throw_on_cancel(); + + Polygons &polygons = layers[line_idx]; + polygons = make_loops(lines[line_idx]); + + auto this_mode = line_idx < params.slicing_mode_normal_below_layer ? params.mode_below : params.mode; + if (! polygons.empty()) { + if (this_mode == MeshSlicingParams::SlicingMode::Positive) { + // Reorient all loops to be CCW. + for (Polygon& p : polygons) + p.make_counter_clockwise(); + } + else if (this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour) { + // Keep just the largest polygon, make it CCW. + double max_area = 0.; + Polygon* max_area_polygon = nullptr; + for (Polygon& p : polygons) { + double a = p.area(); + if (std::abs(a) > std::abs(max_area)) { + max_area = a; + max_area_polygon = &p; + } + } + assert(max_area_polygon != nullptr); + if (max_area < 0.) + max_area_polygon->reverse(); + Polygon p(std::move(*max_area_polygon)); + polygons.clear(); + polygons.emplace_back(std::move(p)); + } + } + } + } + ); + + return layers; } // Used to cut the mesh into two halves. @@ -808,15 +896,11 @@ static ExPolygons make_expolygons_simple(std::vector &lines) ExPolygons slices; Polygons holes; - { - Polygons loops; - make_loops(lines, &loops); - for (Polygon &loop : loops) - if (loop.area() >= 0.) - slices.emplace_back(std::move(loop)); - else - holes.emplace_back(std::move(loop)); - } + for (Polygon &loop : make_loops(lines)) + if (loop.area() >= 0.) + slices.emplace_back(std::move(loop)); + else + holes.emplace_back(std::move(loop)); // If there are holes, then there should also be outer contours. assert(holes.empty() || ! slices.empty()); @@ -961,45 +1045,13 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c union_ex(loops)); } -/* -static void make_expolygons(std::vector &lines, const float closing_radius, ExPolygons* slices) -{ - Polygons pp; - make_loops(lines, &pp); - Slic3r::make_expolygons(pp, closing_radius, 0.f, slices); -} -*/ - -void TriangleMeshSlicer::init(const TriangleMesh *mesh, throw_on_cancel_callback_type throw_on_cancel) -{ - if (! mesh->has_shared_vertices()) - throw Slic3r::InvalidArgument("TriangleMeshSlicer was passed a mesh without shared vertices."); - - this->init(&mesh->its, throw_on_cancel); -} - -void TriangleMeshSlicer::init(const indexed_triangle_set *its, throw_on_cancel_callback_type throw_on_cancel) -{ - m_its = its; - facets_edges = create_face_neighbors_index(*its, throw_on_cancel); - v_scaled_shared.assign(its->vertices.size(), stl_vertex()); - for (size_t i = 0; i < v_scaled_shared.size(); ++ i) - this->v_scaled_shared[i] = its->vertices[i] / float(SCALING_FACTOR); -} - -void TriangleMeshSlicer::set_up_direction(const Vec3f& up) -{ - m_quaternion.setFromTwoVectors(up, Vec3f::UnitZ()); - m_use_quaternion = true; -} - -void TriangleMeshSlicer::slice( - const std::vector &z, +std::vector slice_mesh( + const indexed_triangle_set &mesh, + const std::vector &zs, const MeshSlicingParams ¶ms, - std::vector *layers, - throw_on_cancel_callback_type throw_on_cancel) const + std::function throw_on_cancel) { - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice"; + BOOST_LOG_TRIVIAL(debug) << "slice_mesh to polygons"; /* This method gets called with a list of unscaled Z coordinates and outputs @@ -1027,72 +1079,28 @@ void TriangleMeshSlicer::slice( NOTE: this method accepts a vector of floats because the mesh coordinate type is float. */ + + std::vector lines; - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice_facet_at_zs"; - std::vector lines(z.size()); { - std::vector scaled_z(z); - for (float &z : scaled_z) + std::vector scaled_zs(zs); + for (float &z : scaled_zs) z = scaled(z); - boost::mutex lines_mutex; - tbb::parallel_for( - tbb::blocked_range(0, int(m_its->indices.size())), - [&lines, &lines_mutex, &scaled_z, throw_on_cancel, this](const tbb::blocked_range& range) { - const Eigen::Quaternion *rotation = m_use_quaternion ? &m_quaternion : nullptr; - for (int facet_idx = range.begin(); facet_idx < range.end(); ++ facet_idx) { - if ((facet_idx & 0x0ffff) == 0) - throw_on_cancel(); - slice_facet_at_zs(m_its->indices[facet_idx], this->v_scaled_shared, this->facets_edges.data() + facet_idx * 3, rotation, &lines, &lines_mutex, scaled_z); - } - } - ); + + std::vector v_scaled_shared(mesh.vertices); + for (stl_vertex &v : v_scaled_shared) + v *= float(1. / SCALING_FACTOR); + + std::vector facets_edges = create_face_neighbors_index(mesh); + lines = params.trafo.matrix() == Transform3f::Identity().matrix() ? + slice_make_lines(v_scaled_shared, [](const Vec3f &p) { return p; }, mesh.indices, facets_edges, scaled_zs, throw_on_cancel) : + slice_make_lines(v_scaled_shared, [¶ms](const Vec3f &p) { return params.trafo * p; }, mesh.indices, facets_edges, scaled_zs, throw_on_cancel); + throw_on_cancel(); } - throw_on_cancel(); // v_scaled_shared could be freed here - - // build loops - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::_make_loops_do"; - layers->resize(z.size()); - tbb::parallel_for( - tbb::blocked_range(0, z.size()), - [&lines, &layers, ¶ms, throw_on_cancel](const tbb::blocked_range& range) { - for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx) { - if ((line_idx & 0x0ffff) == 0) - throw_on_cancel(); - Polygons &polygons = (*layers)[line_idx]; - make_loops(lines[line_idx], &polygons); - - auto this_mode = line_idx < params.slicing_mode_normal_below_layer ? params.mode_below : params.mode; - if (! polygons.empty()) { - if (this_mode == SlicingMode::Positive) { - // Reorient all loops to be CCW. - for (Polygon& p : polygons) - p.make_counter_clockwise(); - } else if (this_mode == SlicingMode::PositiveLargestContour) { - // Keep just the largest polygon, make it CCW. - double max_area = 0.; - Polygon* max_area_polygon = nullptr; - for (Polygon& p : polygons) { - double a = p.area(); - if (std::abs(a) > std::abs(max_area)) { - max_area = a; - max_area_polygon = &p; - } - } - assert(max_area_polygon != nullptr); - if (max_area < 0.) - max_area_polygon->reverse(); - Polygon p(std::move(*max_area_polygon)); - polygons.clear(); - polygons.emplace_back(std::move(p)); - } - } - } - } - ); - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice finished"; + std::vector layers = make_loops(lines, params, throw_on_cancel); #ifdef SLIC3R_DEBUG { @@ -1126,65 +1134,104 @@ void TriangleMeshSlicer::slice( ++ iRun; } #endif + + return layers; } -void TriangleMeshSlicer::slice( - // Where to slice. - const std::vector &z, - const MeshSlicingParamsExtended ¶ms, - std::vector *layers, - throw_on_cancel_callback_type throw_on_cancel) const +std::vector slice_mesh_ex( + const indexed_triangle_set &mesh, + const std::vector &zs, + const MeshSlicingParamsEx ¶ms, + std::function throw_on_cancel) { std::vector layers_p; { MeshSlicingParams slicing_params(params); - if (params.mode == SlicingMode::PositiveLargestContour) - slicing_params.mode = SlicingMode::Positive; - if (params.mode_below == SlicingMode::PositiveLargestContour) - slicing_params.mode_below = SlicingMode::Positive; - this->slice(z, slicing_params, &layers_p, throw_on_cancel); + if (params.mode == MeshSlicingParams::SlicingMode::PositiveLargestContour) + slicing_params.mode = MeshSlicingParams::SlicingMode::Positive; + if (params.mode_below == MeshSlicingParams::SlicingMode::PositiveLargestContour) + slicing_params.mode_below = MeshSlicingParams::SlicingMode::Positive; + layers_p = slice_mesh(mesh, zs, slicing_params, throw_on_cancel); } - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start"; - layers->resize(z.size()); + BOOST_LOG_TRIVIAL(debug) << "slice_mesh make_expolygons in parallel - start"; + std::vector layers(layers_p.size(), ExPolygons{}); tbb::parallel_for( - tbb::blocked_range(0, z.size()), - [&layers_p, ¶ms, layers, throw_on_cancel] + tbb::blocked_range(0, layers_p.size()), + [&layers_p, ¶ms, &layers, throw_on_cancel] (const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { #ifdef SLIC3R_TRIANGLEMESH_DEBUG printf("Layer %zu (slice_z = %.2f):\n", layer_id, z[layer_id]); #endif throw_on_cancel(); - ExPolygons &expolygons = (*layers)[layer_id]; + ExPolygons &expolygons = layers[layer_id]; Slic3r::make_expolygons(layers_p[layer_id], params.closing_radius, params.extra_offset, &expolygons); //FIXME simplify const auto this_mode = layer_id < params.slicing_mode_normal_below_layer ? params.mode_below : params.mode; - if (this_mode == SlicingMode::PositiveLargestContour) + if (this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour) keep_largest_contour_only(expolygons); } }); - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "slice_mesh make_expolygons in parallel - end"; + + return layers; } -static void triangulate_slice(indexed_triangle_set &its, IntersectionLines &lines, const std::vector &slice_vertices, float z) +static void triangulate_slice( + indexed_triangle_set &its, + IntersectionLines &lines, + std::vector &slice_vertices, + // Vertices of the original (unsliced) mesh. Newly added vertices are those on the slice. + int num_original_vertices, + // Z height of the slice. + float z) { -// BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating the cut"; + sort_remove_duplicates(slice_vertices); - ExPolygons section = make_expolygons_simple(lines); - Pointf3s triangles = triangulate_expolygons_3d(section, z, true); + // 1) Create map of the slice vertices from positions to mesh indices. + // As the caller will likely add duplicate points when intersecting triangle edges, there will be duplicates. std::vector> map_vertex_to_index; map_vertex_to_index.reserve(slice_vertices.size()); for (int i : slice_vertices) map_vertex_to_index.emplace_back(to_2d(its.vertices[i]), i); std::sort(map_vertex_to_index.begin(), map_vertex_to_index.end(), [](const std::pair &l, const std::pair &r) { - return l.first.x() < r.first.x() || (l.first.x() == r.first.x() && l.first.y() < r.first.y()); }); + return l.first.x() < r.first.x() || + (l.first.x() == r.first.x() && (l.first.y() < r.first.y() || + (l.first.y() == r.first.y() && l.second < r.second))); }); + + // 2) Discover duplicate points on the slice. Remap duplicate vertices to a vertex with a lowest index. + { + std::vector map_duplicate_vertex(int(its.vertices.size()) - num_original_vertices, -1); + int i = 0; + for (; i < int(map_vertex_to_index.size()); ++ i) { + const Vec2f &ipos = map_vertex_to_index[i].first; + const int iidx = map_vertex_to_index[i].second; + if (iidx >= num_original_vertices) + // map to itself + map_duplicate_vertex[iidx - num_original_vertices] = iidx; + int j = i; + for (++ j; j < int(map_vertex_to_index.size()) && ipos.x() == map_vertex_to_index[j].first.x() && ipos.y() == map_vertex_to_index[j].first.y(); ++ j) { + const int jidx = map_vertex_to_index[j].second; + assert(jidx >= num_original_vertices); + if (jidx >= num_original_vertices) + // map to the first vertex + map_duplicate_vertex[jidx - num_original_vertices] = iidx; + } + } + for (stl_triangle_vertex_indices &f : its.indices) + for (i = 0; i < 3; ++ i) + if (f(i) >= num_original_vertices) + f(i) = map_duplicate_vertex[f(i) - num_original_vertices]; + } + size_t idx_vertex_new_first = its.vertices.size(); + Pointf3s triangles = triangulate_expolygons_3d(make_expolygons_simple(lines), z, true); for (size_t i = 0; i < triangles.size(); ) { stl_triangle_vertex_indices facet; for (size_t j = 0; j < 3; ++ j) { - Vec3f v = triangles[i++].cast(); + Vec3f v = triangles[i ++].cast(); auto it = lower_bound_by_predicate(map_vertex_to_index.begin(), map_vertex_to_index.end(), [&v](const std::pair &l) { return l.first.x() < v.x() || (l.first.x() == v.x() && l.first.y() < v.y()); }); int idx = -1; @@ -1204,48 +1251,64 @@ static void triangulate_slice(indexed_triangle_set &its, IntersectionLines &line } facet(j) = idx; } - its.indices.emplace_back(facet); + if (facet(0) != facet(1) && facet(0) != facet(2) && facet(1) != facet(2)) + its.indices.emplace_back(facet); } + // Remove vertices, which are not referenced by any face. its_compactify_vertices(its); - its_remove_degenerate_faces(its); + + // Degenerate faces should not be created. + // its_remove_degenerate_faces(its); } -void TriangleMeshSlicer::cut(float z, indexed_triangle_set *upper, indexed_triangle_set *lower) const +void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *upper, indexed_triangle_set *lower) { assert(upper || lower); if (upper == nullptr && lower == nullptr) return; + BOOST_LOG_TRIVIAL(trace) << "cut_mesh - slicing object"; + if (upper) { upper->clear(); - upper->vertices = m_its->vertices; - upper->indices.reserve(m_its->indices.size()); + upper->vertices = mesh.vertices; + upper->indices.reserve(mesh.indices.size()); } + if (lower) { lower->clear(); - lower->vertices = m_its->vertices; - lower->indices.reserve(m_its->indices.size()); + lower->vertices = mesh.vertices; + lower->indices.reserve(mesh.indices.size()); } - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object"; - const auto scaled_z = scaled(z); - // To triangulate the caps after slicing. - IntersectionLines upper_lines, lower_lines; - std::vector upper_slice_vertices, lower_slice_vertices; + IntersectionLines upper_lines, lower_lines; + std::vector upper_slice_vertices, lower_slice_vertices; + std::vector facets_edges = create_face_neighbors_index(mesh); - for (int facet_idx = 0; facet_idx < int(m_its->indices.size()); ++ facet_idx) { - const stl_triangle_vertex_indices &facet = m_its->indices[facet_idx]; - Vec3f vertices[3] { m_its->vertices[facet(0)], m_its->vertices[facet(1)], m_its->vertices[facet(2)] }; - stl_vertex vertices_scaled[3]{ this->v_scaled_shared[facet[0]], this->v_scaled_shared[facet[1]], this->v_scaled_shared[facet[2]] }; + for (int facet_idx = 0; facet_idx < int(mesh.indices.size()); ++ facet_idx) { + const stl_triangle_vertex_indices &facet = mesh.indices[facet_idx]; + Vec3f vertices[3] { mesh.vertices[facet(0)], mesh.vertices[facet(1)], mesh.vertices[facet(2)] }; float min_z = std::min(vertices[0].z(), std::min(vertices[1].z(), vertices[2].z())); float max_z = std::max(vertices[0].z(), std::max(vertices[1].z(), vertices[2].z())); // intersect facet with cutting plane IntersectionLine line; int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); - FacetSliceType slice_type = slice_facet(scaled_z, vertices_scaled, m_its->indices[facet_idx], this->facets_edges.data() + facet_idx * 3, idx_vertex_lowest, min_z == max_z, &line); + FacetSliceType slice_type = FacetSliceType::NoSlice; + if (z > min_z - EPSILON && z < max_z + EPSILON) { + Vec3f vertices_scaled[3]; + for (int i = 0; i < 3; ++ i) { + const Vec3f &src = vertices[i]; + Vec3f &dst = vertices_scaled[i]; + dst.x() = scale_(src.x()); + dst.y() = scale_(src.y()); + dst.z() = src.z(); + } + slice_type = slice_facet(z, vertices_scaled, mesh.indices[facet_idx], facets_edges[facet_idx], idx_vertex_lowest, min_z == max_z, line); + } + if (slice_type != FacetSliceType::NoSlice) { // Save intersection lines for generating correct triangulations. if (line.edge_type == IntersectionLine::FacetEdgeType::Top) { @@ -1274,6 +1337,8 @@ void TriangleMeshSlicer::cut(float z, indexed_triangle_set *upper, indexed_trian // Facet is cut by the slicing plane. assert(slice_type == FacetSliceType::Slicing); assert(line.edge_type == IntersectionLine::FacetEdgeType::General); + assert(line.edge_a_id != -1); + assert(line.edge_b_id != -1); // look for the vertex on whose side of the slicing plane there are no other vertices int isolated_vertex = @@ -1282,6 +1347,15 @@ void TriangleMeshSlicer::cut(float z, indexed_triangle_set *upper, indexed_trian // get vertices starting from the isolated one int iv = isolated_vertex; + stl_vertex v0v1, v2v0; + assert(facets_edges[facet_idx](iv) == line.edge_a_id ||facets_edges[facet_idx](iv) == line.edge_b_id); + if (facets_edges[facet_idx](iv) == line.edge_a_id) { + v0v1 = to_3d(unscaled(line.a), z); + v2v0 = to_3d(unscaled(line.b), z); + } else { + v0v1 = to_3d(unscaled(line.b), z); + v2v0 = to_3d(unscaled(line.a), z); + } const stl_vertex &v0 = vertices[iv]; const int iv0 = facet[iv]; if (++ iv == 3) @@ -1294,70 +1368,51 @@ void TriangleMeshSlicer::cut(float z, indexed_triangle_set *upper, indexed_trian const int iv2 = facet[iv]; // intersect v0-v1 and v2-v0 with cutting plane and make new vertices - auto new_vertex = [upper, lower, &upper_slice_vertices, &lower_slice_vertices](const Vec3f &a, const int ia, const Vec3f &b, const int ib, float z, float t) { + auto new_vertex = [upper, lower, &upper_slice_vertices, &lower_slice_vertices](const Vec3f &a, const int ia, const Vec3f &b, const int ib, const Vec3f &c) { int iupper, ilower; - if (t <= 0.f) + if (c == a) iupper = ilower = ia; - else if (t >= 1.f) + else if (c == b) iupper = ilower = ib; else { - const stl_vertex c = Vec3f(lerp(a.x(), b.x(), t), lerp(a.y(), b.y(), t), z); - if (c == a) - iupper = ilower = ia; - else if (c == b) - iupper = ilower = ib; - else { - // Insert a new vertex into upper / lower. - if (upper) { - iupper = int(upper->vertices.size()); - upper->vertices.emplace_back(c); - upper_slice_vertices.emplace_back(iupper); - } - if (lower) { - ilower = int(lower->vertices.size()); - lower->vertices.emplace_back(c); - lower_slice_vertices.emplace_back(ilower); - } + // Insert a new vertex into upper / lower. + if (upper) { + iupper = int(upper->vertices.size()); + upper->vertices.emplace_back(c); + upper_slice_vertices.emplace_back(iupper); + } + if (lower) { + ilower = int(lower->vertices.size()); + lower->vertices.emplace_back(c); + lower_slice_vertices.emplace_back(ilower); } } return std::make_pair(iupper, ilower); }; - auto [iv0v1_upper, iv0v1_lower] = new_vertex(v1, iv1, v0, iv0, z, (z - v1.z()) / (v0.z() - v1.z())); - auto [iv2v0_upper, iv2v0_lower] = new_vertex(v2, iv2, v0, iv0, z, (z - v2.z()) / (v0.z() - v2.z())); - - if (v0(2) > z) { - if (upper != nullptr) - upper->indices.emplace_back(iv0, iv0v1_upper, iv2v0_upper); - if (lower != nullptr) { - lower->indices.emplace_back(iv1, iv2, iv0v1_lower); - lower->indices.emplace_back(iv2, iv2v0_lower, iv0v1_lower); - } + auto [iv0v1_upper, iv0v1_lower] = new_vertex(v1, iv1, v0, iv0, v0v1); + auto [iv2v0_upper, iv2v0_lower] = new_vertex(v2, iv2, v0, iv0, v2v0); + auto new_face = [](indexed_triangle_set *its, int i, int j, int k) { + if (its != nullptr && i != j && i != k && j != k) + its->indices.emplace_back(i, j, k); + }; + + if (v0.z() > z) { + new_face(upper, iv0, iv0v1_upper, iv2v0_upper); + new_face(lower, iv1, iv2, iv0v1_lower); + new_face(lower, iv2, iv2v0_lower, iv0v1_lower); } else { - if (upper != nullptr) { - upper->indices.emplace_back(iv1, iv2, iv0v1_upper); - upper->indices.emplace_back(iv2, iv2v0_upper, iv0v1_upper); - } - if (lower != nullptr) - lower->indices.emplace_back(iv0, iv0v1_lower, iv2v0_lower); + new_face(upper, iv1, iv2, iv0v1_upper); + new_face(upper, iv2, iv2v0_upper, iv0v1_upper); + new_face(lower, iv0, iv0v1_lower, iv2v0_lower); } } } if (upper != nullptr) - triangulate_slice(*upper, upper_lines, upper_slice_vertices, z); + triangulate_slice(*upper, upper_lines, upper_slice_vertices, int(mesh.vertices.size()), z); if (lower != nullptr) - triangulate_slice(*lower, lower_lines, lower_slice_vertices, z); -} - -void TriangleMeshSlicer::cut(float z, TriangleMesh *upper_mesh, TriangleMesh *lower_mesh) const -{ - indexed_triangle_set upper, lower; - this->cut(z, upper_mesh ? &upper : nullptr, lower_mesh ? &lower : nullptr); - if (upper_mesh) - *upper_mesh = TriangleMesh(upper); - if (lower_mesh) - *lower_mesh = TriangleMesh(lower); + triangulate_slice(*lower, lower_lines, lower_slice_vertices, int(mesh.vertices.size()), z); } } diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp index 258ffa04c..b4914f88b 100644 --- a/src/libslic3r/TriangleMeshSlicer.hpp +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -1,42 +1,36 @@ #ifndef slic3r_TriangleMeshSlicer_hpp_ #define slic3r_TriangleMeshSlicer_hpp_ -#include "libslic3r.h" -#include #include #include -#include -#include "BoundingBox.hpp" -#include "Line.hpp" -#include "Point.hpp" #include "Polygon.hpp" #include "ExPolygon.hpp" namespace Slic3r { -class TriangleMesh; - -enum class SlicingMode : uint32_t { - // Regular slicing, maintain all contours and their orientation. - Regular, - // Maintain all contours, orient all contours CCW, therefore all holes are being closed. - Positive, - // Orient all contours CCW and keep only the contour with the largest area. - // This mode is useful for slicing complex objects in vase mode. - PositiveLargestContour, -}; - struct MeshSlicingParams { + enum class SlicingMode : uint32_t { + // Regular slicing, maintain all contours and their orientation. + Regular, + // Maintain all contours, orient all contours CCW, therefore all holes are being closed. + Positive, + // Orient all contours CCW and keep only the contour with the largest area. + // This mode is useful for slicing complex objects in vase mode. + PositiveLargestContour, + }; + SlicingMode mode { SlicingMode::Regular }; // For vase mode: below this layer a different slicing mode will be used to produce a single contour. // 0 = ignore. size_t slicing_mode_normal_below_layer { 0 }; // Mode to apply below slicing_mode_normal_below_layer. Ignored if slicing_mode_nromal_below_layer == 0. SlicingMode mode_below { SlicingMode::Regular }; + // Transforming faces during the slicing. + Transform3f trafo { Transform3f::Identity() }; }; -struct MeshSlicingParamsExtended : public MeshSlicingParams +struct MeshSlicingParamsEx : public MeshSlicingParams { // Morphological closing operation when creating output expolygons. float closing_radius { 0 }; @@ -45,96 +39,44 @@ struct MeshSlicingParamsExtended : public MeshSlicingParams // Resolution for contour simplification. // 0 = don't simplify. double resolution { 0 }; - // Transformation of the object owning the ModelVolume. -// Transform3d object_trafo; }; -class TriangleMeshSlicer +std::vector slice_mesh( + const indexed_triangle_set &mesh, + const std::vector &zs, + const MeshSlicingParams ¶ms, + std::function throw_on_cancel = []{}); + +std::vector slice_mesh_ex( + const indexed_triangle_set &mesh, + const std::vector &zs, + const MeshSlicingParamsEx ¶ms, + std::function throw_on_cancel = []{}); + +inline std::vector slice_mesh_ex( + const indexed_triangle_set &mesh, + const std::vector &zs, + std::function throw_on_cancel = []{}) { -public: - using throw_on_cancel_callback_type = std::function; - TriangleMeshSlicer() = default; - TriangleMeshSlicer(const TriangleMesh *mesh) { this->init(mesh, []{}); } - TriangleMeshSlicer(const indexed_triangle_set *its) { this->init(its, []{}); } - void init(const TriangleMesh *mesh, throw_on_cancel_callback_type throw_on_cancel); - void init(const indexed_triangle_set *its, throw_on_cancel_callback_type); - - void slice( - const std::vector &z, - const MeshSlicingParams ¶ms, - std::vector *layers, - throw_on_cancel_callback_type throw_on_cancel = []{}) const; - - void slice( - // Where to slice. - const std::vector &z, - const MeshSlicingParamsExtended ¶ms, - std::vector *layers, - throw_on_cancel_callback_type throw_on_cancel = []{}) const; - - void cut(float z, indexed_triangle_set *upper, indexed_triangle_set *lower) const; - void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; - - void set_up_direction(const Vec3f& up); - -private: - const indexed_triangle_set *m_its { nullptr }; -// const TriangleMesh *mesh { nullptr }; - // Map from a facet to an edge index. - std::vector facets_edges; - // Scaled copy of this->mesh->stl.v_shared - std::vector v_scaled_shared; - // Quaternion that will be used to rotate every facet before the slicing - Eigen::Quaternion m_quaternion; - // Whether or not the above quaterion should be used - bool m_use_quaternion = false; -}; - -inline void slice_mesh( - const TriangleMesh &mesh, - const std::vector &z, - std::vector &layers, - TriangleMeshSlicer::throw_on_cancel_callback_type thr = []{}) -{ - if (! mesh.empty()) { - TriangleMeshSlicer slicer(&mesh); - slicer.slice(z, MeshSlicingParams{}, &layers, thr); - } + return slice_mesh_ex(mesh, zs, MeshSlicingParamsEx{}, throw_on_cancel); } -inline void slice_mesh( - const TriangleMesh &mesh, - const std::vector &z, - const MeshSlicingParamsExtended ¶ms, - std::vector &layers, - TriangleMeshSlicer::throw_on_cancel_callback_type thr = []{}) +inline std::vector slice_mesh_ex( + const indexed_triangle_set &mesh, + const std::vector &zs, + float closing_radius, + std::function throw_on_cancel = []{}) { - if (! mesh.empty()) { - TriangleMeshSlicer slicer(&mesh); - slicer.slice(z, params, &layers, thr); - } -} - -inline void slice_mesh( - const TriangleMesh &mesh, - const std::vector &z, - float closing_radius, - std::vector &layers, - TriangleMeshSlicer::throw_on_cancel_callback_type thr = []{}) -{ - MeshSlicingParamsExtended params; + MeshSlicingParamsEx params; params.closing_radius = closing_radius; - slice_mesh(mesh, z, params, layers); + return slice_mesh_ex(mesh, zs, params, throw_on_cancel); } -inline void slice_mesh( - const TriangleMesh &mesh, - const std::vector &z, - std::vector &layers, - TriangleMeshSlicer::throw_on_cancel_callback_type thr = []{}) -{ - slice_mesh(mesh, z, MeshSlicingParamsExtended{}, layers); -} +void cut_mesh( + const indexed_triangle_set &mesh, + float z, + indexed_triangle_set *upper, + indexed_triangle_set *lower); } diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 2f575b504..eab46ec2f 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -29,7 +29,6 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh) m_mesh = &mesh; m_triangles_valid = false; m_triangles2d.resize(0); - m_tms.reset(nullptr); } } @@ -68,11 +67,6 @@ void MeshClipper::render_cut() void MeshClipper::recalculate_triangles() { - if (! m_tms) { - m_tms.reset(new TriangleMeshSlicer); - m_tms->init(m_mesh, [](){}); - } - const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast(); const Vec3f& scaling = m_trafo.get_scaling_factor().cast(); // Calculate clipping plane normal in mesh coordinates. @@ -82,16 +76,15 @@ void MeshClipper::recalculate_triangles() float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm()); // Now do the cutting - std::vector list_of_expolys; - m_tms->set_up_direction(up.cast()); - m_tms->slice(std::vector{height_mesh}, MeshSlicingParamsExtended{}, &list_of_expolys); + MeshSlicingParamsEx slicing_params; + slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ()).cast()); + + assert(m_mesh->has_shared_vertices()); + std::vector list_of_expolys = slice_mesh_ex(m_mesh->its, std::vector{height_mesh}, slicing_params); if (m_negative_mesh && !m_negative_mesh->empty()) { - TriangleMeshSlicer negative_tms{m_negative_mesh}; - negative_tms.set_up_direction(up.cast()); - - std::vector neg_polys; - negative_tms.slice(std::vector{height_mesh}, MeshSlicingParamsExtended{}, &neg_polys); + assert(m_negative_mesh->has_shared_vertices()); + std::vector neg_polys = slice_mesh_ex(m_negative_mesh->its, std::vector{height_mesh}, slicing_params); list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front()); } diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 8a430e322..07b01e27f 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -3,7 +3,6 @@ #include "libslic3r/Point.hpp" #include "libslic3r/Geometry.hpp" -#include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/SLA/IndexedMesh.hpp" #include "admesh/stl.h" @@ -96,7 +95,6 @@ private: std::vector m_triangles2d; GLIndexedVertexArray m_vertex_array; bool m_triangles_valid = false; - std::unique_ptr m_tms; }; diff --git a/tests/fff_print/test_trianglemesh.cpp b/tests/fff_print/test_trianglemesh.cpp index a066215c2..fa6237c8b 100644 --- a/tests/fff_print/test_trianglemesh.cpp +++ b/tests/fff_print/test_trianglemesh.cpp @@ -356,27 +356,25 @@ SCENARIO( "TriangleMeshSlicer: Cut behavior.") { TriangleMesh cube(vertices, facets); cube.repair(); WHEN( "Object is cut at the bottom") { - TriangleMesh upper {}; - TriangleMesh lower {}; - TriangleMeshSlicer slicer(&cube); - slicer.cut(0, &upper, &lower); + indexed_triangle_set upper {}; + indexed_triangle_set lower {}; + cut_mesh(cube.its, 0, &upper, &lower); THEN("Upper mesh has all facets except those belonging to the slicing plane.") { - REQUIRE(upper.facets_count() == 12); + REQUIRE(upper.indices.size() == 12); } THEN("Lower mesh has no facets.") { - REQUIRE(lower.facets_count() == 0); + REQUIRE(lower.indices.size() == 0); } } WHEN( "Object is cut at the center") { - TriangleMesh upper {}; - TriangleMesh lower {}; - TriangleMeshSlicer slicer(&cube); - slicer.cut(10, &upper, &lower); + indexed_triangle_set upper {}; + indexed_triangle_set lower {}; + cut_mesh(cube.its, 10, &upper, &lower); THEN("Upper mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") { - REQUIRE(upper.facets_count() == 2+12+6); + REQUIRE(upper.indices.size() == 2+12+6); } THEN("Lower mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") { - REQUIRE(lower.facets_count() == 2+12+6); + REQUIRE(lower.indices.size() == 2+12+6); } } } diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index 66a0060fe..14481f14a 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -320,8 +320,8 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { mesh.translate(tr.x(), tr.y(), tr.z()); bb = mesh.bounding_box(); - std::vector layers; - slice_mesh(mesh, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh), layers); + assert(mesh.has_shared_vertices()); + std::vector layers = slice_mesh_ex(mesh.its, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh)); sla::RasterBase::Resolution res{2560, 1440}; double disp_w = 120.96; diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 75748dd34..54e5ea732 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -57,8 +57,8 @@ TEST_CASE("Support point generator should be deterministic if seeded", auto layer_h = 0.05f; auto slicegrid = grid(float(gnd), float(zmax), layer_h); - std::vector slices; - slice_mesh(mesh, slicegrid, CLOSING_RADIUS, slices); + assert(mesh.has_shared_vertices()); + std::vector slices = slice_mesh_ex(mesh.its, slicegrid, CLOSING_RADIUS); point_gen.seed(0); point_gen.execute(slices, slicegrid); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index fdf77466b..bf94c170c 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -102,7 +102,8 @@ void test_supports(const std::string &obj_filename, auto layer_h = 0.05f; out.slicegrid = grid(float(gnd), float(zmax), layer_h); - slice_mesh(mesh, out.slicegrid, CLOSING_RADIUS, out.model_slices); + assert(mesh.has_shared_vertices()); + out.model_slices = slice_mesh_ex(mesh.its, out.slicegrid, CLOSING_RADIUS); sla::cut_drainholes(out.model_slices, out.slicegrid, CLOSING_RADIUS, drainholes, []{}); // Create the special index-triangle mesh with spatial indexing which @@ -466,10 +467,10 @@ sla::SupportPoints calc_support_pts( const sla::SupportPointGenerator::Config &cfg) { // Prepare the slice grid and the slices - std::vector slices; auto bb = cast(mesh.bounding_box()); std::vector heights = grid(bb.min.z(), bb.max.z(), 0.1f); - slice_mesh(mesh, heights, CLOSING_RADIUS, slices); + assert(mesh.has_shared_vertices()); + std::vector slices = slice_mesh_ex(mesh.its, heights, CLOSING_RADIUS); // Prepare the support point calculator sla::IndexedMesh emesh{mesh}; diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index 377bf7b5e..2b07c78ee 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -181,8 +181,7 @@ TriangleMesh::slice(z) // convert doubles to floats std::vector z_f = cast(z); - std::vector layers; - slice_mesh(*THIS, z_f, 0.049f, layers); + std::vector layers = slice_mesh_ex(THIS->its, z_f, 0.049f); AV* layers_av = newAV(); size_t len = layers.size(); @@ -202,14 +201,18 @@ TriangleMesh::slice(z) RETVAL void -TriangleMesh::cut(z, upper, lower) +TriangleMesh::cut(z, upper_mesh, lower_mesh) float z; - TriangleMesh* upper; - TriangleMesh* lower; + TriangleMesh* upper_mesh; + TriangleMesh* lower_mesh; CODE: THIS->require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer mslicer(THIS); - mslicer.cut(z, upper, lower); + indexed_triangle_set upper, lower; + cut_mesh(THIS->its, z, upper_mesh ? &upper : nullptr, lower_mesh ? &lower : nullptr); + if (upper_mesh) + *upper_mesh = TriangleMesh(upper); + if (lower_mesh) + *lower_mesh = TriangleMesh(lower); std::vector TriangleMesh::bb3() From 78c01995239e1763e28e563adbd3bcaf68c1564d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 18 May 2021 16:12:49 +0200 Subject: [PATCH 71/75] TriangleMeshSlicer: Optimized out unnecessary transformations. --- src/libslic3r/TriangleMeshSlicer.cpp | 88 +++++++++++++--------------- src/libslic3r/TriangleMeshSlicer.hpp | 2 +- src/slic3r/GUI/MeshUtils.cpp | 2 +- 3 files changed, 43 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 06061b034..e7a85e3c5 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -1047,58 +1047,55 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c std::vector slice_mesh( const indexed_triangle_set &mesh, + // Unscaled Zs const std::vector &zs, const MeshSlicingParams ¶ms, std::function throw_on_cancel) { BOOST_LOG_TRIVIAL(debug) << "slice_mesh to polygons"; - - /* - This method gets called with a list of unscaled Z coordinates and outputs - a vector pointer having the same number of items as the original list. - Each item is a vector of polygons created by slicing our mesh at the - given heights. - This method should basically combine the behavior of the existing - Perl methods defined in lib/Slic3r/TriangleMesh.pm: - - - analyze(): this creates the 'facets_edges' and the 'edges_facets' - tables (we don't need the 'edges' table) - - - slice_facet(): this has to be done for each facet. It generates - intersection lines with each plane identified by the Z list. - The get_layer_range() binary search used to identify the Z range - of the facet is already ported to C++ (see Object.xsp) - - - make_loops(): this has to be done for each layer. It creates polygons - from the lines generated by the previous step. - - At the end, we free the tables generated by analyze() as we don't - need them anymore. - - NOTE: this method accepts a vector of floats because the mesh coordinate - type is float. - */ - std::vector lines; - + { - std::vector scaled_zs(zs); - for (float &z : scaled_zs) - z = scaled(z); - - std::vector v_scaled_shared(mesh.vertices); - for (stl_vertex &v : v_scaled_shared) - v *= float(1. / SCALING_FACTOR); - - std::vector facets_edges = create_face_neighbors_index(mesh); - lines = params.trafo.matrix() == Transform3f::Identity().matrix() ? - slice_make_lines(v_scaled_shared, [](const Vec3f &p) { return p; }, mesh.indices, facets_edges, scaled_zs, throw_on_cancel) : - slice_make_lines(v_scaled_shared, [¶ms](const Vec3f &p) { return params.trafo * p; }, mesh.indices, facets_edges, scaled_zs, throw_on_cancel); - throw_on_cancel(); + std::vector facets_edges = create_face_neighbors_index(mesh); + const bool identity = params.trafo.matrix() == Transform3d::Identity().matrix(); + static constexpr const double s = 1. / SCALING_FACTOR; + if (zs.size() <= 1) { + // It likely is not worthwile to copy the vertices. Apply the transformation in place. + if (identity) + lines = slice_make_lines( + mesh.vertices, [](const Vec3f &p) { return Vec3f(scaled(p.x()), scaled(p.y()), p.z()); }, + mesh.indices, facets_edges, zs, throw_on_cancel); + else { + // Transform the vertices, scale up in XY, not in Y. + auto t = params.trafo; + t.prescale(Vec3d(s, s, 1.)); + auto tf = t.cast(); + slice_make_lines(mesh.vertices, [tf](const Vec3f &p) { return tf * p; }, mesh.indices, facets_edges, zs, throw_on_cancel); + } + } else { + // Copy and scale vertices in XY, don't scale in Z. + // Possibly apply the transformation. + std::vector vertices(mesh.vertices); + if (identity) { + for (stl_vertex &v : vertices) { + // Scale just XY, leave Z unscaled. + v.x() *= float(s); + v.y() *= float(s); + } + } else { + // Transform the vertices, scale up in XY, not in Y. + auto t = params.trafo; + t.prescale(Vec3d(s, s, 1.)); + auto tf = t.cast(); + for (stl_vertex &v : vertices) + v = tf * v; + } + lines = slice_make_lines(vertices, [](const Vec3f &p) { return p; }, mesh.indices, facets_edges, zs, throw_on_cancel); + } } - // v_scaled_shared could be freed here + throw_on_cancel(); std::vector layers = make_loops(lines, params, throw_on_cancel); @@ -1154,16 +1151,13 @@ std::vector slice_mesh_ex( layers_p = slice_mesh(mesh, zs, slicing_params, throw_on_cancel); } - BOOST_LOG_TRIVIAL(debug) << "slice_mesh make_expolygons in parallel - start"; +// BOOST_LOG_TRIVIAL(debug) << "slice_mesh make_expolygons in parallel - start"; std::vector layers(layers_p.size(), ExPolygons{}); tbb::parallel_for( tbb::blocked_range(0, layers_p.size()), [&layers_p, ¶ms, &layers, throw_on_cancel] (const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { -#ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("Layer %zu (slice_z = %.2f):\n", layer_id, z[layer_id]); -#endif throw_on_cancel(); ExPolygons &expolygons = layers[layer_id]; Slic3r::make_expolygons(layers_p[layer_id], params.closing_radius, params.extra_offset, &expolygons); @@ -1173,7 +1167,7 @@ std::vector slice_mesh_ex( keep_largest_contour_only(expolygons); } }); - BOOST_LOG_TRIVIAL(debug) << "slice_mesh make_expolygons in parallel - end"; +// BOOST_LOG_TRIVIAL(debug) << "slice_mesh make_expolygons in parallel - end"; return layers; } diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp index b4914f88b..56ad3d9bf 100644 --- a/src/libslic3r/TriangleMeshSlicer.hpp +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -27,7 +27,7 @@ struct MeshSlicingParams // Mode to apply below slicing_mode_normal_below_layer. Ignored if slicing_mode_nromal_below_layer == 0. SlicingMode mode_below { SlicingMode::Regular }; // Transforming faces during the slicing. - Transform3f trafo { Transform3f::Identity() }; + Transform3d trafo { Transform3d::Identity() }; }; struct MeshSlicingParamsEx : public MeshSlicingParams diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index eab46ec2f..c65d2b5a9 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -77,7 +77,7 @@ void MeshClipper::recalculate_triangles() // Now do the cutting MeshSlicingParamsEx slicing_params; - slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ()).cast()); + slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); assert(m_mesh->has_shared_vertices()); std::vector list_of_expolys = slice_mesh_ex(m_mesh->its, std::vector{height_mesh}, slicing_params); From 3481898d4dcb07b4d30785ba93e1f3f28ac8ff97 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 18 May 2021 16:15:54 +0200 Subject: [PATCH 72/75] Fixed missing include --- tests/sla_print/sla_test_utils.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index bf94c170c..8a78c63b3 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -2,6 +2,8 @@ #include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/SLA/AGGRaster.hpp" +#include + void test_support_model_collision(const std::string &obj_filename, const sla::SupportTreeConfig &input_supportcfg, const sla::HollowingConfig &hollowingcfg, From dfc6d399f74e42fce946f5a6abd486b9126857ee Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 18 May 2021 16:58:05 +0200 Subject: [PATCH 73/75] Fixed update of the ObjectManipulation when "autocenter" is on --- src/slic3r/GUI/Plater.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f7fbb6395..e306641a9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2044,6 +2044,9 @@ void Plater::priv::update(unsigned int flags) this->restart_background_process(update_status); else this->schedule_background_process(); + + if (get_config("autocenter") == "1" && this->sidebar->obj_manipul()->IsShown()) + this->sidebar->obj_manipul()->UpdateAndShow(true); } void Plater::priv::select_view(const std::string& direction) From c28cd957d554546cf5f31a6145b4134b5dc43c91 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 18 May 2021 17:57:35 +0200 Subject: [PATCH 74/75] New utility function its_merge_vertices(). Implemented contour simplification inside slice_mesh_ex(). --- src/libslic3r/TriangleMesh.cpp | 63 ++++++++++++++++++++++++++++ src/libslic3r/TriangleMesh.hpp | 9 ++++ src/libslic3r/TriangleMeshSlicer.cpp | 10 +++++ src/libslic3r/TriangleMeshSlicer.hpp | 2 +- 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 2c35c76a2..4a1039b56 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -779,6 +779,69 @@ std::vector create_face_neighbors_index(const indexed_triangle_set &its, return create_face_neighbors_index_impl(its, throw_on_cancel_callback); } +// Merge duplicate vertices, return number of vertices removed. +int its_merge_vertices(indexed_triangle_set &its, bool shrink_to_fit) +{ + // 1) Sort indices to vertices lexicographically by coordinates AND vertex index. + auto sorted = reserve_vector(its.vertices.size()); + for (int i = 0; i < int(its.vertices.size()); ++ i) + sorted.emplace_back(i); + std::sort(sorted.begin(), sorted.end(), [&its](int il, int ir) { + const Vec3f &l = its.vertices[il]; + const Vec3f &r = its.vertices[ir]; + // Sort lexicographically by coordinates AND vertex index. + return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && (l.z() < r.z() || (l.z() == r.z() && il < ir))))); + }); + + // 2) Map duplicate vertices to the one with the lowest vertex index. + // The vertex to stay will have a map_vertices[...] == -1 index assigned, the other vertices will point to it. + std::vector map_vertices(its.vertices.size(), -1); + for (int i = 0; i < int(sorted.size());) { + const int u = sorted[i]; + const Vec3f &p = its.vertices[u]; + int j = i; + for (++ j; j < int(sorted.size()); ++ j) { + const int v = sorted[j]; + const Vec3f &q = its.vertices[v]; + if (p != q) + break; + assert(v > u); + map_vertices[v] = u; + } + i = j; + } + + // 3) Shrink its.vertices, update map_vertices with the new vertex indices. + int k = 0; + for (int i = 0; i < int(its.vertices.size()); ++ i) { + if (map_vertices[i] == -1) { + map_vertices[i] = k; + if (k < i) + its.vertices[k] = its.vertices[i]; + ++ k; + } else { + assert(map_vertices[i] < i); + map_vertices[i] = map_vertices[map_vertices[i]]; + } + } + + int num_erased = int(its.vertices.size()) - k; + + if (num_erased) { + // Shrink the vertices. + its.vertices.erase(its.vertices.begin() + k, its.vertices.end()); + // Remap face indices. + for (stl_triangle_vertex_indices &face : its.indices) + for (int i = 0; i < 3; ++ i) + face(i) = map_vertices[face(i)]; + // Optionally shrink to fit (reallocate) vertices. + if (shrink_to_fit) + its.vertices.shrink_to_fit(); + } + + return num_erased; +} + int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit) { int last = 0; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 2be822350..5d3c15ec1 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -98,10 +98,19 @@ std::vector> create_vertex_faces_index(const indexed_triangl // Used for chaining slice lines into polygons. std::vector create_face_neighbors_index(const indexed_triangle_set &its); std::vector create_face_neighbors_index(const indexed_triangle_set &its, std::function throw_on_cancel_callback); + +// Merge duplicate vertices, return number of vertices removed. +// This function will happily create non-manifolds if more than two faces share the same vertex position +// or more than two faces share the same edge position! +int its_merge_vertices(indexed_triangle_set &its, bool shrink_to_fit = true); + // Remove degenerate faces, return number of faces removed. int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit = true); + // Remove vertices, which none of the faces references. Return number of freed vertices. int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit = true); + +// Shrink the vectors of its.vertices and its.faces to a minimum size by reallocating the two vectors. void its_shrink_to_fit(indexed_triangle_set &its); TriangleMesh make_cube(double x, double y, double z); diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index e7a85e3c5..7d0c2516c 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -1057,6 +1057,10 @@ std::vector slice_mesh( std::vector lines; { + //FIXME facets_edges is likely not needed and quite costly to calculate. + // Instead of edge identifiers, one shall use a sorted pair of edge vertex indices. + // However facets_edges assigns a single edge ID to two triangles only, thus when factoring facets_edges out, one will have + // to make sure that no code relies on it. std::vector facets_edges = create_face_neighbors_index(mesh); const bool identity = params.trafo.matrix() == Transform3d::Identity().matrix(); static constexpr const double s = 1. / SCALING_FACTOR; @@ -1165,6 +1169,9 @@ std::vector slice_mesh_ex( const auto this_mode = layer_id < params.slicing_mode_normal_below_layer ? params.mode_below : params.mode; if (this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour) keep_largest_contour_only(expolygons); + if (params.resolution != 0.) + for (ExPolygon &ex : expolygons) + ex.simplify(params.resolution); } }); // BOOST_LOG_TRIVIAL(debug) << "slice_mesh make_expolygons in parallel - end"; @@ -1199,7 +1206,9 @@ static void triangulate_slice( { std::vector map_duplicate_vertex(int(its.vertices.size()) - num_original_vertices, -1); int i = 0; + int k = 0; for (; i < int(map_vertex_to_index.size()); ++ i) { + map_vertex_to_index[k ++] = map_vertex_to_index[i]; const Vec2f &ipos = map_vertex_to_index[i].first; const int iidx = map_vertex_to_index[i].second; if (iidx >= num_original_vertices) @@ -1214,6 +1223,7 @@ static void triangulate_slice( map_duplicate_vertex[jidx - num_original_vertices] = iidx; } } + map_vertex_to_index.erase(map_vertex_to_index.begin() + k, map_vertex_to_index.end()); for (stl_triangle_vertex_indices &f : its.indices) for (i = 0; i < 3; ++ i) if (f(i) >= num_original_vertices) diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp index 56ad3d9bf..4ae972d00 100644 --- a/src/libslic3r/TriangleMeshSlicer.hpp +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -36,7 +36,7 @@ struct MeshSlicingParamsEx : public MeshSlicingParams float closing_radius { 0 }; // Positive offset applied when creating output expolygons. float extra_offset { 0 }; - // Resolution for contour simplification. + // Resolution for contour simplification, scaled! // 0 = don't simplify. double resolution { 0 }; }; From 4a134f5320cf8f30cc4c0522016c3fc0801d2c8a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 19 May 2021 08:39:04 +0200 Subject: [PATCH 75/75] Follow-up of c37d18f046bfe5d868968815235e75b17e5c6f04 -> Removed assert --- src/libslic3r/Model.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 55eb6b995..4b6b838be 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1936,7 +1936,9 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const Polygon p = get_object()->convex_hull_2d(trafo_instance); +#if !ENABLE_ALLOW_NEGATIVE_Z assert(!p.points.empty()); +#endif // !ENABLE_ALLOW_NEGATIVE_Z // if (!p.points.empty()) { // Polygons pp{p};