From 1a91c94c47cf85a578cfde64bffcf677c58a50af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 1 Nov 2024 21:11:56 +0100 Subject: [PATCH] SPE-2496: Fix negative extrusion widths produced by Arachne. Adjustment of bead widths by DistributedBeadingStrategy could sometimes lead to removing one of the beads. When this happened, the negative bead widths were produced. --- .../DistributedBeadingStrategy.cpp | 87 ++++++++++++++----- .../DistributedBeadingStrategy.hpp | 3 + tests/libslic3r/test_arachne.cpp | 54 ++++++++++++ 3 files changed, 121 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp index 7234015ae2..baf292fc2c 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp @@ -26,41 +26,82 @@ DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_wid name = "DistributedBeadingStrategy"; } -DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(const coord_t thickness, const coord_t bead_count) const +std::vector DistributedBeadingStrategy::calc_normalized_weights(const coord_t to_be_divided, const coord_t bead_count) const +{ + const float middle = static_cast(bead_count - 1) / 2.f; + + const auto calc_weight = [middle, &self = std::as_const(*this)](const size_t bead_idx) -> float { + const float dev_from_middle = static_cast(bead_idx) - middle; + return std::max(0.f, 1.f - self.one_over_distribution_radius_squared * Slic3r::sqr(dev_from_middle)); + }; + + std::vector weights(bead_count); + for (size_t bead_idx = 0; bead_idx < bead_count; ++bead_idx) { + weights[bead_idx] = calc_weight(bead_idx); + } + + // Normalize weights. + const float total_weight = std::accumulate(weights.begin(), weights.end(), 0.f); + std::transform(weights.begin(), weights.end(), weights.begin(), [total_weight](float weight) { return weight / total_weight; }); + + // Find the maximum adjustment needed to prevent negative bead width. + const float max_allowed_weight = -static_cast(optimal_width) / static_cast(to_be_divided) / total_weight; + float max_adjustment = 0.f; + size_t adjustment_cnt = 0; + + for (float weight : weights) { + if (weight > max_allowed_weight) { + max_adjustment = std::max(weight - max_allowed_weight, max_adjustment); + ++adjustment_cnt; + } + } + + if (const size_t increaseable_weight_cnt = weights.size() - adjustment_cnt; adjustment_cnt > 0 && increaseable_weight_cnt > 0) { + // There is at least one weight that can increased. + std::vector new_weights; + for (float &weight : weights) { + if (weight <= max_allowed_weight) { + new_weights.emplace_back(std::max(0.f, weight + (max_adjustment / static_cast(increaseable_weight_cnt)))); + } + } + + return new_weights; + } + + return weights; +} + +DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(const coord_t thickness, coord_t bead_count) const { Beading ret; - ret.total_thickness = thickness; + + const coord_t to_be_divided = thickness - bead_count * optimal_width; + const std::vector normalized_weights = this->calc_normalized_weights(to_be_divided, bead_count); + + // Update the bead count based on calculated weights because the adjustment of bead widths + // may cause one bead to be entirely removed. + bead_count = static_cast(normalized_weights.size()); + 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 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); - }; - - 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); - coord_t accumulated_width = 0; - 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 = (bead_idx == bead_count - 1) ? thickness - accumulated_width : optimal_width + splitup_left_over_weight; + coord_t accumulated_width = 0; + for (size_t bead_idx = 0; bead_idx < bead_count; ++bead_idx) { + const coord_t splitup_left_over_weight = static_cast(static_cast(to_be_divided) * normalized_weights[bead_idx]); + const coord_t width = (bead_idx == bead_count - 1) ? thickness - accumulated_width : + std::max(0, optimal_width + splitup_left_over_weight); // Be aware that toolpath_locations is computed by dividing the width by 2, so toolpath_locations // could be off by 1 because of rounding errors. - if (bead_idx == 0) + if (bead_idx == 0) { ret.toolpath_locations.emplace_back(width / 2); - else + } else { ret.toolpath_locations.emplace_back(ret.toolpath_locations.back() + (ret.bead_widths.back() + width) / 2); + } + ret.bead_widths.emplace_back(width); accumulated_width += width; } + ret.left_over = 0; assert((accumulated_width + ret.left_over) == thickness); } else if (bead_count == 2) { diff --git a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp index 991d5028b7..99a51b0ba7 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp @@ -35,6 +35,9 @@ public: Beading compute(coord_t thickness, coord_t bead_count) const override; coord_t getOptimalBeadCount(coord_t thickness) const override; + +private: + std::vector calc_normalized_weights(coord_t to_be_divided, coord_t bead_count) const; }; } // namespace Slic3r::Arachne diff --git a/tests/libslic3r/test_arachne.cpp b/tests/libslic3r/test_arachne.cpp index 6ebdda2ec2..b0a3cb3ebd 100644 --- a/tests/libslic3r/test_arachne.cpp +++ b/tests/libslic3r/test_arachne.cpp @@ -847,3 +847,57 @@ TEST_CASE("Arachne - SPE-2298 - Missing twin edge - 2", "[ArachneMissingTwinEdge REQUIRE(!perimeters.empty()); } + +TEST_CASE("Arachne - SPE-2496 - Negative extrusion width", "[Arachne_Negative_Extrusion_Width_SPE-2496]") { + Polygon poly_0 = { + Point(-4982523, -5994247), + Point(-4700644, -6050605), + Point( 5959771, 4609799), + Point( 5901029, 4779898), + Point( 5871716, 4899000), + Point( 5864557, 5026026), + Point( 5890832, 5722622), + Point( 5870131, 5738234), + Point( 5304622, 5553229), + Point( 4580330, 5240254), + Point( 4109435, 4998946), + Point( 3606964, 4699087), + Point( 2676357, 4015459), + Point( 1076547, 2101298), + Point(- 900993, 41373), + Point(-1481954, - 514246), + Point(-4119704, -2738265), + Point(-4484070, -3261707), + Point(-4628548, -3650430), + Point(-4712361, -3810835), + Point(-5329484, -4699252), + Point(-5670086, -5625540), + Point(-5727314, -6080805), + Point(-5726304, -6081822) + }; + + Polygons polygons = {poly_0}; + coord_t ext_perimeter_spacing = 407079; + coord_t perimeter_spacing = 407079; + coord_t inset_count = 3; + + PrintObjectConfig print_object_config = PrintObjectConfig::defaults(); + print_object_config.min_bead_width = ConfigOptionFloatOrPercent(0.1, false); + + Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, print_object_config, PrintConfig::defaults()); + wall_tool_paths.generate(); + Arachne::Perimeters perimeters = wall_tool_paths.getToolPaths(); + + bool has_negative_extrusion_width = false; + for (const Arachne::Perimeter &perimeter : perimeters) { + for (const Arachne::ExtrusionLine &extrusion_line : perimeter) { + for (const Arachne::ExtrusionJunction &extrusion_junction : extrusion_line) { + if (extrusion_junction.w <= 0.) { + has_negative_extrusion_width = true; + } + } + } + } + + REQUIRE(!has_negative_extrusion_width); +} \ No newline at end of file