Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve slice performance of wall ordering #2001

Merged
merged 15 commits into from
Jan 2, 2024
2 changes: 1 addition & 1 deletion include/InsetOrderOptimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class InsetOrderOptimizer
*
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
*/
static value_type getRegionOrder(const auto& input, const bool outer_to_inner);
static value_type getRegionOrder(const std::vector<ExtrusionLine>& input, const bool outer_to_inner);

/*!
* Get the order constraints of the insets when printing walls per inset.
Expand Down
50 changes: 50 additions & 0 deletions include/utils/ExtrusionLine.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#ifndef UTILS_EXTRUSION_LINE_H
#define UTILS_EXTRUSION_LINE_H

#include <range/v3/view/enumerate.hpp>
#include <range/v3/view/reverse.hpp>
#include <range/v3/view/sliding.hpp>

#include "ExtrusionJunction.h"
#include "polygon.h"

Expand Down Expand Up @@ -57,6 +61,16 @@ struct ExtrusionLine
return junctions_.size();
}

/*!
* Gets the vertex at the given index.
* \param idx The index of the vertex to get.
* \return The vertex at the given index.
*/
bool is_outer_wall() const
{
return inset_idx_ == 0;
}

/*!
* Whether there are no junctions.
*/
Expand Down Expand Up @@ -215,6 +229,42 @@ struct ExtrusionLine
return ret;
}

/*!
* Create a true-extrusion area shape for the path; this means that each junction follows the bead-width
* set for that junction.
*/
Polygons toExtrusionPolygons() const
{
Polygon poly;

const auto add_line_direction = [&poly](const auto iterator)
{
const auto window = iterator | ranges::views::sliding(2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

window is not used anywhere?


for (const auto& element : iterator | ranges::views::sliding(2))
{
const ExtrusionJunction& j1 = element[0];
const ExtrusionJunction& j2 = element[1];

const auto dir = j2.p_ - j1.p_;
const auto normal = turn90CCW(dir);
const auto mag = vSize(normal);
poly.emplace_back(j1.p_ + normal * j1.w_ / mag);
poly.emplace_back(j2.p_ + normal * j2.w_ / mag);
}
};

// forward pass
add_line_direction(junctions_);
// backward pass
add_line_direction(junctions_ | ranges::views::reverse);

Polygons paths;
paths.emplace_back(poly.poly);
ClipperLib::SimplifyPolygons(paths.paths, ClipperLib::pftNonZero);
return paths;
}

/*!
* Get the minimal width of this path
*/
Expand Down
222 changes: 102 additions & 120 deletions src/InsetOrderOptimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,22 @@

#include "InsetOrderOptimizer.h"

#include <iterator>
#include <functional>
#include <tuple>

#include <range/v3/algorithm/max.hpp>
#include <range/v3/algorithm/sort.hpp>
#include <range/v3/range/conversion.hpp>
#include <range/v3/range/operations.hpp>
#include <range/v3/view/addressof.hpp>
#include <range/v3/view/any_view.hpp>
#include <range/v3/view/drop.hpp>
#include <range/v3/view/drop_last.hpp>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/join.hpp>
#include <range/v3/view/remove_if.hpp>
#include <range/v3/view/reverse.hpp>
#include <range/v3/view/take_exactly.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/view/zip.hpp>
#include <spdlog/spdlog.h>

#include "ExtruderTrain.h"
#include "FffGcodeWriter.h"
Expand Down Expand Up @@ -138,153 +136,137 @@ bool InsetOrderOptimizer::addToLayer()
return added_something;
}

InsetOrderOptimizer::value_type InsetOrderOptimizer::getRegionOrder(const auto& input, const bool outer_to_inner)
InsetOrderOptimizer::value_type InsetOrderOptimizer::getRegionOrder(const std::vector<ExtrusionLine>& extrusion_lines, const bool outer_to_inner)
{
if (input.empty()) // Early out
if (extrusion_lines.empty())
{
// Early out
return {};
}

// Cache the polygons and get the signed area of each extrusion line and store them mapped against the pointers for those lines
struct LineLoc
// view on the extrusion lines, sorted by area
const auto sorted_extrusion_lines = [&extrusion_lines]()
{
const ExtrusionLine* line;
Polygon poly;
double area;
};
auto poly_views = input | views::convert<Polygon>(&ExtrusionLine::toPolygon);
auto pointer_view = input | rv::addressof;
auto locator_view = rv::zip(pointer_view, poly_views)
| rv::transform(
[](const auto& locator)
{
const auto poly = std::get<1>(locator);
const auto line = std::get<0>(locator);
return LineLoc{
.line = line,
.poly = poly,
.area = line->is_closed_ ? poly.area() : 0.0,
};
})
| rg::to_vector;

// Sort polygons by increasing area, we are building the graph from the leaves (smallest area) upwards.
rg::sort(
locator_view,
[](const auto& lhs, const auto& rhs)
{
return std::abs(lhs) < std::abs(rhs);
},
&LineLoc::area);

// Create a bi-direction directed acyclic graph (Tree). Where polygon B is a child of A if B is inside A. The root of the graph is
// the polygon that contains all other polygons. The leaves are polygons that contain no polygons.
// We need a bi-directional graph as we are performing a dfs from the root down and from each of the hole (which are leaves in the graph) up the tree
std::unordered_multimap<const LineLoc*, const LineLoc*> graph;
std::unordered_set<LineLoc*> roots{ &rg::front(locator_view) };
for (const auto& locator : locator_view | rv::addressof | rv::drop(1))
auto extrusion_lines_area = extrusion_lines | ranges::views::addressof
| ranges::views::transform(
[](const ExtrusionLine* line)
{
const auto poly = line->toPolygon();
AABB aabb;
aabb.include(poly);
return std::make_pair(line, aabb.area());
})
| ranges::to_vector;

ranges::sort(
extrusion_lines_area,
[](const auto& lhs, const auto& rhs)
{
return std::get<1>(lhs) < std::get<1>(rhs);
});

return extrusion_lines_area
| ranges::views::transform(
[](const auto& pair)
{
return std::get<0>(pair);
})
| ranges::to_vector;
}();

// graph will contain the parent-child relationships between the extrusion lines
// an edge is added for both the parent to child and child to parent relationship
std::unordered_multimap<const ExtrusionLine*, const ExtrusionLine*> graph;
// during the loop we maintain a list of invariant parents; these are the parents
// that we have found so far
std::unordered_set<const ExtrusionLine*> invariant_outer_parents;
for (const auto& extrusion_line : sorted_extrusion_lines)
{
std::vector<LineLoc*> erase;
for (const auto& root : roots)
// Create a polygon representing the inner area of the extrusion line; any
// point inside this polygon is considered to the child of the extrusion line.
Polygons hole_polygons;
for (const auto& poly : extrusion_line->toExtrusionPolygons().splitIntoParts())
{
// drop first path, as this is the outer contour
for (const auto& hole : poly.paths | ranges::views::drop(1))
{
// reverse the hole polygon to turn a hole into a polygon
hole_polygons.emplace_back(hole | ranges::views::reverse | ranges::to_vector);
}
}
// increase the size of the hole polygons by 10um to make sure we don't miss any invariant parents
hole_polygons.offset(10);

// go through all the invariant parents and see if they are inside the hole polygon
// if they are, then that means we have found a child for this extrusion line
std::vector<const ExtrusionLine*> removed_parent_invariants;
for (const ExtrusionLine* invariant_parent : invariant_outer_parents)
{
if (root->poly.inside(locator->poly))
if (hole_polygons.inside(invariant_parent->junctions_[0].p_, false))
{
// The root polygon is inside the location polygon. It is no longer a root in the graph we are building.
// Add this relationship (locator <-> root) to the graph, and remove root from roots.
graph.emplace(locator, root);
graph.emplace(root, locator);
erase.emplace_back(root);
graph.emplace(extrusion_line, invariant_parent);
graph.emplace(invariant_parent, extrusion_line);
removed_parent_invariants.emplace_back(invariant_parent);
}
}
for (const auto& node : erase)
for (const auto& node : removed_parent_invariants)
{
roots.erase(node);
invariant_outer_parents.erase(node);
}
// We are adding to the graph from smallest area -> largest area. This means locator will always be the largest polygon in the graph so far.
// No polygon in the graph is big enough to contain locator, so it must be a root.
roots.emplace(locator);

// the current extrusion line is now an invariant parent
invariant_outer_parents.emplace(extrusion_line);
}

std::unordered_multimap<const ExtrusionLine*, const ExtrusionLine*> order;
const std::vector<const ExtrusionLine*> outer_walls = extrusion_lines | ranges::views::filter(&ExtrusionLine::is_outer_wall) | ranges::views::addressof | ranges::to_vector;

for (const LineLoc* root : roots)
// find for each line the closest outer line, and store this in closest_outer_wall_line
std::unordered_map<const ExtrusionLine*, const ExtrusionLine*> closest_outer_wall_line;
std::unordered_map<const ExtrusionLine*, unsigned int> min_depth;
for (const ExtrusionLine* outer_wall : outer_walls)
{
std::map<const LineLoc*, unsigned int> min_depth;
std::map<const LineLoc*, const LineLoc*> min_node;
std::vector<const LineLoc*> hole_roots;

// Responsible for the following initialization
// - initialize all reachable nodes to root
// - mark all reachable nodes with their depth from the root
// - find hole roots, these are the innermost polygons enclosing a hole
{
const std::function<void(const LineLoc*, const unsigned int)> initialize_nodes
= [graph, root, &hole_roots, &min_node, &min_depth](const auto current_node, const auto depth)
{
min_node[current_node] = root;
min_depth[current_node] = depth;

// find hole roots (defined by a positive area in clipper1), these are leaves of the tree structure
// as odd walls are also leaves we filter them out by adding a non-zero area check
if (current_node != root && graph.count(current_node) == 1 && current_node->line->is_closed_ && current_node->area > 0)
{
hole_roots.push_back(current_node);
}
};

actions::dfs_depth_state(root, graph, initialize_nodes);
};

// For each hole root perform a dfs, and keep track of depth from hole root
// if the depth to a node is smaller than a depth calculated from another root update
// min_depth and min_node
const std::function<void(const ExtrusionLine*, const unsigned int)> update_nodes
= [&outer_wall, &min_depth, &closest_outer_wall_line](const ExtrusionLine* current_line, const unsigned int depth)
{
for (auto& hole_root : hole_roots)
if (min_depth.find(current_line) == min_depth.end() || depth < min_depth[current_line])
{
const std::function<void(const LineLoc*, const unsigned int)> update_nodes = [hole_root, &min_depth, &min_node](const auto& current_node, auto depth)
{
if (depth < min_depth[current_node])
{
min_depth[current_node] = depth;
min_node[current_node] = hole_root;
}
};

actions::dfs_depth_state(hole_root, graph, update_nodes);
min_depth[current_line] = depth;
closest_outer_wall_line[current_line] = outer_wall;
}
};
actions::dfs_depth_state(outer_wall, graph, update_nodes);
}

// perform a dfs from the root and all hole roots $r$ and set the order constraints for each polyline for which
// the depth is closest to root $r$
// for each of the outer walls, perform a dfs until we have found an extrusion line that is
// _not_ closest to the current outer wall, then stop the dfs traversal for that branch. For
// each extrusion $e$ traversed in the dfs, add an order constraint between to $e$ and the
// previous line in the dfs traversal of $e$.
std::unordered_multimap<const ExtrusionLine*, const ExtrusionLine*> order;
for (const ExtrusionLine* outer_wall : outer_walls)
{
const std::function<void(const ExtrusionLine*, const ExtrusionLine*)> set_order_constraints
= [&order, &closest_outer_wall_line, &outer_wall, &outer_to_inner](const auto& current_line, const auto& parent_line)
{
const LineLoc* root_ = root;
const std::function<void(const LineLoc*, const LineLoc*)> set_order_constraints
= [&order, &min_node, &root_, graph, outer_to_inner](const auto& current_node, const auto& parent_node)
// if the closest
if (closest_outer_wall_line[current_line] == outer_wall && parent_line != nullptr)
{
if (min_node[current_node] == root_ && parent_node != nullptr)
// flip the key values if we want to print from inner to outer walls
if (outer_to_inner)
{
if (outer_to_inner)
{
order.insert(std::make_pair(parent_node->line, current_node->line));
}
else
{
order.insert(std::make_pair(current_node->line, parent_node->line));
}
order.insert(std::make_pair(parent_line, current_line));
}
else
{
order.insert(std::make_pair(current_line, parent_line));
}
};

actions::dfs_parent_state(root, graph, set_order_constraints);

for (auto& hole_root : hole_roots)
{
root_ = hole_root;
actions::dfs_parent_state(hole_root, graph, set_order_constraints);
}
}
};

actions::dfs_parent_state(outer_wall, graph, set_order_constraints);
}

// flip the key values if we want to print from inner to outer walls
return order;
}

Expand Down
Loading