mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-06-04 11:14:17 +08:00
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:
parent
21e549fc9a
commit
1a91c94c47
@ -26,41 +26,82 @@ DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_wid
|
|||||||
name = "DistributedBeadingStrategy";
|
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;
|
Beading ret;
|
||||||
|
|
||||||
ret.total_thickness = thickness;
|
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) {
|
if (bead_count > 2) {
|
||||||
const coord_t to_be_divided = thickness - bead_count * optimal_width;
|
coord_t accumulated_width = 0;
|
||||||
const float middle = static_cast<float>(bead_count - 1) / 2;
|
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 auto getWeight = [middle, this](coord_t bead_idx) {
|
const coord_t width = (bead_idx == bead_count - 1) ? thickness - accumulated_width :
|
||||||
const float dev_from_middle = bead_idx - middle;
|
std::max(0, optimal_width + splitup_left_over_weight);
|
||||||
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;
|
|
||||||
|
|
||||||
// Be aware that toolpath_locations is computed by dividing the width by 2, so toolpath_locations
|
// 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.
|
// could be off by 1 because of rounding errors.
|
||||||
if (bead_idx == 0)
|
if (bead_idx == 0) {
|
||||||
ret.toolpath_locations.emplace_back(width / 2);
|
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.toolpath_locations.emplace_back(ret.toolpath_locations.back() + (ret.bead_widths.back() + width) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
ret.bead_widths.emplace_back(width);
|
ret.bead_widths.emplace_back(width);
|
||||||
accumulated_width += width;
|
accumulated_width += width;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.left_over = 0;
|
ret.left_over = 0;
|
||||||
assert((accumulated_width + ret.left_over) == thickness);
|
assert((accumulated_width + ret.left_over) == thickness);
|
||||||
} else if (bead_count == 2) {
|
} else if (bead_count == 2) {
|
||||||
|
@ -35,6 +35,9 @@ public:
|
|||||||
|
|
||||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||||
coord_t getOptimalBeadCount(coord_t thickness) 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
|
} // namespace Slic3r::Arachne
|
||||||
|
@ -847,3 +847,57 @@ TEST_CASE("Arachne - SPE-2298 - Missing twin edge - 2", "[ArachneMissingTwinEdge
|
|||||||
|
|
||||||
REQUIRE(!perimeters.empty());
|
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);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user