From 2feb8421e94e72365673eb22ca3d82ecbe838438 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 19 Dec 2019 10:59:21 +0100 Subject: [PATCH 1/2] Divide pad blueprint before its filtered. Filtering may remove the outer pad and the division expects an outer part to be present. --- src/libslic3r/SLA/SLAPad.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SLAPad.cpp b/src/libslic3r/SLA/SLAPad.cpp index 7cd9eb4e4..5c6fb4fff 100644 --- a/src/libslic3r/SLA/SLAPad.cpp +++ b/src/libslic3r/SLA/SLAPad.cpp @@ -430,9 +430,11 @@ public: ExPolygons fullpad = diff_ex(fullcvh, model_bp_sticks); - remove_redundant_parts(fullpad); - PadSkeleton divided = divide_blueprint(fullpad); + + remove_redundant_parts(divided.outer); + remove_redundant_parts(divided.inner); + outer = std::move(divided.outer); inner = std::move(divided.inner); } From 42ffc4e3c5670c7bc82858c0fa4df2c8f64bdd2d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 19 Dec 2019 11:27:01 +0100 Subject: [PATCH 2/2] Fix polytree traversal. Put back old traverse_pt and union_pt_chained --- src/libslic3r/ClipperUtils.cpp | 146 ++++++++----------------- src/libslic3r/ClipperUtils.hpp | 95 ++++++++++++++-- src/libslic3r/SLA/SLAPad.cpp | 11 +- tests/libslic3r/test_clipper_utils.cpp | 76 +++++++++++++ 4 files changed, 211 insertions(+), 117 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index f053aea29..d4c2f05e1 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -679,133 +679,73 @@ ClipperLib::PolyTree union_pt(ExPolygons &&subject, bool safety_offset_) return _clipper_do(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_); } -Polygons -union_pt_chained(const Polygons &subject, bool safety_offset_) -{ - ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_); - - Polygons retval; - traverse_pt(polytree.Childs, &retval); - return retval; -} - -static ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes) +// Simple spatial ordering of Polynodes +ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes) { // collect ordering points Points ordering_points; ordering_points.reserve(nodes.size()); + for (const ClipperLib::PolyNode *node : nodes) - ordering_points.emplace_back(Point(node->Contour.front().X, node->Contour.front().Y)); - + ordering_points.emplace_back( + Point(node->Contour.front().X, node->Contour.front().Y)); + // perform the ordering - ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes); - + ClipperLib::PolyNodes ordered_nodes = + chain_clipper_polynodes(ordering_points, nodes); + return ordered_nodes; } -enum class e_ordering { - ORDER_POLYNODES, - DONT_ORDER_POLYNODES -}; - -template -void foreach_node(const ClipperLib::PolyNodes &nodes, - std::function fn); - -template<> void foreach_node( - const ClipperLib::PolyNodes & nodes, - std::function fn) +static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *out) { - for (auto &n : nodes) fn(n); + foreach_node(nodes, [&out](const ClipperLib::PolyNode *node) + { + traverse_pt_noholes(node->Childs, out); + out->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour)); + if (node->IsHole()) out->back().reverse(); // ccw + }); } -template<> void foreach_node( - const ClipperLib::PolyNodes & nodes, - std::function fn) -{ - auto ordered_nodes = order_nodes(nodes); - for (auto &n : ordered_nodes) fn(n); -} - -template -void _traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval) +static void traverse_pt_old(ClipperLib::PolyNodes &nodes, Polygons* retval) { /* use a nearest neighbor search to order these children TODO: supply start_near to chained_path() too? */ + // collect ordering points + Points ordering_points; + ordering_points.reserve(nodes.size()); + for (ClipperLib::PolyNodes::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { + Point p((*it)->Contour.front().X, (*it)->Contour.front().Y); + ordering_points.push_back(p); + } + + // perform the ordering + ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes); + // push results recursively - foreach_node(nodes, [&retval](const ClipperLib::PolyNode *node) { + for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) { // traverse the next depth - _traverse_pt(node->Childs, retval); - retval->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour)); - if (node->IsHole()) retval->back().reverse(); // ccw - }); + traverse_pt_old((*it)->Childs, retval); + retval->push_back(ClipperPath_to_Slic3rPolygon((*it)->Contour)); + if ((*it)->IsHole()) retval->back().reverse(); // ccw + } } -template -void _traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval) +Polygons union_pt_chained(const Polygons &subject, bool safety_offset_) { - if (!retval || !tree) return; + ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_); - ExPolygons &retv = *retval; + Polygons retval; + traverse_pt_old(polytree.Childs, &retval); + return retval; - std::function hole_fn; +// TODO: This needs to be tested: +// ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_); - auto contour_fn = [&retv, &hole_fn](const ClipperLib::PolyNode *pptr) { - ExPolygon poly; - poly.contour.points = ClipperPath_to_Slic3rPolygon(pptr->Contour); - auto fn = std::bind(hole_fn, std::placeholders::_1, poly); - foreach_node(pptr->Childs, fn); - retv.push_back(poly); - }; - - hole_fn = [&contour_fn](const ClipperLib::PolyNode *pptr, ExPolygon& poly) - { - poly.holes.emplace_back(); - poly.holes.back().points = ClipperPath_to_Slic3rPolygon(pptr->Contour); - foreach_node(pptr->Childs, contour_fn); - }; - - contour_fn(tree); -} - -template -void _traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) -{ - // Here is the actual traverse - foreach_node(nodes, [&retval](const ClipperLib::PolyNode *node) { - _traverse_pt(node, retval); - }); -} - -void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval) -{ - _traverse_pt(tree, retval); -} - -void traverse_pt_unordered(const ClipperLib::PolyNode *tree, ExPolygons *retval) -{ - _traverse_pt(tree, retval); -} - -void traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval) -{ - _traverse_pt(nodes, retval); -} - -void traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) -{ - _traverse_pt(nodes, retval); -} - -void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Polygons *retval) -{ - _traverse_pt(nodes, retval); -} - -void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) -{ - _traverse_pt(nodes, retval); +// Polygons retval; +// traverse_pt_noholes(polytree.Childs, &retval); +// return retval; } Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 5a41a6a90..0828ec21f 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -214,7 +214,6 @@ inline Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_ return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); } - ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false); ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject, bool safety_offset_ = false); ClipperLib::PolyTree union_pt(Slic3r::Polygons &&subject, bool safety_offset_ = false); @@ -222,13 +221,95 @@ ClipperLib::PolyTree union_pt(Slic3r::ExPolygons &&subject, bool safety_offset_ Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false); -void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval); -void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval); -void traverse_pt(const ClipperLib::PolyNode *tree, Slic3r::ExPolygons *retval); +ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes); + +// Implementing generalized loop (foreach) over a list of nodes which can be +// ordered or unordered (performance gain) based on template parameter +enum class e_ordering { + ON, + OFF +}; + +// Create a template struct, template functions can not be partially specialized +template struct _foreach_node { + void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn); +}; + +// Specialization with NO ordering +template struct _foreach_node { + void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn) + { + for (auto &n : nodes) fn(n); + } +}; + +// Specialization with ordering +template struct _foreach_node { + void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn) + { + auto ordered_nodes = order_nodes(nodes); + for (auto &n : nodes) fn(n); + } +}; + +// Wrapper function for the foreach_node which can deduce arguments automatically +template +void foreach_node(const ClipperLib::PolyNodes &nodes, Fn &&fn) +{ + _foreach_node()(nodes, std::forward(fn)); +} + +// Collecting polygons of the tree into a list of Polygons, holes have clockwise +// orientation. +template +void traverse_pt(const ClipperLib::PolyNode *tree, Polygons *out) +{ + if (!tree) return; // terminates recursion + + // Push the contour of the current level + out->emplace_back(ClipperPath_to_Slic3rPolygon(tree->Contour)); + + // Do the recursion for all the children. + traverse_pt(tree->Childs, out); +} + +// Collecting polygons of the tree into a list of ExPolygons. +template +void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *out) +{ + if (!tree) return; + else if(tree->IsHole()) { + // Levels of holes are skipped and handled together with the + // contour levels. + traverse_pt(tree->Childs, out); + return; + } + + ExPolygon level; + level.contour = ClipperPath_to_Slic3rPolygon(tree->Contour); + + foreach_node(tree->Childs, + [out, &level] (const ClipperLib::PolyNode *node) { + + // Holes are collected here. + level.holes.emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour)); + + // By doing a recursion, a new level expoly is created with the contour + // and holes of the lower level. Doing this for all the childs. + traverse_pt(node->Childs, out); + }); + + out->emplace_back(level); +} + +template +void traverse_pt(const ClipperLib::PolyNodes &nodes, ExOrJustPolygons *retval) +{ + foreach_node(nodes, [&retval](const ClipperLib::PolyNode *node) { + traverse_pt(node, retval); + }); +} -void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval); -void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval); -void traverse_pt_unordered(const ClipperLib::PolyNode *tree, Slic3r::ExPolygons *retval); /* OTHER */ Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false); diff --git a/src/libslic3r/SLA/SLAPad.cpp b/src/libslic3r/SLA/SLAPad.cpp index 5c6fb4fff..6f0db8e6c 100644 --- a/src/libslic3r/SLA/SLAPad.cpp +++ b/src/libslic3r/SLA/SLAPad.cpp @@ -337,18 +337,15 @@ PadSkeleton divide_blueprint(const ExPolygons &bp) for (ClipperLib::PolyTree::PolyNode *node : ptree.Childs) { ExPolygon poly(ClipperPath_to_Slic3rPolygon(node->Contour)); for (ClipperLib::PolyTree::PolyNode *child : node->Childs) { - if (child->IsHole()) { - poly.holes.emplace_back( - ClipperPath_to_Slic3rPolygon(child->Contour)); + poly.holes.emplace_back( + ClipperPath_to_Slic3rPolygon(child->Contour)); - traverse_pt_unordered(child->Childs, &ret.inner); - } - else traverse_pt_unordered(child, &ret.inner); + traverse_pt(child->Childs, &ret.inner); } ret.outer.emplace_back(poly); } - + return ret; } diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index 21d2c2cd4..69e9d0efb 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -223,3 +224,78 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") { } } } + +template +double polytree_area(const Tree &tree, std::vector

*out) +{ + traverse_pt(tree, out); + + return std::accumulate(out->begin(), out->end(), 0.0, + [](double a, const P &p) { return a + p.area(); }); +} + +size_t count_polys(const ExPolygons& expolys) +{ + size_t c = 0; + for (auto &ep : expolys) c += ep.holes.size() + 1; + + return c; +} + +TEST_CASE("Traversing Clipper PolyTree", "[ClipperUtils]") { + // Create a polygon representing unit box + Polygon unitbox; + const auto UNIT = coord_t(1. / SCALING_FACTOR); + unitbox.points = {{0, 0}, {UNIT, 0}, {UNIT, UNIT}, {0, UNIT}}; + + Polygon box_frame = unitbox; + box_frame.scale(20, 10); + + Polygon hole_left = unitbox; + hole_left.scale(8); + hole_left.translate(UNIT, UNIT); + hole_left.reverse(); + + Polygon hole_right = hole_left; + hole_right.translate(UNIT * 10, 0); + + Polygon inner_left = unitbox; + inner_left.scale(4); + inner_left.translate(UNIT * 3, UNIT * 3); + + Polygon inner_right = inner_left; + inner_right.translate(UNIT * 10, 0); + + Polygons reference = union_({box_frame, hole_left, hole_right, inner_left, inner_right}); + + ClipperLib::PolyTree tree = union_pt(reference); + double area_sum = box_frame.area() + hole_left.area() + + hole_right.area() + inner_left.area() + + inner_right.area(); + + REQUIRE(area_sum > 0); + + SECTION("Traverse into Polygons WITHOUT spatial ordering") { + Polygons output; + REQUIRE(area_sum == Approx(polytree_area(tree.GetFirst(), &output))); + REQUIRE(output.size() == reference.size()); + } + + SECTION("Traverse into ExPolygons WITHOUT spatial ordering") { + ExPolygons output; + REQUIRE(area_sum == Approx(polytree_area(tree.GetFirst(), &output))); + REQUIRE(count_polys(output) == reference.size()); + } + + SECTION("Traverse into Polygons WITH spatial ordering") { + Polygons output; + REQUIRE(area_sum == Approx(polytree_area(tree.GetFirst(), &output))); + REQUIRE(output.size() == reference.size()); + } + + SECTION("Traverse into ExPolygons WITH spatial ordering") { + ExPolygons output; + REQUIRE(area_sum == Approx(polytree_area(tree.GetFirst(), &output))); + REQUIRE(count_polys(output) == reference.size()); + } +}