From 9797110a9668003aec294affd80a754c646c2e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Tue, 21 Nov 2023 14:48:25 +0100 Subject: [PATCH] Refactor: Move "LayerRegion" to separate header and add a test. Move declarations associated with LayerRegion to a separate header file. Add test for bridge surface expansion to enable further refactoring. --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/IndexRange.hpp | 44 +++++++ src/libslic3r/Layer.hpp | 176 +------------------------- src/libslic3r/LayerRegion.hpp | 175 +++++++++++++++++++++++++ tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_layer_region.cpp | 98 ++++++++++++++ 6 files changed, 322 insertions(+), 174 deletions(-) create mode 100644 src/libslic3r/IndexRange.hpp create mode 100644 src/libslic3r/LayerRegion.hpp create mode 100644 tests/libslic3r/test_layer_region.cpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 9e6d1434fb..959e9d9bd7 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -221,8 +221,10 @@ set(SLIC3R_SOURCES JumpPointSearch.cpp JumpPointSearch.hpp KDTreeIndirect.hpp + IndexRange.hpp Layer.cpp Layer.hpp + LayerRegion.hpp LayerRegion.cpp libslic3r.h "${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h" diff --git a/src/libslic3r/IndexRange.hpp b/src/libslic3r/IndexRange.hpp new file mode 100644 index 0000000000..1c8b69ec87 --- /dev/null +++ b/src/libslic3r/IndexRange.hpp @@ -0,0 +1,44 @@ +#ifndef slic3r_IndexRange_hpp_ +#define slic3r_IndexRange_hpp_ + +#include +#include + +namespace Slic3r { +// Range of indices, providing support for range based loops. +template +class IndexRange +{ +public: + IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {} + IndexRange() = default; + + // Just a bare minimum functionality iterator required by range-for loop. + class Iterator { + public: + T operator*() const { return m_idx; } + bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; } + void operator++() { ++ m_idx; } + private: + friend class IndexRange; + Iterator(T idx) : m_idx(idx) {} + T m_idx; + }; + + Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); }; + Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); }; + + bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; } + T size() const { assert(m_begin <= m_end); return m_end - m_begin; } + +private: + // Index of the first extrusion in LayerRegion. + T m_begin { 0 }; + // Index of the last extrusion in LayerRegion. + T m_end { 0 }; +}; + +template class IndexRange; +} // Slic3r + +#endif // slic3r_IndexRange_hpp_ diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index e66a6c8a0c..d926edb3e9 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -17,6 +17,8 @@ #include "Flow.hpp" #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" +#include "IndexRange.hpp" +#include "LayerRegion.hpp" #include @@ -39,42 +41,6 @@ namespace FillLightning { class Generator; }; -// Range of indices, providing support for range based loops. -template -class IndexRange -{ -public: - IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {} - IndexRange() = default; - - // Just a bare minimum functionality iterator required by range-for loop. - class Iterator { - public: - T operator*() const { return m_idx; } - bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; } - void operator++() { ++ m_idx; } - private: - friend class IndexRange; - Iterator(T idx) : m_idx(idx) {} - T m_idx; - }; - - Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); }; - Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); }; - - bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; } - T size() const { assert(m_begin <= m_end); return m_end - m_begin; } - -private: - // Index of the first extrusion in LayerRegion. - T m_begin { 0 }; - // Index of the last extrusion in LayerRegion. - T m_end { 0 }; -}; - -using ExtrusionRange = IndexRange; -using ExPolygonRange = IndexRange; - // Range of extrusions, referencing the source region by an index. class LayerExtrusionRange : public ExtrusionRange { @@ -101,144 +67,6 @@ using LayerExtrusionRanges = std::vector; #endif // NDEBUG -class LayerRegion -{ -public: - [[nodiscard]] Layer* layer() { return m_layer; } - [[nodiscard]] const Layer* layer() const { return m_layer; } - [[nodiscard]] const PrintRegion& region() const { return *m_region; } - - // collection of surfaces generated by slicing the original geometry - // divided by type top/bottom/internal - [[nodiscard]] const SurfaceCollection& slices() const { return m_slices; } - - // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") - // and for re-starting of infills. - [[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; } - // and their bounding boxes - [[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; } - // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. - // Not used for a plain single material print with no infill modifiers. - [[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; } - // and their bounding boxes - [[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; } - - // collection of surfaces generated by slicing the original geometry - // divided by type top/bottom/internal - [[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; } - - // collection of extrusion paths/loops filling gaps - // These fills are generated by the perimeter generator. - // They are not printed on their own, but they are copied to this->fills during infill generation. - [[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; } - - // collection of polylines representing the unsupported bridge edges - [[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; } - - // ordered collection of extrusion paths/loops to build all perimeters - // (this collection contains only ExtrusionEntityCollection objects) - [[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; } - - // ordered collection of extrusion paths to fill surfaces - // (this collection contains only ExtrusionEntityCollection objects) - [[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; } - - Flow flow(FlowRole role) const; - Flow flow(FlowRole role, double layer_height) const; - Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const; - - void slices_to_fill_surfaces_clipped(); - void prepare_fill_surfaces(); - // Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices. - void make_perimeters( - // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. - const SurfaceCollection &slices, - // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing - // newly created extrusions stored at this LayerRegion. - std::vector> &perimeter_and_gapfill_ranges, - // All fill areas produced for all input slices above. - ExPolygons &fill_expolygons, - // Ranges of fill areas above per input slice. - std::vector &fill_expolygons_ranges); - void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered); - double infill_area_threshold() const; - // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. - void trim_surfaces(const Polygons &trimming_polygons); - // Single elephant foot compensation step, used by the elephant foor compensation at the 1st layer. - // Trim surfaces by trimming polygons (shrunk by an elephant foot compensation step), but don't shrink narrow parts so much that no perimeter would fit. - void elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons); - - void export_region_slices_to_svg(const char *path) const; - void export_region_fill_surfaces_to_svg(const char *path) const; - // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. - void export_region_slices_to_svg_debug(const char *name) const; - void export_region_fill_surfaces_to_svg_debug(const char *name) const; - - // Is there any valid extrusion assigned to this LayerRegion? - bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); } - -protected: - friend class Layer; - friend class PrintObject; - - LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {} - ~LayerRegion() = default; - -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); - - Layer *m_layer; - const PrintRegion *m_region; - - // Backed up slices before they are split into top/bottom/internal. - // Only backed up for multi-region layers or layers with elephant foot compensation. - //FIXME Review whether not to simplify the code by keeping the raw_slices all the time. - ExPolygons m_raw_slices; - -//FIXME make m_slices public for unit tests -public: - // collection of surfaces generated by slicing the original geometry - // divided by type top/bottom/internal - SurfaceCollection m_slices; - -private: - // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") - // and for re-starting of infills. - ExPolygons m_fill_expolygons; - // and their bounding boxes - BoundingBoxes m_fill_expolygons_bboxes; - // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. - // Not used for a plain single material print with no infill modifiers. - ExPolygons m_fill_expolygons_composite; - // and their bounding boxes - BoundingBoxes m_fill_expolygons_composite_bboxes; - - // Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons. - SurfaceCollection m_fill_surfaces; - - // Collection of extrusion paths/loops filling gaps - // These fills are generated by the perimeter generator. - // They are not printed on their own, but they are copied to this->fills during infill generation. - ExtrusionEntityCollection m_thin_fills; - - // collection of polylines representing the unsupported bridge edges - Polylines m_unsupported_bridge_edges; - - // ordered collection of extrusion paths/loops to build all perimeters - // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection m_perimeters; - - // ordered collection of extrusion paths to fill surfaces - // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection m_fills; - - // collection of expolygons representing the bridged areas (thus not - // needing support material) -// Polygons bridged; -}; // LayerSlice contains one or more LayerIsland objects, // each LayerIsland containing a set of perimeter extrusions extruded with one particular PrintRegionConfig parameters diff --git a/src/libslic3r/LayerRegion.hpp b/src/libslic3r/LayerRegion.hpp new file mode 100644 index 0000000000..9f6deb6673 --- /dev/null +++ b/src/libslic3r/LayerRegion.hpp @@ -0,0 +1,175 @@ +#ifndef slic3r_LayerRegion_hpp_ +#define slic3r_LayerRegion_hpp_ + +#include "BoundingBox.hpp" +#include "ExtrusionEntityCollection.hpp" +#include "SurfaceCollection.hpp" +#include "IndexRange.hpp" +#include "libslic3r/Algorithm/RegionExpansion.hpp" + + +namespace Slic3r { + +class Layer; +using LayerPtrs = std::vector; +class PrintRegion; + +using ExtrusionRange = IndexRange; +using ExPolygonRange = IndexRange; + +class LayerRegion +{ +public: + [[nodiscard]] Layer* layer() { return m_layer; } + [[nodiscard]] const Layer* layer() const { return m_layer; } + [[nodiscard]] const PrintRegion& region() const { return *m_region; } + + // collection of surfaces generated by slicing the original geometry + // divided by type top/bottom/internal + [[nodiscard]] const SurfaceCollection& slices() const { return m_slices; } + + // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") + // and for re-starting of infills. + [[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; } + // and their bounding boxes + [[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; } + // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. + // Not used for a plain single material print with no infill modifiers. + [[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; } + // and their bounding boxes + [[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; } + + // collection of surfaces generated by slicing the original geometry + // divided by type top/bottom/internal + [[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; } + + // collection of extrusion paths/loops filling gaps + // These fills are generated by the perimeter generator. + // They are not printed on their own, but they are copied to this->fills during infill generation. + [[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; } + + // collection of polylines representing the unsupported bridge edges + [[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; } + + // ordered collection of extrusion paths/loops to build all perimeters + // (this collection contains only ExtrusionEntityCollection objects) + [[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; } + + // ordered collection of extrusion paths to fill surfaces + // (this collection contains only ExtrusionEntityCollection objects) + [[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; } + + Flow flow(FlowRole role) const; + Flow flow(FlowRole role, double layer_height) const; + Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const; + + void slices_to_fill_surfaces_clipped(); + void prepare_fill_surfaces(); + // Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices. + void make_perimeters( + // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. + const SurfaceCollection &slices, + // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing + // newly created extrusions stored at this LayerRegion. + std::vector> &perimeter_and_gapfill_ranges, + // All fill areas produced for all input slices above. + ExPolygons &fill_expolygons, + // Ranges of fill areas above per input slice. + std::vector &fill_expolygons_ranges); + void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered); + double infill_area_threshold() const; + // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. + void trim_surfaces(const Polygons &trimming_polygons); + // Single elephant foot compensation step, used by the elephant foor compensation at the 1st layer. + // Trim surfaces by trimming polygons (shrunk by an elephant foot compensation step), but don't shrink narrow parts so much that no perimeter would fit. + void elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons); + + void export_region_slices_to_svg(const char *path) const; + void export_region_fill_surfaces_to_svg(const char *path) const; + // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. + void export_region_slices_to_svg_debug(const char *name) const; + void export_region_fill_surfaces_to_svg_debug(const char *name) const; + + // Is there any valid extrusion assigned to this LayerRegion? + bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); } + +protected: + friend class Layer; + friend class PrintObject; + + LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {} + ~LayerRegion() = default; + +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); + + Layer *m_layer; + const PrintRegion *m_region; + + // Backed up slices before they are split into top/bottom/internal. + // Only backed up for multi-region layers or layers with elephant foot compensation. + //FIXME Review whether not to simplify the code by keeping the raw_slices all the time. + ExPolygons m_raw_slices; + +//FIXME make m_slices public for unit tests +public: + // collection of surfaces generated by slicing the original geometry + // divided by type top/bottom/internal + SurfaceCollection m_slices; + +private: + // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") + // and for re-starting of infills. + ExPolygons m_fill_expolygons; + // and their bounding boxes + BoundingBoxes m_fill_expolygons_bboxes; + // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. + // Not used for a plain single material print with no infill modifiers. + ExPolygons m_fill_expolygons_composite; + // and their bounding boxes + BoundingBoxes m_fill_expolygons_composite_bboxes; + + // Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons. + SurfaceCollection m_fill_surfaces; + + // Collection of extrusion paths/loops filling gaps + // These fills are generated by the perimeter generator. + // They are not printed on their own, but they are copied to this->fills during infill generation. + ExtrusionEntityCollection m_thin_fills; + + // collection of polylines representing the unsupported bridge edges + Polylines m_unsupported_bridge_edges; + + // ordered collection of extrusion paths/loops to build all perimeters + // (this collection contains only ExtrusionEntityCollection objects) + ExtrusionEntityCollection m_perimeters; + + // ordered collection of extrusion paths to fill surfaces + // (this collection contains only ExtrusionEntityCollection objects) + ExtrusionEntityCollection m_fills; + + // collection of expolygons representing the bridged areas (thus not + // needing support material) +// Polygons bridged; +}; + +/** +* Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params, +* detect bridges. +* Trim "shells" by the expanded bridges. +*/ +Surfaces expand_bridges_detect_orientations( + Surfaces &surfaces, + ExPolygons &shells, + const Algorithm::RegionExpansionParameters &expansion_params_into_solid_infill, + ExPolygons &sparse, + const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill, + const float closing_radius +); + +} + +#endif // slic3r_LayerRegion_hpp_ diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 69539e82d3..60a6cd789e 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -42,6 +42,7 @@ add_executable(${_TEST_NAME}_tests test_anyptr.cpp test_jump_point_search.cpp test_support_spots_generator.cpp + test_layer_region.cpp ../data/prusaparts.cpp ../data/prusaparts.hpp test_static_map.cpp diff --git a/tests/libslic3r/test_layer_region.cpp b/tests/libslic3r/test_layer_region.cpp new file mode 100644 index 0000000000..b447b7a20c --- /dev/null +++ b/tests/libslic3r/test_layer_region.cpp @@ -0,0 +1,98 @@ +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/SVG.hpp" +#include +#include + +using namespace Slic3r; + +constexpr bool export_svgs = false; + +ExPolygon rectangle(const Point& origin, const int width, const int height) { + return { + origin, + origin + Point{width, 0}, + origin + Point{width, height}, + origin + Point{0, height}, + }; +} + +TEST_CASE("test the bridge expansion with the bridge angle detection", "[LayerRegion]") { + using namespace Slic3r::Algorithm; + Surfaces surfaces{ + Surface{ + stBottomBridge, + rectangle({scaled(-1.0), scaled(0.0)}, scaled(1.0), scaled(1.0)) + }, + Surface{ + stBottomBridge, + rectangle({scaled(0.0), scaled(0.0)}, scaled(1.0), scaled(1.0)) + }, + Surface{ + stBottomBridge, + rectangle({scaled(-3.0), scaled(0.0)}, scaled(1.0), scaled(1.0)) + } + }; + + ExPolygons shells{{ + rectangle({scaled(-1.0), scaled(1.0)}, scaled(3.0), scaled(1.0)) + }}; + ExPolygons sparse {{ + rectangle({scaled(-2.0), scaled(-1.0)}, scaled(1.0), scaled(3.0)) + }}; + + const float scaled_spacing{scaled(0.3)}; + + static constexpr const float expansion_step = scaled(0.1); + // Don't take more than max_nr_steps for small expansion_step. + static constexpr const size_t max_nr_expansion_steps = 5; + const float closing_radius = 0.55f * 0.65f * 1.05f * scaled_spacing; + const int shells_expansion_depth = scaled(0.6); + const auto expansion_params_into_solid_infill = RegionExpansionParameters::build( + shells_expansion_depth, + expansion_step, + max_nr_expansion_steps + ); + const int sparse_expansion_depth = scaled(0.3); + const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build( + sparse_expansion_depth, + expansion_step, + max_nr_expansion_steps + ); + + Surfaces result{expand_bridges_detect_orientations( + surfaces, + shells, + expansion_params_into_solid_infill, + sparse, + expansion_params_into_sparse_infill, closing_radius + )}; + + if constexpr (export_svgs) { + SVG svg("bridge_expansion.svg", BoundingBox{ + Point{scaled(-3.0), scaled(-1.0)}, + Point{scaled(2.0), scaled(2.0)} + }); + + svg.draw(surfaces, "blue"); + svg.draw(shells, "green"); + svg.draw(sparse, "red"); + svg.draw_outline(result, "black", "", scale_(0.01)); + } + + REQUIRE(result.size() == 2); + CHECK(result.at(0).bridge_angle == Approx(1.5707963268)); + CHECK(result.at(1).bridge_angle == Approx(0)); + CHECK(result.at(0).expolygon.contour.size() == 22); + CHECK(result.at(1).expolygon.contour.size() == 14); + + // These lines in the polygons should correspond to the expansion depth. + CHECK(result.at(0).expolygon.contour.lines().at(2).length() == shells_expansion_depth); + CHECK(result.at(1).expolygon.contour.lines().at(7).length() == sparse_expansion_depth); + CHECK(result.at(1).expolygon.contour.lines().at(11).length() == sparse_expansion_depth); + + CHECK(intersection_ex({result.at(0).expolygon}, sparse).size() == 0); + CHECK(intersection_ex({result.at(0).expolygon}, shells).size() == 0); + CHECK(intersection_ex({result.at(1).expolygon}, sparse).size() == 0); + CHECK(intersection_ex({result.at(1).expolygon}, shells).size() == 0); +}