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