From 3610afd393996e02614f0e27b520bafd38c6cbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 31 Mar 2022 14:29:02 +0200 Subject: [PATCH] Updated Arachne with Cura master. --- .../BeadingStrategy/BeadingStrategy.cpp | 41 +- .../BeadingStrategy/BeadingStrategy.hpp | 37 +- .../BeadingStrategyFactory.cpp | 75 +-- .../BeadingStrategyFactory.hpp | 29 +- .../CenterDeviationBeadingStrategy.cpp | 88 --- .../CenterDeviationBeadingStrategy.hpp | 42 -- .../DistributedBeadingStrategy.cpp | 62 +- .../DistributedBeadingStrategy.hpp | 24 +- .../LimitedBeadingStrategy.cpp | 24 +- .../LimitedBeadingStrategy.hpp | 8 +- .../OuterWallInsetBeadingStrategy.cpp | 9 +- .../OuterWallInsetBeadingStrategy.hpp | 4 +- .../RedistributeBeadingStrategy.cpp | 165 ++--- .../RedistributeBeadingStrategy.hpp | 52 +- .../WideningBeadingStrategy.cpp | 25 +- .../WideningBeadingStrategy.hpp | 22 +- .../Arachne/SkeletalTrapezoidation.cpp | 156 ++--- .../Arachne/SkeletalTrapezoidation.hpp | 68 +- .../Arachne/SkeletalTrapezoidationEdge.hpp | 26 +- .../Arachne/SkeletalTrapezoidationGraph.cpp | 37 +- .../Arachne/SkeletalTrapezoidationGraph.hpp | 1 - src/libslic3r/Arachne/WallToolPaths.cpp | 597 +++++++++--------- src/libslic3r/Arachne/WallToolPaths.hpp | 50 +- .../Arachne/utils/ExtrusionJunction.cpp | 7 +- .../Arachne/utils/ExtrusionJunction.hpp | 13 +- src/libslic3r/Arachne/utils/ExtrusionLine.cpp | 53 +- src/libslic3r/Arachne/utils/ExtrusionLine.hpp | 95 ++- .../Arachne/utils/PolygonsPointIndex.hpp | 85 ++- .../Arachne/utils/PolylineStitcher.cpp | 42 ++ .../Arachne/utils/PolylineStitcher.hpp | 234 +++++++ .../Arachne/utils/SparsePointGrid.hpp | 90 +++ src/libslic3r/CMakeLists.txt | 6 +- src/libslic3r/ClipperUtils.cpp | 2 + src/libslic3r/ClipperUtils.hpp | 1 + src/libslic3r/PerimeterGenerator.cpp | 76 ++- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 28 - src/libslic3r/PrintConfig.hpp | 11 - src/libslic3r/PrintObject.cpp | 1 - src/slic3r/GUI/Tab.cpp | 1 - 40 files changed, 1219 insertions(+), 1170 deletions(-) delete mode 100644 src/libslic3r/Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.cpp delete mode 100644 src/libslic3r/Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.hpp create mode 100644 src/libslic3r/Arachne/utils/PolylineStitcher.cpp create mode 100644 src/libslic3r/Arachne/utils/PolylineStitcher.hpp create mode 100644 src/libslic3r/Arachne/utils/SparsePointGrid.hpp diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp index 10817099ef..b57c84d639 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include @@ -9,20 +9,29 @@ namespace Slic3r::Arachne { -BeadingStrategy::BeadingStrategy(coord_t optimal_width, coord_t default_transition_length, float transitioning_angle) +BeadingStrategy::BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle) : optimal_width(optimal_width) + , wall_split_middle_threshold(wall_split_middle_threshold) + , wall_add_middle_threshold(wall_add_middle_threshold) , default_transition_length(default_transition_length) , transitioning_angle(transitioning_angle) { name = "Unknown"; } +BeadingStrategy::BeadingStrategy(const BeadingStrategy &other) + : optimal_width(other.optimal_width) + , wall_split_middle_threshold(other.wall_split_middle_threshold) + , wall_add_middle_threshold(other.wall_add_middle_threshold) + , default_transition_length(other.default_transition_length) + , transitioning_angle(other.transitioning_angle) + , name(other.name) +{} + coord_t BeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const { if (lower_bead_count == 0) - { return scaled(0.01); - } return default_transition_length; } @@ -36,7 +45,7 @@ float BeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const std::vector BeadingStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const { - return std::vector(); + return {}; } std::string BeadingStrategy::toString() const @@ -44,14 +53,9 @@ std::string BeadingStrategy::toString() const return name; } -coord_t BeadingStrategy::getDefaultTransitionLength() const +double BeadingStrategy::getSplitMiddleThreshold() const { - return default_transition_length; -} - -coord_t BeadingStrategy::getOptimalWidth() const -{ - return optimal_width; + return wall_split_middle_threshold; } double BeadingStrategy::getTransitioningAngle() const @@ -59,4 +63,17 @@ double BeadingStrategy::getTransitioningAngle() const return transitioning_angle; } +coord_t BeadingStrategy::getOptimalThickness(coord_t bead_count) const +{ + return optimal_width * bead_count; +} + +coord_t BeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const +{ + const coord_t lower_ideal_width = getOptimalThickness(lower_bead_count); + const coord_t higher_ideal_width = getOptimalThickness(lower_bead_count + 1); + const double threshold = lower_bead_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold; + return lower_ideal_width + threshold * (higher_ideal_width - lower_ideal_width); +} + } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp index 85b86fa9d1..99e38239f9 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Ultimaker B.V. +// Copyright (c) 2022 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef BEADING_STRATEGY_H @@ -15,11 +15,11 @@ template constexpr T pi_div(const T div) { return static_cast(M_P /*! * Mostly virtual base class template. - * + * * Strategy for covering a given (constant) horizontal model thickness with a number of beads. - * + * * The beads may have different widths. - * + * * TODO: extend with printing order? */ class BeadingStrategy @@ -36,15 +36,17 @@ public: coord_t left_over; //! The distance not covered by any bead; gap area. }; - BeadingStrategy(coord_t optimal_width, coord_t default_transition_length, float transitioning_angle = pi_div(3)); + BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle = pi_div(3)); - virtual ~BeadingStrategy() {} + BeadingStrategy(const BeadingStrategy &other); + + virtual ~BeadingStrategy() = default; /*! * Retrieve the bead widths with which to cover a given thickness. - * + * * Requirement: Given a constant \p bead_count the output of each bead width must change gradually along with the \p thickness. - * + * * \note The \p bead_count might be different from the \ref BeadingStrategy::optimal_bead_count */ virtual Beading compute(coord_t thickness, coord_t bead_count) const = 0; @@ -52,12 +54,12 @@ public: /*! * The ideal thickness for a given \param bead_count */ - virtual coord_t getOptimalThickness(coord_t bead_count) const = 0; + virtual coord_t getOptimalThickness(coord_t bead_count) const; /*! * The model thickness at which \ref BeadingStrategy::optimal_bead_count transitions from \p lower_bead_count to \p lower_bead_count + 1 */ - virtual coord_t getTransitionThickness(coord_t lower_bead_count) const = 0; + virtual coord_t getTransitionThickness(coord_t lower_bead_count) const; /*! * The number of beads should we ideally usefor a given model thickness @@ -66,14 +68,14 @@ public: /*! * The length of the transitioning region along the marked / significant regions of the skeleton. - * + * * Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps with some incline defined by their length. */ virtual coord_t getTransitioningLength(coord_t lower_bead_count) const; /*! * The fraction of the transition length to put between the lower end of the transition and the point where the unsmoothed bead count jumps. - * + * * Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps which could be positioned relative to the jump location. */ virtual float getTransitionAnchorPos(coord_t lower_bead_count) const; @@ -81,15 +83,14 @@ public: /*! * Get the locations in a bead count region where \ref BeadingStrategy::compute exhibits a bend in the widths. * Ordered from lower thickness to higher. - * + * * This is used to insert extra support bones into the skeleton, so that the resulting beads in long trapezoids don't linearly change between the two ends. */ virtual std::vector getNonlinearThicknesses(coord_t lower_bead_count) const; - + virtual std::string toString() const; - coord_t getOptimalWidth() const; - coord_t getDefaultTransitionLength() const; + double getSplitMiddleThreshold() const; double getTransitioningAngle() const; protected: @@ -97,6 +98,10 @@ protected: coord_t optimal_width; //! Optimal bead width, nominal width off the walls in 'ideal' circumstances. + double wall_split_middle_threshold; //! Threshold when a middle wall should be split into two, as a ratio of the optimal wall width. + + double wall_add_middle_threshold; //! Threshold when a new middle wall should be added between an even number of walls, as a ratio of the optimal wall width. + coord_t default_transition_length; //! The length of the region to smoothly transfer between bead counts /*! diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp index 38b2ee24da..4044c90138 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp @@ -1,10 +1,9 @@ -//Copyright (c) 2021 Ultimaker B.V. +//Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include "BeadingStrategyFactory.hpp" #include "LimitedBeadingStrategy.hpp" -#include "CenterDeviationBeadingStrategy.hpp" #include "WideningBeadingStrategy.hpp" #include "DistributedBeadingStrategy.hpp" #include "RedistributeBeadingStrategy.hpp" @@ -16,28 +15,7 @@ namespace Slic3r::Arachne { -coord_t getWeightedAverage(const coord_t preferred_bead_width_outer, const coord_t preferred_bead_width_inner, const coord_t max_bead_count) -{ - if(max_bead_count > preferred_bead_width_outer - preferred_bead_width_inner) - { - //The difference between outer and inner bead width would be spread out across so many lines that rounding errors would destroy the difference. - //Also catches the case of max_bead_count being "infinite" (max integer). - return (preferred_bead_width_outer + preferred_bead_width_inner) / 2; - } - if (max_bead_count > 2) - { - return ((preferred_bead_width_outer * 2) + preferred_bead_width_inner * (max_bead_count - 2)) / max_bead_count; - } - if (max_bead_count <= 0) - { - return preferred_bead_width_inner; - } - return preferred_bead_width_outer; -} - -BeadingStrategyPtr BeadingStrategyFactory::makeStrategy -( - const BeadingStrategyType type, +BeadingStrategyPtr BeadingStrategyFactory::makeStrategy( const coord_t preferred_bead_width_outer, const coord_t preferred_bead_width_inner, const coord_t preferred_transition_length, @@ -50,48 +28,25 @@ BeadingStrategyPtr BeadingStrategyFactory::makeStrategy const coord_t max_bead_count, const coord_t outer_wall_offset, const int inward_distributed_center_wall_count, - const double minimum_variable_line_width + const double minimum_variable_line_ratio ) { - using std::make_unique; - using std::move; - const coord_t bar_preferred_wall_width = getWeightedAverage(preferred_bead_width_outer, preferred_bead_width_inner, max_bead_count); - BeadingStrategyPtr ret; - switch (type) - { - case BeadingStrategyType::Center: - ret = make_unique(bar_preferred_wall_width, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold); - break; - case BeadingStrategyType::Distributed: - ret = make_unique(bar_preferred_wall_width, preferred_transition_length, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold, std::numeric_limits::max()); - break; - case BeadingStrategyType::InwardDistributed: - ret = make_unique(bar_preferred_wall_width, preferred_transition_length, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold, inward_distributed_center_wall_count); - break; - default: - BOOST_LOG_TRIVIAL(error) << "Cannot make strategy!"; - return nullptr; - } - - if(print_thin_walls) - { + BeadingStrategyPtr ret = std::make_unique(preferred_bead_width_inner, preferred_transition_length, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold, inward_distributed_center_wall_count); + BOOST_LOG_TRIVIAL(debug) << "Applying the Redistribute meta-strategy with outer-wall width = " << preferred_bead_width_outer << ", inner-wall width = " << preferred_bead_width_inner << "."; + ret = std::make_unique(preferred_bead_width_outer, minimum_variable_line_ratio, std::move(ret)); + + if (print_thin_walls) { BOOST_LOG_TRIVIAL(debug) << "Applying the Widening Beading meta-strategy with minimum input width " << min_feature_size << " and minimum output width " << min_bead_width << "."; - ret = make_unique(move(ret), min_feature_size, min_bead_width); + ret = std::make_unique(std::move(ret), min_feature_size, min_bead_width); } - if (max_bead_count > 0) - { - BOOST_LOG_TRIVIAL(debug) << "Applying the Redistribute meta-strategy with outer-wall width = " << preferred_bead_width_outer << ", inner-wall width = " << preferred_bead_width_inner; - ret = make_unique(preferred_bead_width_outer, preferred_bead_width_inner, minimum_variable_line_width, move(ret)); - //Apply the LimitedBeadingStrategy last, since that adds a 0-width marker wall which other beading strategies shouldn't touch. - BOOST_LOG_TRIVIAL(debug) << "Applying the Limited Beading meta-strategy with maximum bead count = " << max_bead_count << "."; - ret = make_unique(max_bead_count, move(ret)); - } - - if (outer_wall_offset > 0) - { + if (outer_wall_offset > 0) { BOOST_LOG_TRIVIAL(debug) << "Applying the OuterWallOffset meta-strategy with offset = " << outer_wall_offset << "."; - ret = make_unique(outer_wall_offset, move(ret)); + ret = std::make_unique(outer_wall_offset, std::move(ret)); } + + //Apply the LimitedBeadingStrategy last, since that adds a 0-width marker wall which other beading strategies shouldn't touch. + BOOST_LOG_TRIVIAL(debug) << "Applying the Limited Beading meta-strategy with maximum bead count = " << max_bead_count << "."; + ret = std::make_unique(max_bead_count, std::move(ret)); return ret; } } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp index 741262b606..a586906f45 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Ultimaker B.V. +// Copyright (c) 2022 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef BEADING_STRATEGY_FACTORY_H @@ -15,20 +15,19 @@ class BeadingStrategyFactory public: static BeadingStrategyPtr makeStrategy ( - const BeadingStrategyType type, - const coord_t preferred_bead_width_outer = scaled(0.0005), - const coord_t preferred_bead_width_inner = scaled(0.0005), - const coord_t preferred_transition_length = scaled(0.0004), - const float transitioning_angle = M_PI / 4.0, - const bool print_thin_walls = false, - const coord_t min_bead_width = 0, - const coord_t min_feature_size = 0, - const double wall_split_middle_threshold = 0.5, - const double wall_add_middle_threshold = 0.5, - const coord_t max_bead_count = 0, - const coord_t outer_wall_offset = 0, - const int inward_distributed_center_wall_count = 2, - const double minimum_variable_line_width = 0.5 + coord_t preferred_bead_width_outer = scaled(0.0005), + coord_t preferred_bead_width_inner = scaled(0.0005), + coord_t preferred_transition_length = scaled(0.0004), + float transitioning_angle = M_PI / 4.0, + bool print_thin_walls = false, + coord_t min_bead_width = 0, + coord_t min_feature_size = 0, + double wall_split_middle_threshold = 0.5, + double wall_add_middle_threshold = 0.5, + coord_t max_bead_count = 0, + coord_t outer_wall_offset = 0, + int inward_distributed_center_wall_count = 2, + double minimum_variable_line_width = 0.5 ); }; diff --git a/src/libslic3r/Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.cpp deleted file mode 100644 index 5c985bd2c6..0000000000 --- a/src/libslic3r/Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2021 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. -#include - -#include "CenterDeviationBeadingStrategy.hpp" - -namespace Slic3r::Arachne -{ -CenterDeviationBeadingStrategy::CenterDeviationBeadingStrategy(const coord_t pref_bead_width, - const double transitioning_angle, - const double wall_split_middle_threshold, - const double wall_add_middle_threshold) - : BeadingStrategy(pref_bead_width, pref_bead_width / 2, transitioning_angle), - minimum_line_width_split(pref_bead_width * wall_split_middle_threshold), - minimum_line_width_add(pref_bead_width * wall_add_middle_threshold) -{ - name = "CenterDeviationBeadingStrategy"; -} - -CenterDeviationBeadingStrategy::Beading CenterDeviationBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const -{ - Beading ret; - - ret.total_thickness = thickness; - if (bead_count > 0) - { - // Set the bead widths - ret.bead_widths = std::vector(static_cast(bead_count), optimal_width); - const coord_t optimal_thickness = getOptimalThickness(bead_count); - const coord_t diff_thickness = thickness - optimal_thickness; //Amount of deviation. Either spread out over the middle 2 lines, or concentrated in the center line. - const size_t center_bead_idx = ret.bead_widths.size() / 2; - if (bead_count % 2 == 0) // Even lines - { - const coord_t inner_bead_widths = optimal_width + diff_thickness / 2; - if (inner_bead_widths < minimum_line_width_add) - { - return compute(thickness, bead_count - 1); - } - ret.bead_widths[center_bead_idx - 1] = inner_bead_widths; - ret.bead_widths[center_bead_idx] = inner_bead_widths; - } - else // Uneven lines - { - const coord_t inner_bead_widths = optimal_width + diff_thickness; - if (inner_bead_widths < minimum_line_width_split) - { - return compute(thickness, bead_count - 1); - } - ret.bead_widths[center_bead_idx] = inner_bead_widths; - } - - // Set the center line location of the bead toolpaths. - ret.toolpath_locations.resize(ret.bead_widths.size()); - ret.toolpath_locations.front() = ret.bead_widths.front() / 2; - for (size_t bead_idx = 1; bead_idx < ret.bead_widths.size(); ++bead_idx) - { - ret.toolpath_locations[bead_idx] = - ret.toolpath_locations[bead_idx - 1] + (ret.bead_widths[bead_idx] + ret.bead_widths[bead_idx - 1]) / 2; - } - ret.left_over = 0; - } - else - { - ret.left_over = thickness; - } - - return ret; -} - -coord_t CenterDeviationBeadingStrategy::getOptimalThickness(coord_t bead_count) const -{ - return bead_count * optimal_width; -} - -coord_t CenterDeviationBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const -{ - return lower_bead_count * optimal_width + (lower_bead_count % 2 == 1 ? minimum_line_width_split : minimum_line_width_add); -} - -coord_t CenterDeviationBeadingStrategy::getOptimalBeadCount(coord_t thickness) const -{ - const coord_t naive_count = thickness / optimal_width; // How many lines we can fit in for sure. - const coord_t remainder = thickness - naive_count * optimal_width; // Space left after fitting that many lines. - const coord_t minimum_line_width = naive_count % 2 == 1 ? minimum_line_width_split : minimum_line_width_add; - return naive_count + (remainder > minimum_line_width); // If there's enough space, fit an extra one. -} - -} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.hpp deleted file mode 100644 index 4dd6c928a4..0000000000 --- a/src/libslic3r/Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.hpp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2021 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. - - -#ifndef CENTER_DEVIATION_BEADING_STRATEGY_H -#define CENTER_DEVIATION_BEADING_STRATEGY_H - -#include "BeadingStrategy.hpp" - -namespace Slic3r::Arachne -{ - -/*! - * This beading strategy makes the deviation in the thickness of the part - * entirely compensated by the innermost wall. - * - * The outermost walls all use the ideal width, as far as possible. - */ -class CenterDeviationBeadingStrategy : public BeadingStrategy -{ - private: - // For uneven numbers of lines: Minimum line width for which the middle line will be split into two lines. - coord_t minimum_line_width_split; - - // For even numbers of lines: Minimum line width for which a new middle line will be added between the two innermost lines. - coord_t minimum_line_width_add; - - public: - CenterDeviationBeadingStrategy(coord_t pref_bead_width, - double transitioning_angle, - double wall_split_middle_threshold, - double wall_add_middle_threshold); - - ~CenterDeviationBeadingStrategy() override{}; - Beading compute(coord_t thickness, coord_t bead_count) const override; - coord_t getOptimalThickness(coord_t bead_count) const override; - coord_t getTransitionThickness(coord_t lower_bead_count) const override; - coord_t getOptimalBeadCount(coord_t thickness) const override; -}; - -} // namespace Slic3r::Arachne -#endif // CENTER_DEVIATION_BEADING_STRATEGY_H diff --git a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp index 42cd98a69f..494b7b0b67 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Ultimaker B.V. +// Copyright (c) 2022 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher. #include #include "DistributedBeadingStrategy.hpp" @@ -7,23 +7,17 @@ namespace Slic3r::Arachne { DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_width, - const coord_t default_transition_length, - const double transitioning_angle, - const double wall_split_middle_threshold, - const double wall_add_middle_threshold, - const int distribution_radius) - : BeadingStrategy(optimal_width, default_transition_length, transitioning_angle) - , wall_split_middle_threshold(wall_split_middle_threshold) - , wall_add_middle_threshold(wall_add_middle_threshold) + const coord_t default_transition_length, + const double transitioning_angle, + const double wall_split_middle_threshold, + const double wall_add_middle_threshold, + const int distribution_radius) + : BeadingStrategy(optimal_width, wall_split_middle_threshold, wall_add_middle_threshold, default_transition_length, transitioning_angle) { if(distribution_radius >= 2) - { one_over_distribution_radius_squared = 1.0f / (distribution_radius - 1) * 1.0f / (distribution_radius - 1); - } else - { one_over_distribution_radius_squared = 1.0f / 1 * 1.0f / 1; - } name = "DistributedBeadingStrategy"; } @@ -32,13 +26,11 @@ DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t Beading ret; ret.total_thickness = thickness; - if (bead_count > 2) - { + if (bead_count > 2) { const coord_t to_be_divided = thickness - bead_count * optimal_width; const float middle = static_cast(bead_count - 1) / 2; - const auto getWeight = [middle, this](coord_t bead_idx) - { + const auto getWeight = [middle, this](coord_t bead_idx) { const float dev_from_middle = bead_idx - middle; return std::max(0.0f, 1.0f - one_over_distribution_radius_squared * dev_from_middle * dev_from_middle); }; @@ -46,65 +38,45 @@ DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t std::vector weights; weights.resize(bead_count); for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) - { weights[bead_idx] = getWeight(bead_idx); - } const float total_weight = std::accumulate(weights.cbegin(), weights.cend(), 0.f); - for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) - { + for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) { const float weight_fraction = weights[bead_idx] / total_weight; const coord_t splitup_left_over_weight = to_be_divided * weight_fraction; const coord_t width = optimal_width + splitup_left_over_weight; if (bead_idx == 0) - { ret.toolpath_locations.emplace_back(width / 2); - } else - { ret.toolpath_locations.emplace_back(ret.toolpath_locations.back() + (ret.bead_widths.back() + width) / 2); - } ret.bead_widths.emplace_back(width); } ret.left_over = 0; - } - else if (bead_count == 2) - { + } else if (bead_count == 2) { const coord_t outer_width = thickness / 2; ret.bead_widths.emplace_back(outer_width); ret.bead_widths.emplace_back(outer_width); ret.toolpath_locations.emplace_back(outer_width / 2); ret.toolpath_locations.emplace_back(thickness - outer_width / 2); ret.left_over = 0; - } - else if (bead_count == 1) - { + } else if (bead_count == 1) { const coord_t outer_width = thickness; ret.bead_widths.emplace_back(outer_width); ret.toolpath_locations.emplace_back(outer_width / 2); ret.left_over = 0; - } - else - { + } else { ret.left_over = thickness; } return ret; } -coord_t DistributedBeadingStrategy::getOptimalThickness(coord_t bead_count) const -{ - return bead_count * optimal_width; -} - -coord_t DistributedBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const -{ - return lower_bead_count * optimal_width + optimal_width * (lower_bead_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold); -} - coord_t DistributedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const { - return (thickness + optimal_width / 2) / optimal_width; + const coord_t naive_count = thickness / optimal_width; // How many lines we can fit in for sure. + const coord_t remainder = thickness - naive_count * optimal_width; // Space left after fitting that many lines. + const coord_t minimum_line_width = optimal_width * (naive_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold); + return naive_count + (remainder >= minimum_line_width); // If there's enough space, fit an extra one. } } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp index a027d781d3..4d651732d4 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Ultimaker B.V. +// Copyright (c) 2022 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef DISTRIBUTED_BEADING_STRATEGY_H @@ -17,30 +17,22 @@ namespace Slic3r::Arachne class DistributedBeadingStrategy : public BeadingStrategy { protected: - // For uneven numbers of lines: Minimum factor of the optimal width for which the middle line will be split into two lines. - double wall_split_middle_threshold; - - // For even numbers of lines: Minimum factor of the optimal width for which a new middle line will be added between the two innermost lines. - double wall_add_middle_threshold; - float one_over_distribution_radius_squared; // (1 / distribution_radius)^2 public: /*! * \param distribution_radius the radius (in number of beads) over which to distribute the discrepancy between the feature size and the optimal thickness */ - DistributedBeadingStrategy( const coord_t optimal_width, - const coord_t default_transition_length, - const double transitioning_angle, - const double wall_split_middle_threshold, - const double wall_add_middle_threshold, - const int distribution_radius); + DistributedBeadingStrategy(coord_t optimal_width, + coord_t default_transition_length, + double transitioning_angle, + double wall_split_middle_threshold, + double wall_add_middle_threshold, + int distribution_radius); - virtual ~DistributedBeadingStrategy() override {} + ~DistributedBeadingStrategy() override = default; Beading compute(coord_t thickness, coord_t bead_count) const override; - coord_t getOptimalThickness(coord_t bead_count) const override; - coord_t getTransitionThickness(coord_t lower_bead_count) const override; coord_t getOptimalBeadCount(coord_t thickness) const override; }; diff --git a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp index f5776ca9b4..97d854b418 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include @@ -26,7 +26,7 @@ float LimitedBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) c } LimitedBeadingStrategy::LimitedBeadingStrategy(const coord_t max_bead_count, BeadingStrategyPtr parent) - : BeadingStrategy(parent->getOptimalWidth(), /*default_transition_length=*/-1, parent->getTransitioningAngle()) + : BeadingStrategy(*parent) , max_bead_count(max_bead_count) , parent(std::move(parent)) { @@ -65,15 +65,12 @@ LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thicknes ret.total_thickness = thickness; // Enforce symmetry - if (bead_count % 2 == 1) - { + if (bead_count % 2 == 1) { ret.toolpath_locations[bead_count / 2] = thickness / 2; ret.bead_widths[bead_count / 2] = thickness - optimal_thickness; } for (coord_t bead_idx = 0; bead_idx < (bead_count + 1) / 2; bead_idx++) - { ret.toolpath_locations[bead_count - 1 - bead_idx] = thickness - ret.toolpath_locations[bead_idx]; - } //Create a "fake" inner wall with 0 width to indicate the edge of the walled area. //This wall can then be used by other structures to e.g. fill the infill area adjacent to the variable-width walls. @@ -95,9 +92,7 @@ LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thicknes coord_t LimitedBeadingStrategy::getOptimalThickness(coord_t bead_count) const { if (bead_count <= max_bead_count) - { return parent->getOptimalThickness(bead_count); - } assert(false); return scaled(1000.); // 1 meter (Cura was returning 10 meter) } @@ -105,13 +100,11 @@ coord_t LimitedBeadingStrategy::getOptimalThickness(coord_t bead_count) const coord_t LimitedBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const { if (lower_bead_count < max_bead_count) - { return parent->getTransitionThickness(lower_bead_count); - } + if (lower_bead_count == max_bead_count) - { return parent->getOptimalThickness(lower_bead_count + 1) - scaled(0.01); - } + assert(false); return scaled(900.); // 0.9 meter; } @@ -119,12 +112,9 @@ coord_t LimitedBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) coord_t LimitedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const { coord_t parent_bead_count = parent->getOptimalBeadCount(thickness); - if (parent_bead_count <= max_bead_count) - { + if (parent_bead_count <= max_bead_count) { return parent->getOptimalBeadCount(thickness); - } - else if (parent_bead_count == max_bead_count + 1) - { + } else if (parent_bead_count == max_bead_count + 1) { if (thickness < parent->getOptimalThickness(max_bead_count + 1) - scaled(0.01)) return max_bead_count; else diff --git a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp index 9098fabb8e..33292bc09f 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef LIMITED_BEADING_STRATEGY_H @@ -26,15 +26,15 @@ namespace Slic3r::Arachne class LimitedBeadingStrategy : public BeadingStrategy { public: - LimitedBeadingStrategy(const coord_t max_bead_count, BeadingStrategyPtr parent); + LimitedBeadingStrategy(coord_t max_bead_count, BeadingStrategyPtr parent); - virtual ~LimitedBeadingStrategy() override = default; + ~LimitedBeadingStrategy() override = default; Beading compute(coord_t thickness, coord_t bead_count) const override; coord_t getOptimalThickness(coord_t bead_count) const override; coord_t getTransitionThickness(coord_t lower_bead_count) const override; coord_t getOptimalBeadCount(coord_t thickness) const override; - virtual std::string toString() const override; + std::string toString() const override; coord_t getTransitioningLength(coord_t lower_bead_count) const override; diff --git a/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp index 9028a0d4e6..1406f7daa8 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include "OuterWallInsetBeadingStrategy.hpp" @@ -7,15 +7,12 @@ namespace Slic3r::Arachne { -OuterWallInsetBeadingStrategy::OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent) : - BeadingStrategy(parent->getOptimalWidth(), parent->getDefaultTransitionLength(), parent->getTransitioningAngle()), - parent(std::move(parent)), - outer_wall_offset(outer_wall_offset) +OuterWallInsetBeadingStrategy::OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent) + : BeadingStrategy(*parent), parent(std::move(parent)), outer_wall_offset(outer_wall_offset) { name = "OuterWallOfsetBeadingStrategy"; } - coord_t OuterWallInsetBeadingStrategy::getOptimalThickness(coord_t bead_count) const { return parent->getOptimalThickness(bead_count); diff --git a/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp index f7fcfe551b..45a700b02e 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef OUTER_WALL_INSET_BEADING_STRATEGY_H @@ -16,7 +16,7 @@ namespace Slic3r::Arachne public: OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent); - virtual ~OuterWallInsetBeadingStrategy() = default; + ~OuterWallInsetBeadingStrategy() override = default; Beading compute(coord_t thickness, coord_t bead_count) const override; diff --git a/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp index 539db3a13e..2b4dda0272 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2021 Ultimaker B.V. +//Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include "RedistributeBeadingStrategy.hpp" @@ -9,35 +9,40 @@ namespace Slic3r::Arachne { -RedistributeBeadingStrategy::RedistributeBeadingStrategy( const coord_t optimal_width_outer, - const coord_t optimal_width_inner, - const double minimum_variable_line_width, - BeadingStrategyPtr parent) : - BeadingStrategy(parent->getOptimalWidth(), parent->getDefaultTransitionLength(), parent->getTransitioningAngle()), - parent(std::move(parent)), - optimal_width_outer(optimal_width_outer), - optimal_width_inner(optimal_width_inner), - minimum_variable_line_width(minimum_variable_line_width) +RedistributeBeadingStrategy::RedistributeBeadingStrategy(const coord_t optimal_width_outer, + const double minimum_variable_line_ratio, + BeadingStrategyPtr parent) + : BeadingStrategy(*parent) + , parent(std::move(parent)) + , optimal_width_outer(optimal_width_outer) + , minimum_variable_line_ratio(minimum_variable_line_ratio) { name = "RedistributeBeadingStrategy"; } coord_t RedistributeBeadingStrategy::getOptimalThickness(coord_t bead_count) const { - const coord_t inner_bead_count = bead_count > 2 ? bead_count - 2 : 0; + const coord_t inner_bead_count = std::max(static_cast(0), bead_count - 2); const coord_t outer_bead_count = bead_count - inner_bead_count; - return parent->getOptimalThickness(inner_bead_count) + optimal_width_outer * outer_bead_count; } coord_t RedistributeBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const { - return parent->getTransitionThickness(lower_bead_count); + switch (lower_bead_count) { + case 0: return minimum_variable_line_ratio * optimal_width_outer; + case 1: return (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer; + default: return parent->getTransitionThickness(lower_bead_count - 2) + 2 * optimal_width_outer; + } } coord_t RedistributeBeadingStrategy::getOptimalBeadCount(coord_t thickness) const { - return parent->getOptimalBeadCount(thickness); + if (thickness < minimum_variable_line_ratio * optimal_width_outer) + return 0; + if (thickness <= 2 * optimal_width_outer) + return thickness > (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer ? 2 : 1; + return parent->getOptimalBeadCount(thickness - 2 * optimal_width_outer) + 2; } coord_t RedistributeBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const @@ -58,123 +63,35 @@ std::string RedistributeBeadingStrategy::toString() const BeadingStrategy::Beading RedistributeBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const { Beading ret; - if (bead_count > 2) - { - const coord_t inner_transition_width = optimal_width_inner * minimum_variable_line_width; - const coord_t outer_bead_width = - getOptimalOuterBeadWidth(thickness, optimal_width_outer, inner_transition_width); - // Outer wall is locked in size and position for wall regions of 3 and higher which have at least a - // thickness equal to two times the optimal outer width and the minimal inner wall width. - const coord_t virtual_thickness = thickness - outer_bead_width * 2; - const coord_t virtual_bead_count = bead_count - 2; - - // Calculate the beads and widths of the inner walls only - ret = parent->compute(virtual_thickness, virtual_bead_count); - - // Insert the outer beads - ret.bead_widths.insert(ret.bead_widths.begin(), outer_bead_width); - ret.bead_widths.emplace_back(outer_bead_width); - } - else - { - ret = parent->compute(thickness, bead_count); + // Take care of all situations in which no lines are actually produced: + if (bead_count == 0 || thickness < minimum_variable_line_ratio * optimal_width_outer) { + ret.left_over = thickness; + ret.total_thickness = thickness; + return ret; } - // Filter out beads that violate the minimum inner wall widths and recompute if necessary - const coord_t outer_transition_width = optimal_width_inner * minimum_variable_line_width; - const bool removed_inner_beads = validateInnerBeadWidths(ret, outer_transition_width); - if (removed_inner_beads) - { - ret = compute(thickness, bead_count - 1); + // Compute the beadings of the inner walls, if any: + const coord_t inner_bead_count = bead_count - 2; + const coord_t inner_thickness = thickness - 2 * optimal_width_outer; + if (inner_bead_count > 0 && inner_thickness > 0) { + ret = parent->compute(inner_thickness, inner_bead_count); + for (auto &toolpath_location : ret.toolpath_locations) toolpath_location += optimal_width_outer; } - // Ensure that the positions of the beads are distributed over the thickness - resetToolPathLocations(ret, thickness); + // Insert the outer wall(s) around the previously computed inner wall(s), which may be empty: + const coord_t actual_outer_thickness = bead_count > 2 ? std::min(thickness / 2, optimal_width_outer) : thickness / bead_count; + ret.bead_widths.insert(ret.bead_widths.begin(), actual_outer_thickness); + ret.toolpath_locations.insert(ret.toolpath_locations.begin(), actual_outer_thickness / 2); + if (bead_count > 1) { + ret.bead_widths.push_back(actual_outer_thickness); + ret.toolpath_locations.push_back(thickness - actual_outer_thickness / 2); + } + // Ensure correct total and left over thickness. + ret.total_thickness = thickness; + ret.left_over = thickness - std::accumulate(ret.bead_widths.cbegin(), ret.bead_widths.cend(), static_cast(0)); return ret; } -coord_t RedistributeBeadingStrategy::getOptimalOuterBeadWidth(const coord_t thickness, const coord_t optimal_width_outer, const coord_t minimum_width_inner) -{ - const coord_t total_outer_optimal_width = optimal_width_outer * 2; - coord_t outer_bead_width = thickness / 2; - if (total_outer_optimal_width < thickness) - { - if (total_outer_optimal_width + minimum_width_inner > thickness) - { - outer_bead_width -= minimum_width_inner / 2; - } - else - { - outer_bead_width = optimal_width_outer; - } - } - return outer_bead_width; -} - -void RedistributeBeadingStrategy::resetToolPathLocations(BeadingStrategy::Beading& beading, const coord_t thickness) -{ - const size_t bead_count = beading.bead_widths.size(); - beading.toolpath_locations.resize(bead_count); - - if (bead_count < 1) - { - beading.toolpath_locations.resize(0); - beading.total_thickness = thickness; - beading.left_over = thickness; - return; - } - - // Update the first half of the toolpath-locations with the updated bead-widths (starting from 0, up to half): - coord_t last_coord = 0; - coord_t last_width = 0; - for (size_t i_location = 0; i_location < bead_count / 2; ++i_location) - { - beading.toolpath_locations[i_location] = last_coord + (last_width + beading.bead_widths[i_location]) / 2; - last_coord = beading.toolpath_locations[i_location]; - last_width = beading.bead_widths[i_location]; - } - - // Handle the position of any middle wall (note that the width will already have been set correctly): - if (bead_count % 2 == 1) - { - beading.toolpath_locations[bead_count / 2] = thickness / 2; - } - - // Update the last half of the toolpath-locations with the updated bead-widths (starting from thickness, down to half): - last_coord = thickness; - last_width = 0; - for (size_t i_location = bead_count - 1; i_location >= bead_count - (bead_count / 2); --i_location) - { - beading.toolpath_locations[i_location] = last_coord - (last_width + beading.bead_widths[i_location]) / 2; - last_coord = beading.toolpath_locations[i_location]; - last_width = beading.bead_widths[i_location]; - } - - // Ensure correct total and left over thickness - beading.total_thickness = thickness; - beading.left_over = thickness - std::accumulate(beading.bead_widths.cbegin(), beading.bead_widths.cend(), static_cast(0)); -} - -bool RedistributeBeadingStrategy::validateInnerBeadWidths(BeadingStrategy::Beading& beading, const coord_t minimum_width_inner) -{ - // Filter out bead_widths that violate the transition width and recalculate if needed - const size_t unfiltered_beads = beading.bead_widths.size(); - if(unfiltered_beads <= 2) //Outer walls are exempt. If there are 2 walls the range below will be empty. If there is 1 or 0 walls it would be invalid. - { - return false; - } - auto inner_begin = std::next(beading.bead_widths.begin()); - auto inner_end = std::prev(beading.bead_widths.end()); - beading.bead_widths.erase( - std::remove_if(inner_begin, inner_end, - [&minimum_width_inner](const coord_t width) - { - return width < minimum_width_inner; - }), - inner_end); - return unfiltered_beads != beading.bead_widths.size(); - } - } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp index ca2e3cb83e..f0fefe2389 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H @@ -30,17 +30,11 @@ namespace Slic3r::Arachne * /param optimal_width_outer Outer wall width, guaranteed to be the actual (save rounding errors) at a * bead count if the parent strategies' optimum bead width is a weighted * average of the outer and inner walls at that bead count. - * /param optimal_width_outer Inner wall width, guaranteed to be the actual (save rounding errors) at a - * bead count if the parent strategies' optimum bead width is a weighted - * average of the outer and inner walls at that bead count. - * /param minimum_variable_line_width Minimum factor that the variable line might deviate from the optimal width. + * /param minimum_variable_line_ratio Minimum factor that the variable line might deviate from the optimal width. */ - RedistributeBeadingStrategy(const coord_t optimal_width_outer, - const coord_t optimal_width_inner, - const double minimum_variable_line_width, - BeadingStrategyPtr parent); + RedistributeBeadingStrategy(coord_t optimal_width_outer, double minimum_variable_line_ratio, BeadingStrategyPtr parent); - virtual ~RedistributeBeadingStrategy() override = default; + ~RedistributeBeadingStrategy() override = default; Beading compute(coord_t thickness, coord_t bead_count) const override; @@ -53,45 +47,9 @@ namespace Slic3r::Arachne std::string toString() const override; protected: - /*! - * Determine the outer bead width. - * - * According to the following logic: - * - If the thickness of the model is more then twice the optimal outer bead width and the minimum inner bead - * width it will return the optimal outer bead width. - * - If the thickness is less then twice the optimal outer bead width and the minimum inner bead width, but - * more them twice the optimal outer bead with it will return the optimal bead width minus half the inner bead - * width. - * - If the thickness is less then twice the optimal outer bead width it will return half the thickness as - * outer bead width - * - * \param thickness Thickness of the total beads. - * \param optimal_width_outer User specified optimal outer bead width. - * \param minimum_width_inner Inner bead width times the minimum variable line width. - * \return The outer bead width. - */ - static coord_t getOptimalOuterBeadWidth(coord_t thickness, coord_t optimal_width_outer, coord_t minimum_width_inner); - - /*! - * Moves the beads towards the outer edges of thickness and ensures that the outer walls are locked in location - * \param beading The beading instance. - * \param thickness The thickness of the bead. - */ - static void resetToolPathLocations(Beading& beading, coord_t thickness); - - /*! - * Filters and validates the beads, to ensure that all inner beads are at least the minimum bead width. - * - * \param beading The beading instance. - * \param minimum_width_inner Inner bead width times the minimum variable line width. - * \return true if beads are removed. - */ - static bool validateInnerBeadWidths(Beading& beading, coord_t minimum_width_inner); - BeadingStrategyPtr parent; coord_t optimal_width_outer; - coord_t optimal_width_inner; - double minimum_variable_line_width; + double minimum_variable_line_ratio; }; } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.cpp index ad4cad9640..eefcab8e7b 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include "WideningBeadingStrategy.hpp" @@ -7,7 +7,7 @@ namespace Slic3r::Arachne { WideningBeadingStrategy::WideningBeadingStrategy(BeadingStrategyPtr parent, const coord_t min_input_width, const coord_t min_output_width) - : BeadingStrategy(parent->getOptimalWidth(), /*default_transition_length=*/-1, parent->getTransitioningAngle()) + : BeadingStrategy(*parent) , parent(std::move(parent)) , min_input_width(min_input_width) , min_output_width(min_output_width) @@ -21,23 +21,18 @@ std::string WideningBeadingStrategy::toString() const WideningBeadingStrategy::Beading WideningBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const { - if (thickness < optimal_width) - { + if (thickness < optimal_width) { Beading ret; ret.total_thickness = thickness; if (thickness >= min_input_width) { ret.bead_widths.emplace_back(std::max(thickness, min_output_width)); ret.toolpath_locations.emplace_back(thickness / 2); - } - else - { + } else { ret.left_over = thickness; } return ret; - } - else - { + } else { return parent->compute(thickness, bead_count); } } @@ -50,20 +45,18 @@ coord_t WideningBeadingStrategy::getOptimalThickness(coord_t bead_count) const coord_t WideningBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const { if (lower_bead_count == 0) - { return min_input_width; - } else - { return parent->getTransitionThickness(lower_bead_count); - } } coord_t WideningBeadingStrategy::getOptimalBeadCount(coord_t thickness) const { - if (thickness < min_input_width) return 0; + if (thickness < min_input_width) + return 0; coord_t ret = parent->getOptimalBeadCount(thickness); - if (thickness >= min_input_width && ret < 1) return 1; + if (thickness >= min_input_width && ret < 1) + return 1; return ret; } diff --git a/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.hpp index 32aa9f0584..3e799b9af7 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.hpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef WIDENING_BEADING_STRATEGY_H @@ -23,18 +23,18 @@ public: /*! * Takes responsibility for deleting \param parent */ - WideningBeadingStrategy(BeadingStrategyPtr parent, const coord_t min_input_width, const coord_t min_output_width); + WideningBeadingStrategy(BeadingStrategyPtr parent, coord_t min_input_width, coord_t min_output_width); - virtual ~WideningBeadingStrategy() override = default; + ~WideningBeadingStrategy() override = default; - virtual Beading compute(coord_t thickness, coord_t bead_count) const override; - virtual coord_t getOptimalThickness(coord_t bead_count) const override; - virtual coord_t getTransitionThickness(coord_t lower_bead_count) const override; - virtual coord_t getOptimalBeadCount(coord_t thickness) const override; - virtual coord_t getTransitioningLength(coord_t lower_bead_count) const override; - virtual float getTransitionAnchorPos(coord_t lower_bead_count) const override; - virtual std::vector getNonlinearThicknesses(coord_t lower_bead_count) const override; - virtual std::string toString() const override; + Beading compute(coord_t thickness, coord_t bead_count) const override; + coord_t getOptimalThickness(coord_t bead_count) const override; + coord_t getTransitionThickness(coord_t lower_bead_count) const override; + coord_t getOptimalBeadCount(coord_t thickness) const override; + coord_t getTransitioningLength(coord_t lower_bead_count) const override; + float getTransitionAnchorPos(coord_t lower_bead_count) const override; + std::vector getNonlinearThicknesses(coord_t lower_bead_count) const override; + std::string toString() const override; protected: BeadingStrategyPtr parent; diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index c07dafd542..50c45abc14 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -542,7 +542,6 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) separatePointyQuadEndNodes(); - graph.fixNodeDuplication(); graph.collapseSmallEdges(); // Set [incident_edge] the the first possible edge that way we can iterate over all reachable edges from node.incident_edge, @@ -606,7 +605,7 @@ static void export_graph_to_svg(const std::string &path, const SkeletalTrapezoid } #endif -void SkeletalTrapezoidation::generateToolpaths(VariableWidthPaths& generated_toolpaths, bool filter_outermost_central_edges) +void SkeletalTrapezoidation::generateToolpaths(std::vector &generated_toolpaths, bool filter_outermost_central_edges) { p_generated_toolpaths = &generated_toolpaths; @@ -625,11 +624,7 @@ void SkeletalTrapezoidation::generateToolpaths(VariableWidthPaths& generated_too generateExtraRibs(); - markRegions(); - generateSegments(); - - liftRegionInfoToLines(); } void SkeletalTrapezoidation::updateIsCentral() @@ -1496,52 +1491,6 @@ void SkeletalTrapezoidation::generateExtraRibs() // // ^^^^^^^^^^^^^^^^^^^^^ // TRANSTISIONING -// ===================== - -void SkeletalTrapezoidation::markRegions() -{ - // Painters algorithm, loop over all edges and skip those that have already been 'painted' with a region. - size_t region = 0; // <- Region zero is 'None', it will be incremented before the first edge. - for (edge_t& edge : graph.edges) - { - if (edge.data.regionIsSet()) - { - continue; - } - - // An edge that didn't have a region painted is encountered, so make a new region and start a worklist: - ++region; - std::queue worklist; - worklist.push(&edge); - - // Loop over all edges that are connected to this one, except don't cross any medial axis edges: - while (!worklist.empty()) - { - edge_t* p_side = worklist.front(); - worklist.pop(); - - edge_t* p_next = p_side; - do - { - if (!p_next->data.regionIsSet()) - { - p_next->data.setRegion(region); - if(p_next->twin != nullptr && (p_next->next == nullptr || p_next->prev == nullptr)) - { - worklist.push(p_next->twin); - } - } - else - { - assert(region == p_next->data.getRegion()); - } - - p_next = p_next->next; - } while (p_next != nullptr && p_next != p_side); - } - } -} - // ===================== // TOOLPATH GENERATION // vvvvvvvvvvvvvvvvvvvvv @@ -1891,7 +1840,7 @@ void SkeletalTrapezoidation::generateJunctions(ptr_vector_t& { // Snap to start node if it is really close, in order to be able to see 3-way intersection later on more robustly junction = a; } - ret.emplace_back(junction, beading->bead_widths[junction_idx], junction_idx, edge_.data.getRegion()); + ret.emplace_back(junction, beading->bead_widths[junction_idx], junction_idx); } } } @@ -1979,11 +1928,11 @@ std::shared_ptr SkeletalTrapezo return nullptr; } -void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path) +void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way) { if (from == to) return; - VariableWidthPaths& generated_toolpaths = *p_generated_toolpaths; + std::vector &generated_toolpaths = *p_generated_toolpaths; size_t inset_idx = from.perimeter_index; if (inset_idx >= generated_toolpaths.size()) @@ -1991,24 +1940,33 @@ void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, c generated_toolpaths.resize(inset_idx + 1); } assert((generated_toolpaths[inset_idx].empty() || !generated_toolpaths[inset_idx].back().junctions.empty()) && "empty extrusion lines should never have been generated"); - if (!force_new_path - && !generated_toolpaths[inset_idx].empty() - && generated_toolpaths[inset_idx].back().is_odd == is_odd - && shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - to.p, scaled(0.01)) - && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - to.w) < scaled(0.01) - ) + if (generated_toolpaths[inset_idx].empty() + || generated_toolpaths[inset_idx].back().is_odd != is_odd + || generated_toolpaths[inset_idx].back().junctions.back().perimeter_index != inset_idx // inset_idx should always be consistent + ) { - generated_toolpaths[inset_idx].back().junctions.push_back(from); + force_new_path = true; } - else if (!force_new_path - && !generated_toolpaths[inset_idx].empty() - && generated_toolpaths[inset_idx].back().is_odd == is_odd - && shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - from.p, scaled(0.01)) - && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - from.w) < scaled(0.01) - ) + if (!force_new_path + && shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - from.p, scaled(0.010)) + && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - from.w) < scaled(0.010) + && ! from_is_3way // force new path at 3way intersection + ) { generated_toolpaths[inset_idx].back().junctions.push_back(to); } + else if (!force_new_path + && shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - to.p, scaled(0.010)) + && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - to.w) < scaled(0.010) + && ! to_is_3way // force new path at 3way intersection + ) + { + if ( ! is_odd) + { + BOOST_LOG_TRIVIAL(error) << "Reversing even wall line causes it to be printed CCW instead of CW!"; + } + generated_toolpaths[inset_idx].back().junctions.push_back(from); + } else { generated_toolpaths[inset_idx].emplace_back(inset_idx, is_odd); @@ -2027,13 +1985,14 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ unprocessed_quad_starts.insert(&edge); } } - + std::unordered_set passed_odd_edges; - + while (!unprocessed_quad_starts.empty()) { edge_t* poly_domain_start = *unprocessed_quad_starts.begin(); edge_t* quad_start = poly_domain_start; + bool new_domain_start = true; do { edge_t* quad_end = quad_start; @@ -2045,20 +2004,22 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ edge_t* edge_to_peak = getQuadMaxRedgeTo(quad_start); // walk down on both sides and connect junctions edge_t* edge_from_peak = edge_to_peak->next; assert(edge_from_peak); - + unprocessed_quad_starts.erase(quad_start); - + if (! edge_to_peak->data.hasExtrusionJunctions()) { edge_junctions.emplace_back(std::make_shared()); edge_to_peak->data.setExtrusionJunctions(edge_junctions.back()); } + // The junctions on the edge(s) from the start of the quad to the node with highest R LineJunctions from_junctions = *edge_to_peak->data.getExtrusionJunctions(); if (! edge_from_peak->twin->data.hasExtrusionJunctions()) { edge_junctions.emplace_back(std::make_shared()); edge_from_peak->twin->data.setExtrusionJunctions(edge_junctions.back()); } + // The junctions on the edge(s) from the end of the quad to the node with highest R LineJunctions to_junctions = *edge_from_peak->twin->data.getExtrusionJunctions(); if (edge_to_peak->prev) { @@ -2106,22 +2067,29 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ { BOOST_LOG_TRIVIAL(warning) << "Connecting two perimeters with different indices! Perimeter " << from.perimeter_index << " and " << to.perimeter_index; } - - const bool is_odd_segment = edge_to_peak->to->data.bead_count > 0 && edge_to_peak->to->data.bead_count % 2 == 1 // quad contains single bead segment - && edge_to_peak->to->data.transition_ratio == 0 && edge_to_peak->from->data.transition_ratio == 0 && edge_from_peak->to->data.transition_ratio == 0 // We're not in a transition + const bool from_is_odd = + quad_start->to->data.bead_count > 0 && quad_start->to->data.bead_count % 2 == 1 // quad contains single bead segment + && quad_start->to->data.transition_ratio == 0 // We're not in a transition && junction_rev_idx == segment_count - 1 // Is single bead segment - && shorter_then(from.p - quad_start->to->p, scaled(0.005)) && shorter_then(to.p - quad_end->from->p, scaled(0.005)); - + && shorter_then(from.p - quad_start->to->p, scaled(0.005)); + const bool to_is_odd = + quad_end->from->data.bead_count > 0 && quad_end->from->data.bead_count % 2 == 1 // quad contains single bead segment + && quad_end->from->data.transition_ratio == 0 // We're not in a transition + && junction_rev_idx == segment_count - 1 // Is single bead segment + && shorter_then(to.p - quad_end->from->p, scaled(0.005)); + const bool is_odd_segment = from_is_odd && to_is_odd; if (is_odd_segment && passed_odd_edges.count(quad_start->next->twin) > 0) // Only generate toolpath for odd segments once { continue; // Prevent duplication of single bead segments } - + bool from_is_3way = from_is_odd && quad_start->to->isMultiIntersection(); + bool to_is_3way = to_is_odd && quad_end->from->isMultiIntersection(); passed_odd_edges.emplace(quad_start->next); - const bool force_new_path = is_odd_segment && quad_start->to->isMultiIntersection(); - addToolpathSegment(from, to, is_odd_segment, force_new_path); + + addToolpathSegment(from, to, is_odd_segment, new_domain_start, from_is_3way, to_is_3way); } + new_domain_start = false; } while(quad_start = quad_start->getNextUnconnected(), quad_start != poly_domain_start); } @@ -2129,7 +2097,7 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads() { - VariableWidthPaths& generated_toolpaths = *p_generated_toolpaths; + std::vector &generated_toolpaths = *p_generated_toolpaths; for (auto& node : graph.nodes) { @@ -2141,7 +2109,6 @@ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads() if (beading.bead_widths.size() % 2 == 1 && node.isLocalMaximum(true) && !node.isCentral()) { const size_t inset_index = beading.bead_widths.size() / 2; - const size_t& region_id = node.incident_edge->data.getRegion(); constexpr bool is_odd = true; if (inset_index >= generated_toolpaths.size()) { @@ -2149,24 +2116,21 @@ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads() } generated_toolpaths[inset_index].emplace_back(inset_index, is_odd); ExtrusionLine& line = generated_toolpaths[inset_index].back(); - line.junctions.emplace_back(node.p, beading.bead_widths[inset_index], inset_index, region_id); - line.junctions.emplace_back(node.p + Point(50, 0), beading.bead_widths[inset_index], inset_index, region_id); - // TODO: ^^^ magic value ... + Point(50, 0) ^^^ + const coord_t width = beading.bead_widths[inset_index]; + // total area to be extruded is pi*(w/2)^2 = pi*w*w/4 + // Width a constant extrusion width w, that would be a length of pi*w/4 + // If we make a small circle to fill up the hole, then that circle would have a circumference of 2*pi*r + // So our circle needs to be such that r=w/8 + const coord_t r = width / 8; + constexpr coord_t n_segments = 6; + for (coord_t segment = 0; segment < n_segments; segment++) { + float a = 2.0 * M_PI / n_segments * segment; + line.junctions.emplace_back(node.p + Point(r * cos(a), r * sin(a)), width, inset_index); + } } } } -void SkeletalTrapezoidation::liftRegionInfoToLines() -{ - std::for_each(p_generated_toolpaths->begin(), p_generated_toolpaths->end(), [](VariableWidthLines& lines) - { - std::for_each(lines.begin(), lines.end(), [](ExtrusionLine& line) - { - line.region_id = line.junctions.front().region_id; - }); - }); -} - // // ^^^^^^^^^^^^^^^^^^^^^ // TOOLPATH GENERATION diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index fe5e35b571..f9b619c266 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -24,16 +24,16 @@ namespace Slic3r::Arachne /*! * Main class of the dynamic beading strategies. - * + * * The input polygon region is decomposed into trapezoids and represented as a half-edge data-structure. - * + * * We determine which edges are 'central' accordinding to the transitioning_angle of the beading strategy, * and determine the bead count for these central regions and apply them outward when generating toolpaths. [oversimplified] - * + * * The method can be visually explained as generating the 3D union of cones surface on the outline polygons, - * and changing the heights along central regions of that surface so that they are flat. + * and changing the heights along central regions of that surface so that they are flat. * For more info, please consult the paper "A framework for adaptive width control of dense contour-parallel toolpaths in fused -deposition modeling" by Kuipers et al. +deposition modeling" by Kuipers et al. * This visual explanation aid explains the use of "upward", "lower" etc, * i.e. the radial distance and/or the bead count are used as heights of this visualization, there is no coordinate called 'Z'. * @@ -93,7 +93,7 @@ public: * beadings propagated from below and from above, use this transitioning * distance. */ - SkeletalTrapezoidation(const Polygons& polys, + SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy, double transitioning_angle , coord_t discretization_step_size = scaled(0.0008) @@ -118,7 +118,7 @@ public: * "central" but as if it's a obtuse corner. As a result, sharp corners will * no longer end in a single line but will just loop. */ - void generateToolpaths(VariableWidthPaths& generated_toolpaths, bool filter_outermost_central_edges = false); + void generateToolpaths(std::vector &generated_toolpaths, bool filter_outermost_central_edges = false); protected: /*! @@ -136,14 +136,14 @@ protected: /*! * Compute the skeletal trapezoidation decomposition of the input shape. - * + * * Compute the Voronoi Diagram (VD) and transfer all inside edges into our half-edge (HE) datastructure. - * + * * The algorithm is currently a bit overcomplicated, because the discretization of parabolic edges is performed at the same time as all edges are being transfered, * which means that there is no one-to-one mapping from VD edges to HE edges. * Instead we map from a VD edge to the last HE edge. * This could be cimplified by recording the edges which should be discretized and discretizing the mafterwards. - * + * * Another complication arises because the VD uses floating logic, which can result in zero-length segments after rounding to integers. * We therefore collapse edges and their whole cells afterwards. */ @@ -160,7 +160,7 @@ protected: /*! * (Eventual) returned 'polylines per index' result (from generateToolpaths): */ - VariableWidthPaths* p_generated_toolpaths; + std::vector *p_generated_toolpaths; /*! * Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges) @@ -261,7 +261,7 @@ protected: /*! * Filter out small central areas. - * + * * Only used to get rid of small edges which get marked as central because * of rounding errors because the region is so small. */ @@ -277,9 +277,9 @@ protected: /*! * Unmark the outermost edges directly connected to the outline, as not * being central. - * + * * Only used to emulate some related literature. - * + * * The paper shows that this function is bad for the stability of the framework. */ void filterOuterCentral(); @@ -458,12 +458,6 @@ protected: // ^ transitioning ^ - /*! - * It's useful to know when the paths get back to the consumer, to (what part of) a polygon the paths 'belong'. - * A single polygon without a hole is one region, a polygon with (a) hole(s) has 2 regions. - */ - void markRegions(); - // v toolpath generation v /*! @@ -473,7 +467,7 @@ protected: /*! * From a quad (a group of linked edges in one cell of the Voronoi), find - * the edge that is furthest away from the border of the polygon. + * the edge pointing to the node that is furthest away from the border of the polygon. * \param quad_start_edge The first edge of the quad. * \return The edge of the quad that is furthest away from the border. */ @@ -482,27 +476,27 @@ protected: /*! * Propagate beading information from nodes that are closer to the edge * (low radius R) to nodes that are farther from the edge (high R). - * + * * only propagate from nodes with beading info upward to nodes without beading info - * + * * Edges are sorted by their radius, so that we can do a depth-first walk * without employing a recursive algorithm. - * + * * In upward propagated beadings we store the distance traveled, so that we can merge these beadings with the downward propagated beadings in \ref propagateBeadingsDownward(.) - * + * * \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first. */ void propagateBeadingsUpward(std::vector& upward_quad_mids, ptr_vector_t& node_beadings); /*! * propagate beading info from higher R nodes to lower R nodes - * + * * merge with upward propagated beadings if they are encountered - * + * * don't transfer to nodes which lie on the outline polygon - * + * * edges are sorted so that we can do a depth-first walk without employing a recursive algorithm - * + * * \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first. */ void propagateBeadingsDownward(std::vector& upward_quad_mids, ptr_vector_t& node_beadings); @@ -573,9 +567,16 @@ protected: void generateJunctions(ptr_vector_t& node_beadings, ptr_vector_t& edge_junctions); /*! - * add a new toolpath segment, defined between two extrusion-juntions + * Add a new toolpath segment, defined between two extrusion-juntions. + * + * \param from The junction from which to add a segment. + * \param to The junction to which to add a segment. + * \param is_odd Whether this segment is an odd gap filler along the middle of the skeleton. + * \param force_new_path Whether to prevent adding this path to an existing path which ends in \p from + * \param from_is_3way Whether the \p from junction is a splitting junction where two normal wall lines and a gap filler line come together. + * \param to_is_3way Whether the \p to junction is a splitting junction where two normal wall lines and a gap filler line come together. */ - void addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path); + void addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way); /*! * connect junctions in each quad @@ -586,11 +587,6 @@ protected: * Genrate small segments for local maxima where the beading would only result in a single bead */ void generateLocalMaximaSingleBeads(); - - /*! - * Extract region information from the junctions, for easier access to that info directly from the lines. - */ - void liftRegionInfoToLines(); }; } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp index c2b5889794..ddc1c3ce83 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp @@ -52,14 +52,8 @@ public: }; EdgeType type; - SkeletalTrapezoidationEdge() - : SkeletalTrapezoidationEdge(EdgeType::NORMAL) - {} - SkeletalTrapezoidationEdge(const EdgeType& type) - : type(type) - , is_central(Central::UNKNOWN) - , region(0) - {} + SkeletalTrapezoidationEdge() : SkeletalTrapezoidationEdge(EdgeType::NORMAL) {} + SkeletalTrapezoidationEdge(const EdgeType &type) : type(type), is_central(Central::UNKNOWN) {} bool isCentral() const { @@ -75,21 +69,6 @@ public: return is_central != Central::UNKNOWN; } - size_t getRegion() const - { - assert(region != 0); - return region; - } - void setRegion(const size_t& r) - { - assert(region == 0); - region = r; - } - bool regionIsSet() const - { - return region > 0; - } - bool hasTransitions(bool ignore_empty = false) const { return transitions.use_count() > 0 && (ignore_empty || ! transitions.lock()->empty()); @@ -131,7 +110,6 @@ public: private: Central is_central; //! whether the edge is significant; whether the source segments have a sharp angle; -1 is unknown - size_t region; //! what 'region' this edge is in ... if the originating polygon has no holes, there's one region -- useful for later algorithms that need to know where the paths came from std::weak_ptr> transitions; std::weak_ptr> transition_ends; diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp index a28c69f87a..4ef96eda1a 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp @@ -128,6 +128,10 @@ bool STHalfEdgeNode::isMultiIntersection() edge_t* outgoing = this->incident_edge; do { + if ( ! outgoing) + { // This is a node on the outside + return false; + } if (outgoing->data.isCentral()) { odd_path_count++; @@ -174,39 +178,6 @@ bool STHalfEdgeNode::isLocalMaximum(bool strict) const return true; } -void SkeletalTrapezoidationGraph::fixNodeDuplication() -{ - for (auto node_it = nodes.begin(); node_it != nodes.end();) - { - node_t* replacing_node = nullptr; - for (edge_t* outgoing = node_it->incident_edge; outgoing != node_it->incident_edge; outgoing = outgoing->twin->next) - { - assert(outgoing); - if (outgoing->from != &*node_it) - { - replacing_node = outgoing->from; - } - if (outgoing->twin->to != &*node_it) - { - replacing_node = outgoing->twin->to; - } - } - if (replacing_node) - { - for (edge_t* outgoing = node_it->incident_edge; outgoing != node_it->incident_edge; outgoing = outgoing->twin->next) - { - outgoing->twin->to = replacing_node; - outgoing->from = replacing_node; - } - node_it = nodes.erase(node_it); - } - else - { - ++node_it; - } - } -} - void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist) { std::unordered_map::iterator> edge_locator; diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp index 92aba36a04..49e93f1272 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp @@ -70,7 +70,6 @@ class SkeletalTrapezoidationGraph: public HalfEdgeGraph //For std::partition_copy and std::min_element. @@ -8,10 +8,15 @@ #include "SkeletalTrapezoidation.hpp" #include "../ClipperUtils.hpp" -#include "Arachne/utils/linearAlg2D.hpp" +#include "utils/linearAlg2D.hpp" #include "EdgeGrid.hpp" #include "utils/SparseLineGrid.hpp" #include "Geometry.hpp" +#include "utils/PolylineStitcher.hpp" +#include "SVG.hpp" +#include "Utils.hpp" + +#include namespace Slic3r::Arachne { @@ -40,7 +45,6 @@ WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0 , bead_width_x(bead_width_x) , inset_count(inset_count) , wall_0_inset(wall_0_inset) - , strategy_type(print_object_config.beading_strategy_type.value) , print_thin_walls(Slic3r::Arachne::fill_outline_gaps) , min_feature_size(scaled(print_object_config.min_feature_size.value)) , min_bead_width(scaled(print_object_config.min_bead_width.value)) @@ -225,21 +229,6 @@ void simplify(Polygons &thiss, const int64_t smallest_line_segment = scaled operator()(const PolygonsPointIndex &val) const - { - const Polygon &poly = (*val.polygons)[val.poly_idx]; - const Point start = poly[val.point_idx]; - unsigned int next_point_idx = (val.point_idx + 1) % poly.size(); - const Point end = poly[next_point_idx]; - return std::pair(start, end); - } -}; - typedef SparseLineGrid LocToLineGrid; std::unique_ptr createLocToLineGrid(const Polygons &polygons, int square_size) { @@ -309,7 +298,7 @@ void fixSelfIntersections(const coord_t epsilon, Polygons &thiss) */ void removeDegenerateVerts(Polygons &thiss) { - for (unsigned int poly_idx = 0; poly_idx < thiss.size(); poly_idx++) { + for (size_t poly_idx = 0; poly_idx < thiss.size(); poly_idx++) { Polygon &poly = thiss[poly_idx]; Polygon result; @@ -319,14 +308,17 @@ void removeDegenerateVerts(Polygons &thiss) return last_line.dot(next_line) == -1 * last_line.norm() * next_line.norm(); }; bool isChanged = false; - for (unsigned int idx = 0; idx < poly.size(); idx++) { + for (size_t idx = 0; idx < poly.size(); idx++) { const Point &last = (result.size() == 0) ? poly.back() : result.back(); - if (idx + 1 == poly.size() && result.size() == 0) { break; } - Point &next = (idx + 1 == poly.size()) ? result[0] : poly[idx + 1]; + if (idx + 1 == poly.size() && result.size() == 0) + break; + + const Point &next = (idx + 1 == poly.size()) ? result[0] : poly[idx + 1]; if (isDegenerate(last, poly[idx], next)) { // lines are in the opposite direction // don't add vert to the result isChanged = true; - while (result.size() > 1 && isDegenerate(result[result.size() - 2], result.back(), next)) { result.points.pop_back(); } + while (result.size() > 1 && isDegenerate(result[result.size() - 2], result.back(), next)) + result.points.pop_back(); } else { result.points.emplace_back(poly[idx]); } @@ -486,7 +478,7 @@ void removeColinearEdges(Polygons &thiss, const double max_deviation_angle = 0.0 } } -const VariableWidthPaths& WallToolPaths::generate() +const std::vector &WallToolPaths::generate() { const coord_t smallest_segment = Slic3r::Arachne::meshfix_maximum_resolution; const coord_t allowed_distance = Slic3r::Arachne::meshfix_maximum_deviation; @@ -506,42 +498,49 @@ const VariableWidthPaths& WallToolPaths::generate() removeDegenerateVerts(prepared_outline); removeSmallAreas(prepared_outline, small_area_length * small_area_length, false); - if (area(prepared_outline) > 0) - { - const coord_t wall_transition_length = scaled(this->print_object_config.wall_transition_length.value); - const double wall_split_middle_threshold = this->print_object_config.wall_split_middle_threshold.value / 100.; // For an uneven nr. of lines: When to split the middle wall into two. - const double wall_add_middle_threshold = this->print_object_config.wall_add_middle_threshold.value / 100.; // For an even nr. of lines: When to add a new middle in between the innermost two walls. - const int wall_distribution_count = this->print_object_config.wall_distribution_count.value; - const size_t max_bead_count = (inset_count < std::numeric_limits::max() / 2) ? 2 * inset_count : std::numeric_limits::max(); - const auto beading_strat = BeadingStrategyFactory::makeStrategy - ( - strategy_type, - bead_width_0, - bead_width_x, - wall_transition_length, - transitioning_angle, - print_thin_walls, - min_bead_width, - min_feature_size, - wall_split_middle_threshold, - wall_add_middle_threshold, - max_bead_count, - wall_0_inset, - wall_distribution_count - ); - const coord_t transition_filter_dist = scaled(this->print_object_config.wall_transition_filter_distance.value); - SkeletalTrapezoidation wall_maker - ( - prepared_outline, - *beading_strat, - beading_strat->getTransitioningAngle(), - discretization_step_size, - transition_filter_dist, - wall_transition_length - ); - wall_maker.generateToolpaths(toolpaths); - computeInnerContour(); + if (area(prepared_outline) <= 0) { + assert(toolpaths.empty()); + return toolpaths; } + + const coord_t wall_transition_length = scaled(this->print_object_config.wall_transition_length.value); + const double wall_split_middle_threshold = this->print_object_config.wall_split_middle_threshold.value / 100.; // For an uneven nr. of lines: When to split the middle wall into two. + const double wall_add_middle_threshold = this->print_object_config.wall_add_middle_threshold.value / 100.; // For an even nr. of lines: When to add a new middle in between the innermost two walls. + const int wall_distribution_count = this->print_object_config.wall_distribution_count.value; + const size_t max_bead_count = (inset_count < std::numeric_limits::max() / 2) ? 2 * inset_count : std::numeric_limits::max(); + const auto beading_strat = BeadingStrategyFactory::makeStrategy + ( + bead_width_0, + bead_width_x, + wall_transition_length, + transitioning_angle, + print_thin_walls, + min_bead_width, + min_feature_size, + wall_split_middle_threshold, + wall_add_middle_threshold, + max_bead_count, + wall_0_inset, + wall_distribution_count + ); + const coord_t transition_filter_dist = scaled(this->print_object_config.wall_transition_filter_distance.value); + SkeletalTrapezoidation wall_maker + ( + prepared_outline, + *beading_strat, + beading_strat->getTransitioningAngle(), + discretization_step_size, + transition_filter_dist, + wall_transition_length + ); + wall_maker.generateToolpaths(toolpaths); + + stitchToolPaths(toolpaths, this->bead_width_x); + + removeSmallLines(toolpaths); + + separateOutInnerContour(); + simplifyToolPaths(toolpaths); removeEmptyToolPaths(toolpaths); @@ -554,7 +553,127 @@ const VariableWidthPaths& WallToolPaths::generate() return toolpaths; } -void WallToolPaths::simplifyToolPaths(VariableWidthPaths& toolpaths/*, const Settings& settings*/) +void WallToolPaths::stitchToolPaths(std::vector &toolpaths, const coord_t bead_width_x) +{ + const coord_t stitch_distance = bead_width_x - 1; //In 0-width contours, junctions can cause up to 1-line-width gaps. Don't stitch more than 1 line width. + + for (unsigned int wall_idx = 0; wall_idx < toolpaths.size(); wall_idx++) { + VariableWidthLines& wall_lines = toolpaths[wall_idx]; + + VariableWidthLines stitched_polylines; + VariableWidthLines closed_polygons; + PolylineStitcher::stitch(wall_lines, stitched_polylines, closed_polygons, stitch_distance); +#ifdef DEBUG + for (const ExtrusionLine& line : stitched_polylines) { + if ( ! line.is_odd && line.polylineLength() > 3 * stitch_distance && line.size() > 3) { + BOOST_LOG_TRIVIAL(error) << "Some even contour lines could not be closed into polygons!"; + assert(false && "Some even contour lines could not be closed into polygons!"); + BoundingBox aabb; + for (auto line2 : wall_lines) + for (auto j : line2) + aabb.merge(j.p); + { + static int iRun = 0; + SVG svg(debug_out_path("contours_before.svg-%d.png", iRun), aabb); + std::array colors = {"gray", "black", "blue", "green", "lime", "purple", "red", "yellow"}; + size_t color_idx = 0; + for (auto& inset : toolpaths) + for (auto& line2 : inset) { + // svg.writePolyline(line2.toPolygon(), col); + + Polygon poly = line2.toPolygon(); + Point last = poly.front(); + for (size_t idx = 1 ; idx < poly.size(); idx++) { + Point here = poly[idx]; + svg.draw(Line(last, here), colors[color_idx]); +// svg.draw_text((last + here) / 2, std::to_string(line2.junctions[idx].region_id).c_str(), "black"); + last = here; + } + svg.draw(poly[0], colors[color_idx]); + // svg.nextLayer(); + // svg.writePoints(poly, true, 0.1); + // svg.nextLayer(); + color_idx = (color_idx + 1) % colors.size(); + } + } + { + static int iRun = 0; + SVG svg(debug_out_path("contours-%d.svg", iRun), aabb); + for (auto& inset : toolpaths) + for (auto& line2 : inset) + svg.draw_outline(line2.toPolygon(), "gray"); + for (auto& line2 : stitched_polylines) { + const char *col = line2.is_odd ? "gray" : "red"; + if ( ! line2.is_odd) + std::cerr << "Non-closed even wall of size: " << line2.size() << " at " << line2.front().p << "\n"; + if ( ! line2.is_odd) + svg.draw(line2.front().p); + Polygon poly = line2.toPolygon(); + Point last = poly.front(); + for (size_t idx = 1 ; idx < poly.size(); idx++) + { + Point here = poly[idx]; + svg.draw(Line(last, here), col); + last = here; + } + } + for (auto line2 : closed_polygons) + svg.draw(line2.toPolygon()); + } + } + } +#endif // DEBUG + wall_lines = stitched_polylines; // replace input toolpaths with stitched polylines + + for (ExtrusionLine& wall_polygon : closed_polygons) + { + if (wall_polygon.junctions.empty()) + { + continue; + } + wall_polygon.is_closed = true; + wall_lines.emplace_back(std::move(wall_polygon)); // add stitched polygons to result + } +#ifdef DEBUG + for (ExtrusionLine& line : wall_lines) + { + assert(line.inset_idx == wall_idx); + } +#endif // DEBUG + } +} + +template bool shorterThan(const T &shape, const coord_t check_length) +{ + const auto *p0 = &shape.back(); + int64_t length = 0; + for (const auto &p1 : shape) { + length += (*p0 - p1).template cast().norm(); + if (length >= check_length) + return false; + p0 = &p1; + } + return true; +} + +void WallToolPaths::removeSmallLines(std::vector &toolpaths) +{ + for (VariableWidthLines &inset : toolpaths) { + for (size_t line_idx = 0; line_idx < inset.size(); line_idx++) { + ExtrusionLine &line = inset[line_idx]; + coord_t min_width = std::numeric_limits::max(); + for (const ExtrusionJunction &j : line) + min_width = std::min(min_width, j.w); + if (line.is_odd && !line.is_closed && shorterThan(line, min_width / 2)) { // remove line + line = std::move(inset.back()); + inset.erase(--inset.end()); + line_idx--; // reconsider the current position + } + } + } +} + +void WallToolPaths::simplifyToolPaths(std::vector &toolpaths) { for (size_t toolpaths_idx = 0; toolpaths_idx < toolpaths.size(); ++toolpaths_idx) { @@ -568,57 +687,62 @@ void WallToolPaths::simplifyToolPaths(VariableWidthPaths& toolpaths/*, const Set } } -const VariableWidthPaths& WallToolPaths::getToolPaths() +const std::vector &WallToolPaths::getToolPaths() { if (!toolpaths_generated) - { return generate(); - } return toolpaths; } -void WallToolPaths::computeInnerContour() +void WallToolPaths::separateOutInnerContour() { //We'll remove all 0-width paths from the original toolpaths and store them separately as polygons. - VariableWidthPaths actual_toolpaths; + std::vector actual_toolpaths; actual_toolpaths.reserve(toolpaths.size()); //A bit too much, but the correct order of magnitude. - VariableWidthPaths contour_paths; + std::vector contour_paths; contour_paths.reserve(toolpaths.size() / inset_count); - std::partition_copy(toolpaths.begin(), toolpaths.end(), std::back_inserter(actual_toolpaths), std::back_inserter(contour_paths), - [](const VariableWidthLines& path) - { - for(const ExtrusionLine& line : path) - { - for(const ExtrusionJunction& junction : line.junctions) - { - return junction.w != 0; //On the first actual junction, decide: If it's got 0 width, this is a contour. Otherwise it is an actual toolpath. - } - } - return true; //No junctions with any vertices? Classify it as a toolpath then. - }); - if (! actual_toolpaths.empty()) - { - toolpaths = std::move(actual_toolpaths); //Filtered out the 0-width paths. - } - else - { - toolpaths.clear(); - } - - //Now convert the contour_paths to Polygons to denote the inner contour of the walled areas. inner_contour.clear(); + for (const VariableWidthLines &inset : toolpaths) { + if (inset.empty()) + continue; + bool is_contour = false; + for (const ExtrusionLine &line : inset) { + for (const ExtrusionJunction &j : line) { + if (j.w == 0) + is_contour = true; + else + is_contour = false; + break; + } + } - //We're going to have to stitch these paths since not all walls may be closed contours. - //Since these walls have 0 width they should theoretically be closed. But there may be rounding errors. - const coord_t minimum_line_width = bead_width_0 / 2; - stitchContours(contour_paths, minimum_line_width, inner_contour); + if (is_contour) { +#ifdef DEBUG + for (const ExtrusionLine &line : inset) + for (const ExtrusionJunction &j : line) + assert(j.w == 0); +#endif // DEBUG + for (const ExtrusionLine &line : inset) { + if (line.is_odd) + continue; // odd lines don't contribute to the contour + else if (line.is_closed) // sometimes an very small even polygonal wall is not stitched into a polygon + inner_contour.emplace_back(line.toPolygon()); + } + } else { + actual_toolpaths.emplace_back(inset); + } + } + if (!actual_toolpaths.empty()) + toolpaths = std::move(actual_toolpaths); // Filtered out the 0-width paths. + else + toolpaths.clear(); //The output walls from the skeletal trapezoidation have no known winding order, especially if they are joined together from polylines. //They can be in any direction, clockwise or counter-clockwise, regardless of whether the shapes are positive or negative. //To get a correct shape, we need to make the outside contour positive and any holes inside negative. //This can be done by applying the even-odd rule to the shape. This rule is not sensitive to the winding order of the polygon. //The even-odd rule would be incorrect if the polygon self-intersects, but that should never be generated by the skeletal trapezoidation. - inner_contour = union_(inner_contour); + inner_contour = union_(inner_contour, ClipperLib::PolyFillType::pftEvenOdd); } const Polygons& WallToolPaths::getInnerContour() @@ -634,7 +758,7 @@ const Polygons& WallToolPaths::getInnerContour() return inner_contour; } -bool WallToolPaths::removeEmptyToolPaths(VariableWidthPaths& toolpaths) +bool WallToolPaths::removeEmptyToolPaths(std::vector &toolpaths) { toolpaths.erase(std::remove_if(toolpaths.begin(), toolpaths.end(), [](const VariableWidthLines& lines) { @@ -643,222 +767,99 @@ bool WallToolPaths::removeEmptyToolPaths(VariableWidthPaths& toolpaths) return toolpaths.empty(); } -void WallToolPaths::stitchContours(const VariableWidthPaths& input, const coord_t stitch_distance, Polygons& output) +/*! + * Get the order constraints of the insets when printing walls per region / hole. + * Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right. + * + * Odd walls should always go after their enclosing wall polygons. + * + * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. + */ +std::unordered_set, boost::hash>> WallToolPaths::getRegionOrder(const std::vector &input, const bool outer_to_inner) { - // Create a bucket grid to find endpoints that are close together. - struct ExtrusionLineStartLocator + std::unordered_set, boost::hash>> order_requirements; + + // We build a grid where we map toolpath vertex locations to toolpaths, + // so that we can easily find which two toolpaths are next to each other, + // which is the requirement for there to be an order constraint. + // + // We use a PointGrid rather than a LineGrid to save on computation time. + // In very rare cases two insets might lie next to each other without having neighboring vertices, e.g. + // \ . + // | / . + // | / . + // || . + // | \ . + // | \ . + // / . + // However, because of how Arachne works this will likely never be the case for two consecutive insets. + // On the other hand one could imagine that two consecutive insets of a very large circle + // could be simplify()ed such that the remaining vertices of the two insets don't align. + // In those cases the order requirement is not captured, + // which means that the PathOrderOptimizer *might* result in a violation of the user set path order. + // This problem is expected to be not so severe and happen very sparsely. + + coord_t max_line_w = 0u; + for (const ExtrusionLine *line : input) // compute max_line_w + for (const ExtrusionJunction &junction : *line) + max_line_w = std::max(max_line_w, junction.w); + if (max_line_w == 0u) + return order_requirements; + + struct LineLoc { - const Point *operator()(const ExtrusionLine *line) { return &line->junctions.front().p; } + ExtrusionJunction j; + const ExtrusionLine *line; }; - struct ExtrusionLineEndLocator + struct Locator { - const Point *operator()(const ExtrusionLine *line) { return &line->junctions.back().p; } + Point operator()(const LineLoc &elem) { return elem.j.p; } }; - // Only find endpoints closer than minimum_line_width, so we can't ever accidentally make crossing contours. - ClosestPointInRadiusLookup line_starts(coord_t(stitch_distance * std::sqrt(2.))); - ClosestPointInRadiusLookup line_ends(coord_t(stitch_distance * std::sqrt(2.))); + // How much farther two verts may be apart due to corners. + // This distance must be smaller than 2, because otherwise + // we could create an order requirement between e.g. + // wall 2 of one region and wall 3 of another region, + // while another wall 3 of the first region would lie in between those two walls. + // However, higher values are better against the limitations of using a PointGrid rather than a LineGrid. + constexpr float diagonal_extension = 1.9f; + const auto searching_radius = coord_t(max_line_w * diagonal_extension); + using GridT = SparsePointGrid; + GridT grid(searching_radius); - auto get_search_bbox = [](const Point &pt, const coord_t radius) -> BoundingBox { - const Point min_grid((pt - Point(radius, radius)) / radius); - const Point max_grid((pt + Point(radius, radius)) / radius); - return {min_grid * radius, (max_grid + Point(1, 1)) * radius - Point(1, 1)}; - }; - - for (const VariableWidthLines &path : input) { - for (const ExtrusionLine &line : path) { - line_starts.insert(&line); - line_ends.insert(&line); - } - } - //Then go through all lines and construct chains of polylines if the endpoints are nearby. - std::unordered_set processed_lines; //Track which lines were already processed to not process them twice. - for(const VariableWidthLines& path : input) - { - for(const ExtrusionLine& line : path) - { - if(processed_lines.find(&line) != processed_lines.end()) //We already added this line before. It got added as a nearby line. - { + for (const ExtrusionLine *line : input) + for (const ExtrusionJunction &junction : *line) grid.insert(LineLoc{junction, line}); + for (const std::pair &pair : grid) { + const LineLoc &lineloc_here = pair.second; + const ExtrusionLine *here = lineloc_here.line; + Point loc_here = pair.second.j.p; + std::vector nearby_verts = grid.getNearby(loc_here, searching_radius); + for (const LineLoc &lineloc_nearby : nearby_verts) { + const ExtrusionLine *nearby = lineloc_nearby.line; + if (nearby == here) continue; - } - //We'll create a chain of polylines that get joined together. We can add polylines on both ends! - std::deque chain; - std::deque is_reversed; //Lines could need to be inserted in reverse. Must coincide with the `chain` deque. - const ExtrusionLine* nearest = &line; //At every iteration, add the polyline that joins together most closely. - bool nearest_reverse = false; //Whether the next line to insert must be inserted in reverse. - bool nearest_before = false; //Whether the next line to insert must be inserted in the front of the chain. - while(nearest) - { - if(processed_lines.find(nearest) != processed_lines.end()) - { - break; //Looping. This contour is already processed. - } - processed_lines.insert(nearest); - if(nearest_before) - { - chain.push_front(nearest); - is_reversed.push_front(nearest_reverse); - } - else - { - chain.push_back(nearest); - is_reversed.push_back(nearest_reverse); - } - - //Find any nearby lines to attach. Look on both ends of our current chain and find both ends of polylines. - const Point chain_start = is_reversed.front() ? chain.front()->junctions.back().p : chain.front()->junctions.front().p; - const Point chain_end = is_reversed.back() ? chain.back()->junctions.front().p : chain.back()->junctions.back().p; - - std::vector> starts_near_start = line_starts.find_all(chain_start); - std::vector> ends_near_start = line_ends.find_all(chain_start); - std::vector> starts_near_end = line_starts.find_all(chain_end); - std::vector> ends_near_end = line_ends.find_all(chain_end); - - nearest = nullptr; - int64_t nearest_dist2 = std::numeric_limits::max(); - for (const auto &candidate_ptr : starts_near_start) { - const ExtrusionLine* candidate = *candidate_ptr.first; - if(processed_lines.find(candidate) != processed_lines.end()) - continue; //Already processed this line before. It's linked to something else. - - if (const int64_t dist2 = (candidate->junctions.front().p - chain_start).cast().squaredNorm(); dist2 < nearest_dist2) { - nearest = candidate; - nearest_dist2 = dist2; - nearest_reverse = true; - nearest_before = true; - } - } - for (const auto &candidate_ptr : ends_near_start) { - const ExtrusionLine* candidate = *candidate_ptr.first; - if(processed_lines.find(candidate) != processed_lines.end()) - continue; - - if (const int64_t dist2 = (candidate->junctions.back().p - chain_start).cast().squaredNorm(); dist2 < nearest_dist2) { - nearest = candidate; - nearest_dist2 = dist2; - nearest_reverse = false; - nearest_before = true; - } - } - for (const auto &candidate_ptr : starts_near_end) { - const ExtrusionLine* candidate = *candidate_ptr.first; - if(processed_lines.find(candidate) != processed_lines.end()) - continue; //Already processed this line before. It's linked to something else. - - if (const int64_t dist2 = (candidate->junctions.front().p - chain_start).cast().squaredNorm(); dist2 < nearest_dist2) { - nearest = candidate; - nearest_dist2 = dist2; - nearest_reverse = false; - nearest_before = false; - } - } - for (const auto &candidate_ptr : ends_near_end) { - const ExtrusionLine* candidate = *candidate_ptr.first; - if (processed_lines.find(candidate) != processed_lines.end()) - continue; - - if (const int64_t dist2 = (candidate->junctions.back().p - chain_start).cast().squaredNorm(); dist2 < nearest_dist2) { - nearest = candidate; - nearest_dist2 = dist2; - nearest_reverse = true; - nearest_before = false; - } - } - } - - //Now serialize the entire chain into one polygon. - output.emplace_back(); - for (size_t i = 0; i < chain.size(); ++i) { - if(!is_reversed[i]) - for (const ExtrusionJunction& junction : chain[i]->junctions) - output.back().points.emplace_back(junction.p); - else - for (auto junction = chain[i]->junctions.rbegin(); junction != chain[i]->junctions.rend(); ++junction) - output.back().points.emplace_back(junction->p); - } - } - } -} - -size_t getOuterRegionId(const Arachne::VariableWidthPaths& toolpaths, size_t& out_max_region_id) -{ - // Polygons show up here one by one, so there are always only a) the outer lines and b) the lines that are part of the holes. - // Therefore, the outer-regions' lines will always have the region-id that is larger then all of the other ones. - - // First, build the bounding boxes: - std::map region_ids_to_bboxes; // Use a sorted map, ordered by region_id, so that we can find the largest region_id quickly. - for (const Arachne::VariableWidthLines &path : toolpaths) { - for (const Arachne::ExtrusionLine &line : path) { - BoundingBox &aabb = - region_ids_to_bboxes[line.region_id]; // Empty AABBs are default initialized when region_ids are encountered for the first time. - for (const auto &junction : line.junctions) aabb.merge(junction.p); - } - } - - // Then, the largest of these will be the one that's needed for the outer region, the others' all belong to hole regions: - BoundingBox outer_bbox; - size_t outer_region_id = 0; // Region-ID 0 is reserved for 'None'. - for (const auto ®ion_id_bbox_pair : region_ids_to_bboxes) { - if (region_id_bbox_pair.second.contains(outer_bbox)) { - outer_bbox = region_id_bbox_pair.second; - outer_region_id = region_id_bbox_pair.first; - } - } - - // Maximum Region-ID (using the ordering of the map) - out_max_region_id = region_ids_to_bboxes.empty() ? 0 : region_ids_to_bboxes.rbegin()->first; - return outer_region_id; -} - -Arachne::BinJunctions variableWidthPathToBinJunctions(const Arachne::VariableWidthPaths& toolpaths, const bool pack_regions_by_inset, const bool center_last, std::set* p_bins_with_index_zero_insets) -{ - // Find the largest inset-index: - size_t max_inset_index = 0; - for (const Arachne::VariableWidthLines &path : toolpaths) - max_inset_index = std::max(path.front().inset_idx, max_inset_index); - - // Find which regions are associated with the outer-outer walls (which region is the one the rest is holes inside of): - size_t max_region_id = 0; - const size_t outer_region_id = getOuterRegionId(toolpaths, max_region_id); - - //Since we're (optionally!) splitting off in the outer and inner regions, it may need twice as many bins as inset-indices. - //Add two extra bins for the center-paths, if they need to be stored separately. One bin for inner and one for outer walls. - const size_t max_bin = (pack_regions_by_inset ? (max_region_id * 2) + 2 : (max_inset_index + 1) * 2) + center_last * 2; - Arachne::BinJunctions insets(max_bin + 1); - for (const Arachne::VariableWidthLines &path : toolpaths) { - if (path.empty()) // Don't bother printing these. - continue; - - const size_t inset_index = path.front().inset_idx; - - // Convert list of extrusion lines to vectors of extrusion junctions, and add those to the binned insets. - for (const Arachne::ExtrusionLine &line : path) { - // Sort into the right bin, ... - size_t bin_index; - const bool in_hole_region = line.region_id != outer_region_id && line.region_id != 0; - if (center_last && line.is_odd) { - bin_index = inset_index > 0; - } else if (pack_regions_by_inset) { - bin_index = std::min(inset_index, static_cast(1)) + 2 * (in_hole_region ? line.region_id : 0) + center_last * 2; + if (nearby->inset_idx == here->inset_idx) + continue; + if (nearby->inset_idx > here->inset_idx + 1) + continue; // not directly adjacent + if (here->inset_idx > nearby->inset_idx + 1) + continue; // not directly adjacent + if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension)) + continue; // points are too far away from each other + if (here->is_odd || nearby->is_odd) { + if (here->is_odd && !nearby->is_odd && nearby->inset_idx < here->inset_idx) + order_requirements.emplace(std::make_pair(nearby, here)); + if (nearby->is_odd && !here->is_odd && here->inset_idx < nearby->inset_idx) + order_requirements.emplace(std::make_pair(here, nearby)); + } else if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) { + order_requirements.emplace(std::make_pair(nearby, here)); } else { - bin_index = inset_index + (in_hole_region ? (max_inset_index + 1) : 0) + center_last * 2; + assert((nearby->inset_idx > here->inset_idx) == outer_to_inner); + order_requirements.emplace(std::make_pair(here, nearby)); } - insets[bin_index].emplace_back(line.junctions.begin(), line.junctions.end()); - - // Collect all bins that have zero-inset indices in them, if needed: - if (inset_index == 0 && p_bins_with_index_zero_insets != nullptr) - p_bins_with_index_zero_insets->insert(bin_index); } } - return insets; -} - -BinJunctions WallToolPaths::getBinJunctions(std::set &bins_with_index_zero_insets) -{ - if (!toolpaths_generated) - generate(); - - return variableWidthPathToBinJunctions(toolpaths, true, true, &bins_with_index_zero_insets); + return order_requirements; } } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/WallToolPaths.hpp b/src/libslic3r/Arachne/WallToolPaths.hpp index 35109de101..bc974e09e6 100644 --- a/src/libslic3r/Arachne/WallToolPaths.hpp +++ b/src/libslic3r/Arachne/WallToolPaths.hpp @@ -5,6 +5,7 @@ #define CURAENGINE_WALLTOOLPATHS_H #include +#include #include "BeadingStrategy/BeadingStrategyFactory.hpp" #include "utils/ExtrusionLine.hpp" @@ -45,22 +46,26 @@ public: * Generates the Toolpaths * \return A reference to the newly create ToolPaths */ - const VariableWidthPaths& generate(); + const std::vector &generate(); /*! * Gets the toolpaths, if this called before \p generate() it will first generate the Toolpaths * \return a reference to the toolpaths */ - const VariableWidthPaths& getToolPaths(); + const std::vector &getToolPaths(); - BinJunctions getBinJunctions(std::set &bins_with_index_zero_insets); + /*! + * Alternate 'get', for when the vector that'll be inserted in already exists. + * \param The already existing (or empty) paths these new toolpaths are pushed into. + */ + void pushToolPaths(std::vector &paths); /*! * Compute the inner contour of the walls. This contour indicates where the walled area ends and its infill begins. * The inside can then be filled, e.g. with skin/infill for the walls of a part, or with a pattern in the case of * infill with extra infill walls. */ - void computeInnerContour(); + void separateOutInnerContour(); /*! * Gets the inner contour of the area which is inside of the generated tool @@ -81,32 +86,38 @@ public: * \param toolpaths the VariableWidthPaths generated with \p generate() * \return true if there are still paths left. If all toolpaths were removed it returns false */ - static bool removeEmptyToolPaths(VariableWidthPaths& toolpaths); + static bool removeEmptyToolPaths(std::vector &toolpaths); /*! - * Stitches toolpaths together to form contours. + * Get the order constraints of the insets when printing walls per region / hole. + * Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right. * - * All toolpaths are used. Paths that are not closed will get closed in the - * output by virtue of becoming polygons. As such, the input is expected to - * consist of almost completely closed contours, which may be split up into - * different polylines. - * This function combines those polylines into the polygons they are - * probably intended to depict. - * \param input The paths to stitch together. - * \param stitch_distance Any endpoints closer than this distance can be - * stitched together. An additional line segment will bridge the gap. - * \param output Where to store the output polygons. + * Odd walls should always go after their enclosing wall polygons. + * + * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. */ - static void stitchContours(const VariableWidthPaths& input, const coord_t stitch_distance, Polygons& output) ; + static std::unordered_set, boost::hash>> getRegionOrder(const std::vector &input, bool outer_to_inner); protected: + /*! + * Stitch the polylines together and form closed polygons. + * + * Works on both toolpaths and inner contours simultaneously. + */ + static void stitchToolPaths(std::vector &toolpaths, coord_t bead_width_x); + + /*! + * Remove polylines shorter than half the smallest line width along that polyline. + */ + static void removeSmallLines(std::vector &toolpaths); + /*! * Simplifies the variable-width toolpaths by calling the simplify on every line in the toolpath using the provided * settings. * \param settings The settings as provided by the user * \return */ - static void simplifyToolPaths(VariableWidthPaths& toolpaths/*, const Settings& settings*/); + static void simplifyToolPaths(std::vector &toolpaths); private: const Polygons& outline; // toolpaths; //; //; //; //; //().norm(); prev = next; } + if (is_closed) + len += (front().p - back().p).cast().norm(); + return len; } @@ -40,17 +37,13 @@ coord_t ExtrusionLine::getMinimalWidth() const })->w; } -void ExtrusionLine::appendJunctionsTo(LineJunctions& result) const -{ - result.insert(result.end(), junctions.begin(), junctions.end()); -} - void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared, const int64_t maximum_extrusion_area_deviation) { - if (junctions.size() <= 3) - { + const size_t min_path_size = is_closed ? 3 : 2; + if (junctions.size() <= min_path_size) return; - } + + // TODO: allow for the first point to be removed in case of simplifying closed Extrusionlines. /* ExtrusionLines are treated as (open) polylines, so in case an ExtrusionLine is actually a closed polygon, its * starting and ending points will be equal (or almost equal). Therefore, the simplification of the ExtrusionLine @@ -126,9 +119,7 @@ void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const // We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed && extrusion_area_error <= maximum_extrusion_area_deviation) { - // Adjust the width of the entire P-N line as a weighted average of the widths of the P-C and C-N lines and - // then remove the current junction (vertex). - next.w = weighted_average_width; + // Remove the current junction (vertex). continue; } @@ -155,7 +146,7 @@ void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const else { // New point seems like a valid one. - const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index, current.region_id); + const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index); // If there was a previous point added, remove it. if(!new_junctions.empty()) { @@ -208,8 +199,8 @@ int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, * | |--------------------------| | |***************************| * | | ------------------------------------------ * --------------- ^ ************** - * ^ C.w ^ - * B.w new_width = weighted_average_width + * ^ B.w + C.w / 2 ^ + * A.w + B.w / 2 new_width = weighted_average_width * * * ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the @@ -218,20 +209,22 @@ int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, * */ const int64_t ab_length = (B - A).cast().norm(); const int64_t bc_length = (C - B).cast().norm(); - const int64_t width_diff = llabs(B.w - C.w); + const coord_t width_diff = std::max(std::abs(B.w - A.w), std::abs(C.w - B.w)); if (width_diff > 1) { // Adjust the width only if there is a difference, or else the rounding errors may produce the wrong // weighted average value. - assert(((int64_t(ab_length) * int64_t(B.w) + int64_t(bc_length) * int64_t(C.w)) / (C - A).cast().norm()) <= std::numeric_limits::max()); - weighted_average_width = (ab_length * int64_t(B.w) + bc_length * int64_t(C.w)) / (C - A).cast().norm(); - assert((double(llabs(B.w - weighted_average_width)) * double(ab_length) + double(llabs(C.w - weighted_average_width)) * double(bc_length)) <= double(std::numeric_limits::max())); - return int64_t(llabs(B.w - weighted_average_width)) * ab_length + int64_t(llabs(C.w - weighted_average_width)) * bc_length; + const int64_t ab_weight = (A.w + B.w) / 2; + const int64_t bc_weight = (B.w + C.w) / 2; + assert(((ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast().norm()) <= std::numeric_limits::max()); + weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast().norm(); + assert((int64_t(std::abs(ab_weight - weighted_average_width)) * ab_length + int64_t(std::abs(bc_weight - weighted_average_width)) * bc_length) <= double(std::numeric_limits::max())); + return std::abs(ab_weight - weighted_average_width) * ab_length + std::abs(bc_weight - weighted_average_width) * bc_length; } else { // If the width difference is very small, then select the width of the segment that is longer - weighted_average_width = ab_length > bc_length ? B.w : C.w; + weighted_average_width = ab_length > bc_length ? A.w : B.w; assert((int64_t(width_diff) * int64_t(bc_length)) <= std::numeric_limits::max()); assert((int64_t(width_diff) * int64_t(ab_length)) <= std::numeric_limits::max()); return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length; diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp index 0f6e894971..a4a356e4f2 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp @@ -42,11 +42,20 @@ struct ExtrusionLine bool is_odd; /*! - * Which region this line is part of. A solid polygon without holes has only one region. - * A polygon with holes has 2. Disconnected parts of the polygon are also separate regions. - * Will be 0 if no region was given. + * Whether this is a closed polygonal path */ - size_t region_id; + bool is_closed; + + /*! + * Gets the number of vertices in this polygon. + * \return The number of vertices in this polygon. + */ + size_t size() const { return junctions.size(); } + + /*! + * Whether there are no junctions. + */ + bool empty() const { return junctions.empty(); } /*! * The list of vertices along which this path runs. @@ -55,23 +64,79 @@ struct ExtrusionLine */ std::vector junctions; - ExtrusionLine(const size_t inset_idx, const bool is_odd, const size_t region_id = 0); + ExtrusionLine(const size_t inset_idx, const bool is_odd); + ExtrusionLine() : inset_idx(-1), is_odd(true), is_closed(false) {} + ExtrusionLine(const ExtrusionLine &other) : inset_idx(other.inset_idx), is_odd(other.is_odd), is_closed(other.is_closed), junctions(other.junctions) {} + + ExtrusionLine &operator=(ExtrusionLine &&other) + { + junctions = std::move(other.junctions); + inset_idx = other.inset_idx; + is_odd = other.is_odd; + is_closed = other.is_closed; + return *this; + } + + ExtrusionLine &operator=(const ExtrusionLine &other) + { + junctions = other.junctions; + inset_idx = other.inset_idx; + is_odd = other.is_odd; + is_closed = other.is_closed; + return *this; + } + + std::vector::const_iterator begin() const { return junctions.begin(); } + std::vector::const_iterator end() const { return junctions.end(); } + std::vector::const_reverse_iterator rbegin() const { return junctions.rbegin(); } + std::vector::const_reverse_iterator rend() const { return junctions.rend(); } + std::vector::const_reference front() const { return junctions.front(); } + std::vector::const_reference back() const { return junctions.back(); } + const ExtrusionJunction &operator[](unsigned int index) const { return junctions[index]; } + ExtrusionJunction &operator[](unsigned int index) { return junctions[index]; } + std::vector::iterator begin() { return junctions.begin(); } + std::vector::iterator end() { return junctions.end(); } + std::vector::reference front() { return junctions.front(); } + std::vector::reference back() { return junctions.back(); } + + template void emplace_back(Args &&...args) { junctions.emplace_back(args...); } + void remove(unsigned int index) { junctions.erase(junctions.begin() + index); } + void insert(size_t index, const ExtrusionJunction &p) { junctions.insert(junctions.begin() + index, p); } + + template + std::vector::iterator insert(std::vector::const_iterator pos, iterator first, iterator last) + { + return junctions.insert(pos, first, last); + } + + void clear() { junctions.clear(); } + void reverse() { std::reverse(junctions.begin(), junctions.end()); } /*! * Sum the total length of this path. */ coord_t getLength() const; + coord_t polylineLength() const { return getLength(); } + + /*! + * Put all junction locations into a polygon object. + * + * When this path is not closed the returned Polygon should be handled as a polyline, rather than a polygon. + */ + Polygon toPolygon() const + { + Polygon ret; + for (const ExtrusionJunction &j : junctions) + ret.points.emplace_back(j.p); + + return ret; + } /*! * Get the minimal width of this path */ coord_t getMinimalWidth() const; - /*! - * Export the included junctions as vector. - */ - void appendJunctionsTo(LineJunctions& result) const; - /*! * Removes vertices of the ExtrusionLines to make sure that they are not too high * resolution. @@ -122,10 +187,7 @@ struct ExtrusionLine static int64_t calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width); }; -using VariableWidthLines = std::vector; //; //= 2); Slic3r::ThickPolyline out; @@ -145,7 +207,8 @@ static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::LineJunctio return out; } -static inline Polygon to_polygon(const ExtrusionLine& line) { +static inline Polygon to_polygon(const ExtrusionLine &line) +{ Polygon out; assert(line.junctions.size() >= 3); assert(line.junctions.front().p == line.junctions.back().p); @@ -155,5 +218,7 @@ static inline Polygon to_polygon(const ExtrusionLine& line) { return out; } +using VariableWidthLines = std::vector; // +class PathsPointIndex { public: /*! * The polygons into which this index is indexing. */ - const Polygons* polygons; // (pointer to const polygons) + const Paths* polygons; // (pointer to const polygons) unsigned int poly_idx; //!< The index of the polygon in \ref PolygonsPointIndex::polygons @@ -35,7 +39,7 @@ public: * needed. Since the `polygons` field is const you can't ever make this * initialisation useful. */ - PolygonsPointIndex() : polygons(nullptr), poly_idx(0), point_idx(0) {} + PathsPointIndex() : polygons(nullptr), poly_idx(0), point_idx(0) {} /*! * Constructs a new point index to a vertex of a polygon. @@ -43,35 +47,50 @@ public: * \param poly_idx The index of the sub-polygon to point to. * \param point_idx The index of the vertex in the sub-polygon. */ - PolygonsPointIndex(const Polygons *polygons, unsigned int poly_idx, unsigned int point_idx) - : polygons(polygons), poly_idx(poly_idx), point_idx(point_idx) {} + PathsPointIndex(const Paths *polygons, unsigned int poly_idx, unsigned int point_idx) : polygons(polygons), poly_idx(poly_idx), point_idx(point_idx) {} /*! * Copy constructor to copy these indices. */ - PolygonsPointIndex(const PolygonsPointIndex& original) = default; + PathsPointIndex(const PathsPointIndex& original) = default; Point p() const { if (!polygons) return {0, 0}; - return (*polygons)[poly_idx][point_idx]; + return make_point((*polygons)[poly_idx][point_idx]); } + /*! + * \brief Returns whether this point is initialised. + */ + bool initialized() const { return polygons; } + + /*! + * Get the polygon to which this PolygonsPointIndex refers + */ + const Polygon &getPolygon() const { return (*polygons)[poly_idx]; } + /*! * Test whether two iterators refer to the same polygon in the same polygon list. - * + * * \param other The PolygonsPointIndex to test for equality * \return Wether the right argument refers to the same polygon in the same ListPolygon as the left argument. */ - bool operator==(const PolygonsPointIndex &other) const + bool operator==(const PathsPointIndex &other) const { return polygons == other.polygons && poly_idx == other.poly_idx && point_idx == other.point_idx; } - bool operator!=(const PolygonsPointIndex &other) const { return !(*this == other); } - bool operator<(const PolygonsPointIndex &other) const { return this->p() < other.p(); } - PolygonsPointIndex &operator=(const PolygonsPointIndex &other) + bool operator!=(const PathsPointIndex &other) const + { + return !(*this == other); + } + bool operator<(const PathsPointIndex &other) const + { + return this->p() < other.p(); + } + PathsPointIndex &operator=(const PathsPointIndex &other) { polygons = other.polygons; poly_idx = other.poly_idx; @@ -79,13 +98,13 @@ public: return *this; } //! move the iterator forward (and wrap around at the end) - PolygonsPointIndex &operator++() + PathsPointIndex &operator++() { point_idx = (point_idx + 1) % (*polygons)[poly_idx].size(); return *this; } //! move the iterator backward (and wrap around at the beginning) - PolygonsPointIndex &operator--() + PathsPointIndex &operator--() { if (point_idx == 0) point_idx = (*polygons)[poly_idx].size(); @@ -93,21 +112,51 @@ public: return *this; } //! move the iterator forward (and wrap around at the end) - PolygonsPointIndex next() const + PathsPointIndex next() const { - PolygonsPointIndex ret(*this); + PathsPointIndex ret(*this); ++ret; return ret; } //! move the iterator backward (and wrap around at the beginning) - PolygonsPointIndex prev() const + PathsPointIndex prev() const { - PolygonsPointIndex ret(*this); + PathsPointIndex ret(*this); --ret; return ret; } }; +using PolygonsPointIndex = PathsPointIndex; + +/*! + * Locator to extract a line segment out of a \ref PolygonsPointIndex + */ +struct PolygonsPointIndexSegmentLocator +{ + std::pair operator()(const PolygonsPointIndex &val) const + { + const Polygon &poly = (*val.polygons)[val.poly_idx]; + Point start = poly[val.point_idx]; + unsigned int next_point_idx = (val.point_idx + 1) % poly.size(); + Point end = poly[next_point_idx]; + return std::pair(start, end); + } +}; + +/*! + * Locator of a \ref PolygonsPointIndex + */ +template +struct PathsPointIndexLocator +{ + Point operator()(const PathsPointIndex& val) const + { + return make_point(val.p()); + } +}; + +using PolygonsPointIndexLocator = PathsPointIndexLocator; }//namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/PolylineStitcher.cpp b/src/libslic3r/Arachne/utils/PolylineStitcher.cpp new file mode 100644 index 0000000000..89ec929540 --- /dev/null +++ b/src/libslic3r/Arachne/utils/PolylineStitcher.cpp @@ -0,0 +1,42 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "PolylineStitcher.hpp" +#include "ExtrusionLine.hpp" + +namespace Slic3r::Arachne { + +template<> bool PolylineStitcher::canReverse(const PathsPointIndex &ppi) +{ + if ((*ppi.polygons)[ppi.poly_idx].is_odd) + return true; + else + return false; +} + +template<> bool PolylineStitcher::canReverse(const PathsPointIndex &) +{ + return true; +} + +template<> bool PolylineStitcher::canConnect(const ExtrusionLine &a, const ExtrusionLine &b) +{ + return a.is_odd == b.is_odd; +} + +template<> bool PolylineStitcher::canConnect(const Polygon &, const Polygon &) +{ + return true; +} + +template<> bool PolylineStitcher::isOdd(const ExtrusionLine &line) +{ + return line.is_odd; +} + +template<> bool PolylineStitcher::isOdd(const Polygon &) +{ + return false; +} + +} // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/PolylineStitcher.hpp b/src/libslic3r/Arachne/utils/PolylineStitcher.hpp new file mode 100644 index 0000000000..6892c3c812 --- /dev/null +++ b/src/libslic3r/Arachne/utils/PolylineStitcher.hpp @@ -0,0 +1,234 @@ +//Copyright (c) 2022 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_POLYLINE_STITCHER_H +#define UTILS_POLYLINE_STITCHER_H + +#include "SparsePointGrid.hpp" +#include "PolygonsPointIndex.hpp" +#include "../../Polygon.hpp" +#include +#include + +namespace Slic3r::Arachne +{ + +/*! + * Class for stitching polylines into longer polylines or into polygons + */ +template +class PolylineStitcher +{ +public: + /*! + * Stitch together the separate \p lines into \p result_lines and if they + * can be closed into \p result_polygons. + * + * Only introduce new segments shorter than \p max_stitch_distance, and + * larger than \p snap_distance but always try to take the shortest + * connection possible. + * + * Only stitch polylines into closed polygons if they are larger than 3 * + * \p max_stitch_distance, in order to prevent small segments to + * accidentally get closed into a polygon. + * + * \warning Tiny polylines (smaller than 3 * max_stitch_distance) will not + * be closed into polygons. + * + * \note Resulting polylines and polygons are added onto the existing + * containers, so you can directly output onto a polygons container with + * existing polygons in it. However, you shouldn't call this function with + * the same parameter in \p lines as \p result_lines, because that would + * duplicate (some of) the polylines. + * \param lines The lines to stitch together. + * \param result_lines[out] The stitched parts that are not closed polygons + * will be stored in here. + * \param result_polygons[out] The stitched parts that were closed as + * polygons will be stored in here. + * \param max_stitch_distance The maximum distance that will be bridged to + * connect two lines. + * \param snap_distance Points closer than this distance are considered to + * be the same point. + */ + static void stitch(const Paths& lines, Paths& result_lines, Paths& result_polygons, coord_t max_stitch_distance = scaled(0.1), coord_t snap_distance = scaled(0.01)) + { + if (lines.empty()) + return; + + SparsePointGrid, PathsPointIndexLocator> grid(max_stitch_distance, lines.size() * 2); + + // populate grid + for (size_t line_idx = 0; line_idx < lines.size(); line_idx++) + { + const auto line = lines[line_idx]; + grid.insert(PathsPointIndex(&lines, line_idx, 0)); + grid.insert(PathsPointIndex(&lines, line_idx, line.size() - 1)); + } + + std::vector processed(lines.size(), false); + + for (size_t line_idx = 0; line_idx < lines.size(); line_idx++) + { + if (processed[line_idx]) + { + continue; + } + processed[line_idx] = true; + const auto line = lines[line_idx]; + bool should_close = isOdd(line); + + Path chain = line; + bool closest_is_closing_polygon = false; + for (bool go_in_reverse_direction : { false, true }) // first go in the unreversed direction, to try to prevent the chain.reverse() operation. + { // NOTE: Implementation only works for this order; we currently only re-reverse the chain when it's closed. + if (go_in_reverse_direction) + { // try extending chain in the other direction + chain.reverse(); + } + coord_t chain_length = chain.polylineLength(); + + while (true) + { + Point from = make_point(chain.back()); + + PathsPointIndex closest; + coord_t closest_distance = std::numeric_limits::max(); + grid.processNearby(from, max_stitch_distance, + std::function&)> ( + [from, &chain, &closest, &closest_is_closing_polygon, &closest_distance, &processed, &chain_length, go_in_reverse_direction, max_stitch_distance, snap_distance, should_close] + (const PathsPointIndex& nearby)->bool + { + bool is_closing_segment = false; + coord_t dist = (nearby.p().template cast() - from.template cast()).norm(); + if (dist > max_stitch_distance) + { + return true; // keep looking + } + if ((nearby.p().template cast() - make_point(chain.front()).template cast()).squaredNorm() < snap_distance * snap_distance) + { + if (chain_length + dist < 3 * max_stitch_distance // prevent closing of small poly, cause it might be able to continue making a larger polyline + || chain.size() <= 2) // don't make 2 vert polygons + { + return true; // look for a better next line + } + is_closing_segment = true; + if (!should_close) + { + dist += scaled(0.01); // prefer continuing polyline over closing a polygon; avoids closed zigzags from being printed separately + // continue to see if closing segment is also the closest + // there might be a segment smaller than [max_stitch_distance] which closes the polygon better + } + else + { + dist -= scaled(0.01); //Prefer closing the polygon if it's 100% even lines. Used to create closed contours. + //Continue to see if closing segment is also the closest. + } + } + else if (processed[nearby.poly_idx]) + { // it was already moved to output + return true; // keep looking for a connection + } + bool nearby_would_be_reversed = nearby.point_idx != 0; + nearby_would_be_reversed = nearby_would_be_reversed != go_in_reverse_direction; // flip nearby_would_be_reversed when searching in the reverse direction + if (!canReverse(nearby) && nearby_would_be_reversed) + { // connecting the segment would reverse the polygon direction + return true; // keep looking for a connection + } + if (!canConnect(chain, (*nearby.polygons)[nearby.poly_idx])) + { + return true; // keep looking for a connection + } + if (dist < closest_distance) + { + closest_distance = dist; + closest = nearby; + closest_is_closing_polygon = is_closing_segment; + } + if (dist < snap_distance) + { // we have found a good enough next line + return false; // stop looking for alternatives + } + return true; // keep processing elements + }) + ); + + if (!closest.initialized() // we couldn't find any next line + || closest_is_closing_polygon // we closed the polygon + ) + { + break; + } + + coord_t segment_dist = (make_point(chain.back()).template cast() - closest.p().template cast()).norm(); + assert(segment_dist <= max_stitch_distance + scaled(0.01)); + const size_t old_size = chain.size(); + if (closest.point_idx == 0) + { + auto start_pos = (*closest.polygons)[closest.poly_idx].begin(); + if (segment_dist < snap_distance) + { + ++start_pos; + } + chain.insert(chain.end(), start_pos, (*closest.polygons)[closest.poly_idx].end()); + } + else + { + auto start_pos = (*closest.polygons)[closest.poly_idx].rbegin(); + if (segment_dist < snap_distance) + { + ++start_pos; + } + chain.insert(chain.end(), (*closest.polygons)[closest.poly_idx].rbegin(), (*closest.polygons)[closest.poly_idx].rend()); + } + for(size_t i = old_size; i < chain.size(); ++i) //Update chain length. + { + chain_length += (make_point(chain[i]).template cast() - make_point(chain[i - 1]).template cast()).norm(); + } + should_close = should_close & !isOdd((*closest.polygons)[closest.poly_idx]); //If we connect an even to an odd line, we should no longer try to close it. + assert( ! processed[closest.poly_idx]); + processed[closest.poly_idx] = true; + } + if (closest_is_closing_polygon) + { + if (go_in_reverse_direction) + { // re-reverse chain to retain original direction + // NOTE: not sure if this code could ever be reached, since if a polygon can be closed that should be already possible in the forward direction + chain.reverse(); + } + + break; // don't consider reverse direction + } + } + if (closest_is_closing_polygon) + { + result_polygons.emplace_back(chain); + } + else + { + PathsPointIndex ppi_here(&lines, line_idx, 0); + if ( ! canReverse(ppi_here)) + { // Since closest_is_closing_polygon is false we went through the second iterations of the for-loop, where go_in_reverse_direction is true + // the polyline isn't allowed to be reversed, so we re-reverse it. + chain.reverse(); + } + result_lines.emplace_back(chain); + } + } + } + + /*! + * Whether a polyline is allowed to be reversed. (Not true for wall polylines which are not odd) + */ + static bool canReverse(const PathsPointIndex &polyline); + + /*! + * Whether two paths are allowed to be connected. + * (Not true for an odd and an even wall.) + */ + static bool canConnect(const Path &a, const Path &b); + + static bool isOdd(const Path &line); +}; + +} // namespace Slic3r::Arachne +#endif // UTILS_POLYLINE_STITCHER_H \ No newline at end of file diff --git a/src/libslic3r/Arachne/utils/SparsePointGrid.hpp b/src/libslic3r/Arachne/utils/SparsePointGrid.hpp new file mode 100644 index 0000000000..31c1965357 --- /dev/null +++ b/src/libslic3r/Arachne/utils/SparsePointGrid.hpp @@ -0,0 +1,90 @@ +// Copyright (c) 2016 Scott Lenser +// Copyright (c) 2020 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SPARSE_POINT_GRID_H +#define UTILS_SPARSE_POINT_GRID_H + +#include +#include +#include + +#include "SparseGrid.hpp" + +namespace Slic3r::Arachne { + +/*! \brief Sparse grid which can locate spatially nearby elements efficiently. + * + * \tparam ElemT The element type to store. + * \tparam Locator The functor to get the location from ElemT. Locator + * must have: Point operator()(const ElemT &elem) const + * which returns the location associated with val. + */ +template class SparsePointGrid : public SparseGrid +{ +public: + using Elem = ElemT; + + /*! \brief Constructs a sparse grid with the specified cell size. + * + * \param[in] cell_size The size to use for a cell (square) in the grid. + * Typical values would be around 0.5-2x of expected query radius. + * \param[in] elem_reserve Number of elements to research space for. + * \param[in] max_load_factor Maximum average load factor before rehashing. + */ + SparsePointGrid(coord_t cell_size, size_t elem_reserve = 0U, float max_load_factor = 1.0f); + + /*! \brief Inserts elem into the sparse grid. + * + * \param[in] elem The element to be inserted. + */ + void insert(const Elem &elem); + + /*! + * Get just any element that's within a certain radius of a point. + * + * Rather than giving a vector of nearby elements, this function just gives + * a single element, any element, in no particular order. + * \param query_pt The point to query for an object nearby. + * \param radius The radius of what is considered "nearby". + */ + const ElemT *getAnyNearby(const Point &query_pt, coord_t radius); + +protected: + using GridPoint = typename SparseGrid::GridPoint; + + /*! \brief Accessor for getting locations from elements. */ + Locator m_locator; +}; + +template +SparsePointGrid::SparsePointGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor) : SparseGrid(cell_size, elem_reserve, max_load_factor) {} + +template +void SparsePointGrid::insert(const Elem &elem) +{ + Point loc = m_locator(elem); + GridPoint grid_loc = SparseGrid::toGridPoint(loc.template cast()); + + SparseGrid::m_grid.emplace(grid_loc, elem); +} + +template +const ElemT *SparsePointGrid::getAnyNearby(const Point &query_pt, coord_t radius) +{ + const ElemT *ret = nullptr; + const std::function &process_func = [&ret, query_pt, radius, this](const ElemT &maybe_nearby) { + if (shorter_then(m_locator(maybe_nearby) - query_pt, radius)) { + ret = &maybe_nearby; + return false; + } + return true; + }; + SparseGrid::processNearby(query_pt, radius, process_func); + + return ret; +} + +} // namespace Slic3r::Arachne + +#endif // UTILS_SPARSE_POINT_GRID_H diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 2eaa438dd0..413756c931 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -296,8 +296,6 @@ add_library(libslic3r STATIC Arachne/BeadingStrategy/BeadingStrategy.cpp Arachne/BeadingStrategy/BeadingStrategyFactory.hpp Arachne/BeadingStrategy/BeadingStrategyFactory.cpp - Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.hpp - Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.cpp Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp @@ -316,10 +314,14 @@ add_library(libslic3r STATIC Arachne/utils/HalfEdgeGraph.hpp Arachne/utils/HalfEdgeNode.hpp Arachne/utils/SparseGrid.hpp + Arachne/utils/SparsePointGrid.hpp + Arachne/utils/SparseLineGrid.hpp Arachne/utils/SquareGrid.hpp Arachne/utils/SquareGrid.cpp Arachne/utils/PolygonsPointIndex.hpp Arachne/utils/PolygonsSegmentIndex.hpp + Arachne/utils/PolylineStitcher.hpp + Arachne/utils/PolylineStitcher.cpp Arachne/utils/VoronoiUtils.hpp Arachne/utils/VoronoiUtils.cpp Arachne/SkeletalTrapezoidation.hpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 25454d5001..fe964f592f 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -570,6 +570,8 @@ Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::ExP { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons union_(const Slic3r::Polygons &subject) { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); } +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType) + { return to_polygons(clipper_do(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), fillType, ApplySafetyOffset::No)); } Slic3r::Polygons union_(const Slic3r::ExPolygons &subject) { return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); } Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index d7027e0ec8..aff6b56c9d 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -447,6 +447,7 @@ inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r:: Slic3r::Polygons union_(const Slic3r::Polygons &subject); Slic3r::Polygons union_(const Slic3r::ExPolygons &subject); +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType); Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2); // May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative). Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index f82d33b564..ccdb57eb79 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace Slic3r { @@ -318,9 +319,7 @@ void PerimeterGenerator::process_arachne() Arachne::WallToolPaths wallToolPaths(last_p, bead_width_0, bead_width_x, coord_t(loop_number + 1), wall_0_inset, *this->object_config); wallToolPaths.generate(); - std::set bins_with_index_zero_perimeters; - Arachne::BinJunctions perimeters = wallToolPaths.getBinJunctions(bins_with_index_zero_perimeters); - + std::vector perimeters = wallToolPaths.getToolPaths(); int start_perimeter = int(perimeters.size()) - 1; int end_perimeter = -1; int direction = -1; @@ -331,27 +330,70 @@ void PerimeterGenerator::process_arachne() direction = 1; } + std::vector all_extrusions; for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { if (perimeters[perimeter_idx].empty()) continue; + for (const Arachne::ExtrusionLine &wall : perimeters[perimeter_idx]) + all_extrusions.emplace_back(&wall); + } + + // Find topological order with constraints from extrusions_constrains. + std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. + std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. + std::unordered_map map_extrusion_to_idx; + for (size_t idx = 0; idx < all_extrusions.size(); idx++) + map_extrusion_to_idx.emplace(all_extrusions[idx], idx); + + auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, this->config->external_perimeters_first); + for (auto [before, after] : extrusions_constrains) { + auto after_it = map_extrusion_to_idx.find(after); + ++blocked[after_it->second]; + blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); + } + + std::stack unblocked_extrusions; + // Add open extrusions before closed extrusions to ensure that on the top of the stack will be closed extrusions. + for (size_t extrusion_idx = 0; extrusion_idx < all_extrusions.size(); ++extrusion_idx) + if (!all_extrusions[extrusion_idx]->is_closed && blocked[extrusion_idx] == 0) + unblocked_extrusions.emplace(all_extrusions[extrusion_idx]); + for (size_t extrusion_idx = 0; extrusion_idx < all_extrusions.size(); ++extrusion_idx) + if (all_extrusions[extrusion_idx]->is_closed && blocked[extrusion_idx] == 0) + unblocked_extrusions.emplace(all_extrusions[extrusion_idx]); + + std::vector ordered_extrusions; + while (!unblocked_extrusions.empty()) { + const Arachne::ExtrusionLine *extrusion = unblocked_extrusions.top(); + unblocked_extrusions.pop(); + ordered_extrusions.emplace_back(extrusion); + + for (const size_t blocking_idx : blocking[map_extrusion_to_idx.find(extrusion)->second]) { + --blocked[blocking_idx]; + if (blocked[blocking_idx] == 0) + unblocked_extrusions.emplace(all_extrusions[blocking_idx]); + } + } + assert(ordered_extrusions.size() == all_extrusions.size()); + + for (const Arachne::ExtrusionLine *extrusion : ordered_extrusions) { + if (extrusion->empty()) + continue; ExtrusionEntityCollection entities_coll; - for (const Arachne::LineJunctions &ej : perimeters[perimeter_idx]) { - ThickPolyline thick_polyline = Arachne::to_thick_polyline(ej); - bool ext_perimeter = bins_with_index_zero_perimeters.count(perimeter_idx) > 0; - ExtrusionPaths paths = thick_polyline_to_extrusion_paths(thick_polyline, ext_perimeter ? erExternalPerimeter : erPerimeter, - ext_perimeter ? this->ext_perimeter_flow : this->perimeter_flow, scaled(0.05), 0); - // Append paths to collection. - if (!paths.empty()) { - if (paths.front().first_point() == paths.back().last_point()) - entities_coll.entities.emplace_back(new ExtrusionLoop(std::move(paths))); - else { - for (ExtrusionPath &path : paths) - entities_coll.entities.emplace_back(new ExtrusionPath(std::move(path))); - } - } + ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); + bool ext_perimeter = extrusion->inset_idx == 0; + ExtrusionPaths paths = thick_polyline_to_extrusion_paths(thick_polyline, ext_perimeter ? erExternalPerimeter : erPerimeter, + ext_perimeter ? this->ext_perimeter_flow : this->perimeter_flow, scaled(0.05), 0); + // Append paths to collection. + if (!paths.empty()) { + if (paths.front().first_point() == paths.back().last_point()) + entities_coll.entities.emplace_back(new ExtrusionLoop(std::move(paths))); + else + for (ExtrusionPath &path : paths) + entities_coll.entities.emplace_back(new ExtrusionPath(std::move(path))); } + this->loops->append(entities_coll); } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index deb0a886a4..11e51a7349 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -449,7 +449,7 @@ static std::vector s_Preset_print_options { "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", - "slicing_engine", "beading_strategy_type", "wall_transition_length", "wall_transition_filter_distance", "wall_transition_angle", + "slicing_engine", "wall_transition_length", "wall_transition_filter_distance", "wall_transition_angle", "wall_distribution_count", "wall_split_middle_threshold", "wall_add_middle_threshold", "min_feature_size", "min_bead_width" }; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 9c250eeb9e..70fde7ec2f 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -201,13 +201,6 @@ static t_config_enum_values s_keys_map_SlicingEngine { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingEngine) -static t_config_enum_values s_keys_map_BeadingStrategyType { - { "center_deviation", int(BeadingStrategyType::Center) }, - { "distributed", int(BeadingStrategyType::Distributed) }, - { "inward_distributed", int(BeadingStrategyType::InwardDistributed) } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeadingStrategyType) - static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology) { for (std::pair &kvp : options) @@ -3064,27 +3057,6 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionEnum(SlicingEngine::Classic)); - def = this->add("beading_strategy_type", coEnum); - def->label = L("Variable Line Strategy"); - def->category = L("Advanced"); - def->tooltip = L("Strategy to use to print the width of a part with a number of walls. This determines " - "how many walls it will use for a certain total width, and how wide each of" - " these lines are. \"Center Deviation\" will print all walls at the nominal" - " line width except the central one(s), causing big variations in the center" - " but very consistent outsides. \"Distributed\" distributes the width equally" - " over all walls. \"Inward Distributed\" is a balance between the other two, " - "distributing the changes in width over all walls but keeping the walls on the" - " outside slightly more consistent."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("center_deviation"); - def->enum_values.push_back("distributed"); - def->enum_values.push_back("inward_distributed"); - def->enum_labels.push_back(L("Center Deviation")); - def->enum_labels.push_back(L("Distributed")); - def->enum_labels.push_back(L("Inward Distributed")); - def->mode = comExpert; - def->set_default_value(new ConfigOptionEnum(BeadingStrategyType::InwardDistributed)); - def = this->add("wall_transition_length", coFloat); def->label = L("Wall Transition Length"); def->category = L("Advanced"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 44882a1358..b2b90dbf97 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -136,15 +136,6 @@ enum class SlicingEngine Arachne }; -enum class BeadingStrategyType -{ - Center, - Distributed, - InwardDistributed, - None, - Count -}; - #define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); @@ -168,7 +159,6 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SlicingEngine) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeadingStrategyType) #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS @@ -498,7 +488,6 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, slice_closing_radius)) ((ConfigOptionEnum, slicing_mode)) ((ConfigOptionEnum, slicing_engine)) - ((ConfigOptionEnum, beading_strategy_type)) ((ConfigOptionFloat, wall_transition_length)) ((ConfigOptionFloat, wall_transition_filter_distance)) ((ConfigOptionFloat, wall_transition_angle)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ab22fdd8fc..7360fb9ea9 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -663,7 +663,6 @@ bool PrintObject::invalidate_state_by_config_options( } } else if ( opt_key == "slicing_engine" - || opt_key == "beading_strategy_type" || opt_key == "wall_transition_length" || opt_key == "wall_transition_filter_distance" || opt_key == "wall_transition_angle" diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 86e6f18188..643feb5ba5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1672,7 +1672,6 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Experimental")); optgroup->append_single_option_line("slicing_engine"); - optgroup->append_single_option_line("beading_strategy_type"); optgroup->append_single_option_line("wall_transition_length"); optgroup->append_single_option_line("wall_transition_filter_distance"); optgroup->append_single_option_line("wall_transition_angle");