From 886faac74ebe6978b828f51be62d26176e2900e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Oct 2024 18:01:12 +0200 Subject: [PATCH 01/10] SPE-2486: Add a new gizmo for fuzzy skin painting. --- resources/icons/fuzzy_skin_painting.svg | 6 + resources/icons/fuzzy_skin_painting_.svg | 6 + src/libslic3r/Format/3mf.cpp | 18 +- src/libslic3r/Model.cpp | 20 ++ src/libslic3r/Model.hpp | 28 +- src/libslic3r/PrintApply.cpp | 5 +- src/libslic3r/TriangleSelector.hpp | 2 + src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GLCanvas3D.cpp | 23 +- src/slic3r/GUI/GUI_ObjectList.cpp | 37 ++- src/slic3r/GUI/GUI_ObjectList.hpp | 1 - src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp | 311 ++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp | 47 +++ .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 3 +- src/slic3r/GUI/Gizmos/GLGizmos.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 16 +- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 3 +- src/slic3r/GUI/KBShortcutsDialog.cpp | 1 + src/slic3r/GUI/NotificationManager.cpp | 3 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 13 +- src/slic3r/GUI/ObjectDataViewModel.hpp | 3 +- src/slic3r/GUI/Plater.cpp | 13 +- 23 files changed, 513 insertions(+), 51 deletions(-) create mode 100644 resources/icons/fuzzy_skin_painting.svg create mode 100644 resources/icons/fuzzy_skin_painting_.svg create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp diff --git a/resources/icons/fuzzy_skin_painting.svg b/resources/icons/fuzzy_skin_painting.svg new file mode 100644 index 0000000000..ebbfc6d870 --- /dev/null +++ b/resources/icons/fuzzy_skin_painting.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/resources/icons/fuzzy_skin_painting_.svg b/resources/icons/fuzzy_skin_painting_.svg new file mode 100644 index 0000000000..bb956b6c8b --- /dev/null +++ b/resources/icons/fuzzy_skin_painting_.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 6cf688b3fb..3dc98acf8a 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -133,6 +133,7 @@ static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; static constexpr const char* MM_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation"; +static constexpr const char* FUZZY_SKIN_ATTR = "slic3rpe:fuzzy_skin"; static constexpr const char* KEY_ATTR = "key"; static constexpr const char* VALUE_ATTR = "value"; @@ -374,6 +375,7 @@ namespace Slic3r { std::vector custom_supports; std::vector custom_seam; std::vector mm_segmentation; + std::vector fuzzy_skin; bool empty() { return vertices.empty() || triangles.empty(); } @@ -383,6 +385,7 @@ namespace Slic3r { custom_supports.clear(); custom_seam.clear(); mm_segmentation.clear(); + fuzzy_skin.clear(); } }; @@ -2075,6 +2078,7 @@ namespace Slic3r { m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); + m_curr_object.geometry.fuzzy_skin.push_back(get_attribute_value_string(attributes, num_attributes, FUZZY_SKIN_ATTR)); // Now load MM segmentation data. Unfortunately, BambuStudio has changed the attribute name after they forked us, // leading to https://github.com/prusa3d/PrusaSlicer/issues/12502. Let's try to load both keys if the usual @@ -2579,10 +2583,11 @@ namespace Slic3r { if (has_transform) volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); - // recreate custom supports, seam and mm segmentation from previously loaded attribute + // recreate custom supports, seam, mm segmentation and fuzzy skin from previously loaded attribute volume->supported_facets.reserve(triangles_count); volume->seam_facets.reserve(triangles_count); volume->mm_segmentation_facets.reserve(triangles_count); + volume->fuzzy_skin_facets.reserve(triangles_count); for (size_t i=0; isupported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); volume->mm_segmentation_facets.set_triangle_from_string(i, geometry.mm_segmentation[index]); + volume->fuzzy_skin_facets.set_triangle_from_string(i, geometry.fuzzy_skin[index]); } volume->supported_facets.shrink_to_fit(); volume->seam_facets.shrink_to_fit(); volume->mm_segmentation_facets.shrink_to_fit(); + volume->fuzzy_skin_facets.shrink_to_fit(); if (auto &es = volume_data.shape_configuration; es.has_value()) volume->emboss_shape = std::move(es); @@ -3278,6 +3285,15 @@ namespace Slic3r { output_buffer += "\""; } + std::string fuzzy_skin_data_string = volume->fuzzy_skin_facets.get_triangle_as_string(i); + if (!fuzzy_skin_data_string.empty()) { + output_buffer += " "; + output_buffer += FUZZY_SKIN_ATTR; + output_buffer += "=\""; + output_buffer += fuzzy_skin_data_string; + output_buffer += "\""; + } + output_buffer += "/>\n"; if (! flush()) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 6d0a416275..3fdfee91be 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -648,6 +648,11 @@ bool Model::is_mm_painted() const return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_mm_painted(); }); } +bool Model::is_fuzzy_skin_painted() const +{ + return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_fuzzy_skin_painted(); }); +} + ModelObject::~ModelObject() { this->clear_volumes(); @@ -831,6 +836,11 @@ bool ModelObject::is_mm_painted() const return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); } +bool ModelObject::is_fuzzy_skin_painted() const +{ + return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_fuzzy_skin_painted(); }); +} + bool ModelObject::is_text() const { return this->volumes.size() == 1 && this->volumes[0]->is_text(); @@ -1248,6 +1258,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con vol->supported_facets.assign(volume->supported_facets); vol->seam_facets.assign(volume->seam_facets); vol->mm_segmentation_facets.assign(volume->mm_segmentation_facets); + vol->fuzzy_skin_facets.assign(volume->fuzzy_skin_facets); // Perform conversion only if the target "imperial" state is different from the current one. // This check supports conversion of "mixed" set of volumes, each with different "imperial" state. @@ -1360,6 +1371,7 @@ void ModelVolume::reset_extra_facets() this->supported_facets.reset(); this->seam_facets.reset(); this->mm_segmentation_facets.reset(); + this->fuzzy_skin_facets.reset(); } @@ -1926,6 +1938,7 @@ void ModelVolume::assign_new_unique_ids_recursive() supported_facets.set_new_unique_id(); seam_facets.set_new_unique_id(); mm_segmentation_facets.set_new_unique_id(); + fuzzy_skin_facets.set_new_unique_id(); } void ModelVolume::rotate(double angle, Axis axis) @@ -2273,6 +2286,13 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mm_segmentation_facets.timestamp_matches(mv_new.mm_segmentation_facets); }); } +bool model_fuzzy_skin_data_changed(const ModelObject &mo, const ModelObject &mo_new) +{ + return model_property_changed(mo, mo_new, + [](const ModelVolumeType t) { return t == ModelVolumeType::MODEL_PART; }, + [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.fuzzy_skin_facets.timestamp_matches(mv_new.fuzzy_skin_facets); }); +} + bool model_has_parameter_modifiers_in_objects(const Model &model) { for (const auto& model_object : model.objects) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 820617f6a9..95dc96a36f 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -393,6 +393,8 @@ public: bool is_seam_painted() const; // Checks if any of object volume is painted using the multi-material painting gizmo. bool is_mm_painted() const; + // Checks if any of object volume is painted using the fuzzy skin painting gizmo. + bool is_fuzzy_skin_painted() const; // Checks if object contains just one volume and it's a text bool is_text() const; // This object may have a varying layer height by painting or by a table. @@ -802,6 +804,9 @@ public: // List of mesh facets painted for MM segmentation. FacetsAnnotation mm_segmentation_facets; + // List of mesh facets painted for fuzzy skin. + FacetsAnnotation fuzzy_skin_facets; + // Is set only when volume is Embossed Text type // Contain information how to re-create volume std::optional text_configuration; @@ -906,11 +911,13 @@ public: this->supported_facets.set_new_unique_id(); this->seam_facets.set_new_unique_id(); this->mm_segmentation_facets.set_new_unique_id(); + this->fuzzy_skin_facets.set_new_unique_id(); } bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } bool is_seam_painted() const { return !this->seam_facets.empty(); } bool is_mm_painted() const { return !this->mm_segmentation_facets.empty(); } + bool is_fuzzy_skin_painted() const { return !this->fuzzy_skin_facets.empty(); } // Returns 0-based indices of extruders painted by multi-material painting gizmo. std::vector get_extruders_from_multi_material_painting() const; @@ -958,10 +965,12 @@ private: assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); assert(this->mm_segmentation_facets.id().valid()); + assert(this->fuzzy_skin_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); assert(this->id() != this->mm_segmentation_facets.id()); + assert(this->id() != this->fuzzy_skin_facets.id()); return true; } @@ -988,13 +997,14 @@ private: name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), supported_facets(other.supported_facets), seam_facets(other.seam_facets), mm_segmentation_facets(other.mm_segmentation_facets), - cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape) + fuzzy_skin_facets(other.fuzzy_skin_facets), cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape) { assert(this->id().valid()); assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); assert(this->mm_segmentation_facets.id().valid()); + assert(this->fuzzy_skin_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); @@ -1004,6 +1014,7 @@ private: assert(this->supported_facets.id() == other.supported_facets.id()); assert(this->seam_facets.id() == other.seam_facets.id()); assert(this->mm_segmentation_facets.id() == other.mm_segmentation_facets.id()); + assert(this->fuzzy_skin_facets.id() == other.fuzzy_skin_facets.id()); this->set_material_id(other.material_id()); } // Providing a new mesh, therefore this volume will get a new unique ID assigned. @@ -1016,10 +1027,12 @@ private: assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); assert(this->mm_segmentation_facets.id().valid()); + assert(this->fuzzy_skin_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); assert(this->id() != this->mm_segmentation_facets.id()); + assert(this->id() != this->fuzzy_skin_facets.id()); assert(this->id() != other.id()); assert(this->config.id() == other.config.id()); this->set_material_id(other.material_id()); @@ -1031,10 +1044,12 @@ private: assert(this->supported_facets.id() != other.supported_facets.id()); assert(this->seam_facets.id() != other.seam_facets.id()); assert(this->mm_segmentation_facets.id() != other.mm_segmentation_facets.id()); + assert(this->fuzzy_skin_facets.id() != other.fuzzy_skin_facets.id()); assert(this->id() != this->config.id()); assert(this->supported_facets.empty()); assert(this->seam_facets.empty()); assert(this->mm_segmentation_facets.empty()); + assert(this->fuzzy_skin_facets.empty()); } ModelVolume& operator=(ModelVolume &rhs) = delete; @@ -1042,12 +1057,13 @@ private: friend class cereal::access; friend class UndoRedo::StackImpl; // Used for deserialization, therefore no IDs are allocated. - ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mm_segmentation_facets(-1), object(nullptr) { + ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mm_segmentation_facets(-1), fuzzy_skin_facets(-1), object(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); assert(this->supported_facets.id().invalid()); assert(this->seam_facets.id().invalid()); assert(this->mm_segmentation_facets.id().invalid()); + assert(this->fuzzy_skin_facets.id().invalid()); } template void load(Archive &ar) { bool has_convex_hull; @@ -1055,6 +1071,7 @@ private: cereal::load_by_value(ar, supported_facets); cereal::load_by_value(ar, seam_facets); cereal::load_by_value(ar, mm_segmentation_facets); + cereal::load_by_value(ar, fuzzy_skin_facets); cereal::load_by_value(ar, config); cereal::load(ar, text_configuration); cereal::load(ar, emboss_shape); @@ -1073,6 +1090,7 @@ private: cereal::save_by_value(ar, supported_facets); cereal::save_by_value(ar, seam_facets); cereal::save_by_value(ar, mm_segmentation_facets); + cereal::save_by_value(ar, fuzzy_skin_facets); cereal::save_by_value(ar, config); cereal::save(ar, text_configuration); cereal::save(ar, emboss_shape); @@ -1337,6 +1355,8 @@ public: bool is_seam_painted() const; // Checks if any of objects is painted using the multi-material painting gizmo. bool is_mm_painted() const; + // Checks if any of objects is painted using the fuzzy skin painting gizmo. + bool is_fuzzy_skin_painted() const; private: explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); } @@ -1381,6 +1401,10 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo // The function assumes that volumes list is synchronized. extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new); +// Test whether the now ModelObject has newer fuzzy skin data than the old one. +// The function assumes that volumes list is synchronized. +extern bool model_fuzzy_skin_data_changed(const ModelObject &mo, const ModelObject &mo_new); + // If the model has object(s) which contains a modofoer, then it is currently not supported by the SLA mode. // Either the model cannot be loaded, or a SLA printer has to be activated. bool model_has_parameter_modifiers_in_objects(const Model& model); diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 9a5ba3169f..d6297710db 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -105,6 +105,8 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, mv_dst.seam_facets.assign(mv_src.seam_facets); assert(mv_dst.mm_segmentation_facets.id() == mv_src.mm_segmentation_facets.id()); mv_dst.mm_segmentation_facets.assign(mv_src.mm_segmentation_facets); + assert(mv_dst.fuzzy_skin_facets.id() == mv_src.fuzzy_skin_facets.id()); + mv_dst.fuzzy_skin_facets.assign(mv_src.fuzzy_skin_facets); //FIXME what to do with the materials? // mv_dst.m_material_id = mv_src.m_material_id; ++ i_src; @@ -1196,7 +1198,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked. bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) || model_mmu_segmentation_data_changed(model_object, model_object_new) || - (model_object_new.is_mm_painted() && num_extruders_changed); + (model_object_new.is_mm_painted() && num_extruders_changed) || + model_fuzzy_skin_data_changed(model_object, model_object_new); bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) || model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty()); diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 0deb6aea52..2cd5a0a980 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -37,6 +37,8 @@ enum class TriangleStateType : int8_t { NONE = 0, ENFORCER = 1, BLOCKER = 2, + // For the fuzzy skin, we use just two values (NONE and FUZZY_SKIN). + FUZZY_SKIN = ENFORCER, // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. Extruder1 = ENFORCER, Extruder2 = BLOCKER, diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 9604c77d66..a8c3d915f4 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -81,6 +81,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoSlaSupports.hpp GUI/Gizmos/GLGizmoFdmSupports.cpp GUI/Gizmos/GLGizmoFdmSupports.hpp + GUI/Gizmos/GLGizmoFuzzySkin.cpp + GUI/Gizmos/GLGizmoFuzzySkin.hpp GUI/Gizmos/GLGizmoFlatten.cpp GUI/Gizmos/GLGizmoFlatten.hpp GUI/Gizmos/GLGizmoCut.cpp diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b7777f9105..8a77950221 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1555,11 +1555,12 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject auto gizmo_type = gm.get_current_type(); if ( (gizmo_type == GLGizmosManager::FdmSupports || gizmo_type == GLGizmosManager::Seam - || gizmo_type == GLGizmosManager::Cut) + || gizmo_type == GLGizmosManager::Cut + || gizmo_type == GLGizmosManager::FuzzySkin) && !vol->is_modifier) { vol->force_neutral_color = true; } - else if (gizmo_type == GLGizmosManager::MmuSegmentation) + else if (gizmo_type == GLGizmosManager::MmSegmentation) vol->is_active = false; else vol->force_native_color = true; @@ -3149,8 +3150,9 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) const GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type(); if (keyCode == WXK_ALT && (gizmo_type == GLGizmosManager::FdmSupports || gizmo_type == GLGizmosManager::Seam || - gizmo_type == GLGizmosManager::MmuSegmentation)) { - // Prevents focusing on the menu bar when ALT is pressed in painting gizmos (FdmSupports, Seam, and MmuSegmentation). + gizmo_type == GLGizmosManager::MmSegmentation || + gizmo_type == GLGizmosManager::FuzzySkin)) { + // Prevents focusing on the menu bar when ALT is pressed in painting gizmos (FdmSupports, Seam, MmSegmentation, and FuzzySkin). evt.Skip(false); } else if (keyCode != WXK_TAB && keyCode != WXK_LEFT @@ -3359,7 +3361,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; // do not return if dragging or tooltip not empty to allow for tooltip update // also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection - if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation || !evt.Moving())) + if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation || !evt.Moving())) return; } @@ -3540,7 +3542,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_gizmos.get_current_type() != GLGizmosManager::Seam && m_gizmos.get_current_type() != GLGizmosManager::Cut && m_gizmos.get_current_type() != GLGizmosManager::Measure && - m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { + m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation && + m_gizmos.get_current_type() != GLGizmosManager::FuzzySkin) { m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); m_dirty = true; } @@ -5797,7 +5800,8 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && m_gizmos.get_current_type() != GLGizmosManager::Hollow && m_gizmos.get_current_type() != GLGizmosManager::Seam - && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation); + && m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation + && m_gizmos.get_current_type() != GLGizmosManager::FuzzySkin); m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_texture); } @@ -5950,12 +5954,13 @@ void GLCanvas3D::_render_sequential_clearance() { case GLGizmosManager::EType::Flatten: case GLGizmosManager::EType::Cut: - case GLGizmosManager::EType::MmuSegmentation: + case GLGizmosManager::EType::MmSegmentation: case GLGizmosManager::EType::Measure: case GLGizmosManager::EType::Emboss: case GLGizmosManager::EType::Simplify: case GLGizmosManager::EType::FdmSupports: - case GLGizmosManager::EType::Seam: { return; } + case GLGizmosManager::EType::Seam: + case GLGizmosManager::EType::FuzzySkin: { return; } default: { break; } } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index dc47d1bc87..c8a2106b1f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1921,13 +1921,20 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type) } break; - case InfoItemType::MmuSegmentation: + case InfoItemType::MmSegmentation: cnv->get_gizmos_manager().reset_all_states(); Plater::TakeSnapshot(plater, _L("Remove Multi Material painting")); for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes) mv->mm_segmentation_facets.reset(); break; + case InfoItemType::FuzzySkin: + cnv->get_gizmos_manager().reset_all_states(); + Plater::TakeSnapshot(plater, _L("Remove paint-on fuzzy skin")); + for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes) + mv->fuzzy_skin_facets.reset(); + break; + case InfoItemType::Sinking: Plater::TakeSnapshot(plater, _L("Shift objects to bed")); (*m_objects)[obj_idx]->ensure_on_bed(); @@ -2132,8 +2139,8 @@ void ObjectList::split() take_snapshot(_(L("Split to Parts"))); - // Before splitting volume we have to remove all custom supports, seams, and multimaterial painting. - wxGetApp().plater()->clear_before_change_mesh(obj_idx, _u8L("Custom supports, seams and multimaterial painting were " + // Before splitting volume we have to remove all custom supports, seams, fuzzy skin and multi-material painting. + wxGetApp().plater()->clear_before_change_mesh(obj_idx, _u8L("Custom supports, seams, fuzzy skin and multi-material painting were " "removed after splitting the object.")); volume->split(nozzle_dmrs_cnt); @@ -2148,8 +2155,8 @@ void ObjectList::split() // update printable state for new volumes on canvas3D wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object(obj_idx); - // After removing custom supports, seams, and multimaterial painting, we have to update info about the object to remove information about - // custom supports, seams, and multimaterial painting in the right panel. + // After removing custom supports, seams, fuzzy skin, and multi-material painting, we have to update info about the object to remove information about + // custom supports, seams, fuzzy skin, and multi-material painting in the right panel. wxGetApp().obj_list()->update_info_items(obj_idx); } @@ -2727,11 +2734,13 @@ void ObjectList::part_selection_changed() } case InfoItemType::CustomSupports: case InfoItemType::CustomSeam: - case InfoItemType::MmuSegmentation: + case InfoItemType::MmSegmentation: + case InfoItemType::FuzzySkin: { GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports : info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam : - GLGizmosManager::EType::MmuSegmentation; + info_type == InfoItemType::FuzzySkin ? GLGizmosManager::EType::FuzzySkin : + GLGizmosManager::EType::MmSegmentation; if (gizmos_mgr.get_current_type() != gizmo_type) gizmos_mgr.open_gizmo(gizmo_type); break; @@ -2901,7 +2910,8 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio for (InfoItemType type : {InfoItemType::CustomSupports, InfoItemType::CustomSeam, InfoItemType::CutConnectors, - InfoItemType::MmuSegmentation, + InfoItemType::MmSegmentation, + InfoItemType::FuzzySkin, InfoItemType::Sinking, InfoItemType::VariableLayerHeight}) { wxDataViewItem item = m_objects_model->GetInfoItemByType(item_obj, type); @@ -2911,12 +2921,14 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio switch (type) { case InfoItemType::CustomSupports : case InfoItemType::CustomSeam : - case InfoItemType::MmuSegmentation : + case InfoItemType::MmSegmentation : + case InfoItemType::FuzzySkin : should_show = printer_technology() == ptFFF && std::any_of(model_object->volumes.begin(), model_object->volumes.end(), [type](const ModelVolume *mv) { return !(type == InfoItemType::CustomSupports ? mv->supported_facets.empty() : type == InfoItemType::CustomSeam ? mv->seam_facets.empty() : + type == InfoItemType::FuzzySkin ? mv->fuzzy_skin_facets.empty() : mv->mm_segmentation_facets.empty()); }); break; @@ -4642,7 +4654,7 @@ void ObjectList::fix_through_winsdk() msg += "\n"; } - plater->clear_before_change_mesh(obj_idx, _u8L("Custom supports, seams and multimaterial painting were " + plater->clear_before_change_mesh(obj_idx, _u8L("Custom supports, seams, fuzzy skin and multimaterial painting were " "removed after repairing the mesh.")); std::string res; if (!fix_model_by_win10_sdk_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res)) @@ -4984,10 +4996,5 @@ ModelObject* ObjectList::object(const int obj_idx) const return (*m_objects)[obj_idx]; } -bool ObjectList::has_paint_on_segmentation() -{ - return m_objects_model->HasInfoItem(InfoItemType::MmuSegmentation); -} - } //namespace GUI } //namespace Slic3r diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index ece9b59430..0efd8092f6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -410,7 +410,6 @@ public: void set_extruder_for_selected_items(const int extruder) const ; wxDataViewItemArray reorder_volumes_and_get_selection(size_t obj_idx, std::function add_to_selection = nullptr); void apply_volumes_order(); - bool has_paint_on_segmentation(); bool is_editing() const { return m_is_editing_started; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp new file mode 100644 index 0000000000..e250b396c9 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp @@ -0,0 +1,311 @@ +#include "GLGizmoFuzzySkin.hpp" + +#include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" + +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/ImGuiWrapper.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + +#include +#include + +namespace Slic3r::GUI { + +void GLGizmoFuzzySkin::on_shutdown() +{ + m_parent.use_slope(false); + m_parent.toggle_model_objects_visibility(true); +} + +std::string GLGizmoFuzzySkin::on_get_name() const +{ + return _u8L("Paint-on fuzzy skin"); +} + +bool GLGizmoFuzzySkin::on_init() +{ + m_shortcut_key = WXK_CONTROL_H; + + m_desc["clipping_of_view"] = _u8L("Clipping of view") + ": "; + m_desc["reset_direction"] = _u8L("Reset direction"); + m_desc["cursor_size"] = _u8L("Brush size") + ": "; + m_desc["cursor_type"] = _u8L("Brush shape") + ": "; + m_desc["add_fuzzy_skin_caption"] = _u8L("Left mouse button") + ": "; + m_desc["add_fuzzy_skin"] = _u8L("Add fuzzy skin"); + m_desc["remove_fuzzy_skin_caption"] = _u8L("Shift + Left mouse button") + ": "; + m_desc["remove_fuzzy_skin"] = _u8L("Remove fuzzy skin"); + m_desc["remove_all"] = _u8L("Remove all selection"); + m_desc["circle"] = _u8L("Circle"); + m_desc["sphere"] = _u8L("Sphere"); + m_desc["pointer"] = _u8L("Triangles"); + m_desc["tool_type"] = _u8L("Tool type") + ": "; + m_desc["tool_brush"] = _u8L("Brush"); + m_desc["tool_smart_fill"] = _u8L("Smart fill"); + m_desc["smart_fill_angle"] = _u8L("Smart fill angle"); + m_desc["split_triangles"] = _u8L("Split triangles"); + + return true; +} + +void GLGizmoFuzzySkin::render_painter_gizmo() +{ + const Selection &selection = m_parent.get_selection(); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + render_triangles(selection); + m_c->object_clipper()->render_cut(); + m_c->instances_hider()->render_cut(); + render_cursor(); + + glsafe(::glDisable(GL_BLEND)); +} + +void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_limit) +{ + if (!m_c->selection_info()->model_object()) + return; + + const float approx_height = m_imgui->scaled(22.f); + + y = std::min(y, bottom_limit - approx_height); + ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); + + ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: + const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float smart_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + + const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_pointer = ImGuiPureWrap::calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); + + const float button_width = ImGuiPureWrap::calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); + const float buttons_width = m_imgui->scaled(0.5f); + const float minimal_slider_width = m_imgui->scaled(4.f); + + const float tool_type_radio_left = ImGuiPureWrap::calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f); + const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + + const float split_triangles_checkbox_width = ImGuiPureWrap::calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); + + float caption_max = 0.f; + float total_text_max = 0.f; + for (const std::string t : {"add_fuzzy_skin", "remove_fuzzy_skin"}) { + caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x); + total_text_max = std::max(total_text_max, ImGuiPureWrap::calc_text_size(m_desc[t]).x); + } + + total_text_max += caption_max + m_imgui->scaled(1.f); + caption_max += m_imgui->scaled(1.f); + + const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left)); + const float slider_icon_width = ImGuiPureWrap::get_slider_icon_size().x; + float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; + window_width = std::max(window_width, total_text_max); + window_width = std::max(window_width, button_width); + window_width = std::max(window_width, split_triangles_checkbox_width); + window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); + window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); + window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); + + auto draw_text_with_caption = [&caption_max](const std::string &caption, const std::string &text) { + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_ORANGE_LIGHT, caption); + ImGui::SameLine(caption_max); + ImGuiPureWrap::text(text); + }; + + for (const std::string t : {"add_fuzzy_skin", "remove_fuzzy_skin"}) { + draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); + } + + ImGui::Separator(); + + std::string format_str = std::string("%.f") + I18N::translate_utf8("°", + "Degree sign to use in the respective slider in fuzzy skin gizmo," + "placed after the number with no whitespace in between."); + + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + + ImGui::AlignTextToFramePadding(); + ImGuiPureWrap::text(m_desc["tool_type"]); + + float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f; + ImGui::SameLine(tool_type_offset); + ImGui::PushItemWidth(tool_type_radio_brush); + if (ImGuiPureWrap::radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) + m_tool_type = ToolType::BRUSH; + + if (ImGui::IsItemHovered()) + ImGuiPureWrap::tooltip(_u8L("Paints facets according to the chosen painting brush."), max_tooltip_width); + + ImGui::SameLine(tool_type_offset + tool_type_radio_brush); + ImGui::PushItemWidth(tool_type_radio_smart_fill); + if (ImGuiPureWrap::radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) + m_tool_type = ToolType::SMART_FILL; + + if (ImGui::IsItemHovered()) + ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); + + ImGui::Separator(); + + if (m_tool_type == ToolType::BRUSH) { + ImGuiPureWrap::text(m_desc.at("cursor_type")); + ImGui::NewLine(); + + float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f; + ImGui::SameLine(cursor_type_offset); + ImGui::PushItemWidth(cursor_type_radio_sphere); + if (ImGuiPureWrap::radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) + m_cursor_type = TriangleSelector::CursorType::SPHERE; + + if (ImGui::IsItemHovered()) + ImGuiPureWrap::tooltip(_u8L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); + + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); + ImGui::PushItemWidth(cursor_type_radio_circle); + + if (ImGuiPureWrap::radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) + m_cursor_type = TriangleSelector::CursorType::CIRCLE; + + if (ImGui::IsItemHovered()) + ImGuiPureWrap::tooltip(_u8L("Ignores facets facing away from the camera."), max_tooltip_width); + + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); + ImGui::PushItemWidth(cursor_type_radio_pointer); + + if (ImGuiPureWrap::radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) + m_cursor_type = TriangleSelector::CursorType::POINTER; + + if (ImGui::IsItemHovered()) + ImGuiPureWrap::tooltip(_u8L("Paints only one facet."), max_tooltip_width); + + m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); + + ImGui::AlignTextToFramePadding(); + ImGuiPureWrap::text(m_desc.at("cursor_size")); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); + + ImGuiPureWrap::checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); + + if (ImGui::IsItemHovered()) + ImGuiPureWrap::tooltip(_u8L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width); + + m_imgui->disabled_end(); + } else { + assert(m_tool_type == ToolType::SMART_FILL); + ImGui::AlignTextToFramePadding(); + ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":"); + + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) + for (auto &triangle_selector : m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); + } + } + + ImGui::Separator(); + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); + ImGuiPureWrap::text(m_desc.at("clipping_of_view")); + } else { + if (ImGuiPureWrap::button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position_by_ratio(-1., false); }); + } + } + + auto clp_dist = float(m_c->object_clipper()->get_position()); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, from_u8(GUI::shortkey_ctrl_prefix()) + _L("Mouse wheel"))) + m_c->object_clipper()->set_position_by_ratio(clp_dist, true); + + ImGui::Separator(); + if (ImGuiPureWrap::button(m_desc.at("remove_all"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); + ModelObject *mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + ++idx; + m_triangle_selectors[idx]->reset(); + m_triangle_selectors[idx]->request_update_render_data(); + } + + update_model_object(); + m_parent.set_as_dirty(); + } + + ImGuiPureWrap::end(); +} + +void GLGizmoFuzzySkin::update_model_object() const +{ + bool updated = false; + ModelObject *mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume *mv : mo->volumes) { + if (!mv->is_model_part()) + continue; + + ++idx; + updated |= mv->fuzzy_skin_facets.set(*m_triangle_selectors[idx]); + } + + if (updated) { + const ModelObjectPtrs &mos = wxGetApp().model().objects; + wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); + + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } +} + +void GLGizmoFuzzySkin::update_from_model_object() +{ + wxBusyCursor wait; + + const ModelObject *mo = m_c->selection_info()->model_object(); + m_triangle_selectors.clear(); + + int volume_id = -1; + for (const ModelVolume *mv : mo->volumes) { + if (!mv->is_model_part()) + continue; + + ++volume_id; + + // This mesh does not account for the possible Z up SLA offset. + const TriangleMesh *mesh = &mv->mesh(); + + m_triangle_selectors.emplace_back(std::make_unique(*mesh)); + // Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize(). + m_triangle_selectors.back()->deserialize(mv->fuzzy_skin_facets.get_data(), false); + m_triangle_selectors.back()->request_update_render_data(); + } +} + +PainterGizmoType GLGizmoFuzzySkin::get_painter_type() const +{ + return PainterGizmoType::FUZZY_SKIN; +} + +wxString GLGizmoFuzzySkin::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const +{ + return shift_down ? _L("Remove fuzzy skin") : _L("Add fuzzy skin"); +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp new file mode 100644 index 0000000000..899e75c295 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp @@ -0,0 +1,47 @@ +#ifndef slic3r_GLGizmoFuzzySkin_hpp_ +#define slic3r_GLGizmoFuzzySkin_hpp_ + +#include "GLGizmoPainterBase.hpp" + +#include "slic3r/GUI/I18N.hpp" + +namespace Slic3r::GUI { + +class GLGizmoFuzzySkin : public GLGizmoPainterBase +{ +public: + GLGizmoFuzzySkin(GLCanvas3D &parent, const std::string &icon_filename, unsigned int sprite_id) : GLGizmoPainterBase(parent, icon_filename, sprite_id) {} + + void render_painter_gizmo() override; + +protected: + void on_render_input_window(float x, float y, float bottom_limit) override; + std::string on_get_name() const override; + + wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; + + std::string get_gizmo_entering_text() const override { return _u8L("Entering Paint-on fuzzy skin"); } + std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Paint-on fuzzy skin"); } + std::string get_action_snapshot_name() const override { return _u8L("Paint-on fuzzy skin editing"); } + + TriangleStateType get_left_button_state_type() const override { return TriangleStateType::FUZZY_SKIN; } + TriangleStateType get_right_button_state_type() const override { return TriangleStateType::NONE; } + +private: + bool on_init() override; + + void update_model_object() const override; + void update_from_model_object() override; + + void on_opening() override {} + void on_shutdown() override; + PainterGizmoType get_painter_type() const override; + + // This map holds all translated description texts, so they can be easily referenced during layout calculations + // etc. When language changes, GUI is recreated, and this class constructed again, so the change takes effect. + std::map m_desc; +}; + +} // namespace Slic3r::GUI + +#endif // slic3r_GLGizmoFuzzySkin_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 62f58c757b..c39eb4edc1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -560,7 +560,7 @@ void GLGizmoMmuSegmentation::update_from_model_object() PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const { - return PainterGizmoType::MMU_SEGMENTATION; + return PainterGizmoType::MM_SEGMENTATION; } ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_left_button_color() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index bd185fce0e..31f38cc743 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -31,7 +31,8 @@ class Selection; enum class PainterGizmoType { FDM_SUPPORTS, SEAM, - MMU_SEGMENTATION + MM_SEGMENTATION, + FUZZY_SKIN }; class TriangleSelectorGUI : public TriangleSelector { diff --git a/src/slic3r/GUI/Gizmos/GLGizmos.hpp b/src/slic3r/GUI/Gizmos/GLGizmos.hpp index 7b57590a1e..c51d73a2e8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmos.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmos.hpp @@ -36,6 +36,7 @@ enum class SLAGizmoEventType : unsigned char { #include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp" #include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp" #include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp" #include "slic3r/GUI/Gizmos/GLGizmoCut.hpp" #include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 8fea76a79c..bce5114591 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -21,6 +21,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp" #include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp" #include "slic3r/GUI/Gizmos/GLGizmoCut.hpp" #include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp" @@ -113,8 +114,9 @@ bool GLGizmosManager::init() m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6)); m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7)); m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8)); - m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 9)); - m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, "measure.svg", 10)); + m_gizmos.emplace_back(new GLGizmoFuzzySkin(m_parent, "fuzzy_skin_painting.svg", 9)); + m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 10)); + m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, "measure.svg", 11)); m_gizmos.emplace_back(new GLGizmoEmboss(m_parent)); m_gizmos.emplace_back(new GLGizmoSVG(m_parent)); m_gizmos.emplace_back(new GLGizmoSimplify(m_parent)); @@ -294,12 +296,14 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return dynamic_cast(m_gizmos[FdmSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Seam) return dynamic_cast(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); - else if (m_current == MmuSegmentation) - return dynamic_cast(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + else if (m_current == MmSegmentation) + return dynamic_cast(m_gizmos[MmSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Measure) return dynamic_cast(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Cut) return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + else if (m_current == FuzzySkin) + return dynamic_cast(m_gizmos[FuzzySkin].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else return false; } @@ -367,7 +371,7 @@ bool GLGizmosManager::on_mouse_wheel(const wxMouseEvent &evt) { bool processed = false; - if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) { + if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmSegmentation || m_current == FuzzySkin) { float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) processed = true; @@ -540,7 +544,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) case 'r' : case 'R' : { - if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) && gizmo_event(SLAGizmoEventType::ResetClippingPlane)) + if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmSegmentation || m_current == FuzzySkin) && gizmo_event(SLAGizmoEventType::ResetClippingPlane)) processed = true; break; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 6e55ce72c2..97d86270dc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -80,7 +80,8 @@ public: SlaSupports, FdmSupports, Seam, - MmuSegmentation, + FuzzySkin, + MmSegmentation, Measure, Emboss, Svg, diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index e425016cd6..69d2d29549 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -164,6 +164,7 @@ void KBShortcutsDialog::fill_shortcuts() { "C", L("Gizmo cut") }, { "F", L("Gizmo Place face on bed") }, { "H", L("Gizmo SLA hollow") }, + { "H", L("Gizmo FDM paint-on fuzzy skin") }, { "L", L("Gizmo SLA support points") }, { "L", L("Gizmo FDM paint-on supports") }, { "P", L("Gizmo FDM paint-on seam") }, diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index c9ed154e09..99021fa8ab 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1722,10 +1722,11 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty switch ((*it).first) { case InfoItemType::CustomSupports: text += format(_L_PLURAL("%1$d object was loaded with custom supports.", "%1$d objects were loaded with custom supports.", (*it).second), (*it).second) + "\n"; break; case InfoItemType::CustomSeam: text += format(_L_PLURAL("%1$d object was loaded with custom seam.", "%1$d objects were loaded with custom seam.", (*it).second), (*it).second) + "\n"; break; - case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d object was loaded with multimaterial painting.", "%1$d objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break; + case InfoItemType::MmSegmentation: text += format(_L_PLURAL("%1$d object was loaded with multimaterial painting.", "%1$d objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break; case InfoItemType::VariableLayerHeight: text += format(_L_PLURAL("%1$d object was loaded with variable layer height.", "%1$d objects were loaded with variable layer height.", (*it).second), (*it).second) + "\n"; break; case InfoItemType::Sinking: text += format(_L_PLURAL("%1$d object was loaded with partial sinking.", "%1$d objects were loaded with partial sinking.", (*it).second), (*it).second) + "\n"; break; case InfoItemType::CutConnectors: text += format(_L_PLURAL("%1$d object was loaded as a part of cut object.", "%1$d objects were loaded as parts of cut object", (*it).second), (*it).second) + "\n"; break; + case InfoItemType::FuzzySkin: text += format(_L_PLURAL("%1$d object was loaded with fuzzy skin painting.", "%1$d objects were loaded with fuzzy skin painting.", (*it).second), (*it).second) + "\n"; break; default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break; } } diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index babbbc0d7c..4def2c650c 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -68,12 +68,13 @@ struct InfoItemAtributes { const std::map INFO_ITEMS{ // info_item Type info_item Name info_item BitmapName - { InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, }, - { InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, }, - { InfoItemType::CutConnectors, {L("Connectors"), "cut_connectors" }, }, - { InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation_"}, }, - { InfoItemType::Sinking, {L("Sinking"), "sinking"}, }, - { InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, }, + { InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, }, + { InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, }, + { InfoItemType::CutConnectors, {L("Connectors"), "cut_connectors" }, }, + { InfoItemType::MmSegmentation, {L("Multimaterial painting"), "mmu_segmentation_" }, }, + { InfoItemType::Sinking, {L("Sinking"), "sinking" }, }, + { InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers" }, }, + { InfoItemType::FuzzySkin, {L("Paint-on fuzzy skin"), "fuzzy_skin_painting_" }, }, }; ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index b366abe51e..26cd045c9c 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -56,7 +56,8 @@ enum class InfoItemType CustomSupports, CustomSeam, CutConnectors, - MmuSegmentation, + MmSegmentation, + FuzzySkin, Sinking, VariableLayerHeight }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e22d7a260a..f66d8dfc9b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2438,6 +2438,7 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const new_volume->supported_facets.assign(old_volume->supported_facets); new_volume->seam_facets.assign(old_volume->seam_facets); new_volume->mm_segmentation_facets.assign(old_volume->mm_segmentation_facets); + new_volume->fuzzy_skin_facets.assign(old_volume->fuzzy_skin_facets); } std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); @@ -6686,11 +6687,12 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology) } void Plater::clear_before_change_volume(ModelVolume &mv, const std::string ¬ification_msg) { - // When we change the geometry of the volume, we remove any custom supports/seams/multi-material painting. - if (const bool paint_removed = !mv.supported_facets.empty() || !mv.seam_facets.empty() || !mv.mm_segmentation_facets.empty(); paint_removed) { + // When we change the geometry of the volume, we remove any custom supports/seams/multi-material/fuzzy skin painting. + if (const bool paint_removed = !mv.supported_facets.empty() || !mv.seam_facets.empty() || !mv.mm_segmentation_facets.empty() || !mv.fuzzy_skin_facets.empty(); paint_removed) { mv.supported_facets.reset(); mv.seam_facets.reset(); mv.mm_segmentation_facets.reset(); + mv.fuzzy_skin_facets.reset(); get_notification_manager()->push_notification( NotificationType::CustomSupportsAndSeamRemovedAfterRepair, @@ -6703,14 +6705,15 @@ void Plater::clear_before_change_mesh(int obj_idx, const std::string ¬ificati { ModelObject* mo = model().objects[obj_idx]; - // If there are custom supports/seams/mmu segmentation, remove them. Fixed mesh + // If there are custom supports/seams/mm segmentation/fuzzy skin, remove them. Fixed mesh // may be different and they would make no sense. bool paint_removed = false; - for (ModelVolume* mv : mo->volumes) { - paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mm_segmentation_facets.empty(); + for (ModelVolume *mv : mo->volumes) { + paint_removed |= !mv->supported_facets.empty() || !mv->seam_facets.empty() || !mv->mm_segmentation_facets.empty() || !mv->fuzzy_skin_facets.empty(); mv->supported_facets.reset(); mv->seam_facets.reset(); mv->mm_segmentation_facets.reset(); + mv->fuzzy_skin_facets.reset(); } if (paint_removed) { // snapshot_time is captured by copy so the lambda knows where to undo/redo to. From 519f5eea8e3be0d7c2cd5d030323ff264727e3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Oct 2024 18:02:24 +0200 Subject: [PATCH 02/10] SPE-2486: Extend multi-material segmentation to allow segmentation of any painted faces. --- src/libslic3r/MultiMaterialSegmentation.cpp | 195 ++++++++++++-------- src/libslic3r/MultiMaterialSegmentation.hpp | 33 +++- 2 files changed, 149 insertions(+), 79 deletions(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 8e93d1cd6d..f183a8ba88 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -524,7 +524,7 @@ static void remove_multiple_edges_in_vertex(const VD::vertex_type &vertex) { // It iterates through all nodes on the border between two different colors, and from this point, // start selection always left most edges for every node to construct CCW polygons. static std::vector extract_colored_segments(const std::vector &colored_polygons, - const size_t num_extruders, + const size_t num_facets_states, const size_t layer_idx) { const ColoredLines colored_lines = to_lines(colored_polygons); @@ -606,7 +606,7 @@ static std::vector extract_colored_segments(const std::vector segmented_expolygons_per_extruder(num_extruders + 1); + std::vector segmented_expolygons_per_extruder(num_facets_states); for (const Voronoi::VD::cell_type &cell : vd.cells()) { if (cell.is_degenerate() || !cell.contains_segment()) continue; @@ -657,7 +657,7 @@ static void cut_segmented_layers(const std::vector &input_exp const float interlocking_depth, const std::function &throw_on_cancel_callback) { - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Cutting segmented layers in parallel - Begin"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Cutting segmented layers in parallel - Begin"; const float interlocking_cut_width = interlocking_depth > 0.f ? std::max(cut_width - interlocking_depth, 0.f) : 0.f; tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &interlocking_cut_width, &throw_on_cancel_callback](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { @@ -673,7 +673,7 @@ static void cut_segmented_layers(const std::vector &input_exp } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Cutting segmented layers in parallel - End"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Cutting segmented layers in parallel - End"; } static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d &trafo) { @@ -686,7 +686,8 @@ static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d return false; } -static inline ExPolygons trim_by_top_or_bottom_layer(ExPolygons expolygons_to_trim, const size_t top_or_bottom_layer_idx, const std::vector> &top_or_bottom_raw_by_extruder) { +static inline ExPolygons trim_by_top_or_bottom_layer(ExPolygons expolygons_to_trim, const size_t top_or_bottom_layer_idx, const std::vector> &top_or_bottom_raw_by_extruder) +{ for (const std::vector &top_or_bottom_raw : top_or_bottom_raw_by_extruder) { if (top_or_bottom_raw.empty()) continue; @@ -699,15 +700,16 @@ static inline ExPolygons trim_by_top_or_bottom_layer(ExPolygons expolygons_to_tr return expolygons_to_trim; } -// Returns MM segmentation of top and bottom layers based on painting in MM segmentation gizmo -static inline std::vector> mm_segmentation_top_and_bottom_layers(const PrintObject &print_object, - const std::vector &input_expolygons, - const std::function &throw_on_cancel_callback) +// Returns segmentation of top and bottom layers based on painting in segmentation gizmos. +static inline std::vector> segmentation_top_and_bottom_layers(const PrintObject &print_object, + const std::vector &input_expolygons, + const std::function &extract_facets_info, + const size_t num_facets_states, + const std::function &throw_on_cancel_callback) { - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Segmentation of top and bottom layers in parallel - Begin"; - const size_t num_extruders = print_object.print()->config().nozzle_diameter.size() + 1; - const size_t num_layers = input_expolygons.size(); - const SpanOfConstPtrs layers = print_object.layers(); + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Segmentation of top and bottom layers in parallel - Begin"; + const size_t num_layers = input_expolygons.size(); + const SpanOfConstPtrs layers = print_object.layers(); // Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group. int max_top_layers = 0; @@ -722,7 +724,7 @@ static inline std::vector> mm_segmentation_top_and_botto // Project upwards pointing painted triangles over top surfaces, // project downards pointing painted triangles over bottom surfaces. - std::vector> top_raw(num_extruders), bottom_raw(num_extruders); + std::vector> top_raw(num_facets_states), bottom_raw(num_facets_states); std::vector zs = zs_from_layers(layers); Transform3d object_trafo = print_object.trafo_centered(); @@ -730,8 +732,8 @@ static inline std::vector> mm_segmentation_top_and_botto for (const ModelVolume *mv : print_object.model_object()->volumes) if (mv->is_model_part()) { const Transform3d volume_trafo = object_trafo * mv->get_matrix(); - for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) { - const indexed_triangle_set painted = mv->mm_segmentation_facets.get_facets_strict(*mv, TriangleStateType(extruder_idx)); + for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) { + const indexed_triangle_set painted = extract_facets_info(*mv).facets_annotation.get_facets_strict(*mv, TriangleStateType(extruder_idx)); if constexpr (MM_SEGMENTATION_DEBUG_TOP_BOTTOM) { its_write_obj(painted, debug_out_path("mm-painted-patch-%d.obj", extruder_idx).c_str()); @@ -779,8 +781,8 @@ static inline std::vector> mm_segmentation_top_and_botto } } - auto filter_out_small_polygons = [&num_extruders, &num_layers](std::vector> &raw_surfaces, double min_area) -> void { - for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) { + auto filter_out_small_polygons = [&num_facets_states, &num_layers](std::vector> &raw_surfaces, double min_area) -> void { + for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) { if (raw_surfaces[extruder_idx].empty()) continue; @@ -798,7 +800,7 @@ static inline std::vector> mm_segmentation_top_and_botto filter_out_small_polygons(bottom_raw, Slic3r::sqr(POLYGON_FILTER_MIN_AREA_SCALED)); // Remove top and bottom surfaces that are covered by the previous or next sliced layer. - for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) { + for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) { for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { const bool has_top_surface = !top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_idx].empty(); const bool has_bottom_surface = !bottom_raw[extruder_idx].empty() && !bottom_raw[extruder_idx][layer_idx].empty(); @@ -817,7 +819,7 @@ static inline std::vector> mm_segmentation_top_and_botto const std::vector colors = {"aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow"}; for (size_t layer_id = 0; layer_id < zs.size(); ++layer_id) { std::vector> svg; - for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) { + for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) { if (!top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_id].empty()) { if (ExPolygons expoly = union_ex(top_raw[extruder_idx][layer_id]); !expoly.empty()) { const std::string &color = colors[extruder_idx]; @@ -837,10 +839,10 @@ static inline std::vector> mm_segmentation_top_and_botto } } - std::vector> triangles_by_color_bottom(num_extruders); - std::vector> triangles_by_color_top(num_extruders); - triangles_by_color_bottom.assign(num_extruders, std::vector(num_layers * 2)); - triangles_by_color_top.assign(num_extruders, std::vector(num_layers * 2)); + std::vector> triangles_by_color_bottom(num_facets_states); + std::vector> triangles_by_color_top(num_facets_states); + triangles_by_color_bottom.assign(num_facets_states, std::vector(num_layers * 2)); + triangles_by_color_top.assign(num_facets_states, std::vector(num_layers * 2)); struct LayerColorStat { // Number of regions for a queried color. @@ -878,12 +880,12 @@ static inline std::vector> mm_segmentation_top_and_botto return out; }; - tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&granularity, &num_layers, &num_extruders, &layer_color_stat, &top_raw, &triangles_by_color_top, + tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top, &throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom](const tbb::blocked_range &range) { size_t group_idx = range.begin() / granularity; size_t layer_idx_offset = (group_idx & 1) * num_layers; for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - for (size_t color_idx = 0; color_idx < num_extruders; ++ color_idx) { + for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) { throw_on_cancel_callback(); LayerColorStat stat = layer_color_stat(layer_idx, color_idx); if (std::vector &top = top_raw[color_idx]; !top.empty() && !top[layer_idx].empty()) { @@ -949,8 +951,8 @@ static inline std::vector> mm_segmentation_top_and_botto } }); - std::vector> triangles_by_color_merged(num_extruders); - triangles_by_color_merged.assign(num_extruders, std::vector(num_layers)); + std::vector> triangles_by_color_merged(num_facets_states); + triangles_by_color_merged.assign(num_facets_states, std::vector(num_layers)); tbb::parallel_for(tbb::blocked_range(0, num_layers), [&triangles_by_color_merged, &triangles_by_color_bottom, &triangles_by_color_top, &num_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { throw_on_cancel_callback(); @@ -968,39 +970,42 @@ static inline std::vector> mm_segmentation_top_and_botto triangles_by_color_merged[color_idx - 1][layer_idx]); } }); - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Segmentation of top and bottom layers in parallel - End"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Segmentation of top and bottom layers in parallel - End"; return triangles_by_color_merged; } -static std::vector> merge_segmented_layers( - const std::vector> &segmented_regions, - std::vector> &&top_and_bottom_layers, - const size_t num_extruders, - const std::function &throw_on_cancel_callback) +static std::vector> merge_segmented_layers(const std::vector> &segmented_regions, + std::vector> &&top_and_bottom_layers, + const size_t num_facets_states, + const std::function &throw_on_cancel_callback) { const size_t num_layers = segmented_regions.size(); std::vector> segmented_regions_merged(num_layers); - segmented_regions_merged.assign(num_layers, std::vector(num_extruders)); - assert(num_extruders + 1 == top_and_bottom_layers.size()); + segmented_regions_merged.assign(num_layers, std::vector(num_facets_states - 1)); + assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size()); - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Merging segmented layers in parallel - Begin"; - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) { + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin"; + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - assert(segmented_regions[layer_idx].size() == num_extruders + 1); + assert(segmented_regions[layer_idx].size() == num_facets_states); // Zero is skipped because it is the default color of the volume - for (size_t extruder_id = 1; extruder_id < num_extruders + 1; ++extruder_id) { + for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) { throw_on_cancel_callback(); if (!segmented_regions[layer_idx][extruder_id].empty()) { ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id]; - for (const std::vector &top_and_bottom_by_extruder : top_and_bottom_layers) - if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) - segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]); + if (!top_and_bottom_layers.empty()) { + for (const std::vector &top_and_bottom_by_extruder : top_and_bottom_layers) { + if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) { + segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]); + } + } + } segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed); } - if (!top_and_bottom_layers[extruder_id][layer_idx].empty()) { + if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) { bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty(); append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]); @@ -1011,7 +1016,7 @@ static std::vector> merge_segmented_layers( } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Merging segmented layers in parallel - End"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - End"; return segmented_regions_merged; } @@ -1602,25 +1607,30 @@ static void update_color_changes_using_color_projection_ranges(std::vector= 0) { - const TriangleMesh &mesh = volume.mesh(); - return {mesh.its.indices, mesh.its.vertices, std::vector(mesh.its.indices.size(), uint8_t(volume_extruder_id))}; - } - - return volume.mm_segmentation_facets.get_all_facets_strict_with_colors(volume); -} - -static std::vector slice_model_volume_with_color(const ModelVolume &model_volume, const std::vector &layer_zs, const PrintObject &print_object) +static std::vector slice_model_volume_with_color(const ModelVolume &model_volume, + const std::function &extract_facets_info, + const std::vector &layer_zs, + const PrintObject &print_object) { - const indexed_triangle_set_with_color mesh_with_color = extract_mesh_with_color(model_volume); + const ModelVolumeFacetsInfo facets_info = extract_facets_info(model_volume); + + const auto extract_mesh_with_color = [&model_volume, &facets_info]() -> indexed_triangle_set_with_color { + if (const int volume_extruder_id = model_volume.extruder_id(); facets_info.replace_default_extruder && !facets_info.is_painted && volume_extruder_id >= 0) { + const TriangleMesh &mesh = model_volume.mesh(); + return {mesh.its.indices, mesh.its.vertices, std::vector(mesh.its.indices.size(), uint8_t(volume_extruder_id))}; + } + + return facets_info.facets_annotation.get_all_facets_strict_with_colors(model_volume); + }; + + const indexed_triangle_set_with_color mesh_with_color = extract_mesh_with_color(); const Transform3d trafo = print_object.trafo_centered() * model_volume.get_matrix(); const MeshSlicingParams slicing_params{trafo}; std::vector color_polygons_per_layer = slice_mesh(mesh_with_color, layer_zs, slicing_params); // Replace default painted color (TriangleStateType::NONE) with volume extruder. - if (const int volume_extruder_id = model_volume.extruder_id(); volume_extruder_id > 0 && model_volume.is_mm_painted()) { + if (const int volume_extruder_id = model_volume.extruder_id(); facets_info.replace_default_extruder && facets_info.is_painted && volume_extruder_id > 0) { for (ColorPolygons &color_polygons : color_polygons_per_layer) { for (ColorPolygon &color_polygon : color_polygons) { std::replace(color_polygon.colors.begin(), color_polygon.colors.end(), static_cast(TriangleStateType::NONE), static_cast(volume_extruder_id)); @@ -1631,9 +1641,14 @@ static std::vector slice_model_volume_with_color(const ModelVolum return color_polygons_per_layer; } -std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) +std::vector> segmentation_by_painting(const PrintObject &print_object, + const std::function &extract_facets_info, + const size_t num_facets_states, + const float segmentation_max_width, + const float segmentation_interlocking_depth, + const IncludeTopAndBottomLayers include_top_and_bottom_layers, + const std::function &throw_on_cancel_callback) { - const size_t num_extruders = print_object.print()->config().nozzle_diameter.size(); const size_t num_layers = print_object.layers().size(); const SpanOfConstPtrs layers = print_object.layers(); @@ -1642,7 +1657,7 @@ std::vector> multi_material_segmentation_by_painting(con std::vector> color_polygons_lines_layers(num_layers); // Merge all regions and remove small holes - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Slices preprocessing in parallel - Begin"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slices preprocessing in parallel - Begin"; tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &input_polygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); @@ -1674,12 +1689,12 @@ std::vector> multi_material_segmentation_by_painting(con } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Slices preprocessing in parallel - End"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slices preprocessing in parallel - End"; - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Slicing painted triangles - Begin"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slicing painted triangles - Begin"; const std::vector layer_zs = get_print_object_layers_zs(layers); for (const ModelVolume *mv : print_object.model_object()->volumes) { - std::vector color_polygons_per_layer = slice_model_volume_with_color(*mv, layer_zs, print_object); + std::vector color_polygons_per_layer = slice_model_volume_with_color(*mv, extract_facets_info, layer_zs, print_object); tbb::parallel_for(tbb::blocked_range(0, num_layers), [&color_polygons_per_layer, &color_polygons_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { @@ -1710,7 +1725,7 @@ std::vector> multi_material_segmentation_by_painting(con } }); // end of parallel_for } - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Slicing painted triangles - End"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slicing painted triangles - End"; if constexpr (MM_SEGMENTATION_DEBUG_FILTERED_COLOR_LINES) { for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) { @@ -1719,7 +1734,7 @@ std::vector> multi_material_segmentation_by_painting(con } // Project sliced ColorPolygons on sliced layers (input_expolygons). - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Projection of painted triangles - Begin"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Projection of painted triangles - Begin"; tbb::parallel_for(tbb::blocked_range(0, num_layers), [&color_polygons_lines_layers, &input_polygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); @@ -1766,12 +1781,12 @@ std::vector> multi_material_segmentation_by_painting(con BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Projection of painted triangles - End"; std::vector> segmented_regions(num_layers); - segmented_regions.assign(num_layers, std::vector(num_extruders + 1)); + segmented_regions.assign(num_layers, std::vector(num_facets_states)); // Be aware that after the projection of the ColorPolygons and its postprocessing isn't // ensured that consistency of the color_prev. So, only color_next can be used. - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Layers segmentation in parallel - Begin"; - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&input_polygons_projection_lines_layers, &segmented_regions, &input_expolygons, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) { + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Layers segmentation in parallel - Begin"; + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&input_polygons_projection_lines_layers, &segmented_regions, &input_expolygons, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); @@ -1797,7 +1812,7 @@ std::vector> multi_material_segmentation_by_painting(con assert(!colored_polygons.front().empty()); segmented_regions[layer_idx][size_t(colored_polygons.front().front().color)] = input_expolygons[layer_idx]; } else { - segmented_regions[layer_idx] = extract_colored_segments(colored_polygons, num_extruders, layer_idx); + segmented_regions[layer_idx] = extract_colored_segments(colored_polygons, num_facets_states, layer_idx); } if constexpr (MM_SEGMENTATION_DEBUG_REGIONS) { @@ -1805,19 +1820,22 @@ std::vector> multi_material_segmentation_by_painting(con } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Layers segmentation in parallel - End"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Layers segmentation in parallel - End"; throw_on_cancel_callback(); // The first index is extruder number (includes default extruder), and the second one is layer number - std::vector> top_and_bottom_layers = mm_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback); - throw_on_cancel_callback(); - - if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f) { - cut_segmented_layers(input_expolygons, segmented_regions, float(scale_(max_width)), float(scale_(interlocking_depth)), throw_on_cancel_callback); + std::vector> top_and_bottom_layers; + if (include_top_and_bottom_layers == IncludeTopAndBottomLayers::Yes) { + top_and_bottom_layers = segmentation_top_and_bottom_layers(print_object, input_expolygons, extract_facets_info, num_facets_states, throw_on_cancel_callback); throw_on_cancel_callback(); } - std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_extruders, throw_on_cancel_callback); + if (segmentation_max_width > 0.f) { + cut_segmented_layers(input_expolygons, segmented_regions, scaled(segmentation_max_width), scaled(segmentation_interlocking_depth), throw_on_cancel_callback); + throw_on_cancel_callback(); + } + + std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback); throw_on_cancel_callback(); if constexpr (MM_SEGMENTATION_DEBUG_REGIONS) { @@ -1829,4 +1847,29 @@ std::vector> multi_material_segmentation_by_painting(con return segmented_regions_merged; } +// Returns multi-material segmentation based on painting in multi-material segmentation gizmo +std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { + const size_t num_facets_states = print_object.print()->config().nozzle_diameter.size() + 1; + const float max_width = float(print_object.config().mmu_segmented_region_max_width.value); + const float interlocking_depth = float(print_object.config().mmu_segmented_region_interlocking_depth.value); + + const auto extract_facets_info = [](const ModelVolume &mv) -> ModelVolumeFacetsInfo { + return {mv.mm_segmentation_facets, mv.is_mm_painted(), true}; + }; + + return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, max_width, interlocking_depth, IncludeTopAndBottomLayers::Yes, throw_on_cancel_callback); +} + +// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo +std::vector> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { + const size_t num_facets_states = 2; // Unpainted facets and facets painted with fuzzy skin. + + const auto extract_facets_info = [](const ModelVolume &mv) -> ModelVolumeFacetsInfo { + return {mv.fuzzy_skin_facets, mv.is_fuzzy_skin_painted(), false}; + }; + + return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, 0.f, 0.f, IncludeTopAndBottomLayers::No, throw_on_cancel_callback); +} + + } // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/MultiMaterialSegmentation.hpp b/src/libslic3r/MultiMaterialSegmentation.hpp index 8de0963af3..6e846e7733 100644 --- a/src/libslic3r/MultiMaterialSegmentation.hpp +++ b/src/libslic3r/MultiMaterialSegmentation.hpp @@ -17,8 +17,10 @@ namespace Slic3r { -class PrintObject; class ExPolygon; +class ModelVolume; +class PrintObject; +class FacetsAnnotation; using ExPolygons = std::vector; @@ -32,11 +34,36 @@ struct ColoredLine using ColoredLines = std::vector; -// Returns MMU segmentation based on painting in MMU segmentation gizmo -std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback); +enum class IncludeTopAndBottomLayers { + Yes, + No +}; + +struct ModelVolumeFacetsInfo { + const FacetsAnnotation &facets_annotation; + // Indicate if model volume is painted. + const bool is_painted; + // Indicate if the default extruder (TriangleStateType::NONE) should be replaced with the volume extruder. + const bool replace_default_extruder; +}; BoundingBox get_extents(const std::vector &colored_polygons); +// Returns segmentation based on painting in segmentation gizmos. +std::vector> segmentation_by_painting(const PrintObject &print_object, + const std::function &extract_facets_info, + size_t num_facets_states, + float segmentation_max_width, + float segmentation_interlocking_depth, + IncludeTopAndBottomLayers include_top_and_bottom_layers, + const std::function &throw_on_cancel_callback); + +// Returns multi-material segmentation based on painting in multi-material segmentation gizmo +std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback); + +// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo +std::vector> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback); + } // namespace Slic3r namespace boost::polygon { From 2c06c81159f7aadd6ac20c7a7583c8f4959a5601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Oct 2024 18:03:36 +0200 Subject: [PATCH 03/10] SPE-2486: Refactor function apply_mm_segmentation() to prepare support for fuzzy skin painting. --- src/libslic3r/PrintObjectSlice.cpp | 188 +++++++++++++++++------------ 1 file changed, 108 insertions(+), 80 deletions(-) diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 754a2d1ef0..7f65f0783c 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -39,7 +39,6 @@ #include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/libslic3r.h" -#include "tcbspan/span.hpp" namespace Slic3r { @@ -580,34 +579,36 @@ void PrintObject::slice() template void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel) { - // Returns MMU segmentation based on painting in MMU segmentation gizmo + // Returns MM segmentation based on painting in MM segmentation gizmo std::vector> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel); assert(segmentation.size() == print_object.layer_count()); tbb::parallel_for( tbb::blocked_range(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range &range) { const auto &layer_ranges = print_object.shared_regions()->layer_ranges; - double z = print_object.get_layer(range.begin())->slice_z; + double z = print_object.get_layer(int(range.begin()))->slice_z; auto it_layer_range = layer_range_first(layer_ranges, z); const size_t num_extruders = print_object.print()->config().nozzle_diameter.size(); + struct ByExtruder { ExPolygons expolygons; BoundingBox bbox; }; + std::vector by_extruder; struct ByRegion { ExPolygons expolygons; bool needs_merge { false }; }; std::vector by_region; - for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { + for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) { throw_on_cancel(); - Layer *layer = print_object.get_layer(layer_id); - it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer->slice_z); + Layer &layer = *print_object.get_layer(int(layer_id)); + it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z); const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range; // Gather per extruder expolygons. by_extruder.assign(num_extruders, ByExtruder()); - by_region.assign(layer->region_count(), ByRegion()); + by_region.assign(layer.region_count(), ByRegion()); bool layer_split = false; for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) { ByExtruder ®ion = by_extruder[extruder_id]; @@ -617,87 +618,114 @@ void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_can layer_split = true; } } - if (! layer_split) + + if (!layer_split) continue; + // Split LayerRegions by by_extruder regions. // layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID. - auto it_painted_region = layer_range.painted_regions.begin(); - for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id) - if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices().empty()) { - assert(layerm.region().print_object_region_id() == region_id); - const BoundingBox bbox = get_extents(layerm.slices().surfaces); - assert(it_painted_region < layer_range.painted_regions.end()); - // Find the first it_painted_region which overrides this region. - for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region) - assert(it_painted_region != layer_range.painted_regions.end()); - assert(it_painted_region != layer_range.painted_regions.end()); - assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region()); - // 1-based extruder ID - bool self_trimmed = false; - int self_extruder_id = -1; - for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++ extruder_id) - if (ByExtruder &segmented = by_extruder[extruder_id - 1]; segmented.bbox.defined && bbox.overlap(segmented.bbox)) { - // Find the target region. - for (; int(it_painted_region->extruder_id) < extruder_id; ++ it_painted_region) - assert(it_painted_region != layer_range.painted_regions.end()); - assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region() && int(it_painted_region->extruder_id) == extruder_id); - //FIXME Don't trim by self, it is not reliable. - if (&layerm.region() == it_painted_region->region) { - self_extruder_id = extruder_id; - continue; - } - // Steal from this region. - int target_region_id = it_painted_region->region->print_object_region_id(); - ExPolygons stolen = intersection_ex(layerm.slices().surfaces, segmented.expolygons); - if (! stolen.empty()) { - ByRegion &dst = by_region[target_region_id]; - if (dst.expolygons.empty()) { - dst.expolygons = std::move(stolen); - } else { - append(dst.expolygons, std::move(stolen)); - dst.needs_merge = true; - } - } -#if 0 - if (&layerm.region() == it_painted_region->region) - // Slices of this LayerRegion were trimmed by a MMU region of the same PrintRegion. - self_trimmed = true; -#endif - } - if (! self_trimmed) { - // Trim slices of this LayerRegion with all the MMU regions. - Polygons mine = to_polygons(std::move(layerm.slices().surfaces)); - for (auto &segmented : by_extruder) - if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && bbox.overlap(segmented.bbox)) { - mine = diff(mine, segmented.expolygons); - if (mine.empty()) - break; - } - // Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region(). - // ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region() - // (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from - // layerm.region() could produce a huge number of small unprintable regions for the model's base extruder. - // This could, on some models, produce bulges with the model's base color (#7109). - if (! mine.empty()) - mine = opening(union_ex(mine), float(scale_(5 * EPSILON)), float(scale_(5 * EPSILON))); - if (! mine.empty()) { - ByRegion &dst = by_region[layerm.region().print_object_region_id()]; - if (dst.expolygons.empty()) { - dst.expolygons = union_ex(mine); - } else { - append(dst.expolygons, union_ex(mine)); - dst.needs_merge = true; - } + auto it_painted_region_begin = layer_range.painted_regions.cbegin(); + for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) { + if (it_painted_region_begin == layer_range.painted_regions.cend()) + continue; + + const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx); + const PrintRegion &parent_print_region = parent_layer_region.region(); + assert(parent_print_region.print_object_region_id() == parent_layer_region_idx); + if (parent_layer_region.slices().empty()) + continue; + + // Find the first PaintedRegion, which overrides the parent PrintRegion. + auto it_first_painted_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [&layer_range, &parent_print_region](const auto &painted_region) { + return layer_range.volume_regions[painted_region.parent].region->print_object_region_id() == parent_print_region.print_object_region_id(); + }); + + if (it_first_painted_region == layer_range.painted_regions.cend()) + continue; // This LayerRegion isn't overrides by any PaintedRegion. + + assert(&parent_print_region == layer_range.volume_regions[it_first_painted_region->parent].region); + + // Find the first PaintedRegion with different parent PrintRegion. + auto it_last_painted_region = std::find_if(it_first_painted_region, layer_range.painted_regions.cend(), [&it_first_painted_region](const auto &painted_region) { + return painted_region.parent != it_first_painted_region->parent; + }); + + // Update the beginning PaintedRegion iterator for the next iteration. + it_painted_region_begin = it_last_painted_region; + + const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices().surfaces); + bool self_trimmed = false; + int self_extruder_id = -1; // 1-based extruder ID + for (auto it_painted_region = it_first_painted_region; it_painted_region != it_last_painted_region; ++it_painted_region) { + const int extruder_id = int(it_painted_region->extruder_id); // 1-based extruder ID + assert(extruder_id > 0 && (extruder_id - 1) < int(by_extruder.size())); + assert(layer_range.volume_regions[it_painted_region->parent].region == &parent_print_region); + + const ByExtruder &segmented = by_extruder[extruder_id - 1]; + if (!segmented.bbox.defined || !parent_layer_region_bbox.overlap(segmented.bbox)) + continue; + + // FIXME: Don't trim by self, it is not reliable. + if (it_painted_region->region == &parent_print_region) { + self_extruder_id = extruder_id; + continue; + } + + // Steal from this region. + int target_region_id = it_painted_region->region->print_object_region_id(); + ExPolygons stolen = intersection_ex(parent_layer_region.slices().surfaces, segmented.expolygons); + if (!stolen.empty()) { + ByRegion &dst = by_region[target_region_id]; + if (dst.expolygons.empty()) { + dst.expolygons = std::move(stolen); + } else { + append(dst.expolygons, std::move(stolen)); + dst.needs_merge = true; } } } + + if (!self_trimmed) { + // Trim slices of this LayerRegion with all the MM regions. + Polygons mine = to_polygons(parent_layer_region.slices().surfaces); + for (auto &segmented : by_extruder) { + if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && parent_layer_region_bbox.overlap(segmented.bbox)) { + mine = diff(mine, segmented.expolygons); + if (mine.empty()) + break; + } + } + + // Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region(). + // ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region() + // (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from + // layerm.region() could produce a huge number of small unprintable regions for the model's base extruder. + // This could, on some models, produce bulges with the model's base color (#7109). + if (!mine.empty()) { + mine = opening(union_ex(mine), scaled(5. * EPSILON), scaled(5. * EPSILON)); + } + + if (!mine.empty()) { + ByRegion &dst = by_region[parent_print_region.print_object_region_id()]; + if (dst.expolygons.empty()) { + dst.expolygons = union_ex(mine); + } else { + append(dst.expolygons, union_ex(mine)); + dst.needs_merge = true; + } + } + } + } + // Re-create Surfaces of LayerRegions. - for (size_t region_id = 0; region_id < layer->region_count(); ++ region_id) { + for (int region_id = 0; region_id < layer.region_count(); ++region_id) { ByRegion &src = by_region[region_id]; - if (src.needs_merge) + if (src.needs_merge) { // Multiple regions were merged into one. - src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON))); - layer->get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal); + src.expolygons = closing_ex(src.expolygons, scaled(10. * EPSILON)); + } + + layer.get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal); } } }); @@ -752,7 +780,7 @@ void PrintObject::slice_volumes() m_layers.back()->upper_layer = nullptr; m_print->throw_if_canceled(); - // Is any ModelVolume MMU painted? + // Is any ModelVolume multi-material painted? if (m_print->config().nozzle_diameter.size() > 1 && this->model_object()->is_mm_painted()) { // If XY Size compensation is also enabled, notify the user that XY Size compensation // would not be used because the object is multi-material painted. From 800b742b950438c5ed8323693074b6171300131c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Oct 2024 18:04:48 +0200 Subject: [PATCH 04/10] SPE-2486: Implement segmentation of layers based on fuzzy skin painting. --- src/libslic3r/LayerRegion.hpp | 5 +- src/libslic3r/Print.cpp | 16 ++++ src/libslic3r/Print.hpp | 25 +++++- src/libslic3r/PrintApply.cpp | 67 ++++++++++++++-- src/libslic3r/PrintObjectSlice.cpp | 123 +++++++++++++++++++++++++++++ 5 files changed, 223 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/LayerRegion.hpp b/src/libslic3r/LayerRegion.hpp index 7f6038900c..b05c750241 100644 --- a/src/libslic3r/LayerRegion.hpp +++ b/src/libslic3r/LayerRegion.hpp @@ -152,9 +152,10 @@ protected: private: // Modifying m_slices - friend std::string fix_slicing_errors(LayerPtrs&, const std::function&); template - friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel); + friend void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel); + template + friend void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel); Layer *m_layer; const PrintRegion *m_region; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 4da9807adc..4e14649e53 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1762,5 +1762,21 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co return final_path; } +PrintRegion *PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region(const LayerRangeRegions &layer_range) const +{ + using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType; + + if (this->parent_type == FuzzySkinParentType::PaintedRegion) { + return layer_range.painted_regions[this->parent].region; + } + + assert(this->parent_type == FuzzySkinParentType::VolumeRegion); + return layer_range.volume_regions[this->parent].region; +} + +int PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region_id(const LayerRangeRegions &layer_range) const +{ + return this->parent_print_object_region(layer_range)->print_object_region_id(); +} } // namespace Slic3r diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 6c5947c423..61d4714633 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -150,8 +150,6 @@ using SpanOfConstPtrs = tcb::span; using LayerPtrs = std::vector; using SupportLayerPtrs = std::vector; -class BoundingBoxf3; // TODO: for temporary constructor parameter - // Single instance of a PrintObject. // As multiple PrintObjects may be generated for a single ModelObject (their instances differ in rotation around Z), // ModelObject's instancess will be distributed among these multiple PrintObjects. @@ -204,6 +202,22 @@ public: PrintRegion *region { nullptr }; }; + struct LayerRangeRegions; + + struct FuzzySkinPaintedRegion + { + enum class ParentType { VolumeRegion, PaintedRegion }; + + ParentType parent_type { ParentType::VolumeRegion }; + // Index of a parent VolumeRegion or PaintedRegion. + int parent { -1 }; + // Pointer to PrintObjectRegions::all_regions. + PrintRegion *region { nullptr }; + + PrintRegion *parent_print_object_region(const LayerRangeRegions &layer_range) const; + int parent_print_object_region_id(const LayerRangeRegions &layer_range) const; + }; + // One slice over the PrintObject (possibly the whole PrintObject) and a list of ModelVolumes and their bounding boxes // possibly clipped by the layer_height_range. struct LayerRangeRegions @@ -216,8 +230,9 @@ public: std::vector volumes; // Sorted in the order of their source ModelVolumes, thus reflecting the order of region clipping, modifier overrides etc. - std::vector volume_regions; - std::vector painted_regions; + std::vector volume_regions; + std::vector painted_regions; + std::vector fuzzy_skin_painted_regions; bool has_volume(const ObjectID id) const { auto it = lower_bound_by_predicate(this->volumes.begin(), this->volumes.end(), [id](const VolumeExtents &l) { return l.volume_id < id; }); @@ -340,6 +355,8 @@ public: bool has_support_material() const { return this->has_support() || this->has_raft(); } // Checks if the model object is painted using the multi-material painting gizmo. bool is_mm_painted() const { return this->model_object()->is_mm_painted(); } + // Checks if the model object is painted using the fuzzy skin painting gizmo. + bool is_fuzzy_skin_painted() const { return this->model_object()->is_fuzzy_skin_painted(); } // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) std::vector object_extruders() const; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index d6297710db..493331e70f 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -683,7 +683,6 @@ bool verify_update_print_object_regions( ModelVolumePtrs model_volumes, const PrintRegionConfig &default_region_config, size_t num_extruders, - const std::vector &painting_extruders, PrintObjectRegions &print_object_regions, const std::function &callback_invalidate) { @@ -757,7 +756,7 @@ bool verify_update_print_object_regions( } } - // Verify and / or update PrintRegions produced by color painting. + // Verify and / or update PrintRegions produced by color painting. for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) for (const PrintObjectRegions::PaintedRegion ®ion : layer_range.painted_regions) { const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent]; @@ -781,6 +780,29 @@ bool verify_update_print_object_regions( print_region_ref_inc(*region.region); } + // Verify and / or update PrintRegions produced by fuzzy skin painting. + for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) { + for (const PrintObjectRegions::FuzzySkinPaintedRegion ®ion : layer_range.fuzzy_skin_painted_regions) { + const PrintRegion &parent_print_region = *region.parent_print_object_region(layer_range); + PrintRegionConfig cfg = parent_print_region.config(); + cfg.fuzzy_skin.value = FuzzySkinType::All; + if (cfg != region.region->config()) { + // Region configuration changed. + if (print_region_ref_cnt(*region.region) == 0) { + // Region is referenced for the first time. Just change its parameters. + // Stop the background process before assigning new configuration to the regions. + t_config_option_keys diff = region.region->config().diff(cfg); + callback_invalidate(region.region->config(), cfg, diff); + region.region->config_apply_only(cfg, diff, false); + } else { + // Region is referenced multiple times, thus the region is being split. We need to reslice. + return false; + } + } + print_region_ref_inc(*region.region); + } + } + // Lastly verify, whether some regions were not merged. { std::vector regions; @@ -884,7 +906,8 @@ static PrintObjectRegions* generate_print_object_regions( const Transform3d &trafo, size_t num_extruders, const float xy_size_compensation, - const std::vector &painting_extruders) + const std::vector &painting_extruders, + const bool has_painted_fuzzy_skin) { // Reuse the old object or generate a new one. auto out = print_object_regions_old ? std::unique_ptr(print_object_regions_old) : std::make_unique(); @@ -906,6 +929,7 @@ static PrintObjectRegions* generate_print_object_regions( r.config = range.config; r.volume_regions.clear(); r.painted_regions.clear(); + r.fuzzy_skin_painted_regions.clear(); } } else { out->trafo_bboxes = trafo; @@ -987,13 +1011,42 @@ static PrintObjectRegions* generate_print_object_regions( cfg.infill_extruder.value = painted_extruder_id; layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))}); } - // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MMU segmentation. + // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MM segmentation. std::sort(layer_range.painted_regions.begin(), layer_range.painted_regions.end(), [&layer_range](auto &l, auto &r) { int lid = layer_range.volume_regions[l.parent].region->print_object_region_id(); int rid = layer_range.volume_regions[r.parent].region->print_object_region_id(); return lid < rid || (lid == rid && l.extruder_id < r.extruder_id); }); } + if (has_painted_fuzzy_skin) { + using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType; + + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) { + // FuzzySkinPaintedRegion can override different parts of the Layer than PaintedRegions, + // so FuzzySkinPaintedRegion has to point to both VolumeRegion and PaintedRegion. + for (int parent_volume_region_id = 0; parent_volume_region_id < int(layer_range.volume_regions.size()); ++parent_volume_region_id) { + if (const PrintObjectRegions::VolumeRegion &parent_volume_region = layer_range.volume_regions[parent_volume_region_id]; parent_volume_region.model_volume->is_model_part() || parent_volume_region.model_volume->is_modifier()) { + PrintRegionConfig cfg = parent_volume_region.region->config(); + cfg.fuzzy_skin.value = FuzzySkinType::All; + layer_range.fuzzy_skin_painted_regions.push_back({FuzzySkinParentType::VolumeRegion, parent_volume_region_id, get_create_region(std::move(cfg))}); + } + } + + for (int parent_painted_regions_id = 0; parent_painted_regions_id < int(layer_range.painted_regions.size()); ++parent_painted_regions_id) { + const PrintObjectRegions::PaintedRegion &parent_painted_region = layer_range.painted_regions[parent_painted_regions_id]; + + PrintRegionConfig cfg = parent_painted_region.region->config(); + cfg.fuzzy_skin.value = FuzzySkinType::All; + layer_range.fuzzy_skin_painted_regions.push_back({FuzzySkinParentType::PaintedRegion, parent_painted_regions_id, get_create_region(std::move(cfg))}); + } + + // Sort the regions by parent region::print_object_region_id() to help the slicing algorithm when applying fuzzy skin segmentation. + std::sort(layer_range.fuzzy_skin_painted_regions.begin(), layer_range.fuzzy_skin_painted_regions.end(), [&layer_range](auto &l, auto &r) { + return l.parent_print_object_region_id(layer_range) < r.parent_print_object_region_id(layer_range); + }); + } + } + return out.release(); } @@ -1450,7 +1503,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ print_object.model_object()->volumes, m_default_region_config, num_extruders, - painting_extruders, *print_object_regions, [it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) { for (auto it = it_print_object; it != it_print_object_end; ++it) @@ -1477,7 +1529,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ model_object_status.print_instances.front().trafo, num_extruders, print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value), - painting_extruders); + painting_extruders, + print_object.is_fuzzy_skin_painted()); } for (auto it = it_print_object; it != it_print_object_end; ++it) if ((*it)->m_shared_regions) { @@ -1540,7 +1593,7 @@ void Print::cleanup() auto this_objects = SpanOfConstPtrs(const_cast(&(*it_begin)), it - it_begin); if (! Print::is_shared_print_object_step_valid_unguarded(this_objects, posSupportSpotsSearch)) shared_regions->generated_support_points.reset(); - } + } } bool Print::is_shared_print_object_step_valid_unguarded(SpanOfConstPtrs print_objects, PrintObjectStep print_object_step) diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 7f65f0783c..1e819876f3 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -731,6 +731,114 @@ void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_can }); } +template +void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel) +{ + // Returns fuzzy skin segmentation based on painting in the fuzzy skin painting gizmo. + std::vector> segmentation = fuzzy_skin_segmentation_by_painting(print_object, throw_on_cancel); + assert(segmentation.size() == print_object.layer_count()); + + struct ByRegion + { + ExPolygons expolygons; + bool needs_merge { false }; + }; + + tbb::parallel_for(tbb::blocked_range(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range &range) { + const auto &layer_ranges = print_object.shared_regions()->layer_ranges; + auto it_layer_range = layer_range_first(layer_ranges, print_object.get_layer(int(range.begin()))->slice_z); + + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + throw_on_cancel(); + + Layer &layer = *print_object.get_layer(int(layer_idx)); + it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z); + const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range; + + assert(segmentation[layer_idx].size() == 1); + const ExPolygons &fuzzy_skin_segmentation = segmentation[layer_idx][0]; + const BoundingBox fuzzy_skin_segmentation_bbox = get_extents(fuzzy_skin_segmentation); + if (fuzzy_skin_segmentation.empty()) + continue; + + // Split LayerRegions by painted fuzzy skin regions. + // layer_range.fuzzy_skin_painted_regions are sorted by parent PrintObject region ID. + std::vector by_region(layer.region_count()); + auto it_fuzzy_skin_region_begin = layer_range.fuzzy_skin_painted_regions.cbegin(); + for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) { + if (it_fuzzy_skin_region_begin == layer_range.fuzzy_skin_painted_regions.cend()) + continue; + + const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx); + const PrintRegion &parent_print_region = parent_layer_region.region(); + assert(parent_print_region.print_object_region_id() == parent_layer_region_idx); + if (parent_layer_region.slices().empty()) + continue; + + // Find the first FuzzySkinPaintedRegion, which overrides the parent PrintRegion. + auto it_fuzzy_skin_region = std::find_if(it_fuzzy_skin_region_begin, layer_range.fuzzy_skin_painted_regions.cend(), [&layer_range, &parent_print_region](const auto &fuzzy_skin_region) { + return fuzzy_skin_region.parent_print_object_region_id(layer_range) == parent_print_region.print_object_region_id(); + }); + + if (it_fuzzy_skin_region == layer_range.fuzzy_skin_painted_regions.cend()) + continue; // This LayerRegion isn't overrides by any FuzzySkinPaintedRegion. + + assert(it_fuzzy_skin_region->parent_print_object_region(layer_range) == &parent_print_region); + + // Update the beginning FuzzySkinPaintedRegion iterator for the next iteration. + it_fuzzy_skin_region_begin = std::next(it_fuzzy_skin_region); + + const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices().surfaces); + Polygons layer_region_remaining_polygons = to_polygons(parent_layer_region.slices().surfaces); + // Don't trim by self, it is not reliable. + if (parent_layer_region_bbox.overlap(fuzzy_skin_segmentation_bbox) && it_fuzzy_skin_region->region != &parent_print_region) { + // Steal from this region. + const int target_region_id = it_fuzzy_skin_region->region->print_object_region_id(); + ExPolygons stolen = intersection_ex(parent_layer_region.slices().surfaces, fuzzy_skin_segmentation); + if (!stolen.empty()) { + ByRegion &dst = by_region[target_region_id]; + if (dst.expolygons.empty()) { + dst.expolygons = std::move(stolen); + } else { + append(dst.expolygons, std::move(stolen)); + dst.needs_merge = true; + } + } + + // Trim slices of this LayerRegion by the fuzzy skin region. + layer_region_remaining_polygons = diff(layer_region_remaining_polygons, fuzzy_skin_segmentation); + + // Filter out unprintable polygons. Detailed explanation is inside apply_mm_segmentation. + if (!layer_region_remaining_polygons.empty()) { + layer_region_remaining_polygons = opening(union_ex(layer_region_remaining_polygons), scaled(5. * EPSILON), scaled(5. * EPSILON)); + } + } + + if (!layer_region_remaining_polygons.empty()) { + ByRegion &dst = by_region[parent_print_region.print_object_region_id()]; + if (dst.expolygons.empty()) { + dst.expolygons = union_ex(layer_region_remaining_polygons); + } else { + append(dst.expolygons, union_ex(layer_region_remaining_polygons)); + dst.needs_merge = true; + } + } + } + + // Re-create Surfaces of LayerRegions. + for (int region_id = 0; region_id < layer.region_count(); ++region_id) { + ByRegion &src = by_region[region_id]; + if (src.needs_merge) { + // Multiple regions were merged into one. + src.expolygons = closing_ex(src.expolygons, scaled(10. * EPSILON)); + } + + layer.get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal); + } + } + }); // end of parallel_for +} + // 1) Decides Z positions of the layers, // 2) Initializes layers and their regions // 3) Slices the object meshes @@ -796,6 +904,21 @@ void PrintObject::slice_volumes() apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); }); } + // Is any ModelVolume fuzzy skin painted? + if (this->model_object()->is_fuzzy_skin_painted()) { + // If XY Size compensation is also enabled, notify the user that XY Size compensation + // would not be used because the object has custom fuzzy skin painted. + if (m_config.xy_size_compensation.value != 0.f) { + this->active_step_add_warning( + PrintStateBase::WarningLevel::CRITICAL, + _u8L("An object has enabled XY Size compensation which will not be used because it is also fuzzy skin painted.\nXY Size " + "compensation cannot be combined with fuzzy skin painting.") + + "\n" + (_u8L("Object name")) + ": " + this->model_object()->name); + } + + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - Fuzzy skin segmentation"; + apply_fuzzy_skin_segmentation(*this, [print]() { print->throw_if_canceled(); }); + } BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin"; { From 57a48ef3baccf7133691feaea6482d2d0a4841ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Oct 2024 18:06:00 +0200 Subject: [PATCH 05/10] SPE-2486: Simplification of fuzzy skin application and a few fuzzy skin bug fixes from OrcaSlicer. Co-authored-by: Noisyfox Co-authored-by: Andrew Boktor Co-authored-by: SoftFever --- src/libslic3r/Arachne/PerimeterOrder.hpp | 3 - src/libslic3r/PerimeterGenerator.cpp | 112 +++++++++-------------- 2 files changed, 43 insertions(+), 72 deletions(-) diff --git a/src/libslic3r/Arachne/PerimeterOrder.hpp b/src/libslic3r/Arachne/PerimeterOrder.hpp index 20d8a3da58..f8469d917f 100644 --- a/src/libslic3r/Arachne/PerimeterOrder.hpp +++ b/src/libslic3r/Arachne/PerimeterOrder.hpp @@ -32,9 +32,6 @@ struct PerimeterExtrusion size_t depth = std::numeric_limits::max(); PerimeterExtrusion *nearest_external_perimeter = nullptr; - // Should this extrusion be fuzzyfied during path generation? - bool fuzzify = false; - // Returns if ExtrusionLine is a contour or a hole. bool is_contour() const { return extrusion.is_contour(); } diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index f5cb1d127f..1b9a000bb3 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -176,13 +176,11 @@ public: bool is_contour; // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole. unsigned short depth; - // Should this contur be fuzzyfied on path generation? - bool fuzzify; // Children contour, may be both CCW and CW oriented (outer contours or holes). std::vector children; - - PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool fuzzify) : - polygon(polygon), is_contour(is_contour), depth(depth), fuzzify(fuzzify) {} + + PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour) : + polygon(polygon), is_contour(is_contour), depth(depth) {} // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). bool is_external() const { return this->depth == 0; } // An island, which may have holes, but it does not have another internal island. @@ -210,16 +208,14 @@ static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuz { // 'a' is the (next) new point between p0 and p1 Vec2d p0p1 = (p1 - *p0).cast(); double p0p1_size = p0p1.norm(); - // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size - double dist_last_point = dist_left_over + p0p1_size * 2.; - for (double p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; + double p0pa_dist = dist_left_over; + for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + double(rand()) * range_random_point_dist / double(RAND_MAX)) { double r = double(rand()) * (fuzzy_skin_thickness * 2.) / double(RAND_MAX) - fuzzy_skin_thickness; out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast()); - dist_last_point = p0pa_dist; } - dist_left_over = p0p1_size - dist_last_point; + dist_left_over = p0pa_dist - p0p1_size; p0 = &p1; } while (out.size() < 3) { @@ -252,14 +248,12 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy // 'a' is the (next) new point between p0 and p1 Vec2d p0p1 = (p1.p - p0->p).cast(); double p0p1_size = p0p1.norm(); - // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size - double dist_last_point = dist_left_over + p0p1_size * 2.; - for (double p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + double(rand()) * range_random_point_dist / double(RAND_MAX)) { + double p0pa_dist = dist_left_over; + for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + double(rand()) * range_random_point_dist / double(RAND_MAX)) { double r = double(rand()) * (fuzzy_skin_thickness * 2.) / double(RAND_MAX) - fuzzy_skin_thickness; out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index); - dist_last_point = p0pa_dist; } - dist_left_over = p0p1_size - dist_last_point; + dist_left_over = p0pa_dist - p0p1_size; p0 = &p1; } @@ -278,6 +272,21 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy ext_lines.junctions = std::move(out); } +static bool should_fuzzify(const PerimeterGenerator::Parameters ¶ms, const size_t perimeter_idx, const bool is_contour) +{ + const auto config = params.config; + const auto fuzzy_skin_type = config.fuzzy_skin.value; + + if (fuzzy_skin_type == FuzzySkinType::None || params.layer_id <= 0) { + return false; + } + + const bool fuzzify_contours = perimeter_idx == 0; + const bool fuzzify_holes = fuzzify_contours && fuzzy_skin_type == FuzzySkinType::All; + + return is_contour ? fuzzify_contours : fuzzify_holes; +} + using PerimeterGeneratorLoops = std::vector; static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) @@ -300,17 +309,19 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator } else { loop_role = elrDefault; } - - // detect overhanging/bridging perimeters - ExtrusionPaths paths; - const Polygon &polygon = loop.fuzzify ? fuzzified : loop.polygon; - if (loop.fuzzify) { + + const bool fuzzify = should_fuzzify(params, loop.depth, loop.is_contour); + const Polygon &polygon = fuzzify ? fuzzified : loop.polygon; + if (fuzzify) { fuzzified = loop.polygon; fuzzy_polygon(fuzzified, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); } - if (params.config.overhangs && params.layer_id > params.object_config.raft_layers - && ! ((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) && - params.object_config.support_material_contact_distance.value == 0)) { + + ExtrusionPaths paths; + if (params.config.overhangs && params.layer_id > params.object_config.raft_layers && + !((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) && + params.object_config.support_material_contact_distance.value == 0)) { + // Detect overhanging/bridging perimeters. BoundingBox bbox(polygon.points); bbox.offset(SCALED_EPSILON); Polygons lower_slices_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons_cache, bbox); @@ -335,7 +346,11 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator role_overhang, ExtrusionFlow{ params.mm3_per_mm_overhang, params.overhang_flow.width(), params.overhang_flow.height() } }); - + + if (paths.empty()) { + continue; + } + // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); @@ -503,7 +518,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P ExtrusionRole role_normal = is_external ? ExtrusionRole::ExternalPerimeter : ExtrusionRole::Perimeter; ExtrusionRole role_overhang = role_normal | ExtrusionRoleModifier::Bridge; - if (pg_extrusion.fuzzify) + const bool fuzzify = should_fuzzify(params, pg_extrusion.extrusion.inset_idx, !pg_extrusion.extrusion.is_closed || pg_extrusion.is_contour()); + if (fuzzify) fuzzy_extrusion_line(extrusion, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); ExtrusionPaths paths; @@ -1193,46 +1209,6 @@ void PerimeterGenerator::process_arachne( Arachne::PerimeterOrder::PerimeterExtrusions ordered_extrusions = Arachne::PerimeterOrder::ordered_perimeter_extrusions(perimeters, params.config.external_perimeters_first); - if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) { - std::vector closed_loop_extrusions; - for (Arachne::PerimeterOrder::PerimeterExtrusion &extrusion : ordered_extrusions) - if (extrusion.extrusion.inset_idx == 0) { - if (extrusion.extrusion.is_closed && params.config.fuzzy_skin == FuzzySkinType::External) { - closed_loop_extrusions.emplace_back(&extrusion); - } else { - extrusion.fuzzify = true; - } - } - - if (params.config.fuzzy_skin == FuzzySkinType::External) { - ClipperLib_Z::Paths loops_paths; - loops_paths.reserve(closed_loop_extrusions.size()); - for (const auto &cl_extrusion : closed_loop_extrusions) { - assert(cl_extrusion->extrusion.junctions.front() == cl_extrusion->extrusion.junctions.back()); - size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); - ClipperLib_Z::Path loop_path; - loop_path.reserve(cl_extrusion->extrusion.junctions.size() - 1); - for (auto junction_it = cl_extrusion->extrusion.junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion.junctions.end()); ++junction_it) - loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); - loops_paths.emplace_back(loop_path); - } - - ClipperLib_Z::Clipper clipper; - clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true); - ClipperLib_Z::PolyTree loops_polytree; - clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); - - for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) { - // The whole contour must have the same index. - coord_t polygon_idx = child_node->Contour.front().z(); - bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(), - [&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); }); - if (has_same_idx) - closed_loop_extrusions[polygon_idx]->fuzzify = true; - } - } - } - if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty()) out_loops.append(extrusion_coll); @@ -1421,20 +1397,18 @@ void PerimeterGenerator::process_classic( break; } { - const bool fuzzify_contours = params.config.fuzzy_skin != FuzzySkinType::None && i == 0 && params.layer_id > 0; - const bool fuzzify_holes = fuzzify_contours && params.config.fuzzy_skin == FuzzySkinType::All; for (const ExPolygon &expolygon : offsets) { // Outer contour may overlap with an inner contour, // inner contour may overlap with another inner contour, // outer contour may overlap with itself. //FIXME evaluate the overlaps, annotate each point with an overlap depth, // compensate for the depth of intersection. - contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours); + contours[i].emplace_back(expolygon.contour, i, true); if (! expolygon.holes.empty()) { holes[i].reserve(holes[i].size() + expolygon.holes.size()); for (const Polygon &hole : expolygon.holes) - holes[i].emplace_back(hole, i, false, fuzzify_holes); + holes[i].emplace_back(hole, i, false); } } } From 3c6372901bd9c94c4a3c7d3f97a0e363d4951823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Oct 2024 18:07:12 +0200 Subject: [PATCH 06/10] SPE-2486: Fix weak randomness of fuzzy skin. Co-authored-by: Ryan Cavanaugh Co-authored-by: Noisyfox --- src/libslic3r/PerimeterGenerator.cpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 1b9a000bb3..709c75401d 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,15 @@ namespace Slic3r { +// Produces a random value between 0 and 1. Thread-safe. +static double random_value() { + thread_local std::random_device rd; + // Hash thread ID for random number seed if no hardware rng seed is available + thread_local std::mt19937 gen(rd.entropy() > 0 ? rd() : std::hash()(std::this_thread::get_id())); + thread_local std::uniform_real_distribution dist(0.0, 1.0); + return dist(gen); +} + ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance) { ExtrusionMultiPath multi_path; @@ -196,11 +206,11 @@ public: }; // Thanks Cura developers for this function. -static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuzzy_skin_point_dist) +static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuzzy_skin_point_distance) { - const double min_dist_between_points = fuzzy_skin_point_dist * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value - const double range_random_point_dist = fuzzy_skin_point_dist / 2.; - double dist_left_over = double(rand()) * (min_dist_between_points / 2) / double(RAND_MAX); // the distance to be traversed on the line before making the first new point + const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value + const double range_random_point_dist = fuzzy_skin_point_distance / 2.; + double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point Point* p0 = &poly.points.back(); Points out; out.reserve(poly.points.size()); @@ -210,9 +220,9 @@ static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuz double p0p1_size = p0p1.norm(); double p0pa_dist = dist_left_over; for (; p0pa_dist < p0p1_size; - p0pa_dist += min_dist_between_points + double(rand()) * range_random_point_dist / double(RAND_MAX)) + p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { - double r = double(rand()) * (fuzzy_skin_thickness * 2.) / double(RAND_MAX) - fuzzy_skin_thickness; + double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast()); } dist_left_over = p0pa_dist - p0p1_size; @@ -234,7 +244,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy { const double min_dist_between_points = fuzzy_skin_point_dist * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value const double range_random_point_dist = fuzzy_skin_point_dist / 2.; - double dist_left_over = double(rand()) * (min_dist_between_points / 2) / double(RAND_MAX); // the distance to be traversed on the line before making the first new point + double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point auto *p0 = &ext_lines.front(); std::vector out; @@ -249,8 +259,8 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy Vec2d p0p1 = (p1.p - p0->p).cast(); double p0p1_size = p0p1.norm(); double p0pa_dist = dist_left_over; - for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + double(rand()) * range_random_point_dist / double(RAND_MAX)) { - double r = double(rand()) * (fuzzy_skin_thickness * 2.) / double(RAND_MAX) - fuzzy_skin_thickness; + for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { + double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index); } dist_left_over = p0pa_dist - p0p1_size; From efd95c1c66dc09fca7695fb82405056c687c2291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Oct 2024 18:08:24 +0200 Subject: [PATCH 07/10] SPE-2486: Separate fuzzy skin implementation into the separate file. --- .../Arachne/utils/ExtrusionJunction.hpp | 3 +- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp | 136 ++++++++++++++++++ src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp | 22 +++ src/libslic3r/PerimeterGenerator.cpp | 111 +------------- 5 files changed, 169 insertions(+), 105 deletions(-) create mode 100644 src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp create mode 100644 src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp diff --git a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp index 49f721b5cc..d0139b054a 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp @@ -55,7 +55,8 @@ inline const Point& make_point(const ExtrusionJunction& ej) return ej.p; } -using LineJunctions = std::vector; //; //; } #endif // UTILS_EXTRUSION_JUNCTION_H diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index ff22f71ab9..2e65c77921 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -87,6 +87,8 @@ set(SLIC3R_SOURCES ExtrusionSimulator.cpp ExtrusionSimulator.hpp FileParserError.hpp + Feature/FuzzySkin/FuzzySkin.cpp + Feature/FuzzySkin/FuzzySkin.hpp Fill/Fill.cpp Fill/Fill3DHoneycomb.cpp Fill/Fill3DHoneycomb.hpp diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp new file mode 100644 index 0000000000..ba27b6c734 --- /dev/null +++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp @@ -0,0 +1,136 @@ +#include + +#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" +#include "libslic3r/PerimeterGenerator.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/PrintConfig.hpp" + +#include "FuzzySkin.hpp" + +using namespace Slic3r; + +namespace Slic3r::Feature::FuzzySkin { + +// Produces a random value between 0 and 1. Thread-safe. +static double random_value() +{ + thread_local std::random_device rd; + // Hash thread ID for random number seed if no hardware rng seed is available + thread_local std::mt19937 gen(rd.entropy() > 0 ? rd() : std::hash()(std::this_thread::get_id())); + thread_local std::uniform_real_distribution dist(0.0, 1.0); + return dist(gen); +} + +void fuzzy_polyline(Points &poly, const bool closed, const double fuzzy_skin_thickness, const double fuzzy_skin_point_distance) +{ + const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value + const double range_random_point_dist = fuzzy_skin_point_distance / 2.; + double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point + + Points out; + out.reserve(poly.size()); + + // Skip the first point for open polyline. + Point *p0 = closed ? &poly.back() : &poly.front(); + for (auto it_pt1 = closed ? poly.begin() : std::next(poly.begin()); it_pt1 != poly.end(); ++it_pt1) { + Point &p1 = *it_pt1; + + // 'a' is the (next) new point between p0 and p1 + Vec2d p0p1 = (p1 - *p0).cast(); + double p0p1_size = p0p1.norm(); + double p0pa_dist = dist_left_over; + for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { + double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; + out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast()); + } + + dist_left_over = p0pa_dist - p0p1_size; + p0 = &p1; + } + + while (out.size() < 3) { + size_t point_idx = poly.size() - 2; + out.emplace_back(poly[point_idx]); + if (point_idx == 0) { + break; + } + + --point_idx; + } + + if (out.size() >= 3) { + poly = std::move(out); + } +} + +void fuzzy_polygon(Polygon &polygon, double fuzzy_skin_thickness, double fuzzy_skin_point_distance) +{ + fuzzy_polyline(polygon.points, true, fuzzy_skin_thickness, fuzzy_skin_point_distance); +} + +void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, const double fuzzy_skin_thickness, const double fuzzy_skin_point_distance) +{ + const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value + const double range_random_point_dist = fuzzy_skin_point_distance / 2.; + double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point + + Arachne::ExtrusionJunction *p0 = &ext_lines.front(); + Arachne::ExtrusionJunctions out; + out.reserve(ext_lines.size()); + for (auto &p1 : ext_lines) { + if (p0->p == p1.p) { + // Copy the first point. + out.emplace_back(p1.p, p1.w, p1.perimeter_index); + continue; + } + + // 'a' is the (next) new point between p0 and p1 + Vec2d p0p1 = (p1.p - p0->p).cast(); + double p0p1_size = p0p1.norm(); + double p0pa_dist = dist_left_over; + for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { + double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; + out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index); + } + + dist_left_over = p0pa_dist - p0p1_size; + p0 = &p1; + } + + while (out.size() < 3) { + size_t point_idx = ext_lines.size() - 2; + out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index); + if (point_idx == 0) { + break; + } + + --point_idx; + } + + if (ext_lines.back().p == ext_lines.front().p) { + // Connect endpoints. + out.front().p = out.back().p; + } + + if (out.size() >= 3) { + ext_lines.junctions = std::move(out); + } +} + +bool should_fuzzify(const PrintRegionConfig &config, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour) +{ + const FuzzySkinType fuzzy_skin_type = config.fuzzy_skin.value; + + if (fuzzy_skin_type == FuzzySkinType::None || layer_idx <= 0) { + return false; + } + + const bool fuzzify_contours = perimeter_idx == 0; + const bool fuzzify_holes = fuzzify_contours && fuzzy_skin_type == FuzzySkinType::All; + + return is_contour ? fuzzify_contours : fuzzify_holes; +} + +} // namespace Slic3r::Feature::FuzzySkin diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp new file mode 100644 index 0000000000..e852b4114f --- /dev/null +++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp @@ -0,0 +1,22 @@ +#ifndef libslic3r_FuzzySkin_hpp_ +#define libslic3r_FuzzySkin_hpp_ + +namespace Slic3r::Arachne { +struct ExtrusionLine; +} // namespace Slic3r::Arachne + +namespace Slic3r::PerimeterGenerator { +struct Parameters; +} // namespace Slic3r::PerimeterGenerator + +namespace Slic3r::Feature::FuzzySkin { + +void fuzzy_polygon(Polygon &polygon, double fuzzy_skin_thickness, double fuzzy_skin_point_distance); + +void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy_skin_thickness, double fuzzy_skin_point_dist); + +bool should_fuzzify(const PrintRegionConfig &config, size_t layer_idx, size_t perimeter_idx, bool is_contour); + +} // namespace Slic3r::Feature::FuzzySkin + +#endif // libslic3r_FuzzySkin_hpp_ diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 709c75401d..2f73a704c9 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -28,6 +27,7 @@ #include "ExPolygon.hpp" #include "ExtrusionEntity.hpp" #include "ExtrusionEntityCollection.hpp" +#include "Feature/FuzzySkin/FuzzySkin.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "Polyline.hpp" @@ -53,15 +53,6 @@ namespace Slic3r { -// Produces a random value between 0 and 1. Thread-safe. -static double random_value() { - thread_local std::random_device rd; - // Hash thread ID for random number seed if no hardware rng seed is available - thread_local std::mt19937 gen(rd.entropy() > 0 ? rd() : std::hash()(std::this_thread::get_id())); - thread_local std::uniform_real_distribution dist(0.0, 1.0); - return dist(gen); -} - ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance) { ExtrusionMultiPath multi_path; @@ -205,102 +196,12 @@ public: } }; -// Thanks Cura developers for this function. -static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuzzy_skin_point_distance) -{ - const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value - const double range_random_point_dist = fuzzy_skin_point_distance / 2.; - double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point - Point* p0 = &poly.points.back(); - Points out; - out.reserve(poly.points.size()); - for (Point &p1 : poly.points) - { // 'a' is the (next) new point between p0 and p1 - Vec2d p0p1 = (p1 - *p0).cast(); - double p0p1_size = p0p1.norm(); - double p0pa_dist = dist_left_over; - for (; p0pa_dist < p0p1_size; - p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) - { - double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; - out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast()); - } - dist_left_over = p0pa_dist - p0p1_size; - p0 = &p1; - } - while (out.size() < 3) { - size_t point_idx = poly.size() - 2; - out.emplace_back(poly[point_idx]); - if (point_idx == 0) - break; - -- point_idx; - } - if (out.size() >= 3) - poly.points = std::move(out); -} - -// Thanks Cura developers for this function. -static void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy_skin_thickness, double fuzzy_skin_point_dist) -{ - const double min_dist_between_points = fuzzy_skin_point_dist * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value - const double range_random_point_dist = fuzzy_skin_point_dist / 2.; - double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point - - auto *p0 = &ext_lines.front(); - std::vector out; - out.reserve(ext_lines.size()); - for (auto &p1 : ext_lines) { - if (p0->p == p1.p) { // Connect endpoints. - out.emplace_back(p1.p, p1.w, p1.perimeter_index); - continue; - } - - // 'a' is the (next) new point between p0 and p1 - Vec2d p0p1 = (p1.p - p0->p).cast(); - double p0p1_size = p0p1.norm(); - double p0pa_dist = dist_left_over; - for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { - double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; - out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index); - } - dist_left_over = p0pa_dist - p0p1_size; - p0 = &p1; - } - - while (out.size() < 3) { - size_t point_idx = ext_lines.size() - 2; - out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index); - if (point_idx == 0) - break; - -- point_idx; - } - - if (ext_lines.back().p == ext_lines.front().p) // Connect endpoints. - out.front().p = out.back().p; - - if (out.size() >= 3) - ext_lines.junctions = std::move(out); -} - -static bool should_fuzzify(const PerimeterGenerator::Parameters ¶ms, const size_t perimeter_idx, const bool is_contour) -{ - const auto config = params.config; - const auto fuzzy_skin_type = config.fuzzy_skin.value; - - if (fuzzy_skin_type == FuzzySkinType::None || params.layer_id <= 0) { - return false; - } - - const bool fuzzify_contours = perimeter_idx == 0; - const bool fuzzify_holes = fuzzify_contours && fuzzy_skin_type == FuzzySkinType::All; - - return is_contour ? fuzzify_contours : fuzzify_holes; -} - using PerimeterGeneratorLoops = std::vector; static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) { + using namespace Slic3r::Feature::FuzzySkin; + // loops is an arrayref of ::Loop objects // turn each one into an ExtrusionLoop object ExtrusionEntityCollection coll; @@ -320,7 +221,7 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator loop_role = elrDefault; } - const bool fuzzify = should_fuzzify(params, loop.depth, loop.is_contour); + const bool fuzzify = should_fuzzify(params.config, params.layer_id, loop.depth, loop.is_contour); const Polygon &polygon = fuzzify ? fuzzified : loop.polygon; if (fuzzify) { fuzzified = loop.polygon; @@ -518,6 +419,8 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, Arachne::PerimeterOrder::PerimeterExtrusions &pg_extrusions) { + using namespace Slic3r::Feature::FuzzySkin; + ExtrusionEntityCollection extrusion_coll; for (Arachne::PerimeterOrder::PerimeterExtrusion &pg_extrusion : pg_extrusions) { Arachne::ExtrusionLine &extrusion = pg_extrusion.extrusion; @@ -528,7 +431,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P ExtrusionRole role_normal = is_external ? ExtrusionRole::ExternalPerimeter : ExtrusionRole::Perimeter; ExtrusionRole role_overhang = role_normal | ExtrusionRoleModifier::Bridge; - const bool fuzzify = should_fuzzify(params, pg_extrusion.extrusion.inset_idx, !pg_extrusion.extrusion.is_closed || pg_extrusion.is_contour()); + const bool fuzzify = should_fuzzify(params.config, params.layer_id, pg_extrusion.extrusion.inset_idx, !pg_extrusion.extrusion.is_closed || pg_extrusion.is_contour()); if (fuzzify) fuzzy_extrusion_line(extrusion, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); From ed2cdfec61a1d40725e849af2f3eae4256f317b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Oct 2024 18:09:36 +0200 Subject: [PATCH 08/10] SPE-2486: Implement support for PerimeterRegions that will apply configurations affecting just perimeters only to parts of perimeters instead of creating different LayerRegion. --- .../LineSegmentation/LineSegmentation.cpp | 574 ++++++++++++++++++ .../LineSegmentation/LineSegmentation.hpp | 69 +++ src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/ClipperZUtils.hpp | 18 + src/libslic3r/Layer.cpp | 220 ++++--- src/libslic3r/LayerRegion.cpp | 3 + src/libslic3r/LayerRegion.hpp | 5 + src/libslic3r/PerimeterGenerator.cpp | 41 ++ src/libslic3r/PerimeterGenerator.hpp | 28 +- tests/fff_print/test_perimeters.cpp | 2 + 10 files changed, 868 insertions(+), 94 deletions(-) create mode 100644 src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp create mode 100644 src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp diff --git a/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp new file mode 100644 index 0000000000..4b41a624c9 --- /dev/null +++ b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp @@ -0,0 +1,574 @@ +#include +#include +#include + +#include "clipper/clipper_z.hpp" +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" +#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" +#include "libslic3r/ClipperZUtils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/PerimeterGenerator.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/libslic3r.h" + +#include "LineSegmentation.hpp" + +namespace Slic3r::Algorithm::LineSegmentation { + +const constexpr coord_t POINT_IS_ON_LINE_THRESHOLD_SQR = Slic3r::sqr(scaled(EPSILON)); + +struct ZAttributes +{ + bool is_clip_point = false; + bool is_new_point = false; + uint32_t point_index = 0; + + ZAttributes() = default; + + explicit ZAttributes(const uint32_t clipper_coord) : + is_clip_point((clipper_coord >> 31) & 0x1), is_new_point((clipper_coord >> 30) & 0x1), point_index(clipper_coord & 0x3FFFFFFF) {} + + explicit ZAttributes(const ClipperLib_Z::IntPoint &clipper_pt) : ZAttributes(clipper_pt.z()) {} + + ZAttributes(const bool is_clip_point, const bool is_new_point, const uint32_t point_index) : + is_clip_point(is_clip_point), is_new_point(is_new_point), point_index(point_index) + { + assert(this->point_index < (1u << 30) && "point_index exceeds 30 bits!"); + } + + // Encode the structure to uint32_t. + constexpr uint32_t encode() const + { + assert(this->point_index < (1u << 30) && "point_index exceeds 30 bits!"); + return (this->is_clip_point << 31) | (this->is_new_point << 30) | (this->point_index & 0x3FFFFFFF); + } + + // Decode the uint32_t to the structure. + static ZAttributes decode(const uint32_t clipper_coord) + { + return { bool((clipper_coord >> 31) & 0x1), bool((clipper_coord >> 30) & 0x1), clipper_coord & 0x3FFFFFFF }; + } + + static ZAttributes decode(const ClipperLib_Z::IntPoint &clipper_pt) { return ZAttributes::decode(clipper_pt.z()); } +}; + +struct LineRegionRange +{ + size_t begin_idx; // Index of the line on which the region begins. + double begin_t; // Scalar position on the begin_idx line in which the region begins. The value is from range <0., 1.>. + size_t end_idx; // Index of the line on which the region ends. + double end_t; // Scalar position on the end_idx line in which the region ends. The value is from range <0., 1.>. + size_t clip_idx; // Index of clipping ExPolygons to identified which ExPolygons group contains this line. + + LineRegionRange(size_t begin_idx, double begin_t, size_t end_idx, double end_t, size_t clip_idx) + : begin_idx(begin_idx), begin_t(begin_t), end_idx(end_idx), end_t(end_t), clip_idx(clip_idx) {} + + // Check if 'other' overlaps with this LineRegionRange. + bool is_overlap(const LineRegionRange &other) const + { + if (this->end_idx < other.begin_idx || this->begin_idx > other.end_idx) { + return false; + } else if (this->end_idx == other.begin_idx && this->end_t <= other.begin_t) { + return false; + } else if (this->begin_idx == other.end_idx && this->begin_t >= other.end_t) { + return false; + } + + return true; + } + + // Check if 'inner' is whole inside this LineRegionRange. + bool is_inside(const LineRegionRange &inner) const + { + if (!this->is_overlap(inner)) { + return false; + } + + const bool starts_after = (this->begin_idx < inner.begin_idx) || (this->begin_idx == inner.begin_idx && this->begin_t <= inner.begin_t); + const bool ends_before = (this->end_idx > inner.end_idx) || (this->end_idx == inner.end_idx && this->end_t >= inner.end_t); + + return starts_after && ends_before; + } + + bool is_zero_length() const { return this->begin_idx == this->end_idx && this->begin_t == this->end_t; } + + bool operator<(const LineRegionRange &rhs) const + { + return this->begin_idx < rhs.begin_idx || (this->begin_idx == rhs.begin_idx && this->begin_t < rhs.begin_t); + } +}; + +using LineRegionRanges = std::vector; + +inline Point make_point(const ClipperLib_Z::IntPoint &clipper_pt) { return { clipper_pt.x(), clipper_pt.y() }; } + +inline ClipperLib_Z::Paths to_clip_zpaths(const ExPolygons &clips) { return ClipperZUtils::expolygons_to_zpaths_with_same_z(clips, coord_t(ZAttributes(true, false, 0).encode())); } + +static ClipperLib_Z::Path subject_to_zpath(const Points &subject, const bool is_closed) +{ + ZAttributes z_attributes(false, false, 0); + + ClipperLib_Z::Path out; + if (!subject.empty()) { + out.reserve((subject.size() + is_closed) ? 1 : 0); + for (const Point &p : subject) { + out.emplace_back(p.x(), p.y(), z_attributes.encode()); + ++z_attributes.point_index; + } + + if (is_closed) { + // If it is closed, then duplicate the first point at the end to make a closed path open. + out.emplace_back(subject.front().x(), subject.front().y(), z_attributes.encode()); + } + } + + return out; +} + +static ClipperLib_Z::Path subject_to_zpath(const Arachne::ExtrusionLine &subject) +{ + // Closed Arachne::ExtrusionLine already has duplicated the last point. + ZAttributes z_attributes(false, false, 0); + + ClipperLib_Z::Path out; + if (!subject.empty()) { + out.reserve(subject.size()); + for (const Arachne::ExtrusionJunction &junction : subject) { + out.emplace_back(junction.p.x(), junction.p.y(), z_attributes.encode()); + ++z_attributes.point_index; + } + } + + return out; +} + +static ClipperLib_Z::Path subject_to_zpath(const Polyline &subject) { return subject_to_zpath(subject.points, false); } + +[[maybe_unused]] static ClipperLib_Z::Path subject_to_zpath(const Polygon &subject) { return subject_to_zpath(subject.points, true); } + +struct ProjectionInfo +{ + double projected_t; + double distance_sqr; +}; + +static ProjectionInfo project_point_on_line(const Point &line_from_pt, const Point &line_to_pt, const Point &query_pt) +{ + const Vec2d line_vec = (line_to_pt - line_from_pt).template cast(); + const Vec2d query_vec = (query_pt - line_from_pt).template cast(); + const double line_length_sqr = line_vec.squaredNorm(); + + if (line_length_sqr <= 0.) { + return { std::numeric_limits::max(), std::numeric_limits::max() }; + } + + const double projected_t = query_vec.dot(line_vec); + const double projected_t_normalized = std::clamp(projected_t / line_length_sqr, 0., 1.); + // Projected point have to line on the line. + if (projected_t < 0. || projected_t > line_length_sqr) { + return { projected_t_normalized, std::numeric_limits::max() }; + } + + const Vec2d projected_vec = projected_t_normalized * line_vec; + const double distance_sqr = (projected_vec - query_vec).squaredNorm(); + + return { projected_t_normalized, distance_sqr }; +} + +static int32_t find_closest_line_to_point(const ClipperLib_Z::Path &subject, const ClipperLib_Z::IntPoint &query) +{ + auto it_min = subject.end(); + double distance_sqr_min = std::numeric_limits::max(); + + const Point query_pt = make_point(query); + Point prev_pt = make_point(subject.front()); + for (auto it_curr = std::next(subject.begin()); it_curr != subject.end(); ++it_curr) { + const Point curr_pt = make_point(*it_curr); + + const double distance_sqr = project_point_on_line(prev_pt, curr_pt, query_pt).distance_sqr; + if (distance_sqr <= POINT_IS_ON_LINE_THRESHOLD_SQR) { + return int32_t(std::distance(subject.begin(), std::prev(it_curr))); + } + + if (distance_sqr < distance_sqr_min) { + distance_sqr_min = distance_sqr; + it_min = std::prev(it_curr); + } + + prev_pt = curr_pt; + } + + if (it_min != subject.end()) { + return int32_t(std::distance(subject.begin(), it_min)); + } + + return -1; +} + +std::optional create_line_region_range(ClipperLib_Z::Path &&intersection, const ClipperLib_Z::Path &subject, const size_t region_idx) +{ + if (intersection.size() < 2) { + return std::nullopt; + } + + auto need_reverse = [&subject](const ClipperLib_Z::Path &intersection) -> bool { + for (size_t curr_idx = 1; curr_idx < intersection.size(); ++curr_idx) { + ZAttributes prev_z(intersection[curr_idx - 1]); + ZAttributes curr_z(intersection[curr_idx]); + + if (!prev_z.is_clip_point && !curr_z.is_clip_point) { + if (prev_z.point_index > curr_z.point_index) { + return true; + } else if (curr_z.point_index == prev_z.point_index) { + assert(curr_z.point_index < subject.size()); + const Point subject_pt = make_point(subject[curr_z.point_index]); + const Point prev_pt = make_point(intersection[curr_idx - 1]); + const Point curr_pt = make_point(intersection[curr_idx]); + + const double prev_dist = (prev_pt - subject_pt).cast().squaredNorm(); + const double curr_dist = (curr_pt - subject_pt).cast().squaredNorm(); + if (prev_dist > curr_dist) { + return true; + } + } + } + } + + return false; + }; + + for (ClipperLib_Z::IntPoint &clipper_pt : intersection) { + const ZAttributes clipper_pt_z(clipper_pt); + if (!clipper_pt_z.is_clip_point) { + continue; + } + + // FIXME @hejllukas: We could save searing for the source line in some cases using other intersection points, + // but in reality, the clip point will be inside the intersection in very rare cases. + if (int32_t subject_line_idx = find_closest_line_to_point(subject, clipper_pt); subject_line_idx != -1) { + clipper_pt.z() = coord_t(ZAttributes(false, true, subject_line_idx).encode()); + } + + assert(!ZAttributes(clipper_pt).is_clip_point); + if (ZAttributes(clipper_pt).is_clip_point) { + return std::nullopt; + } + } + + // Ensure that indices of source input are ordered in increasing order. + if (need_reverse(intersection)) { + std::reverse(intersection.begin(), intersection.end()); + } + + ZAttributes begin_z(intersection.front()); + ZAttributes end_z(intersection.back()); + + assert(begin_z.point_index <= subject.size() && end_z.point_index <= subject.size()); + const size_t begin_idx = begin_z.point_index; + const size_t end_idx = end_z.point_index; + const double begin_t = begin_z.is_new_point ? project_point_on_line(make_point(subject[begin_idx]), make_point(subject[begin_idx + 1]), make_point(intersection.front())).projected_t : 0.; + const double end_t = end_z.is_new_point ? project_point_on_line(make_point(subject[end_idx]), make_point(subject[end_idx + 1]), make_point(intersection.back())).projected_t : 0.; + + if (begin_t == std::numeric_limits::max() || end_t == std::numeric_limits::max()) { + return std::nullopt; + } + + return LineRegionRange{ begin_idx, begin_t, end_idx, end_t, region_idx }; +} + +LineRegionRanges intersection_with_region(const ClipperLib_Z::Path &subject, const ClipperLib_Z::Paths &clips, const size_t region_config_idx) +{ + ClipperLib_Z::Clipper clipper; + clipper.PreserveCollinear(true); // Especially with Arachne, we don't want to remove collinear edges. + clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, + const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, + ClipperLib_Z::IntPoint &new_pt) { + const ZAttributes e1bot_z(e1bot), e1top_z(e1top), e2bot_z(e2bot), e2top_z(e2top); + + assert(e1bot_z.is_clip_point == e1top_z.is_clip_point); + assert(e2bot_z.is_clip_point == e2top_z.is_clip_point); + + if (!e1bot_z.is_clip_point && !e1top_z.is_clip_point) { + assert(e1bot_z.point_index + 1 == e1top_z.point_index || e1bot_z.point_index == e1top_z.point_index + 1); + new_pt.z() = coord_t(ZAttributes(false, true, std::min(e1bot_z.point_index, e1top_z.point_index)).encode()); + } else if (!e2bot_z.is_clip_point && !e2top_z.is_clip_point) { + assert(e2bot_z.point_index + 1 == e2top_z.point_index || e2bot_z.point_index == e2top_z.point_index + 1); + new_pt.z() = coord_t(ZAttributes(false, true, std::min(e2bot_z.point_index, e2top_z.point_index)).encode()); + } else { + assert(false && "At least one of the conditions above has to be met."); + } + }); + + clipper.AddPath(subject, ClipperLib_Z::ptSubject, false); + clipper.AddPaths(clips, ClipperLib_Z::ptClip, true); + + ClipperLib_Z::Paths intersections; + { + ClipperLib_Z::PolyTree clipped_polytree; + clipper.Execute(ClipperLib_Z::ctIntersection, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), intersections); + } + + LineRegionRanges line_region_ranges; + line_region_ranges.reserve(intersections.size()); + for (ClipperLib_Z::Path &intersection : intersections) { + if (std::optional region_range = create_line_region_range(std::move(intersection), subject, region_config_idx); region_range.has_value()) { + line_region_ranges.emplace_back(*region_range); + } + } + + return line_region_ranges; +} + +LineRegionRanges create_continues_line_region_ranges(LineRegionRanges &&line_region_ranges, const size_t default_clip_idx, const size_t total_lines_cnt) +{ + if (line_region_ranges.empty()) { + return line_region_ranges; + } + + std::sort(line_region_ranges.begin(), line_region_ranges.end()); + + // Resolve overlapping regions if it happens, but it should never happen. + for (size_t region_range_idx = 1; region_range_idx < line_region_ranges.size(); ++region_range_idx) { + LineRegionRange &prev_range = line_region_ranges[region_range_idx - 1]; + LineRegionRange &curr_range = line_region_ranges[region_range_idx]; + + assert(!prev_range.is_overlap(curr_range)); + if (prev_range.is_inside(curr_range)) { + // Make the previous range zero length to remove it later. + curr_range = prev_range; + prev_range.begin_idx = curr_range.begin_idx; + prev_range.begin_t = curr_range.begin_t; + prev_range.end_idx = curr_range.begin_idx; + prev_range.end_t = curr_range.begin_t; + } else if (prev_range.is_overlap(curr_range)) { + curr_range.begin_idx = prev_range.end_idx; + curr_range.begin_t = prev_range.end_t; + } + } + + // Fill all gaps between regions with the default region. + LineRegionRanges line_region_ranges_out; + size_t prev_line_idx = 0.; + double prev_t = 0.; + for (const LineRegionRange &curr_line_region : line_region_ranges) { + if (curr_line_region.is_zero_length()) { + continue; + } + + assert(prev_line_idx < curr_line_region.begin_idx || (prev_line_idx == curr_line_region.begin_idx && prev_t <= curr_line_region.begin_t)); + + // Fill the gap if it is necessary. + if (prev_line_idx != curr_line_region.begin_idx || prev_t != curr_line_region.begin_t) { + line_region_ranges_out.emplace_back(prev_line_idx, prev_t, curr_line_region.begin_idx, curr_line_region.begin_t, default_clip_idx); + } + + // Add the current region. + line_region_ranges_out.emplace_back(curr_line_region); + prev_line_idx = curr_line_region.end_idx; + prev_t = curr_line_region.end_t; + } + + // Fill the last remaining gap if it exists. + const size_t last_line_idx = total_lines_cnt - 1; + if ((prev_line_idx == last_line_idx && prev_t == 1.) || ((prev_line_idx == total_lines_cnt && prev_t == 0.))) { + // There is no gap at the end. + return line_region_ranges_out; + } + + // Fill the last remaining gap. + line_region_ranges_out.emplace_back(prev_line_idx, prev_t, last_line_idx, 1., default_clip_idx); + + return line_region_ranges_out; +} + +LineRegionRanges subject_segmentation(const ClipperLib_Z::Path &subject, const std::vector &expolygons_clips, const size_t default_clip_idx = 0) +{ + LineRegionRanges line_region_ranges; + for (const ExPolygons &expolygons_clip : expolygons_clips) { + const size_t expolygons_clip_idx = &expolygons_clip - expolygons_clips.data(); + const ClipperLib_Z::Paths clips = to_clip_zpaths(expolygons_clip); + Slic3r::append(line_region_ranges, intersection_with_region(subject, clips, expolygons_clip_idx + default_clip_idx + 1)); + } + + return create_continues_line_region_ranges(std::move(line_region_ranges), default_clip_idx, subject.size() - 1); +} + +PolylineSegment create_polyline_segment(const LineRegionRange &line_region_range, const Polyline &subject) +{ + Polyline polyline_out; + if (line_region_range.begin_t == 0.) { + polyline_out.points.emplace_back(subject[line_region_range.begin_idx]); + } else { + assert(line_region_range.begin_idx <= subject.size()); + Point interpolated_start_pt = lerp(subject[line_region_range.begin_idx], subject[line_region_range.begin_idx + 1], line_region_range.begin_t); + polyline_out.points.emplace_back(interpolated_start_pt); + } + + for (size_t line_idx = line_region_range.begin_idx + 1; line_idx <= line_region_range.end_idx; ++line_idx) { + polyline_out.points.emplace_back(subject[line_idx]); + } + + if (line_region_range.end_t == 0.) { + polyline_out.points.emplace_back(subject[line_region_range.end_idx]); + } else if (line_region_range.end_t == 1.) { + assert(line_region_range.end_idx <= subject.size()); + polyline_out.points.emplace_back(subject[line_region_range.end_idx + 1]); + } else { + assert(line_region_range.end_idx <= subject.size()); + Point interpolated_end_pt = lerp(subject[line_region_range.end_idx], subject[line_region_range.end_idx + 1], line_region_range.end_t); + polyline_out.points.emplace_back(interpolated_end_pt); + } + + return { polyline_out, line_region_range.clip_idx }; +} + +PolylineSegments create_polyline_segments(const LineRegionRanges &line_region_ranges, const Polyline &subject) +{ + PolylineSegments polyline_segments; + polyline_segments.reserve(line_region_ranges.size()); + for (const LineRegionRange ®ion_range : line_region_ranges) { + polyline_segments.emplace_back(create_polyline_segment(region_range, subject)); + } + + return polyline_segments; +} + +ExtrusionSegment create_extrusion_segment(const LineRegionRange &line_region_range, const Arachne::ExtrusionLine &subject) +{ + // When we call this function, we split ExtrusionLine into at least two segments, so none of those segments are closed. + Arachne::ExtrusionLine extrusion_out(subject.inset_idx, subject.is_odd); + if (line_region_range.begin_t == 0.) { + extrusion_out.junctions.emplace_back(subject[line_region_range.begin_idx]); + } else { + assert(line_region_range.begin_idx <= subject.size()); + const Arachne::ExtrusionJunction &junction_from = subject[line_region_range.begin_idx]; + const Arachne::ExtrusionJunction &junction_to = subject[line_region_range.begin_idx + 1]; + + const Point interpolated_start_pt = lerp(junction_from.p, junction_to.p, line_region_range.begin_t); + const coord_t interpolated_start_w = lerp(junction_from.w, junction_to.w, line_region_range.begin_t); + + assert(junction_from.perimeter_index == junction_to.perimeter_index); + extrusion_out.junctions.emplace_back(interpolated_start_pt, interpolated_start_w, junction_from.perimeter_index); + } + + for (size_t line_idx = line_region_range.begin_idx + 1; line_idx <= line_region_range.end_idx; ++line_idx) { + extrusion_out.junctions.emplace_back(subject[line_idx]); + } + + if (line_region_range.end_t == 0.) { + extrusion_out.junctions.emplace_back(subject[line_region_range.end_idx]); + } else if (line_region_range.end_t == 1.) { + assert(line_region_range.end_idx <= subject.size()); + extrusion_out.junctions.emplace_back(subject[line_region_range.end_idx + 1]); + } else { + assert(line_region_range.end_idx <= subject.size()); + const Arachne::ExtrusionJunction &junction_from = subject[line_region_range.end_idx]; + const Arachne::ExtrusionJunction &junction_to = subject[line_region_range.end_idx + 1]; + + const Point interpolated_end_pt = lerp(junction_from.p, junction_to.p, line_region_range.end_t); + const coord_t interpolated_end_w = lerp(junction_from.w, junction_to.w, line_region_range.end_t); + + assert(junction_from.perimeter_index == junction_to.perimeter_index); + extrusion_out.junctions.emplace_back(interpolated_end_pt, interpolated_end_w, junction_from.perimeter_index); + } + + return { extrusion_out, line_region_range.clip_idx }; +} + +ExtrusionSegments create_extrusion_segments(const LineRegionRanges &line_region_ranges, const Arachne::ExtrusionLine &subject) +{ + ExtrusionSegments extrusion_segments; + extrusion_segments.reserve(line_region_ranges.size()); + for (const LineRegionRange ®ion_range : line_region_ranges) { + extrusion_segments.emplace_back(create_extrusion_segment(region_range, subject)); + } + + return extrusion_segments; +} + +PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector &expolygons_clips, const size_t default_clip_idx) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), expolygons_clips, default_clip_idx); + if (line_region_ranges.empty()) { + return { PolylineSegment{subject, default_clip_idx} }; + } else if (line_region_ranges.size() == 1) { + return { PolylineSegment{subject, line_region_ranges.front().clip_idx} }; + } + + return create_polyline_segments(line_region_ranges, subject); +} + +PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector &expolygons_clips, const size_t default_clip_idx) +{ + return polyline_segmentation(to_polyline(subject), expolygons_clips, default_clip_idx); +} + +ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector &expolygons_clips, const size_t default_clip_idx) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), expolygons_clips, default_clip_idx); + if (line_region_ranges.empty()) { + return { ExtrusionSegment{subject, default_clip_idx} }; + } else if (line_region_ranges.size() == 1) { + return { ExtrusionSegment{subject, line_region_ranges.front().clip_idx} }; + } + + return create_extrusion_segments(line_region_ranges, subject); +} + +inline std::vector to_expolygons_clips(const PerimeterRegions &perimeter_regions_clips) +{ + std::vector expolygons_clips; + expolygons_clips.reserve(perimeter_regions_clips.size()); + for (const PerimeterRegion &perimeter_region_clip : perimeter_regions_clips) { + expolygons_clips.emplace_back(perimeter_region_clip.expolygons); + } + + return expolygons_clips; +} + +PolylineRegionSegments polyline_segmentation(const Polyline &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), to_expolygons_clips(perimeter_regions_clips)); + if (line_region_ranges.empty()) { + return { PolylineRegionSegment{subject, base_config} }; + } else if (line_region_ranges.size() == 1) { + return { PolylineRegionSegment{subject, perimeter_regions_clips[line_region_ranges.front().clip_idx - 1].region->config()} }; + } + + PolylineRegionSegments segments_out; + for (PolylineSegment &segment : create_polyline_segments(line_region_ranges, subject)) { + const PrintRegionConfig &config = segment.clip_idx == 0 ? base_config : perimeter_regions_clips[segment.clip_idx - 1].region->config(); + segments_out.emplace_back(std::move(segment.polyline), config); + } + + return segments_out; +} + +PolylineRegionSegments polygon_segmentation(const Polygon &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips) +{ + return polyline_segmentation(to_polyline(subject), base_config, perimeter_regions_clips); +} + +ExtrusionRegionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), to_expolygons_clips(perimeter_regions_clips)); + if (line_region_ranges.empty()) { + return { ExtrusionRegionSegment{subject, base_config} }; + } else if (line_region_ranges.size() == 1) { + return { ExtrusionRegionSegment{subject, perimeter_regions_clips[line_region_ranges.front().clip_idx - 1].region->config()} }; + } + + ExtrusionRegionSegments segments_out; + for (ExtrusionSegment &segment : create_extrusion_segments(line_region_ranges, subject)) { + const PrintRegionConfig &config = segment.clip_idx == 0 ? base_config : perimeter_regions_clips[segment.clip_idx - 1].region->config(); + segments_out.emplace_back(std::move(segment.extrusion), config); + } + + return segments_out; +} + +} // namespace Slic3r::Algorithm::LineSegmentation diff --git a/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp new file mode 100644 index 0000000000..5e53e0add0 --- /dev/null +++ b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp @@ -0,0 +1,69 @@ +#ifndef libslic3r_LineSegmentation_hpp_ +#define libslic3r_LineSegmentation_hpp_ + +#include + +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" + +namespace Slic3r { +class ExPolygon; +class Polyline; +class Polygon; +class PrintRegionConfig; + +struct PerimeterRegion; + +using ExPolygons = std::vector; +using PerimeterRegions = std::vector; +} // namespace Slic3r + +namespace Slic3r::Arachne { +struct ExtrusionLine; +} + +namespace Slic3r::Algorithm::LineSegmentation { + +struct PolylineSegment +{ + Polyline polyline; + size_t clip_idx; +}; + +struct PolylineRegionSegment +{ + Polyline polyline; + const PrintRegionConfig &config; + + PolylineRegionSegment(const Polyline &polyline, const PrintRegionConfig &config) : polyline(polyline), config(config) {} +}; + +struct ExtrusionSegment +{ + Arachne::ExtrusionLine extrusion; + size_t clip_idx; +}; + +struct ExtrusionRegionSegment +{ + Arachne::ExtrusionLine extrusion; + const PrintRegionConfig &config; + + ExtrusionRegionSegment(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &config) : extrusion(extrusion), config(config) {} +}; + +using PolylineSegments = std::vector; +using ExtrusionSegments = std::vector; +using PolylineRegionSegments = std::vector; +using ExtrusionRegionSegments = std::vector; + +PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector &expolygons_clips, size_t default_clip_idx = 0); +PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector &expolygons_clips, size_t default_clip_idx = 0); +ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector &expolygons_clips, size_t default_clip_idx = 0); + +PolylineRegionSegments polyline_segmentation(const Polyline &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips); +PolylineRegionSegments polygon_segmentation(const Polygon &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips); +ExtrusionRegionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips); + +} // namespace Slic3r::Algorithm::LineSegmentation + +#endif // libslic3r_LineSegmentation_hpp_ diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 2e65c77921..de31aaba16 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -34,6 +34,8 @@ set(SLIC3R_SOURCES AABBTreeLines.hpp AABBMesh.hpp AABBMesh.cpp + Algorithm/LineSegmentation/LineSegmentation.cpp + Algorithm/LineSegmentation/LineSegmentation.hpp Algorithm/PathSorting.hpp Algorithm/RegionExpansion.hpp Algorithm/RegionExpansion.cpp diff --git a/src/libslic3r/ClipperZUtils.hpp b/src/libslic3r/ClipperZUtils.hpp index f6b249b47f..2c71f4bfd5 100644 --- a/src/libslic3r/ClipperZUtils.hpp +++ b/src/libslic3r/ClipperZUtils.hpp @@ -71,6 +71,24 @@ inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t &base_idx) return out; } +// Convert multiple expolygons into z-paths with a given Z coordinate. +// If Open, then duplicate the first point of each path at its end. +template +inline ZPaths expolygons_to_zpaths_with_same_z(const ExPolygons &src, const coord_t z) +{ + ZPaths out; + out.reserve(std::accumulate(src.begin(), src.end(), size_t(0), + [](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); })); + for (const ExPolygon &expoly : src) { + out.emplace_back(to_zpath(expoly.contour.points, z)); + for (const Polygon &hole : expoly.holes) { + out.emplace_back(to_zpath(hole.points, z)); + } + } + + return out; +} + // Convert a single path to path with a given Z coordinate. // If Open, then duplicate the first point at the end. template diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 2a8ce5d7be..1cf9e1882d 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -29,6 +29,7 @@ #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" #include "libslic3r/LayerRegion.hpp" +#include "libslic3r/PerimeterGenerator.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/Surface.hpp" #include "libslic3r/SurfaceCollection.hpp" @@ -632,6 +633,36 @@ ExPolygons Layer::merged(float offset_scaled) const return out; } +// If there is any incompatibility, separate LayerRegions have to be created. +inline bool has_compatible_dynamic_overhang_speed(const PrintRegionConfig &config, const PrintRegionConfig &other_config) +{ + bool dynamic_overhang_speed_compatibility = config.enable_dynamic_overhang_speeds == other_config.enable_dynamic_overhang_speeds; + if (dynamic_overhang_speed_compatibility && config.enable_dynamic_overhang_speeds) { + dynamic_overhang_speed_compatibility = config.overhang_speed_0 == other_config.overhang_speed_0 && + config.overhang_speed_1 == other_config.overhang_speed_1 && + config.overhang_speed_2 == other_config.overhang_speed_2 && + config.overhang_speed_3 == other_config.overhang_speed_3; + } + + return dynamic_overhang_speed_compatibility; +} + +// If there is any incompatibility, separate LayerRegions have to be created. +inline bool has_compatible_layer_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config) +{ + return config.perimeter_extruder == other_config.perimeter_extruder && + config.perimeters == other_config.perimeters && + config.perimeter_speed == other_config.perimeter_speed && + config.external_perimeter_speed == other_config.external_perimeter_speed && + (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) == (other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.) && + config.overhangs == other_config.overhangs && + config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") && + config.thin_walls == other_config.thin_walls && + config.external_perimeters_first == other_config.external_perimeters_first && + config.infill_overlap == other_config.infill_overlap && + has_compatible_dynamic_overhang_speed(config, other_config); +} + // Here the perimeters are created cummulatively for all layer regions sharing the same parameters influencing the perimeters. // The perimeter paths and the thin fills (ExtrusionEntityCollection) are assigned to the first compatible layer region. // The resulting fill surface is split back among the originating regions. @@ -662,98 +693,105 @@ void Layer::make_perimeters() for (LayerSlice &lslice : this->lslices_ex) lslice.islands.clear(); - for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) - if (size_t region_id = layerm - m_regions.begin(); ! done[region_id]) { - layer_region_reset_perimeters(**layerm); - if (! (*layerm)->slices().empty()) { - BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; - done[region_id] = true; - const PrintRegionConfig &config = (*layerm)->region().config(); - - perimeter_and_gapfill_ranges.clear(); - fill_expolygons.clear(); - fill_expolygons_ranges.clear(); - surfaces_to_merge.clear(); - - // find compatible regions - layer_region_ids.clear(); - layer_region_ids.push_back(region_id); - for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) - if (! (*it)->slices().empty()) { - LayerRegion *other_layerm = *it; - const PrintRegionConfig &other_config = other_layerm->region().config(); - bool dynamic_overhang_speed_compatibility = config.enable_dynamic_overhang_speeds == - other_config.enable_dynamic_overhang_speeds; - if (dynamic_overhang_speed_compatibility && config.enable_dynamic_overhang_speeds) { - dynamic_overhang_speed_compatibility = config.overhang_speed_0 == other_config.overhang_speed_0 && - config.overhang_speed_1 == other_config.overhang_speed_1 && - config.overhang_speed_2 == other_config.overhang_speed_2 && - config.overhang_speed_3 == other_config.overhang_speed_3; - } - - if (config.perimeter_extruder == other_config.perimeter_extruder - && config.perimeters == other_config.perimeters - && config.perimeter_speed == other_config.perimeter_speed - && config.external_perimeter_speed == other_config.external_perimeter_speed - && dynamic_overhang_speed_compatibility - && (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) == - (other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.) - && config.overhangs == other_config.overhangs - && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") - && config.thin_walls == other_config.thin_walls - && config.external_perimeters_first == other_config.external_perimeters_first - && config.infill_overlap == other_config.infill_overlap - && config.fuzzy_skin == other_config.fuzzy_skin - && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness - && config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist) - { - layer_region_reset_perimeters(*other_layerm); - layer_region_ids.push_back(it - m_regions.begin()); - done[it - m_regions.begin()] = true; - } - } - - if (layer_region_ids.size() == 1) { // optimization - (*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); - this->sort_perimeters_into_islands((*layerm)->slices(), region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); - } else { - SurfaceCollection new_slices; - // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. - uint32_t region_id_config = layer_region_ids.front(); - LayerRegion* layerm_config = m_regions[region_id_config]; - { - // Merge slices (surfaces) according to number of extra perimeters. - for (uint32_t region_id : layer_region_ids) { - LayerRegion &layerm = *m_regions[region_id]; - for (const Surface &surface : layerm.slices()) - surfaces_to_merge.emplace_back(&surface); - if (layerm.region().config().fill_density > layerm_config->region().config().fill_density) { - region_id_config = region_id; - layerm_config = &layerm; - } - } - std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r){ return l->extra_perimeters < r->extra_perimeters; }); - for (size_t i = 0; i < surfaces_to_merge.size();) { - size_t j = i; - const Surface &first = *surfaces_to_merge[i]; - size_t extra_perimeters = first.extra_perimeters; - for (; j < surfaces_to_merge.size() && surfaces_to_merge[j]->extra_perimeters == extra_perimeters; ++ j) ; - if (i + 1 == j) - // Nothing to merge, just copy. - new_slices.surfaces.emplace_back(*surfaces_to_merge[i]); - else { - surfaces_to_merge_temp.assign(surfaces_to_merge.begin() + i, surfaces_to_merge.begin() + j); - new_slices.append(offset_ex(surfaces_to_merge_temp, ClipperSafetyOffset), first); - } - i = j; - } - } - // make perimeters - layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); - this->sort_perimeters_into_islands(new_slices, region_id_config, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); - } - } + for (auto it_curr_region = m_regions.cbegin(); it_curr_region != m_regions.cend(); ++it_curr_region) { + const size_t curr_region_id = std::distance(m_regions.cbegin(), it_curr_region); + if (done[curr_region_id]) { + continue; } + + LayerRegion &curr_region = **it_curr_region; + layer_region_reset_perimeters(curr_region); + if (curr_region.slices().empty()) { + continue; + } + + BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << curr_region_id; + done[curr_region_id] = true; + const PrintRegionConfig &curr_config = curr_region.region().config(); + + perimeter_and_gapfill_ranges.clear(); + fill_expolygons.clear(); + fill_expolygons_ranges.clear(); + surfaces_to_merge.clear(); + + // Find compatible regions. + layer_region_ids.clear(); + layer_region_ids.push_back(curr_region_id); + + PerimeterRegions perimeter_regions; + for (auto it_next_region = std::next(it_curr_region); it_next_region != m_regions.cend(); ++it_next_region) { + const size_t next_region_id = std::distance(m_regions.cbegin(), it_next_region); + LayerRegion &next_region = **it_next_region; + const PrintRegionConfig &next_config = next_region.region().config(); + if (next_region.slices().empty()) { + continue; + } + + if (!has_compatible_layer_regions(curr_config, next_config)) { + continue; + } + + // Now, we are sure that we want to merge LayerRegions in any case. + layer_region_reset_perimeters(next_region); + layer_region_ids.push_back(next_region_id); + done[next_region_id] = true; + + // If any parameters affecting just perimeters are incompatible, then we also create PerimeterRegion. + if (!PerimeterRegion::has_compatible_perimeter_regions(curr_config, next_config)) { + perimeter_regions.emplace_back(next_region); + } + } + + if (layer_region_ids.size() == 1) { // Optimization. + curr_region.make_perimeters(curr_region.slices(), perimeter_regions, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); + this->sort_perimeters_into_islands(curr_region.slices(), curr_region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); + } else { + SurfaceCollection new_slices; + // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. + uint32_t region_id_config = layer_region_ids.front(); + LayerRegion *layerm_config = m_regions[region_id_config]; + { + // Merge slices (surfaces) according to number of extra perimeters. + for (uint32_t region_id : layer_region_ids) { + LayerRegion &layerm = *m_regions[region_id]; + for (const Surface &surface : layerm.slices()) + surfaces_to_merge.emplace_back(&surface); + if (layerm.region().config().fill_density > layerm_config->region().config().fill_density) { + region_id_config = region_id; + layerm_config = &layerm; + } + } + + std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r) { return l->extra_perimeters < r->extra_perimeters; }); + for (size_t i = 0; i < surfaces_to_merge.size();) { + size_t j = i; + const Surface &first = *surfaces_to_merge[i]; + size_t extra_perimeters = first.extra_perimeters; + for (; j < surfaces_to_merge.size() && surfaces_to_merge[j]->extra_perimeters == extra_perimeters; ++j); + + if (i + 1 == j) { + // Nothing to merge, just copy. + new_slices.surfaces.emplace_back(*surfaces_to_merge[i]); + } else { + surfaces_to_merge_temp.assign(surfaces_to_merge.begin() + i, surfaces_to_merge.begin() + j); + new_slices.append(offset_ex(surfaces_to_merge_temp, ClipperSafetyOffset), first); + } + + i = j; + } + } + + // Try to merge compatible PerimeterRegions. + if (perimeter_regions.size() > 1) { + PerimeterRegion::merge_compatible_perimeter_regions(perimeter_regions); + } + + // Make perimeters. + layerm_config->make_perimeters(new_slices, perimeter_regions, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); + this->sort_perimeters_into_islands(new_slices, region_id_config, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); + } + } + BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done"; } diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 89e27cd8bc..c913082636 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -89,6 +89,8 @@ void LayerRegion::slices_to_fill_surfaces_clipped() void LayerRegion::make_perimeters( // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. const SurfaceCollection &slices, + // Configuration regions that will be applied to parts of created perimeters. + const PerimeterRegions &perimeter_regions, // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing // newly created extrusions stored at this LayerRegion. std::vector> &perimeter_and_gapfill_ranges, @@ -123,6 +125,7 @@ void LayerRegion::make_perimeters( region_config, this->layer()->object()->config(), print_config, + perimeter_regions, spiral_vase ); diff --git a/src/libslic3r/LayerRegion.hpp b/src/libslic3r/LayerRegion.hpp index b05c750241..30e9f18e8d 100644 --- a/src/libslic3r/LayerRegion.hpp +++ b/src/libslic3r/LayerRegion.hpp @@ -29,6 +29,9 @@ class PrintObject; using LayerPtrs = std::vector; class PrintRegion; +struct PerimeterRegion; +using PerimeterRegions = std::vector; + // Range of indices, providing support for range based loops. template class IndexRange @@ -119,6 +122,8 @@ public: void make_perimeters( // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. const SurfaceCollection &slices, + // Configuration regions that will be applied to parts of created perimeters. + const PerimeterRegions &perimeter_regions, // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing // newly created extrusions stored at this LayerRegion. std::vector> &perimeter_and_gapfill_ranges, diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 2f73a704c9..01f8dd7f8f 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -42,7 +42,9 @@ #include "Arachne/utils/ExtrusionJunction.hpp" #include "libslic3r.h" #include "libslic3r/Flow.hpp" +#include "libslic3r/LayerRegion.hpp" #include "libslic3r/Line.hpp" +#include "libslic3r/Print.hpp" //#define ARACHNE_DEBUG @@ -1544,4 +1546,43 @@ void PerimeterGenerator::process_classic( append(out_fill_expolygons, std::move(infill_areas)); } +PerimeterRegion::PerimeterRegion(const LayerRegion &layer_region) : region(&layer_region.region()) +{ + this->expolygons = to_expolygons(layer_region.slices().surfaces); + this->bbox = get_extents(this->expolygons); +} + +bool PerimeterRegion::has_compatible_perimeter_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config) +{ + return config.fuzzy_skin == other_config.fuzzy_skin && + config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness && + config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist; +} + +void PerimeterRegion::merge_compatible_perimeter_regions(PerimeterRegions &perimeter_regions) +{ + if (perimeter_regions.size() <= 1) { + return; + } + + PerimeterRegions perimeter_regions_merged; + for (auto it_curr_region = perimeter_regions.begin(); it_curr_region != perimeter_regions.end();) { + PerimeterRegion current_merge = *it_curr_region; + auto it_next_region = std::next(it_curr_region); + for (; it_next_region != perimeter_regions.end() && has_compatible_perimeter_regions(it_next_region->region->config(), it_curr_region->region->config()); ++it_next_region) { + Slic3r::append(current_merge.expolygons, std::move(it_next_region->expolygons)); + current_merge.bbox.merge(it_next_region->bbox); + } + + if (std::distance(it_curr_region, it_next_region) > 1) { + current_merge.expolygons = union_ex(current_merge.expolygons); + } + + perimeter_regions_merged.emplace_back(std::move(current_merge)); + it_curr_region = it_next_region; + } + + perimeter_regions = perimeter_regions_merged; +} + } diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 6df0923635..1e699abb79 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -22,11 +22,31 @@ namespace Slic3r { class ExtrusionEntityCollection; +class LayerRegion; class Surface; +class PrintRegion; struct ThickPolyline; -namespace PerimeterGenerator +struct PerimeterRegion { + const PrintRegion *region; + ExPolygons expolygons; + BoundingBox bbox; + + explicit PerimeterRegion(const LayerRegion &layer_region); + + // If there is any incompatibility, we don't need to create separate LayerRegions. + // Because it is enough to split perimeters by PerimeterRegions. + static bool has_compatible_perimeter_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config); + + static void merge_compatible_perimeter_regions(std::vector &perimeter_regions); +}; + +using PerimeterRegions = std::vector; + +} // namespace Slic3r + +namespace Slic3r::PerimeterGenerator { struct Parameters { Parameters( @@ -39,6 +59,7 @@ struct Parameters { const PrintRegionConfig &config, const PrintObjectConfig &object_config, const PrintConfig &print_config, + const PerimeterRegions &perimeter_regions, const bool spiral_vase) : layer_height(layer_height), layer_id(layer_id), @@ -49,6 +70,7 @@ struct Parameters { config(config), object_config(object_config), print_config(print_config), + perimeter_regions(perimeter_regions), spiral_vase(spiral_vase), scaled_resolution(scaled(print_config.gcode_resolution.value)), mm3_per_mm(perimeter_flow.mm3_per_mm()), @@ -67,6 +89,7 @@ struct Parameters { const PrintRegionConfig &config; const PrintObjectConfig &object_config; const PrintConfig &print_config; + const PerimeterRegions &perimeter_regions; // Derived parameters bool spiral_vase; @@ -113,7 +136,6 @@ void process_arachne( ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, float tolerance, float merge_tolerance); -} // namespace PerimeterGenerator -} // namespace Slic3r +} // namespace Slic3r::PerimeterGenerator #endif diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp index c154f5a0c2..fadebe76d4 100644 --- a/tests/fff_print/test_perimeters.cpp +++ b/tests/fff_print/test_perimeters.cpp @@ -46,6 +46,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]") ExtrusionEntityCollection gap_fill; ExPolygons fill_expolygons; Flow flow(1., 1., 1.); + PerimeterRegions perimeter_regions; PerimeterGenerator::Parameters perimeter_generator_params( 1., // layer height -1, // layer ID @@ -53,6 +54,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]") static_cast(config), static_cast(config), static_cast(config), + perimeter_regions, false); // spiral_vase Polygons lower_layer_polygons_cache; for (const Surface &surface : slices) From 6dadcee6abb55aee9d6867452b6da108eac603f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Oct 2024 18:10:48 +0200 Subject: [PATCH 09/10] SPE-2486: Reimplement fuzzy skin to be applied only to perimeter parts instead of creating different LayerRegion. --- src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp | 94 +++++++++++++++++++ src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp | 4 + src/libslic3r/PerimeterGenerator.cpp | 18 ++-- 3 files changed, 104 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp index ba27b6c734..9d2b9d3178 100644 --- a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp +++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp @@ -1,5 +1,6 @@ #include +#include "libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp" #include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" #include "libslic3r/Arachne/utils/ExtrusionLine.hpp" #include "libslic3r/PerimeterGenerator.hpp" @@ -133,4 +134,97 @@ bool should_fuzzify(const PrintRegionConfig &config, const size_t layer_idx, con return is_contour ? fuzzify_contours : fuzzify_holes; } +Polygon apply_fuzzy_skin(const Polygon &polygon, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour) +{ + using namespace Slic3r::Algorithm::LineSegmentation; + + auto apply_fuzzy_skin_on_polygon = [&layer_idx, &perimeter_idx, &is_contour](const Polygon &polygon, const PrintRegionConfig &config) -> Polygon { + if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) { + Polygon fuzzified_polygon = polygon; + fuzzy_polygon(fuzzified_polygon, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_dist.value)); + + return fuzzified_polygon; + } else { + return polygon; + } + }; + + if (perimeter_regions.empty()) { + return apply_fuzzy_skin_on_polygon(polygon, base_config); + } + + PolylineRegionSegments segments = polygon_segmentation(polygon, base_config, perimeter_regions); + if (segments.size() == 1) { + const PrintRegionConfig &config = segments.front().config; + return apply_fuzzy_skin_on_polygon(polygon, config); + } + + Polygon fuzzified_polygon; + for (PolylineRegionSegment &segment : segments) { + const PrintRegionConfig &config = segment.config; + if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) { + fuzzy_polyline(segment.polyline.points, false, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_dist.value)); + } + + assert(!segment.polyline.empty()); + if (segment.polyline.empty()) { + continue; + } else if (!fuzzified_polygon.empty() && fuzzified_polygon.back() == segment.polyline.front()) { + // Remove the last point to avoid duplicate points. + fuzzified_polygon.points.pop_back(); + } + + Slic3r::append(fuzzified_polygon.points, std::move(segment.polyline.points)); + } + + assert(!fuzzified_polygon.empty()); + if (fuzzified_polygon.front() == fuzzified_polygon.back()) { + // Remove the last point to avoid duplicity between the first and the last point. + fuzzified_polygon.points.pop_back(); + } + + return fuzzified_polygon; +} + +Arachne::ExtrusionLine apply_fuzzy_skin(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour) +{ + using namespace Slic3r::Algorithm::LineSegmentation; + using namespace Slic3r::Arachne; + + if (perimeter_regions.empty()) { + if (should_fuzzify(base_config, layer_idx, perimeter_idx, is_contour)) { + ExtrusionLine fuzzified_extrusion = extrusion; + fuzzy_extrusion_line(fuzzified_extrusion, scaled(base_config.fuzzy_skin_thickness.value), scaled(base_config.fuzzy_skin_point_dist.value)); + + return fuzzified_extrusion; + } else { + return extrusion; + } + } + + ExtrusionRegionSegments segments = extrusion_segmentation(extrusion, base_config, perimeter_regions); + ExtrusionLine fuzzified_extrusion; + + for (ExtrusionRegionSegment &segment : segments) { + const PrintRegionConfig &config = segment.config; + if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) { + fuzzy_extrusion_line(segment.extrusion, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_dist.value)); + } + + assert(!segment.extrusion.empty()); + if (segment.extrusion.empty()) { + continue; + } else if (!fuzzified_extrusion.empty() && fuzzified_extrusion.back().p == segment.extrusion.front().p) { + // Remove the last point to avoid duplicate points (We don't care if the width of both points is different.). + fuzzified_extrusion.junctions.pop_back(); + } + + Slic3r::append(fuzzified_extrusion.junctions, std::move(segment.extrusion.junctions)); + } + + assert(!fuzzified_extrusion.empty()); + + return fuzzified_extrusion; +} + } // namespace Slic3r::Feature::FuzzySkin diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp index e852b4114f..fb5db546f3 100644 --- a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp +++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp @@ -17,6 +17,10 @@ void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy_skin_t bool should_fuzzify(const PrintRegionConfig &config, size_t layer_idx, size_t perimeter_idx, bool is_contour); +Polygon apply_fuzzy_skin(const Polygon &polygon, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, size_t layer_idx, size_t perimeter_idx, bool is_contour); + +Arachne::ExtrusionLine apply_fuzzy_skin(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, size_t layer_idx, size_t perimeter_idx, bool is_contour); + } // namespace Slic3r::Feature::FuzzySkin #endif // libslic3r_FuzzySkin_hpp_ diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 01f8dd7f8f..fa0e11b5a1 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -206,8 +206,7 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator // loops is an arrayref of ::Loop objects // turn each one into an ExtrusionLoop object - ExtrusionEntityCollection coll; - Polygon fuzzified; + ExtrusionEntityCollection coll; for (const PerimeterGeneratorLoop &loop : loops) { bool is_external = loop.is_external(); @@ -223,12 +222,8 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator loop_role = elrDefault; } - const bool fuzzify = should_fuzzify(params.config, params.layer_id, loop.depth, loop.is_contour); - const Polygon &polygon = fuzzify ? fuzzified : loop.polygon; - if (fuzzify) { - fuzzified = loop.polygon; - fuzzy_polygon(fuzzified, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); - } + // Apply fuzzy skin if it is enabled for at least some part of the polygon. + const Polygon polygon = apply_fuzzy_skin(loop.polygon, params.config, params.perimeter_regions, params.layer_id, loop.depth, loop.is_contour); ExtrusionPaths paths; if (params.config.overhangs && params.layer_id > params.object_config.raft_layers && @@ -425,7 +420,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P ExtrusionEntityCollection extrusion_coll; for (Arachne::PerimeterOrder::PerimeterExtrusion &pg_extrusion : pg_extrusions) { - Arachne::ExtrusionLine &extrusion = pg_extrusion.extrusion; + Arachne::ExtrusionLine extrusion = pg_extrusion.extrusion; if (extrusion.empty()) continue; @@ -433,9 +428,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P ExtrusionRole role_normal = is_external ? ExtrusionRole::ExternalPerimeter : ExtrusionRole::Perimeter; ExtrusionRole role_overhang = role_normal | ExtrusionRoleModifier::Bridge; - const bool fuzzify = should_fuzzify(params.config, params.layer_id, pg_extrusion.extrusion.inset_idx, !pg_extrusion.extrusion.is_closed || pg_extrusion.is_contour()); - if (fuzzify) - fuzzy_extrusion_line(extrusion, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); + // Apply fuzzy skin if it is enabled for at least some part of the ExtrusionLine. + extrusion = apply_fuzzy_skin(extrusion, params.config, params.perimeter_regions, params.layer_id, pg_extrusion.extrusion.inset_idx, !pg_extrusion.extrusion.is_closed || pg_extrusion.is_contour()); ExtrusionPaths paths; // detect overhanging/bridging perimeters From fa2663f02647f80b239da4f45d92ef66f5ce048a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 25 Oct 2024 16:11:20 +0200 Subject: [PATCH 10/10] SPE-2486: Limit the depth of the painted fuzzy skin regions to make regions cover just external perimeters. This reduces the possibility of artifacts that could happen during regions merging. --- src/libslic3r/MultiMaterialSegmentation.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index f183a8ba88..b913e17242 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1868,7 +1868,15 @@ std::vector> fuzzy_skin_segmentation_by_painting(const P return {mv.fuzzy_skin_facets, mv.is_fuzzy_skin_painted(), false}; }; - return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, 0.f, 0.f, IncludeTopAndBottomLayers::No, throw_on_cancel_callback); + // Because we apply fuzzy skin just on external perimeters, we limit the depth of fuzzy skin + // by the maximal extrusion width of external perimeters. + float max_external_perimeter_width = 0.; + for (size_t region_idx = 0; region_idx < print_object.num_printing_regions(); ++region_idx) { + const PrintRegion ®ion = print_object.printing_region(region_idx); + max_external_perimeter_width = std::max(max_external_perimeter_width, region.flow(print_object, frExternalPerimeter, print_object.config().layer_height).width()); + } + + return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, max_external_perimeter_width, 0.f, IncludeTopAndBottomLayers::No, throw_on_cancel_callback); }