diff --git a/src/libslic3r/Fill/Lightning/TreeNode.cpp b/src/libslic3r/Fill/Lightning/TreeNode.cpp index 982d47b10e..3d57ebae4a 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.cpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.cpp @@ -351,19 +351,23 @@ void Node::convertToPolylines(Polylines &output, const coord_t line_overlap) con { Polylines result; result.emplace_back(); - convertToPolylines(0, result); + // Orca: the layers are filled in parallel, so they would consume a shared generator in a + // different order every run, and a model would not slice the same way twice. Each tree seeds + // its own from where it is rooted; one constant seed would start them all on the same pick. + std::mt19937_64 rng { uint64_t(PointHash{}(m_p)) }; + convertToPolylines(0, result, rng); removeJunctionOverlap(result, line_overlap); append(output, std::move(result)); } -void Node::convertToPolylines(size_t long_line_idx, Polylines &output) const +void Node::convertToPolylines(size_t long_line_idx, Polylines &output, std::mt19937_64 &rng) const { if (m_children.empty()) { output[long_line_idx].points.push_back(m_p); return; } - size_t first_child_idx = rand() % m_children.size(); - m_children[first_child_idx]->convertToPolylines(long_line_idx, output); + const size_t first_child_idx = rng() % m_children.size(); + m_children[first_child_idx]->convertToPolylines(long_line_idx, output, rng); output[long_line_idx].points.push_back(m_p); for (size_t idx_offset = 1; idx_offset < m_children.size(); idx_offset++) { @@ -371,7 +375,7 @@ void Node::convertToPolylines(size_t long_line_idx, Polylines &output) const const Node& child = *m_children[child_idx]; output.emplace_back(); size_t child_line_idx = output.size() - 1; - child.convertToPolylines(child_line_idx, output); + child.convertToPolylines(child_line_idx, output, rng); output[child_line_idx].points.emplace_back(m_p); } } diff --git a/src/libslic3r/Fill/Lightning/TreeNode.hpp b/src/libslic3r/Fill/Lightning/TreeNode.hpp index 14aa5e4888..95559524ba 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.hpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "../../EdgeGrid.hpp" @@ -259,8 +260,9 @@ protected: * * \param long_line a reference to a polyline in \p output which to continue building on in the recursion * \param output all branches in this tree connected into polylines + * \param rng the generator the junctions draw from, carried through the recursion */ - void convertToPolylines(size_t long_line_idx, Polylines &output) const; + void convertToPolylines(size_t long_line_idx, Polylines &output, std::mt19937_64 &rng) const; void removeJunctionOverlap(Polylines &polylines, coord_t line_overlap) const; diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 21a5000401..d26639c659 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -755,6 +755,9 @@ struct SparseInfillShape { size_t sharp_turns { 0 }; size_t path_count { 0 }; double length { 0. }; + // Digest of every point in the order it is printed. The counts above all survive the same + // extrusions being joined into different polylines, so only this tells two such fills apart. + uint64_t sequence { 14695981039346656037ull }; }; static SparseInfillShape sparse_infill_shape(const Print &print) @@ -767,6 +770,9 @@ static SparseInfillShape sparse_infill_shape(const Print &print) const Points3 &pts = path.polyline.points; ++shape.path_count; shape.point_count += pts.size(); + for (const auto &pt : pts) + for (const coord_t coordinate : {pt.x(), pt.y(), pt.z()}) + shape.sequence = (shape.sequence ^ uint64_t(coordinate)) * 1099511628211ull; for (size_t i = 1; i < pts.size(); ++i) shape.length += (pts[i] - pts[i - 1]).head<2>().cast().norm(); for (size_t i = 1; i + 1 < pts.size(); ++i) { @@ -793,6 +799,33 @@ static SparseInfillShape sparse_infill_shape(const Print &print) return shape; } +TEST_CASE("Lightning infill slices the same model the same way twice", "[Fill][Regression]") +{ + // Slicing twice in one process catches a generator that carries state from one slice to the + // next, or whose result depends on how the parallel layer fill interleaves. + auto shape = [] { + Print print; + Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print, + {{"sparse_infill_pattern", "lightning"}, + {"sparse_infill_density", "50%"}, + {"layer_height", 0.2}}); + return sparse_infill_shape(print); + }; + + const SparseInfillShape first = shape(); + const SparseInfillShape second = shape(); + + REQUIRE(first.path_count > 0); + REQUIRE(second.path_count == first.path_count); + REQUIRE(second.point_count == first.point_count); + REQUIRE(second.sharp_turns == first.sharp_turns); + // No tolerance: the same extrusions in the same order add up to the very same number. + REQUIRE_THAT(second.length, Catch::Matchers::WithinAbs(first.length, 0.)); + // All of the above agree when the same branches are joined into different polylines, so the + // point sequence is what actually decides whether the two slices produced the same infill. + REQUIRE(second.sequence == first.sequence); +} + TEST_CASE("Lightning infill rounds the turns of its branches with the smooth factor", "[Fill]") { auto shape_for = [](const std::string &smooth_factor) {