diff --git a/src/libslic3r/Arachne/PerimeterOrder.cpp b/src/libslic3r/Arachne/PerimeterOrder.cpp new file mode 100644 index 0000000000..01d50feb1e --- /dev/null +++ b/src/libslic3r/Arachne/PerimeterOrder.cpp @@ -0,0 +1,276 @@ +#include "PerimeterOrder.hpp" + +namespace Slic3r::Arachne::PerimeterOrder { + +using namespace Arachne; + +static size_t get_extrusion_lines_count(const Perimeters &perimeters) { + size_t extrusion_lines_count = 0; + for (const Perimeter &perimeter : perimeters) + extrusion_lines_count += perimeter.size(); + + return extrusion_lines_count; +} + +static PerimeterExtrusions get_sorted_perimeter_extrusions_by_area(const Perimeters &perimeters) { + PerimeterExtrusions sorted_perimeter_extrusions; + sorted_perimeter_extrusions.reserve(get_extrusion_lines_count(perimeters)); + + for (const Perimeter &perimeter : perimeters) { + for (const ExtrusionLine &extrusion_line : perimeter) { + if (extrusion_line.empty()) + continue; // This shouldn't ever happen. + + const BoundingBox bbox = get_extents(extrusion_line); + // Be aware that Arachne produces contours with clockwise orientation and holes with counterclockwise orientation. + const double area = std::abs(extrusion_line.area()); + const Polygon polygon = extrusion_line.is_closed ? to_polygon(extrusion_line) : Polygon{}; + + sorted_perimeter_extrusions.emplace_back(extrusion_line, area, polygon, bbox); + } + } + + // Open extrusions have an area equal to zero, so sorting based on the area ensures that open extrusions will always be before closed ones. + std::sort(sorted_perimeter_extrusions.begin(), sorted_perimeter_extrusions.end(), + [](const PerimeterExtrusion &l, const PerimeterExtrusion &r) { return l.area < r.area; }); + + return sorted_perimeter_extrusions; +} + +// Functions fill adjacent_perimeter_extrusions field for every PerimeterExtrusion by pointers to PerimeterExtrusions that contain or are inside this PerimeterExtrusion. +static void construct_perimeter_extrusions_adjacency_graph(PerimeterExtrusions &sorted_perimeter_extrusions) { + // Construct a graph (defined using adjacent_perimeter_extrusions field) where two PerimeterExtrusion are adjacent when one is inside the other. + std::vector root_candidates(sorted_perimeter_extrusions.size(), false); + for (PerimeterExtrusion &perimeter_extrusion : sorted_perimeter_extrusions) { + const size_t perimeter_extrusion_idx = &perimeter_extrusion - sorted_perimeter_extrusions.data(); + + if (!perimeter_extrusion.is_closed()) { + root_candidates[perimeter_extrusion_idx] = true; + continue; + } + + for (PerimeterExtrusion &root_candidate : sorted_perimeter_extrusions) { + const size_t root_candidate_idx = &root_candidate - sorted_perimeter_extrusions.data(); + + if (!root_candidates[root_candidate_idx]) + continue; + + if (perimeter_extrusion.bbox.contains(root_candidate.bbox) && perimeter_extrusion.polygon.contains(root_candidate.extrusion.junctions.front().p)) { + perimeter_extrusion.adjacent_perimeter_extrusions.emplace_back(&root_candidate); + root_candidate.adjacent_perimeter_extrusions.emplace_back(&perimeter_extrusion); + root_candidates[root_candidate_idx] = false; + } + } + + root_candidates[perimeter_extrusion_idx] = true; + } +} + +// Perform the depth-first search to assign the nearest external perimeter for every PerimeterExtrusion. +// When some PerimeterExtrusion is achievable from more than one external perimeter, then we choose the +// one that comes from a contour. +static void assign_nearest_external_perimeter(PerimeterExtrusions &sorted_perimeter_extrusions) { + std::stack stack; + for (PerimeterExtrusion &perimeter_extrusion : sorted_perimeter_extrusions) { + if (perimeter_extrusion.is_external_perimeter()) { + perimeter_extrusion.depth = 0; + perimeter_extrusion.nearest_external_perimeter = &perimeter_extrusion; + stack.push(&perimeter_extrusion); + } + } + + while (!stack.empty()) { + PerimeterExtrusion *current_extrusion = stack.top(); + stack.pop(); + + for (PerimeterExtrusion *adjacent_extrusion : current_extrusion->adjacent_perimeter_extrusions) { + const size_t adjacent_extrusion_depth = current_extrusion->depth + 1; + // Update depth when the new depth is smaller or when we can achieve the same depth from a contour. + // This will ensure that the internal perimeter will be extruded before the outer external perimeter + // when there are two external perimeters and one internal. + if (adjacent_extrusion_depth < adjacent_extrusion->depth) { + adjacent_extrusion->nearest_external_perimeter = current_extrusion->nearest_external_perimeter; + adjacent_extrusion->depth = adjacent_extrusion_depth; + stack.push(adjacent_extrusion); + } else if (adjacent_extrusion_depth == adjacent_extrusion->depth && !adjacent_extrusion->nearest_external_perimeter->is_contour() && current_extrusion->is_contour()) { + adjacent_extrusion->nearest_external_perimeter = current_extrusion->nearest_external_perimeter; + stack.push(adjacent_extrusion); + } + } + } +} + +inline Point get_end_position(const ExtrusionLine &extrusion) { + if (extrusion.is_closed) + return extrusion.junctions[0].p; // We ended where we started. + else + return extrusion.junctions.back().p; // Pick the other end from where we started. +} + +// Returns ordered extrusions. +static std::vector ordered_perimeter_extrusions_to_minimize_distances(Point current_position, std::vector extrusions) { + // Ensure that open extrusions will be placed before the closed one. + std::sort(extrusions.begin(), extrusions.end(), + [](const PerimeterExtrusion *l, const PerimeterExtrusion *r) -> bool { return l->is_closed() < r->is_closed(); }); + + std::vector ordered_extrusions; + std::vector already_selected(extrusions.size(), false); + while (ordered_extrusions.size() < extrusions.size()) { + double nearest_distance_sqr = std::numeric_limits::max(); + size_t nearest_extrusion_idx = 0; + bool is_nearest_closed = false; + + for (size_t extrusion_idx = 0; extrusion_idx < extrusions.size(); ++extrusion_idx) { + if (already_selected[extrusion_idx]) + continue; + + const ExtrusionLine &extrusion_line = extrusions[extrusion_idx]->extrusion; + const Point &extrusion_start_position = extrusion_line.junctions.front().p; + const double distance_sqr = (current_position - extrusion_start_position).cast().squaredNorm(); + if (distance_sqr < nearest_distance_sqr) { + if (extrusion_line.is_closed || (!extrusion_line.is_closed && nearest_distance_sqr != std::numeric_limits::max()) || (!extrusion_line.is_closed && !is_nearest_closed)) { + nearest_extrusion_idx = extrusion_idx; + nearest_distance_sqr = distance_sqr; + is_nearest_closed = extrusion_line.is_closed; + } + } + } + + already_selected[nearest_extrusion_idx] = true; + const PerimeterExtrusion *nearest_extrusion = extrusions[nearest_extrusion_idx]; + current_position = get_end_position(nearest_extrusion->extrusion); + ordered_extrusions.emplace_back(nearest_extrusion); + } + + return ordered_extrusions; +} + +struct GroupedPerimeterExtrusions +{ + GroupedPerimeterExtrusions() = delete; + explicit GroupedPerimeterExtrusions(const PerimeterExtrusion *external_perimeter_extrusion) + : external_perimeter_extrusion(external_perimeter_extrusion) {} + + std::vector extrusions; + const PerimeterExtrusion *external_perimeter_extrusion = nullptr; +}; + +// Returns vector of indexes that represent the order of grouped extrusions in grouped_extrusions. +static std::vector order_of_grouped_perimeter_extrusions_to_minimize_distances(Point current_position, std::vector grouped_extrusions) { + // Ensure that holes will be placed before contour and open extrusions before the closed one. + std::sort(grouped_extrusions.begin(), grouped_extrusions.end(), [](const GroupedPerimeterExtrusions &l, const GroupedPerimeterExtrusions &r) -> bool { + return (l.external_perimeter_extrusion->is_contour() < r.external_perimeter_extrusion->is_contour()) || + (l.external_perimeter_extrusion->is_contour() == r.external_perimeter_extrusion->is_contour() && l.external_perimeter_extrusion->is_closed() < r.external_perimeter_extrusion->is_closed()); + }); + + const size_t holes_cnt = std::count_if(grouped_extrusions.begin(), grouped_extrusions.end(), [](const GroupedPerimeterExtrusions &grouped_extrusions) { + return !grouped_extrusions.external_perimeter_extrusion->is_contour(); + }); + + std::vector grouped_extrusions_order; + std::vector already_selected(grouped_extrusions.size(), false); + while (grouped_extrusions_order.size() < grouped_extrusions.size()) { + double nearest_distance_sqr = std::numeric_limits::max(); + size_t nearest_grouped_extrusions_idx = 0; + bool is_nearest_closed = false; + + // First we order all holes and then we start ordering contours. + const size_t grouped_extrusion_end = grouped_extrusions_order.size() < holes_cnt ? holes_cnt: grouped_extrusions.size(); + for (size_t grouped_extrusion_idx = 0; grouped_extrusion_idx < grouped_extrusion_end; ++grouped_extrusion_idx) { + if (already_selected[grouped_extrusion_idx]) + continue; + + const ExtrusionLine &external_perimeter_extrusion_line = grouped_extrusions[grouped_extrusion_idx].external_perimeter_extrusion->extrusion; + const Point &extrusion_start_position = external_perimeter_extrusion_line.junctions.front().p; + const double distance_sqr = (current_position - extrusion_start_position).cast().squaredNorm(); + if (distance_sqr < nearest_distance_sqr) { + if (external_perimeter_extrusion_line.is_closed || (!external_perimeter_extrusion_line.is_closed && nearest_distance_sqr != std::numeric_limits::max()) || (!external_perimeter_extrusion_line.is_closed && !is_nearest_closed)) { + nearest_grouped_extrusions_idx = grouped_extrusion_idx; + nearest_distance_sqr = distance_sqr; + is_nearest_closed = external_perimeter_extrusion_line.is_closed; + } + } + } + + grouped_extrusions_order.emplace_back(nearest_grouped_extrusions_idx); + already_selected[nearest_grouped_extrusions_idx] = true; + const GroupedPerimeterExtrusions &nearest_grouped_extrusions = grouped_extrusions[nearest_grouped_extrusions_idx]; + const ExtrusionLine &last_extrusion_line = nearest_grouped_extrusions.extrusions.back()->extrusion; + current_position = get_end_position(last_extrusion_line); + } + + return grouped_extrusions_order; +} + +static PerimeterExtrusions extract_ordered_perimeter_extrusions(const PerimeterExtrusions &sorted_perimeter_extrusions, const bool external_perimeters_first) { + // Extrusions are ordered inside each group. + std::vector grouped_extrusions; + + std::stack stack; + std::vector visited(sorted_perimeter_extrusions.size(), false); + for (const PerimeterExtrusion &perimeter_extrusion : sorted_perimeter_extrusions) { + if (!perimeter_extrusion.is_external_perimeter()) + continue; + + stack.push(&perimeter_extrusion); + visited.assign(sorted_perimeter_extrusions.size(), false); + + grouped_extrusions.emplace_back(&perimeter_extrusion); + while (!stack.empty()) { + const PerimeterExtrusion *current_extrusion = stack.top(); + const size_t current_extrusion_idx = current_extrusion - sorted_perimeter_extrusions.data(); + stack.pop(); + + if (visited[current_extrusion_idx]) + continue; + + if (current_extrusion->nearest_external_perimeter == &perimeter_extrusion) + grouped_extrusions.back().extrusions.emplace_back(current_extrusion); + + if (current_extrusion->adjacent_perimeter_extrusions.size() == 1) { + const PerimeterExtrusion *adjacent_extrusion = current_extrusion->adjacent_perimeter_extrusions.front(); + stack.push(adjacent_extrusion); + } else if (current_extrusion->adjacent_perimeter_extrusions.size() > 1) { + // When there is more than one available candidate, then order candidates to minimize distances between + // candidates and also to minimize the distance from the current_position. + std::vector available_candidates; + for (const PerimeterExtrusion *adjacent_extrusion : current_extrusion->adjacent_perimeter_extrusions) { + if (const size_t adjacent_extrusion_idx = adjacent_extrusion - sorted_perimeter_extrusions.data(); !visited[adjacent_extrusion_idx]) + available_candidates.emplace_back(adjacent_extrusion); + } + + std::vector adjacent_extrusions = ordered_perimeter_extrusions_to_minimize_distances(Point::Zero(), available_candidates); + std::reverse(adjacent_extrusions.begin(), adjacent_extrusions.end()); + for (const PerimeterExtrusion *adjacent_extrusion : adjacent_extrusions) + stack.push(adjacent_extrusion); + } + + visited[current_extrusion_idx] = true; + } + + if (!external_perimeters_first) + std::reverse(grouped_extrusions.back().extrusions.begin(), grouped_extrusions.back().extrusions.end()); + } + + const std::vector grouped_extrusion_order = order_of_grouped_perimeter_extrusions_to_minimize_distances(Point::Zero(), grouped_extrusions); + assert(grouped_extrusion_order.size() == grouped_ordered_extrusions.size()); + + PerimeterExtrusions ordered_extrusions; + for (size_t order_idx : grouped_extrusion_order) { + for (const PerimeterExtrusion *perimeter_extrusion : grouped_extrusions[order_idx].extrusions) + ordered_extrusions.emplace_back(*perimeter_extrusion); + } + + return ordered_extrusions; +} + +// FIXME: From the point of better patch planning, it should be better to do ordering when we have generated all extrusions (for now, when G-Code is exported). +// FIXME: It would be better to extract the adjacency graph of extrusions from the SkeletalTrapezoidation graph. +PerimeterExtrusions ordered_perimeter_extrusions(const Perimeters &perimeters, const bool external_perimeters_first) { + PerimeterExtrusions sorted_perimeter_extrusions = get_sorted_perimeter_extrusions_by_area(perimeters); + construct_perimeter_extrusions_adjacency_graph(sorted_perimeter_extrusions); + assign_nearest_external_perimeter(sorted_perimeter_extrusions); + return extract_ordered_perimeter_extrusions(sorted_perimeter_extrusions, external_perimeters_first); +} + +} // namespace Slic3r::Arachne::PerimeterOrder \ No newline at end of file diff --git a/src/libslic3r/Arachne/PerimeterOrder.hpp b/src/libslic3r/Arachne/PerimeterOrder.hpp new file mode 100644 index 0000000000..20fc3983d3 --- /dev/null +++ b/src/libslic3r/Arachne/PerimeterOrder.hpp @@ -0,0 +1,47 @@ +#ifndef slic3r_GCode_PerimeterOrder_hpp_ +#define slic3r_GCode_PerimeterOrder_hpp_ + +#include + +namespace Slic3r::Arachne::PerimeterOrder { + +// Data structure stores ExtrusionLine (closed and open) together with additional data. +struct PerimeterExtrusion +{ + explicit PerimeterExtrusion(const Arachne::ExtrusionLine &extrusion, const double area, const Polygon &polygon, const BoundingBox &bbox) + : extrusion(extrusion), area(area), polygon(polygon), bbox(bbox) {} + + Arachne::ExtrusionLine extrusion; + // Absolute value of the area of the polygon. The value is always non-negative, even for holes. + double area = 0; + + // Polygon is non-empty only for closed extrusions. + Polygon polygon; + BoundingBox bbox; + + std::vector adjacent_perimeter_extrusions; + + // How far is this perimeter from the nearest external perimeter. Contour is always preferred over holes. + size_t depth = std::numeric_limits::max(); + PerimeterExtrusion *nearest_external_perimeter = nullptr; + + // Should this extrusion be fuzzyfied during path generation? + bool fuzzify = false; + + // Returns if ExtrusionLine is a contour or a hole. + bool is_contour() const { return extrusion.is_contour(); } + + // Returns if ExtrusionLine is closed or opened. + bool is_closed() const { return extrusion.is_closed; } + + // Returns if ExtrusionLine is an external or an internal perimeter. + bool is_external_perimeter() const { return extrusion.is_external_perimeter(); } +}; + +using PerimeterExtrusions = std::vector; + +PerimeterExtrusions ordered_perimeter_extrusions(const Perimeters &perimeters, bool external_perimeters_first); + +} // namespace Slic3r::Arachne::PerimeterOrder + +#endif // slic3r_GCode_Travels_hpp_ diff --git a/src/libslic3r/Arachne/WallToolPaths.cpp b/src/libslic3r/Arachne/WallToolPaths.cpp index 6c5dafdac4..086644e7fa 100644 --- a/src/libslic3r/Arachne/WallToolPaths.cpp +++ b/src/libslic3r/Arachne/WallToolPaths.cpp @@ -758,99 +758,4 @@ bool WallToolPaths::removeEmptyToolPaths(std::vector &toolpa return toolpaths.empty(); } -/*! - * Get the order constraints of the insets when printing walls per region / hole. - * Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right. - * - * Odd walls should always go after their enclosing wall polygons. - * - * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. - */ -WallToolPaths::ExtrusionLineSet WallToolPaths::getRegionOrder(const std::vector &input, const bool outer_to_inner) -{ - ExtrusionLineSet order_requirements; - - // We build a grid where we map toolpath vertex locations to toolpaths, - // so that we can easily find which two toolpaths are next to each other, - // which is the requirement for there to be an order constraint. - // - // We use a PointGrid rather than a LineGrid to save on computation time. - // In very rare cases two insets might lie next to each other without having neighboring vertices, e.g. - // \ . - // | / . - // | / . - // || . - // | \ . - // | \ . - // / . - // However, because of how Arachne works this will likely never be the case for two consecutive insets. - // On the other hand one could imagine that two consecutive insets of a very large circle - // could be simplify()ed such that the remaining vertices of the two insets don't align. - // In those cases the order requirement is not captured, - // which means that the PathOrderOptimizer *might* result in a violation of the user set path order. - // This problem is expected to be not so severe and happen very sparsely. - - coord_t max_line_w = 0u; - for (const ExtrusionLine *line : input) // compute max_line_w - for (const ExtrusionJunction &junction : *line) - max_line_w = std::max(max_line_w, junction.w); - if (max_line_w == 0u) - return order_requirements; - - struct LineLoc - { - ExtrusionJunction j; - const ExtrusionLine *line; - }; - struct Locator - { - Point operator()(const LineLoc &elem) { return elem.j.p; } - }; - - // How much farther two verts may be apart due to corners. - // This distance must be smaller than 2, because otherwise - // we could create an order requirement between e.g. - // wall 2 of one region and wall 3 of another region, - // while another wall 3 of the first region would lie in between those two walls. - // However, higher values are better against the limitations of using a PointGrid rather than a LineGrid. - constexpr float diagonal_extension = 1.9f; - const auto searching_radius = coord_t(max_line_w * diagonal_extension); - using GridT = SparsePointGrid; - GridT grid(searching_radius); - - for (const ExtrusionLine *line : input) - for (const ExtrusionJunction &junction : *line) grid.insert(LineLoc{junction, line}); - for (const std::pair &pair : grid) { - const LineLoc &lineloc_here = pair.second; - const ExtrusionLine *here = lineloc_here.line; - Point loc_here = pair.second.j.p; - std::vector nearby_verts = grid.getNearby(loc_here, searching_radius); - for (const LineLoc &lineloc_nearby : nearby_verts) { - const ExtrusionLine *nearby = lineloc_nearby.line; - if (nearby == here) - continue; - if (nearby->inset_idx == here->inset_idx) - continue; - if (nearby->inset_idx > here->inset_idx + 1) - continue; // not directly adjacent - if (here->inset_idx > nearby->inset_idx + 1) - continue; // not directly adjacent - if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension)) - continue; // points are too far away from each other - if (here->is_odd || nearby->is_odd) { - if (here->is_odd && !nearby->is_odd && nearby->inset_idx < here->inset_idx) - order_requirements.emplace(std::make_pair(nearby, here)); - if (nearby->is_odd && !here->is_odd && here->inset_idx < nearby->inset_idx) - order_requirements.emplace(std::make_pair(here, nearby)); - } else if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) { - order_requirements.emplace(std::make_pair(nearby, here)); - } else { - assert((nearby->inset_idx > here->inset_idx) == outer_to_inner); - order_requirements.emplace(std::make_pair(here, nearby)); - } - } - } - return order_requirements; -} - } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/WallToolPaths.hpp b/src/libslic3r/Arachne/WallToolPaths.hpp index 44f3affb69..bdc8593cab 100644 --- a/src/libslic3r/Arachne/WallToolPaths.hpp +++ b/src/libslic3r/Arachne/WallToolPaths.hpp @@ -75,15 +75,6 @@ public: static bool removeEmptyToolPaths(std::vector &toolpaths); using ExtrusionLineSet = ankerl::unordered_dense::set, boost::hash>>; - /*! - * Get the order constraints of the insets when printing walls per region / hole. - * Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right. - * - * Odd walls should always go after their enclosing wall polygons. - * - * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. - */ - static ExtrusionLineSet getRegionOrder(const std::vector &input, bool outer_to_inner); protected: /*! diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp index 8631d9e17b..82f0969048 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp @@ -236,9 +236,10 @@ bool ExtrusionLine::is_contour() const return poly.is_clockwise(); } -double ExtrusionLine::area() const -{ - assert(this->is_closed); +double ExtrusionLine::area() const { + if (!this->is_closed) + return 0.; + double a = 0.; if (this->junctions.size() >= 3) { Vec2d p1 = this->junctions.back().p.cast(); @@ -248,9 +249,25 @@ double ExtrusionLine::area() const p1 = p2; } } + return 0.5 * a; } +Points to_points(const ExtrusionLine &extrusion_line) { + Points points; + points.reserve(extrusion_line.junctions.size()); + for (const ExtrusionJunction &junction : extrusion_line.junctions) + points.emplace_back(junction.p); + return points; +} + +BoundingBox get_extents(const ExtrusionLine &extrusion_line) { + BoundingBox bbox; + for (const ExtrusionJunction &junction : extrusion_line.junctions) + bbox.merge(junction.p); + return bbox; +} + } // namespace Slic3r::Arachne namespace Slic3r { diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp index d39e1e07b9..0eef4cd9aa 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp @@ -192,6 +192,8 @@ struct ExtrusionLine bool is_contour() const; double area() const; + + bool is_external_perimeter() const { return this->inset_idx == 0; } }; static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::ExtrusionLine &line_junctions) @@ -237,6 +239,7 @@ static inline Slic3r::ThickPolyline to_thick_polyline(const ClipperLib_Z::Path & static inline Polygon to_polygon(const ExtrusionLine &line) { Polygon out; + assert(line.is_closed); assert(line.junctions.size() >= 3); assert(line.junctions.front().p == line.junctions.back().p); out.points.reserve(line.junctions.size() - 1); @@ -245,15 +248,11 @@ static inline Polygon to_polygon(const ExtrusionLine &line) return out; } -#if 0 -static BoundingBox get_extents(const ExtrusionLine &extrusion_line) -{ - BoundingBox bbox; - for (const ExtrusionJunction &junction : extrusion_line.junctions) - bbox.merge(junction.p); - return bbox; -} +Points to_points(const ExtrusionLine &extrusion_line); +BoundingBox get_extents(const ExtrusionLine &extrusion_line); + +#if 0 static BoundingBox get_extents(const std::vector &extrusion_lines) { BoundingBox bbox; @@ -272,15 +271,6 @@ static BoundingBox get_extents(const std::vector &extrusi return bbox; } -static Points to_points(const ExtrusionLine &extrusion_line) -{ - Points points; - points.reserve(extrusion_line.junctions.size()); - for (const ExtrusionJunction &junction : extrusion_line.junctions) - points.emplace_back(junction.p); - return points; -} - static std::vector to_points(const std::vector &extrusion_lines) { std::vector points; @@ -293,6 +283,8 @@ static std::vector to_points(const std::vector &e #endif using VariableWidthLines = std::vector; //; } // namespace Slic3r::Arachne diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 09eca00720..004de96040 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -500,6 +500,8 @@ set(SLIC3R_SOURCES Geometry/Voronoi.cpp Geometry/VoronoiUtils.hpp Geometry/VoronoiUtils.cpp + Arachne/PerimeterOrder.hpp + Arachne/PerimeterOrder.cpp Arachne/SkeletalTrapezoidation.hpp Arachne/SkeletalTrapezoidation.cpp Arachne/SkeletalTrapezoidationEdge.hpp diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index fff8be41b3..722294321b 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -6,7 +6,6 @@ ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher ///|/ #include "PerimeterGenerator.hpp" -#include "AABBTreeIndirect.hpp" #include "AABBTreeLines.hpp" #include "BoundingBox.hpp" #include "BridgeDetector.hpp" @@ -14,8 +13,6 @@ #include "ExPolygon.hpp" #include "ExtrusionEntity.hpp" #include "ExtrusionEntityCollection.hpp" -#include "Geometry/MedialAxis.hpp" -#include "KDTreeIndirect.hpp" #include "MultiPoint.hpp" #include "Point.hpp" #include "Polygon.hpp" @@ -25,9 +22,9 @@ #include "Surface.hpp" #include "Geometry/ConvexHull.hpp" -#include "SurfaceCollection.hpp" #include "clipper/clipper_z.hpp" +#include "Arachne/PerimeterOrder.hpp" #include "Arachne/WallToolPaths.hpp" #include "Arachne/utils/ExtrusionLine.hpp" #include "Arachne/utils/ExtrusionJunction.hpp" @@ -36,16 +33,10 @@ #include #include #include -#include #include -#include #include #include -#include -#include -#include #include -#include #include #include #include @@ -53,15 +44,13 @@ #include -// #define ARACHNE_DEBUG +//#define ARACHNE_DEBUG #ifdef ARACHNE_DEBUG #include "SVG.hpp" #include "Utils.hpp" #endif -#include "SVG.hpp" - namespace Slic3r { ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance) @@ -503,29 +492,20 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con return clipped_paths; } -struct PerimeterGeneratorArachneExtrusion -{ - Arachne::ExtrusionLine *extrusion = nullptr; - // Indicates if closed ExtrusionLine is a contour or a hole. Used it only when ExtrusionLine is a closed loop. - bool is_contour = false; - // Should this extrusion be fuzzyfied on path generation? - bool fuzzify = false; -}; - -static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, std::vector &pg_extrusions) +static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, Arachne::PerimeterOrder::PerimeterExtrusions &pg_extrusions) { ExtrusionEntityCollection extrusion_coll; - for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) { - Arachne::ExtrusionLine *extrusion = pg_extrusion.extrusion; - if (extrusion->empty()) + for (Arachne::PerimeterOrder::PerimeterExtrusion &pg_extrusion : pg_extrusions) { + Arachne::ExtrusionLine &extrusion = pg_extrusion.extrusion; + if (extrusion.empty()) continue; - const bool is_external = extrusion->inset_idx == 0; + const bool is_external = extrusion.inset_idx == 0; ExtrusionRole role_normal = is_external ? ExtrusionRole::ExternalPerimeter : ExtrusionRole::Perimeter; ExtrusionRole role_overhang = role_normal | ExtrusionRoleModifier::Bridge; if (pg_extrusion.fuzzify) - fuzzy_extrusion_line(*extrusion, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); + fuzzy_extrusion_line(extrusion, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); ExtrusionPaths paths; // detect overhanging/bridging perimeters @@ -534,9 +514,9 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P params.object_config.support_material_contact_distance.value == 0)) { ClipperLib_Z::Path extrusion_path; - extrusion_path.reserve(extrusion->size()); + extrusion_path.reserve(extrusion.size()); BoundingBox extrusion_path_bbox; - for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) { + for (const Arachne::ExtrusionJunction &ej : extrusion.junctions) { extrusion_path.emplace_back(ej.p.x(), ej.p.y(), ej.w); extrusion_path_bbox.merge(Point{ej.p.x(), ej.p.y()}); } @@ -574,7 +554,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P // Arachne sometimes creates extrusion with zero-length (just two same endpoints); if (!paths.empty()) { Point start_point = paths.front().first_point(); - if (!extrusion->is_closed) { + if (!extrusion.is_closed) { // Especially for open extrusion, we need to select a starting point that is at the start // or the end of the extrusions to make one continuous line. Also, we prefer a non-overhang // starting point. @@ -607,15 +587,15 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P chain_and_reorder_extrusion_paths(paths, &start_point); } } else { - extrusion_paths_append(paths, *extrusion, role_normal, is_external ? params.ext_perimeter_flow : params.perimeter_flow); + extrusion_paths_append(paths, extrusion, role_normal, is_external ? params.ext_perimeter_flow : params.perimeter_flow); } // Append paths to collection. if (!paths.empty()) { - if (extrusion->is_closed) { + if (extrusion.is_closed) { ExtrusionLoop extrusion_loop(std::move(paths)); // Restore the orientation of the extrusion loop. - if (pg_extrusion.is_contour == extrusion_loop.is_clockwise()) + if (pg_extrusion.is_contour() == extrusion_loop.is_clockwise()) extrusion_loop.reverse_loop(); for (auto it = std::next(extrusion_loop.paths.begin()); it != extrusion_loop.paths.end(); ++it) { @@ -654,7 +634,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P } #ifdef ARACHNE_DEBUG -static void export_perimeters_to_svg(const std::string &path, const Polygons &contours, const std::vector &perimeters, const ExPolygons &infill_area) +static void export_perimeters_to_svg(const std::string &path, const Polygons &contours, const Arachne::Perimeters &perimeters, const ExPolygons &infill_area) { coordf_t stroke_width = scale_(0.03); BoundingBox bbox = get_extents(contours); @@ -663,7 +643,7 @@ static void export_perimeters_to_svg(const std::string &path, const Polygons &co svg.draw(infill_area, "cyan"); - for (const Arachne::VariableWidthLines &perimeter : perimeters) + for (const Arachne::Perimeter &perimeter : perimeters) for (const Arachne::ExtrusionLine &extrusion_line : perimeter) { ThickPolyline thick_polyline = to_thick_polyline(extrusion_line); svg.draw({thick_polyline}, "green", "blue", stroke_width); @@ -1130,8 +1110,8 @@ void PerimeterGenerator::process_arachne( ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); Polygons last_p = to_polygons(last); Arachne::WallToolPaths wall_tool_paths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config); - std::vector perimeters = wall_tool_paths.getToolPaths(); - ExPolygons infill_contour = union_ex(wall_tool_paths.getInnerContour()); + Arachne::Perimeters perimeters = wall_tool_paths.getToolPaths(); + ExPolygons infill_contour = union_ex(wall_tool_paths.getInnerContour()); // Check if there are some remaining perimeters to generate (the number of perimeters // is greater than one together with enabled the single perimeter on top surface feature). @@ -1168,7 +1148,7 @@ void PerimeterGenerator::process_arachne( const Polygons not_top_polygons = to_polygons(not_top_expolygons); Arachne::WallToolPaths inner_wall_tool_paths(not_top_polygons, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, params.layer_height, params.object_config, params.print_config); - std::vector inner_perimeters = inner_wall_tool_paths.getToolPaths(); + Arachne::Perimeters inner_perimeters = inner_wall_tool_paths.getToolPaths(); // Recalculate indexes of inner perimeters before merging them. if (!perimeters.empty()) { @@ -1197,7 +1177,7 @@ void PerimeterGenerator::process_arachne( #ifdef ARACHNE_DEBUG { static int iRun = 0; - export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); + export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", params.layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); } #endif @@ -1205,107 +1185,20 @@ void PerimeterGenerator::process_arachne( // But in rare cases, Arachne produce ExtrusionLine marked as closed but without // equal the first and the last point. assert([&perimeters = std::as_const(perimeters)]() -> bool { - for (const Arachne::VariableWidthLines &perimeter : perimeters) + for (const Arachne::Perimeter &perimeter : perimeters) for (const Arachne::ExtrusionLine &el : perimeter) if (el.is_closed && el.junctions.front().p != el.junctions.back().p) return false; return true; }()); - int start_perimeter = int(perimeters.size()) - 1; - int end_perimeter = -1; - int direction = -1; - - if (params.config.external_perimeters_first) { - start_perimeter = 0; - end_perimeter = int(perimeters.size()); - direction = 1; - } - - std::vector all_extrusions; - for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { - if (perimeters[perimeter_idx].empty()) - continue; - for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx]) - all_extrusions.emplace_back(&wall); - } - - // Find topological order with constraints from extrusions_constrains. - std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. - std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. - ankerl::unordered_dense::map map_extrusion_to_idx; - for (size_t idx = 0; idx < all_extrusions.size(); idx++) - map_extrusion_to_idx.emplace(all_extrusions[idx], idx); - - Arachne::WallToolPaths::ExtrusionLineSet extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first); - for (auto [before, after] : extrusions_constrains) { - auto after_it = map_extrusion_to_idx.find(after); - ++blocked[after_it->second]; - blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); - } - - std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. - Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. - std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. - ordered_extrusions.reserve(all_extrusions.size()); - - while (ordered_extrusions.size() < all_extrusions.size()) { - size_t best_candidate = 0; - double best_distance_sqr = std::numeric_limits::max(); - bool is_best_closed = false; - - std::vector available_candidates; - for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { - if (processed[candidate] || blocked[candidate]) - continue; // Not a valid candidate. - available_candidates.push_back(candidate); - } - - std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { - return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; - }); - - for (const size_t candidate_path_idx : available_candidates) { - auto& path = all_extrusions[candidate_path_idx]; - - if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. - if (best_distance_sqr == std::numeric_limits::max()) { - best_candidate = candidate_path_idx; - is_best_closed = path->is_closed; - } - continue; - } - - const Point candidate_position = path->junctions.front().p; - double distance_sqr = (current_position - candidate_position).cast().norm(); - if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. - if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { - best_candidate = candidate_path_idx; - best_distance_sqr = distance_sqr; - is_best_closed = path->is_closed; - } - } - } - - auto &best_path = all_extrusions[best_candidate]; - ordered_extrusions.push_back({best_path, best_path->is_contour(), false}); - processed[best_candidate] = true; - for (size_t unlocked_idx : blocking[best_candidate]) - blocked[unlocked_idx]--; - - if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. - if(best_path->is_closed) - current_position = best_path->junctions[0].p; //We end where we started. - else - current_position = best_path->junctions.back().p; //Pick the other end from where we started. - } - } + Arachne::PerimeterOrder::PerimeterExtrusions ordered_extrusions = Arachne::PerimeterOrder::ordered_perimeter_extrusions(perimeters, params.config.external_perimeters_first); if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) { - std::vector closed_loop_extrusions; - for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions) - if (extrusion.extrusion->inset_idx == 0) { - if (extrusion.extrusion->is_closed && params.config.fuzzy_skin == FuzzySkinType::External) { + std::vector closed_loop_extrusions; + for (Arachne::PerimeterOrder::PerimeterExtrusion &extrusion : ordered_extrusions) + if (extrusion.extrusion.inset_idx == 0) { + if (extrusion.extrusion.is_closed && params.config.fuzzy_skin == FuzzySkinType::External) { closed_loop_extrusions.emplace_back(&extrusion); } else { extrusion.fuzzify = true; @@ -1316,11 +1209,11 @@ void PerimeterGenerator::process_arachne( ClipperLib_Z::Paths loops_paths; loops_paths.reserve(closed_loop_extrusions.size()); for (const auto &cl_extrusion : closed_loop_extrusions) { - assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back()); + assert(cl_extrusion->extrusion.junctions.front() == cl_extrusion->extrusion.junctions.back()); size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); ClipperLib_Z::Path loop_path; - loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1); - for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it) + loop_path.reserve(cl_extrusion->extrusion.junctions.size() - 1); + for (auto junction_it = cl_extrusion->extrusion.junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion.junctions.end()); ++junction_it) loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); loops_paths.emplace_back(loop_path); }