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.
This commit is contained in:
Lukáš Hejl 2024-11-01 21:11:56 +01:00 committed by Lukas Matena
parent 21e549fc9a
commit 1a91c94c47
3 changed files with 121 additions and 23 deletions

View File

@ -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<float> DistributedBeadingStrategy::calc_normalized_weights(const coord_t to_be_divided, const coord_t bead_count) const
{
const float middle = static_cast<float>(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<float>(bead_idx) - middle;
return std::max(0.f, 1.f - self.one_over_distribution_radius_squared * Slic3r::sqr(dev_from_middle));
};
std::vector<float> 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<float>(optimal_width) / static_cast<float>(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<float> new_weights;
for (float &weight : weights) {
if (weight <= max_allowed_weight) {
new_weights.emplace_back(std::max(0.f, weight + (max_adjustment / static_cast<float>(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<float> 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<coord_t>(normalized_weights.size());
if (bead_count > 2) {
const coord_t to_be_divided = thickness - bead_count * optimal_width;
const float middle = static_cast<float>(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<float> 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<coord_t>(static_cast<float>(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) {

View File

@ -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<float> calc_normalized_weights(coord_t to_be_divided, coord_t bead_count) const;
};
} // namespace Slic3r::Arachne

View File

@ -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);
}