Merge branch 'lh_arachne_perimeters_order'

This commit is contained in:
Lukas Matena 2024-05-03 16:20:09 +02:00
commit 560c54f3ef
16 changed files with 394 additions and 336 deletions

View File

@ -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<bool> 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<PerimeterExtrusion *> 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<const PerimeterExtrusion *> ordered_perimeter_extrusions_to_minimize_distances(Point current_position, std::vector<const PerimeterExtrusion *> 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<const PerimeterExtrusion *> ordered_extrusions;
std::vector<bool> already_selected(extrusions.size(), false);
while (ordered_extrusions.size() < extrusions.size()) {
double nearest_distance_sqr = std::numeric_limits<double>::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<double>().squaredNorm();
if (distance_sqr < nearest_distance_sqr) {
if (extrusion_line.is_closed || (!extrusion_line.is_closed && nearest_distance_sqr != std::numeric_limits<double>::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<const PerimeterExtrusion *> extrusions;
const PerimeterExtrusion *external_perimeter_extrusion = nullptr;
};
// Returns vector of indexes that represent the order of grouped extrusions in grouped_extrusions.
static std::vector<size_t> order_of_grouped_perimeter_extrusions_to_minimize_distances(Point current_position, std::vector<GroupedPerimeterExtrusions> 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<size_t> grouped_extrusions_order;
std::vector<bool> already_selected(grouped_extrusions.size(), false);
while (grouped_extrusions_order.size() < grouped_extrusions.size()) {
double nearest_distance_sqr = std::numeric_limits<double>::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<double>().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<double>::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<GroupedPerimeterExtrusions> grouped_extrusions;
std::stack<const PerimeterExtrusion *> stack;
std::vector<bool> 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<const PerimeterExtrusion *> 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<const PerimeterExtrusion *> 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<size_t> 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

View File

@ -0,0 +1,47 @@
#ifndef slic3r_GCode_PerimeterOrder_hpp_
#define slic3r_GCode_PerimeterOrder_hpp_
#include <Arachne/utils/ExtrusionLine.hpp>
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<PerimeterExtrusion *> 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<size_t>::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<PerimeterExtrusion>;
PerimeterExtrusions ordered_perimeter_extrusions(const Perimeters &perimeters, bool external_perimeters_first);
} // namespace Slic3r::Arachne::PerimeterOrder
#endif // slic3r_GCode_Travels_hpp_

View File

@ -152,8 +152,7 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_t
assert(twin->prev->twin); // Back rib
assert(twin->prev->twin->prev); // Prev segment along parabola
constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped
graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end);
graph.makeRib(prev_edge, start_source_point, end_source_point);
}
assert(prev_edge);
}
@ -203,10 +202,8 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_t
p0 = p1;
v0 = v1;
if (p1_idx < discretized.size() - 1)
{ // Rib for last segment gets introduced outside this function!
constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped
graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end);
if (p1_idx < discretized.size() - 1) { // Rib for last segment gets introduced outside this function!
graph.makeRib(prev_edge, start_source_point, end_source_point);
}
}
assert(prev_edge);
@ -462,8 +459,7 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
node_t *starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
starting_node->data.distance_to_boundary = 0;
constexpr bool is_next_to_start_or_end = true;
graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end);
graph.makeRib(prev_edge, start_source_point, end_source_point);
for (const VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) {
assert(vd_edge->is_finite());
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*vd_edge));
@ -471,7 +467,7 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast<coord_t>();
Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast<coord_t>();
transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments);
graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge);
graph.makeRib(prev_edge, start_source_point, end_source_point);
}
transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);

View File

@ -572,8 +572,6 @@ protected:
* Genrate small segments for local maxima where the beading would only result in a single bead
*/
void generateLocalMaximaSingleBeads();
friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector<Segment> &segments);
};
} // namespace Slic3r::Arachne

View File

@ -314,7 +314,7 @@ void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist)
}
}
void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end)
void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point)
{
Point p;
Line(start_source_point, end_source_point).distance_to_infinite_squared(prev_edge->to->p, &p);

View File

@ -88,7 +88,7 @@ public:
*/
void collapseSmallEdges(coord_t snap_dist = 5);
void makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end);
void makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point);
/*!
* Insert a node into the graph and connect it to the input polygon using ribs

View File

@ -6,7 +6,6 @@
#include "WallToolPaths.hpp"
#include "SkeletalTrapezoidation.hpp"
#include "../ClipperUtils.hpp"
#include "utils/linearAlg2D.hpp"
#include "EdgeGrid.hpp"
#include "utils/SparseLineGrid.hpp"
@ -758,99 +757,4 @@ bool WallToolPaths::removeEmptyToolPaths(std::vector<VariableWidthLines> &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<ExtrusionLine *> &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<LineLoc, Locator>;
GridT grid(searching_radius);
for (const ExtrusionLine *line : input)
for (const ExtrusionJunction &junction : *line) grid.insert(LineLoc{junction, line});
for (const std::pair<const SquareGrid::GridPoint, LineLoc> &pair : grid) {
const LineLoc &lineloc_here = pair.second;
const ExtrusionLine *here = lineloc_here.line;
Point loc_here = pair.second.j.p;
std::vector<LineLoc> 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

View File

@ -75,15 +75,6 @@ public:
static bool removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths);
using ExtrusionLineSet = ankerl::unordered_dense::set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>>;
/*!
* 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<ExtrusionLine *> &input, bool outer_to_inner);
protected:
/*!

View File

@ -1,18 +0,0 @@
//Copyright (c) 2020 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "ExtrusionJunction.hpp"
namespace Slic3r::Arachne
{
bool ExtrusionJunction::operator ==(const ExtrusionJunction& other) const
{
return p == other.p
&& w == other.w
&& perimeter_index == other.perimeter_index;
}
ExtrusionJunction::ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), perimeter_index(perimeter_index) {}
}

View File

@ -37,9 +37,11 @@ struct ExtrusionJunction
*/
size_t perimeter_index;
ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index);
ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), perimeter_index(perimeter_index) {}
bool operator==(const ExtrusionJunction& other) const;
bool operator==(const ExtrusionJunction &other) const {
return p == other.p && w == other.w && perimeter_index == other.perimeter_index;
}
};
inline Point operator-(const ExtrusionJunction& a, const ExtrusionJunction& b)

View File

@ -29,15 +29,6 @@ int64_t ExtrusionLine::getLength() const
return len;
}
coord_t ExtrusionLine::getMinimalWidth() const
{
return std::min_element(junctions.cbegin(), junctions.cend(),
[](const ExtrusionJunction& l, const ExtrusionJunction& r)
{
return l.w < r.w;
})->w;
}
void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared, const int64_t maximum_extrusion_area_deviation)
{
const size_t min_path_size = is_closed ? 3 : 2;
@ -236,9 +227,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<double>();
@ -248,9 +240,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 {

View File

@ -136,11 +136,6 @@ struct ExtrusionLine
return ret;
}
/*!
* Get the minimal width of this path
*/
coord_t getMinimalWidth() const;
/*!
* Removes vertices of the ExtrusionLines to make sure that they are not too high
* resolution.
@ -192,6 +187,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 +234,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 +243,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<ExtrusionLine> &extrusion_lines)
{
BoundingBox bbox;
@ -272,15 +266,6 @@ static BoundingBox get_extents(const std::vector<const ExtrusionLine *> &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<Points> to_points(const std::vector<const ExtrusionLine *> &extrusion_lines)
{
std::vector<Points> points;
@ -293,6 +278,8 @@ static std::vector<Points> to_points(const std::vector<const ExtrusionLine *> &e
#endif
using VariableWidthLines = std::vector<ExtrusionLine>; //<! The ExtrusionLines generated by libArachne
using Perimeter = VariableWidthLines;
using Perimeters = std::vector<Perimeter>;
} // namespace Slic3r::Arachne

View File

@ -39,16 +39,6 @@ public:
*/
void insert(const Elem &elem);
/*!
* Get just any element that's within a certain radius of a point.
*
* Rather than giving a vector of nearby elements, this function just gives
* a single element, any element, in no particular order.
* \param query_pt The point to query for an object nearby.
* \param radius The radius of what is considered "nearby".
*/
const ElemT *getAnyNearby(const Point &query_pt, coord_t radius);
protected:
using GridPoint = typename SparseGrid<ElemT>::GridPoint;
@ -68,22 +58,6 @@ void SparsePointGrid<ElemT, Locator>::insert(const Elem &elem)
SparseGrid<ElemT>::m_grid.emplace(grid_loc, elem);
}
template<class ElemT, class Locator>
const ElemT *SparsePointGrid<ElemT, Locator>::getAnyNearby(const Point &query_pt, coord_t radius)
{
const ElemT *ret = nullptr;
const std::function<bool(const ElemT &)> &process_func = [&ret, query_pt, radius, this](const ElemT &maybe_nearby) {
if (shorter_then(m_locator(maybe_nearby) - query_pt, radius)) {
ret = &maybe_nearby;
return false;
}
return true;
};
SparseGrid<ElemT>::processNearby(query_pt, radius, process_func);
return ret;
}
} // namespace Slic3r::Arachne
#endif // UTILS_SPARSE_POINT_GRID_H

View File

@ -2,7 +2,6 @@
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "SquareGrid.hpp"
#include "../../Point.hpp"
using namespace Slic3r::Arachne;

View File

@ -482,7 +482,6 @@ set(SLIC3R_SOURCES
Arachne/BeadingStrategy/WideningBeadingStrategy.hpp
Arachne/BeadingStrategy/WideningBeadingStrategy.cpp
Arachne/utils/ExtrusionJunction.hpp
Arachne/utils/ExtrusionJunction.cpp
Arachne/utils/ExtrusionLine.hpp
Arachne/utils/ExtrusionLine.cpp
Arachne/utils/HalfEdge.hpp
@ -500,6 +499,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

View File

@ -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 <algorithm>
#include <cmath>
#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <functional>
#include <iterator>
#include <limits>
#include <list>
#include <math.h>
#include <ostream>
#include <stack>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
@ -53,15 +44,13 @@
#include <ankerl/unordered_dense.h>
// #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 &params, const Polygons &lower_slices_polygons_cache, std::vector<PerimeterGeneratorArachneExtrusion> &pg_extrusions)
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters &params, 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<float>(params.config.fuzzy_skin_thickness.value), scaled<float>(params.config.fuzzy_skin_point_dist.value));
fuzzy_extrusion_line(extrusion, scaled<float>(params.config.fuzzy_skin_thickness.value), scaled<float>(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<Arachne::VariableWidthLines> &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<Arachne::VariableWidthLines> 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<Arachne::VariableWidthLines> 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<Arachne::ExtrusionLine *> 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<size_t> blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion.
std::vector<std::vector<size_t>> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion.
ankerl::unordered_dense::map<const Arachne::ExtrusionLine *, size_t> 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<bool> 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<PerimeterGeneratorArachneExtrusion> 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<double>::max();
bool is_best_closed = false;
std::vector<size_t> 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<double>::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<double>().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<double>::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<PerimeterGeneratorArachneExtrusion *> 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<Arachne::PerimeterOrder::PerimeterExtrusion *> 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);
}