From 8b0455ce52d8846a1e3c4c22132f74966ed20986 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 11 Jan 2022 10:31:07 +0100 Subject: [PATCH 01/71] prevent stitching from reversing wall printing direction --- src/utils/PolylineStitcher.cpp | 21 ++++++++++++++++++++- src/utils/PolylineStitcher.h | 19 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/utils/PolylineStitcher.cpp b/src/utils/PolylineStitcher.cpp index d3e314576b..9b0de48e35 100644 --- a/src/utils/PolylineStitcher.cpp +++ b/src/utils/PolylineStitcher.cpp @@ -3,10 +3,29 @@ #include "PolylineStitcher.h" +#include "ExtrusionLine.h" +#include "polygon.h" + namespace cura { +template<> +bool PolylineStitcher::canReverse(const PathsPointIndex& ppi) +{ + if ((*ppi.polygons)[ppi.poly_idx].is_odd) + { + return true; + } + else + { + return false; + } +} - +template<> +bool PolylineStitcher::canReverse(const PathsPointIndex& ppi) +{ + return true; +} }//namespace cura diff --git a/src/utils/PolylineStitcher.h b/src/utils/PolylineStitcher.h index efee5d30ef..cd369258fc 100644 --- a/src/utils/PolylineStitcher.h +++ b/src/utils/PolylineStitcher.h @@ -78,7 +78,8 @@ class PolylineStitcher coord_t closest_distance = std::numeric_limits::max(); grid.processNearby(from, max_stitch_distance, std::function&)> ( - [from, &chain, &closest, &closest_is_closing_polygon, &closest_distance, &processed, max_stitch_distance, snap_distance](const PathsPointIndex& nearby)->bool + [from, &chain, &closest, &closest_is_closing_polygon, &closest_distance, &processed, go_in_reverse_direction, max_stitch_distance, snap_distance] + (const PathsPointIndex& nearby)->bool { bool is_closing_segment = false; coord_t dist = vSize(nearby.p() - from); @@ -102,6 +103,10 @@ class PolylineStitcher { // it was already moved to output return true; // keep looking for a connection } + if (!canReverse(nearby) && ((nearby.point_idx == 0) == go_in_reverse_direction)) + { // connecting the segment would reverse the polygon direction + return true; // keep looking for a connection + } if (dist < closest_distance) { closest_distance = dist; @@ -147,8 +152,15 @@ class PolylineStitcher assert( ! processed[closest.poly_idx]); processed[closest.poly_idx] = true; } + if (closest_is_closing_polygon) { + if (go_in_reverse_direction) + { // re-reverse chain to retain original direction + // NOTE: not sure if this code could ever be reached, since if a polygon can be closed that should be already possible in the forward direction + chain.reverse(); + } + break; // don't consider reverse direction } } @@ -162,6 +174,11 @@ class PolylineStitcher } } } + + /*! + * Whether a polyline is allowed to be reversed. (Not true for wall polylines which are not odd) + */ + static bool canReverse(const PathsPointIndex& polyline); }; }//namespace cura From e3a8620513f090df507ebaa5f9c360c3a11937da Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sat, 8 Jan 2022 17:36:51 +0100 Subject: [PATCH 02/71] stitch variable width walls --- src/LayerPlan.cpp | 1 + src/WallToolPaths.cpp | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 81f6e242ed..401f59be66 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1091,6 +1091,7 @@ void LayerPlan::addWalls order_optimizer.optimize(); for(const PathOrderOptimizer::Path& path : order_optimizer.paths) { + if (path.vertices->empty()) continue; p_end = path.backwards ? path.vertices->back().p : path.vertices->front().p; const cura::Point p_start = path.backwards ? path.vertices->front().p : path.vertices->back().p; const bool linked_path = p_start != p_end; diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 1d7a7a1369..ca8b39a65a 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -10,6 +10,7 @@ #include "utils/SparsePointGrid.h" //To stitch the inner contour. #include "utils/polygonUtils.h" #include "ExtruderTrain.h" +#include "utils/PolylineStitcher.h" namespace cura { @@ -104,6 +105,37 @@ const VariableWidthPaths& WallToolPaths::generate() wall_maker.generateToolpaths(toolpaths); computeInnerContour(); } + + const size_t inset_count = toolpaths.size(); + for (unsigned int wall_idx = 0; wall_idx < inset_count; wall_idx++) + { + VariableWidthLines& wall_lines = toolpaths[wall_idx]; + + VariableWidthLines stitched_polylines; + VariableWidthLines closed_polygons; + PolylineStitcher::stitch(wall_lines, stitched_polylines, closed_polygons); + wall_lines.clear(); + + if (wall_idx >= wall_lines.size()) + { + wall_lines.resize(wall_idx + 1); + } + wall_lines.insert(wall_lines.end(), stitched_polylines.begin(), stitched_polylines.end()); + + for (ExtrusionLine& wall_polygon : closed_polygons) + { + if (wall_polygon.junctions.empty()) + { + continue; + } + if (wall_polygon.junctions.size() > 1) + { + wall_polygon.junctions.emplace_back(wall_polygon.junctions.front()); // Make polygon back into polyline + } + wall_lines.emplace_back(std::move(wall_polygon)); + } + } + simplifyToolPaths(toolpaths, settings); removeEmptyToolPaths(toolpaths); From 68e63a48fc14d8abcdccb8517a91d40fe8d008db Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sat, 8 Jan 2022 17:56:51 +0100 Subject: [PATCH 03/71] fix inset index and only stitch polygonal wall lines --- src/WallToolPaths.cpp | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index ca8b39a65a..9c2b33a44c 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -110,11 +110,29 @@ const VariableWidthPaths& WallToolPaths::generate() for (unsigned int wall_idx = 0; wall_idx < inset_count; wall_idx++) { VariableWidthLines& wall_lines = toolpaths[wall_idx]; + for (ExtrusionLine& line : wall_lines) + { + assert(line.inset_idx == wall_idx); + } + + VariableWidthLines wall_polygon_lines; + VariableWidthLines odd_gap_filling_wall_lines; + for (ExtrusionLine& line : wall_lines) + { + if (line.is_odd) + { + odd_gap_filling_wall_lines.emplace_back(line); + } + else + { + wall_polygon_lines.emplace_back(line); + } + } VariableWidthLines stitched_polylines; VariableWidthLines closed_polygons; - PolylineStitcher::stitch(wall_lines, stitched_polylines, closed_polygons); - wall_lines.clear(); + PolylineStitcher::stitch(wall_polygon_lines, stitched_polylines, closed_polygons); + wall_lines = odd_gap_filling_wall_lines; if (wall_idx >= wall_lines.size()) { @@ -134,6 +152,11 @@ const VariableWidthPaths& WallToolPaths::generate() } wall_lines.emplace_back(std::move(wall_polygon)); } + + for (ExtrusionLine& line : wall_lines) + { + line.inset_idx = wall_idx; + } } simplifyToolPaths(toolpaths, settings); From decf0bdb319a138f123d5af171b7a48d5bf5265e Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sat, 8 Jan 2022 18:04:07 +0100 Subject: [PATCH 04/71] Move wall stitching to separate function --- src/WallToolPaths.cpp | 41 ++++++++++++++++++++++++++--------------- src/WallToolPaths.h | 6 ++++++ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 9c2b33a44c..85c26882f0 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -106,6 +106,26 @@ const VariableWidthPaths& WallToolPaths::generate() computeInnerContour(); } + stitchToolPaths(toolpaths, settings); + + simplifyToolPaths(toolpaths, settings); + + removeEmptyToolPaths(toolpaths); + assert(std::is_sorted(toolpaths.cbegin(), toolpaths.cend(), + [](const VariableWidthLines& l, const VariableWidthLines& r) + { + return l.front().inset_idx < r.front().inset_idx; + }) && "WallToolPaths should be sorted from the outer 0th to inner_walls"); + toolpaths_generated = true; + return toolpaths; +} + + +void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Settings& settings) +{ + const coord_t stitch_distance = settings.get("wall_line_width_x") / 4; + + // separate polygonal wall lines and gap filling odd lines and stitch the polygonal lines into closed polygons const size_t inset_count = toolpaths.size(); for (unsigned int wall_idx = 0; wall_idx < inset_count; wall_idx++) { @@ -131,8 +151,8 @@ const VariableWidthPaths& WallToolPaths::generate() VariableWidthLines stitched_polylines; VariableWidthLines closed_polygons; - PolylineStitcher::stitch(wall_polygon_lines, stitched_polylines, closed_polygons); - wall_lines = odd_gap_filling_wall_lines; + PolylineStitcher::stitch(wall_polygon_lines, stitched_polylines, closed_polygons, stitch_distance); + wall_lines.clear(); if (wall_idx >= wall_lines.size()) { @@ -148,27 +168,18 @@ const VariableWidthPaths& WallToolPaths::generate() } if (wall_polygon.junctions.size() > 1) { - wall_polygon.junctions.emplace_back(wall_polygon.junctions.front()); // Make polygon back into polyline + wall_polygon.junctions.emplace_back(wall_polygon.junctions.front()); // Make polygon back into polyline. TODO: make it possibleto simplify polygonal toolpaths and don't convert from polygons backto polylines here } wall_lines.emplace_back(std::move(wall_polygon)); } - + for (ExtrusionLine& line : wall_lines) { line.inset_idx = wall_idx; } - } - - simplifyToolPaths(toolpaths, settings); - removeEmptyToolPaths(toolpaths); - assert(std::is_sorted(toolpaths.cbegin(), toolpaths.cend(), - [](const VariableWidthLines& l, const VariableWidthLines& r) - { - return l.front().inset_idx < r.front().inset_idx; - }) && "WallToolPaths should be sorted from the outer 0th to inner_walls"); - toolpaths_generated = true; - return toolpaths; + wall_lines.insert(wall_lines.end(), odd_gap_filling_wall_lines.begin(), odd_gap_filling_wall_lines.end()); + } } void WallToolPaths::simplifyToolPaths(VariableWidthPaths& toolpaths, const Settings& settings) diff --git a/src/WallToolPaths.h b/src/WallToolPaths.h index 2156c02b0e..1f9aa52d7f 100644 --- a/src/WallToolPaths.h +++ b/src/WallToolPaths.h @@ -100,6 +100,12 @@ class WallToolPaths static void stitchContours(const VariableWidthPaths& input, const coord_t stitch_distance, Polygons& output) ; protected: + /*! + * Stitch the polylines together and form closed polygons. + * \param settings The settings as provided by the user + */ + static void stitchToolPaths(VariableWidthPaths& toolpaths, const Settings& settings); + /*! * Simplifies the variable-width toolpaths by calling the simplify on every line in the toolpath using the provided * settings. From a5726b5ac88eaba5fa32dec0e28c6cdfe646c022 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 10 Jan 2022 16:48:51 +0100 Subject: [PATCH 05/71] stitch odd gap filling walls --- src/WallToolPaths.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 85c26882f0..58e2158422 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -152,6 +152,7 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting VariableWidthLines stitched_polylines; VariableWidthLines closed_polygons; PolylineStitcher::stitch(wall_polygon_lines, stitched_polylines, closed_polygons, stitch_distance); + PolylineStitcher::stitch(odd_gap_filling_wall_lines, stitched_polylines, closed_polygons, stitch_distance); wall_lines.clear(); if (wall_idx >= wall_lines.size()) @@ -177,8 +178,6 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting { line.inset_idx = wall_idx; } - - wall_lines.insert(wall_lines.end(), odd_gap_filling_wall_lines.begin(), odd_gap_filling_wall_lines.end()); } } From 7619882324e6dad6325b28802ce6bab8b1b167b2 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 11 Jan 2022 12:07:57 +0100 Subject: [PATCH 06/71] don't stitch odd wall polylines to even wall polygons --- src/WallToolPaths.cpp | 25 ++----------------------- src/utils/PolylineStitcher.cpp | 15 ++++++++++++++- src/utils/PolylineStitcher.h | 10 ++++++++++ 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 58e2158422..47192b4f60 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -135,31 +135,10 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting assert(line.inset_idx == wall_idx); } - VariableWidthLines wall_polygon_lines; - VariableWidthLines odd_gap_filling_wall_lines; - for (ExtrusionLine& line : wall_lines) - { - if (line.is_odd) - { - odd_gap_filling_wall_lines.emplace_back(line); - } - else - { - wall_polygon_lines.emplace_back(line); - } - } - VariableWidthLines stitched_polylines; VariableWidthLines closed_polygons; - PolylineStitcher::stitch(wall_polygon_lines, stitched_polylines, closed_polygons, stitch_distance); - PolylineStitcher::stitch(odd_gap_filling_wall_lines, stitched_polylines, closed_polygons, stitch_distance); - wall_lines.clear(); - - if (wall_idx >= wall_lines.size()) - { - wall_lines.resize(wall_idx + 1); - } - wall_lines.insert(wall_lines.end(), stitched_polylines.begin(), stitched_polylines.end()); + PolylineStitcher::stitch(wall_lines, stitched_polylines, closed_polygons, stitch_distance); + wall_lines = stitched_polylines; for (ExtrusionLine& wall_polygon : closed_polygons) { diff --git a/src/utils/PolylineStitcher.cpp b/src/utils/PolylineStitcher.cpp index 9b0de48e35..be71cee923 100644 --- a/src/utils/PolylineStitcher.cpp +++ b/src/utils/PolylineStitcher.cpp @@ -23,9 +23,22 @@ bool PolylineStitcher::can } template<> -bool PolylineStitcher::canReverse(const PathsPointIndex& ppi) +bool PolylineStitcher::canReverse(const PathsPointIndex&) { return true; } + +template<> +bool PolylineStitcher::canConnect(const ExtrusionLine& a, const ExtrusionLine& b) +{ + return a.is_odd == b.is_odd; +} + +template<> +bool PolylineStitcher::canConnect(const Polygon&, const Polygon&) +{ + return true; +} + }//namespace cura diff --git a/src/utils/PolylineStitcher.h b/src/utils/PolylineStitcher.h index cd369258fc..5ab55e3a65 100644 --- a/src/utils/PolylineStitcher.h +++ b/src/utils/PolylineStitcher.h @@ -107,6 +107,10 @@ class PolylineStitcher { // connecting the segment would reverse the polygon direction return true; // keep looking for a connection } + if (!canConnect(chain, (*nearby.polygons)[nearby.poly_idx])) + { + return true; // keep looking for a connection + } if (dist < closest_distance) { closest_distance = dist; @@ -179,6 +183,12 @@ class PolylineStitcher * Whether a polyline is allowed to be reversed. (Not true for wall polylines which are not odd) */ static bool canReverse(const PathsPointIndex& polyline); + + /*! + * Whether two paths are allowed to be connected. + * (Not true for an odd and an even wall.) + */ + static bool canConnect(const Path& a, const Path& b); }; }//namespace cura From 663f2f1c004bce4dde3cc4e82534fa0b5c795f67 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 19 Jan 2022 17:15:36 +0100 Subject: [PATCH 07/71] Function to get the nesting information from a Polygons object --- src/utils/polygon.cpp | 107 ++++++++++++++++++++++++++++++++++++++++++ src/utils/polygon.h | 19 ++++++++ 2 files changed, 126 insertions(+) diff --git a/src/utils/polygon.cpp b/src/utils/polygon.cpp index 86d72af25b..fa4b35f6e0 100644 --- a/src/utils/polygon.cpp +++ b/src/utils/polygon.cpp @@ -11,6 +11,7 @@ #include "ListPolyIt.h" #include "PolylineStitcher.h" +#include "logoutput.h" namespace cura { @@ -1576,6 +1577,112 @@ void Polygons::splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Poly } +std::vector> Polygons::getNesting() const +{ + ClipperLib::Clipper clipper(clipper_init); + clipper.AddPaths(paths, ClipperLib::ptSubject, true); + ClipperLib::PolyTree resultPolyTree; + clipper.Execute(ClipperLib::ctUnion, resultPolyTree, ClipperLib::pftEvenOdd); + + std::unordered_map node_to_index = getPolyTreeToPolygonsMapping(resultPolyTree); + + std::vector> ret(size()); + + for (auto [node, index] : node_to_index) + { + for (auto child : node->Childs) + { + auto it = node_to_index.find(child); + assert(it != node_to_index.end() && "Each PolyNode should be mapped to the corresponding Polygon index!"); + ret[index].emplace_back(it->second); + } + } + return ret; +} + + +std::unordered_map Polygons::getPolyTreeToPolygonsMapping(const ClipperLib::PolyNode& root) const +{ + std::unordered_map result; + + std::unordered_map start_loc_to_index; + for (size_t idx = 0; idx < paths.size(); idx++) + { + const ClipperLib::Path& path = paths[idx]; + if (path.empty()) continue; + start_loc_to_index.emplace(path.front(), idx); + } + + std::queue queue; + std::vector unprocessed; + for (int i = 0; i < 2; i++) + { + + + queue.emplace(&root); + while ( ! queue.empty()) + { + const ClipperLib::PolyNode* node = queue.front(); + queue.pop(); + for (auto child : node->Childs) + queue.emplace(child); + if (node->Contour.empty()) continue; + + std::unordered_map::iterator it; + for (Point p : node->Contour) + { + it = start_loc_to_index.find(p); + if (it != start_loc_to_index.end()) + { + // If Clipper preserves the order of points then the first iteration should already get here + result.emplace(node, it->second); + break; + } + } + if (it == start_loc_to_index.end()) + { + unprocessed.emplace_back(node); + } + } + + if (unprocessed.empty()) + { + return result; + } + + // some points in the original polygons were removed by clipper, probably because of colinear segments + // retry with mapping all points + start_loc_to_index.clear(); + for (size_t idx = 0; idx < paths.size(); idx++) + { + const ClipperLib::Path& path = paths[idx]; + for (Point p : path) + { + start_loc_to_index.emplace(p, idx); + } + } + for (const ClipperLib::PolyNode* node : unprocessed) + queue.emplace(node); + unprocessed.clear(); + } + + for (auto node : unprocessed) + { + std::cerr << "Couldn't find match for node with locations:\n"; + for (auto p : node->Contour) + std::cerr << Point(p) << ", "; + std::cerr << '\n'; + } + std::cerr << "Among registered locations: \n"; + for (auto [p, i] : start_loc_to_index) + std::cerr << p << " to index " << i << '\n'; + std::cerr <<'\n'; + + assert(false && "The first Point in each polygon should be contained in the clipper output!"); + logError("Polygons::getPolyTreeToPolygonsMapping(.): Extensive polygon matching not implemented.\n"); + return result; +} + void Polygons::ensureManifold() { const Polygons& polys = *this; diff --git a/src/utils/polygon.h b/src/utils/polygon.h index c6afb0feee..83e82357e9 100644 --- a/src/utils/polygon.h +++ b/src/utils/polygon.h @@ -12,6 +12,7 @@ #include "clipper.hpp" #include // std::reverse, fill_n array +#include #include // fabs #include // int64_t.min #include @@ -1197,6 +1198,24 @@ class Polygons private: void splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Polygons& reordered, ClipperLib::PolyNode* node) const; public: + + /*! + * Compute nesting information. + * For each polygon in the input we return a vector of indices to polygons which lie directly inside of it. + * + * \warning The polygons should not be overlapping each other + */ + std::vector> getNesting() const; +private: + /*! + * Get a mapping from the nodes in a PolyTree to their corresponding Polygon indices + * + * \warning The polygons should not be overlapping each other + */ + std::unordered_map getPolyTreeToPolygonsMapping(const ClipperLib::PolyNode& root) const; +public: + + /*! * Removes polygons with area smaller than \p min_area_size (note that min_area_size is in mm^2, not in micron^2). * Unless \p remove_holes is true, holes are not removed even if their area is below \p min_area_size. From e7afd76763afa3ea713f970d1aa59bd1d3b4d806 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 20 Jan 2022 18:19:34 +0100 Subject: [PATCH 08/71] helper functions --- src/utils/ExtrusionLine.cpp | 12 +++++ src/utils/ExtrusionLine.h | 91 +++++++++++++++++++++++++++++++++++++ src/utils/IntPoint.h | 5 ++ 3 files changed, 108 insertions(+) diff --git a/src/utils/ExtrusionLine.cpp b/src/utils/ExtrusionLine.cpp index f471c4eedf..9ca14ecdf5 100644 --- a/src/utils/ExtrusionLine.cpp +++ b/src/utils/ExtrusionLine.cpp @@ -15,6 +15,18 @@ ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd, const si , region_id(region_id) {} +template <> +void ExtrusionLine::erase(std::vector::iterator begin, std::vector::iterator end) +{ + junctions.erase(begin, end); +} + +template <> +void ExtrusionLine::erase(std::vector::reverse_iterator begin, std::vector::reverse_iterator end) +{ + junctions.erase(--end.base(), --begin.base()); +} + coord_t ExtrusionLine::getLength() const { if (junctions.empty()) diff --git a/src/utils/ExtrusionLine.h b/src/utils/ExtrusionLine.h index 486f63410c..e41f184f24 100644 --- a/src/utils/ExtrusionLine.h +++ b/src/utils/ExtrusionLine.h @@ -6,6 +6,7 @@ #define UTILS_EXTRUSION_LINE_H #include "ExtrusionJunction.h" +#include "polygon.h" namespace cura { @@ -51,6 +52,14 @@ struct ExtrusionLine return junctions.size(); } + /*! + * Whether there are no junctions. + */ + bool empty() const + { + return junctions.empty(); + } + /*! * The list of vertices along which this path runs. * @@ -189,6 +198,16 @@ struct ExtrusionLine coord_t getLength() const; coord_t polylineLength() const { return getLength(); } + Polygon toPolygon() const + { + Polygon ret; + + for (const ExtrusionJunction& j : junctions) + ret.add(j.p); + + return ret; + } + /*! * Get the minimal width of this path */ @@ -199,6 +218,78 @@ struct ExtrusionLine */ void appendJunctionsTo(LineJunctions& result) const; + /*! + * Chop off a segment of \p length of either end of this extrusionline + * + * \warning Should only be called on non closed extrusionlines. + * + * \param start_at_front Whether we chop from the beginning or from th eend of this line. + * \return whether the line has collapsed to a single point + */ + bool chopEnd(bool start_at_front, coord_t length) + { + assert(length > 10 && "Too small lengths will never be chopped due to rounding."); + if (start_at_front) + return chopEnd(junctions.begin(), junctions.end(), length); + else + return chopEnd(junctions.rbegin(), junctions.rend(), length); + } +protected: + /*! + * Chop off a segment of \p length of either end of this extrusionline + * + * \warning Should only be called on non closed extrusionlines. + * + * \warning the \p start_pos and \p other_end should refer to iterators in this ExtrusionLine + * + * \param start_pos Iterator to either begin() or rbegin() + * \param other_end Iterator to either end() or rend() + * \return whether the line has collapsed to a single point + */ + template + bool chopEnd(iterator start_pos, iterator other_end, coord_t length) + { + iterator current_it = start_pos; + + coord_t length_removed = 0; + ExtrusionJunction last = *current_it; + for (++current_it; current_it != other_end; ++current_it) + { + ExtrusionJunction here = *current_it; + Point p1 = last.p; + Point p2 = here.p; + Point v12 = p2 - p1; + coord_t dist = vSize(v12); + if (length_removed + dist >= length - 10) + { + if (length_removed + dist <= length) + { + erase(start_pos, current_it); + return junctions.size() <= 1; + } + else + { // Cut My Line Into Pieces + --current_it; + current_it->p = p1 + (length - length_removed) * v12 / dist; + current_it->w = last.w + (length - length_removed) * (here.w - last.w) / dist; + erase(start_pos, current_it); + return false; + } + } + length_removed += dist; + last = here; + } + erase(start_pos, --other_end); + junctions.emplace_back(junctions.front()); + junctions.back().p.X += 10; + return true; + } + + + template + void erase(iterator begin, iterator end); + +public: /*! * Removes vertices of the ExtrusionLines to make sure that they are not too high * resolution. diff --git a/src/utils/IntPoint.h b/src/utils/IntPoint.h index ab66e3a8ce..2a7d0c8188 100644 --- a/src/utils/IntPoint.h +++ b/src/utils/IntPoint.h @@ -95,6 +95,11 @@ INLINE bool shorterThen(const Point& p0, const coord_t len) return vSize2(p0) <= len * len; } +INLINE bool shorterThan(const Point& p0, const coord_t len) +{ + return shorterThen(p0, len); +} + INLINE coord_t vSize(const Point& p0) { return sqrt(vSize2(p0)); From 089f575071a33a5b6b4226193dedcfa223bec3dd Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 20 Jan 2022 18:22:43 +0100 Subject: [PATCH 09/71] WallToolPaths::getWeakOrder to compute the ordering requirements on wall lines when the outline and hole regions interact then the ordering is more strict than need be. This is because adjacency information is either expensive to compute, or requires a lot of score-keeping in arachne --- src/WallToolPaths.cpp | 152 ++++++++++++++++++++++++++++++++++++++++++ src/WallToolPaths.h | 27 +++++++- 2 files changed, 177 insertions(+), 2 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 47192b4f60..ae5a656d04 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -415,4 +415,156 @@ void WallToolPaths::stitchContours(const VariableWidthPaths& input, const coord_ } +std::unordered_set> WallToolPaths::getWeakOrder(const VariableWidthPaths& input, const bool outer_to_inner, const bool include_transitive) +{ + size_t max_inset_idx = 0; + Polygons all_polygons; + std::unordered_map poly_idx_to_extrusionline; + for (const VariableWidthLines& inset : input) + { + for (const ExtrusionLine& line : inset) + { + if (line.empty()) continue; + max_inset_idx = std::max(max_inset_idx, line.inset_idx); + if ( ! shorterThan(line.front().p - line.back().p, coincident_point_distance)) // TODO: check if it is a closed polygon or not + { + // Make a small triangle representative of the polyline + // otherwise the polyline would get erased by the clipping operation + all_polygons.emplace_back(); + assert(line.junctions.size() >= 2); + Point middle = ( line.junctions[line.junctions.size() / 2 - 1].p + line.junctions[line.junctions.size() / 2].p ) / 2; + PolygonRef poly = all_polygons.back(); + poly.emplace_back(middle); + poly.emplace_back(middle + Point(5, 0)); + poly.emplace_back(middle + Point(0, 5)); + } + else + { + all_polygons.emplace_back(line.toPolygon()); + } + poly_idx_to_extrusionline.emplace(all_polygons.size() - 1, &line); + } + } + + std::vector> nesting = all_polygons.getNesting(); + + { + SVG svg("/tmp/nesting.svg", AABB(all_polygons)); + svg.writePolygons(all_polygons, SVG::Color::BLUE); + for (size_t i = 0; i < all_polygons.size(); i++) + { + for (size_t child_idx : nesting[i]) + { + svg.writeArrow(all_polygons[i][0], all_polygons[child_idx][1]); + } + } + } + + std::unordered_set> result; + + getWeakOrder(0, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); + + if (include_transitive) + { + std::unordered_multimap order_mapping; + for (auto [from, to] : result) + { + order_mapping.emplace(from, to); + } + std::unordered_set> transitive_order = result; + for (auto [from, to] : result) + { + std::queue starts_of_next_relation; + starts_of_next_relation.emplace(to); + while ( ! starts_of_next_relation.empty()) + { + const ExtrusionLine* start_of_next_relation = starts_of_next_relation.front(); + starts_of_next_relation.pop(); + auto range = order_mapping.equal_range(start_of_next_relation); + for (auto it = range.first; it != range.second; ++it) + { + auto [ next_from, next_to ] = *it; + starts_of_next_relation.emplace(next_to); + transitive_order.emplace(from, next_to); + } + } + } + result = transitive_order; + } + + return result; +} + +void WallToolPaths::getWeakOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result) +{ + auto parent_it = poly_idx_to_extrusionline.find(node_idx); + assert(parent_it != poly_idx_to_extrusionline.end()); + const ExtrusionLine* parent = parent_it->second; + + assert(node_idx < nesting.size()); + for (size_t child_idx : nesting[node_idx]) + { + auto child_it = poly_idx_to_extrusionline.find(child_idx); + assert(child_it != poly_idx_to_extrusionline.end()); + const ExtrusionLine* child = child_it->second; + + if ( ! child->is_odd && child->inset_idx == parent->inset_idx && child->inset_idx == max_inset_idx) + { + // There is no order requirement between the innermost wall of a hole and the innermost wall of the outline. + } + else if ( ! child->is_odd && child->inset_idx == parent->inset_idx && child->inset_idx <= max_inset_idx) + { + // There are insets with one higher inset index which are adjacent to both this child and the parent. + // And potentially also insets which are adjacent to this child and other children. + // Moreover there are probably gap filler lines in between the child and the parent. + // The nesting information doesn't tell which ones are adjacent, + // so just to be safe we add order requirements between the child and all gap fillers and wall lines. + for (size_t other_child_idx : nesting[node_idx]) + { + auto other_child_it = poly_idx_to_extrusionline.find(other_child_idx); + assert(other_child_it != poly_idx_to_extrusionline.end()); + const ExtrusionLine* other_child = other_child_it->second; + + if (other_child == child) continue; + if (other_child->is_odd) + { + assert(other_child->inset_idx == child->inset_idx + 1); + result.emplace(child, other_child); + } + else + { + if (other_child->inset_idx == child->inset_idx) continue; + assert(other_child->inset_idx == child->inset_idx + 1); + + const ExtrusionLine* before = child; + const ExtrusionLine* after = other_child; + if ( ! outer_to_inner) + { + std::swap(before, after); + } + result.emplace(before, after); + } + } + } + else + { + assert( ! parent->is_odd && "There can be no polygons inside a polyline"); + + const ExtrusionLine* before = parent; + const ExtrusionLine* after = child; + if ( (child->inset_idx < parent->inset_idx) == outer_to_inner + // ^ Order should be reversed for hole polyons + // and it should be reversed again when the global order is the other way around + && ! child->is_odd) // Odd polylines should always go after their enclosing wall polygon + { + std::swap(before, after); + } + result.emplace(before, after); + } + + // Recurvise call + getWeakOrder(child_idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); + } +} + } // namespace cura diff --git a/src/WallToolPaths.h b/src/WallToolPaths.h index 1f9aa52d7f..a7686bc0a8 100644 --- a/src/WallToolPaths.h +++ b/src/WallToolPaths.h @@ -5,6 +5,7 @@ #define CURAENGINE_WALLTOOLPATHS_H #include +#include #include "BeadingStrategy/BeadingStrategyFactory.h" #include "settings/Settings.h" @@ -83,6 +84,30 @@ class WallToolPaths */ static bool removeEmptyToolPaths(VariableWidthPaths& toolpaths); + /*! + * Get the order constraints of the insets assuming the Wall Ordering is outer to inner. + * 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 std::unordered_set> getWeakOrder(const VariableWidthPaths& input, const bool outer_to_inner, const bool include_transitive = true); +protected: + + /*! + * Recursive part of \ref WallToolpPaths::getWeakOrder. + * For each node at \p node_idx we recurse on all its children at nesting[node_idx] + */ + static void getWeakOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result); + + /*! + * Endpoints of polylines that are closer together than this distance + * will be considered to be coincident, + * closing that polyline into a polygon. + */ + constexpr static coord_t coincident_point_distance = 10; + /*! * Stitches toolpaths together to form contours. * @@ -98,8 +123,6 @@ class WallToolPaths * \param output Where to store the output polygons. */ static void stitchContours(const VariableWidthPaths& input, const coord_t stitch_distance, Polygons& output) ; - -protected: /*! * Stitch the polylines together and form closed polygons. * \param settings The settings as provided by the user From 7f549a77498a234dd570f2df38e9fc6f9fe15d34 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 20 Jan 2022 18:23:17 +0100 Subject: [PATCH 10/71] unit test for WallToolPaths::getWeakOrder --- tests/WallsComputationTest.cpp | 107 ++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/tests/WallsComputationTest.cpp b/tests/WallsComputationTest.cpp index dc212829ff..ea84d8a7db 100644 --- a/tests/WallsComputationTest.cpp +++ b/tests/WallsComputationTest.cpp @@ -2,11 +2,21 @@ //CuraEngine is released under the terms of the AGPLv3 or higher. #include +#include #include "../src/settings/Settings.h" //Settings to generate walls with. #include "../src/utils/polygon.h" //To create example polygons. #include "../src/sliceDataStorage.h" //Sl #include "../src/WallsComputation.h" //Unit under test. +#include "../src/WallToolPaths.h" //Unit also under test. + +#define WALLS_COMPUTATION_TEST_SVG_OUTPUT +#ifdef WALLS_COMPUTATION_TEST_SVG_OUTPUT +#include "../src/utils/polygon.h" +#include +#include "../src/utils/SVG.h" +#endif //WALLS_COMPUTATION_TEST_SVG_OUTPUT + namespace cura { @@ -32,6 +42,11 @@ class WallsComputationTest : public testing::Test */ Polygons square_shape; + /*! + * A rectangle enclosing two triangular holes; + */ + Polygons ff_holes; + WallsComputationTest() : walls_computation(settings, LayerIndex(100)) { @@ -41,6 +56,20 @@ class WallsComputationTest : public testing::Test square_shape.back().emplace_back(MM2INT(10), MM2INT(10)); square_shape.back().emplace_back(0, MM2INT(10)); + ff_holes.emplace_back(); + ff_holes.back().emplace_back(0, 0); + ff_holes.back().emplace_back(10000, 0); + ff_holes.back().emplace_back(10000, 5000); + ff_holes.back().emplace_back(0, 5000); + ff_holes.emplace_back(); + ff_holes.back().emplace_back(1000, 1000); + ff_holes.back().emplace_back(1000, 4000); + ff_holes.back().emplace_back(4000, 2500); + ff_holes.emplace_back(); + ff_holes.back().emplace_back(6000, 1000); + ff_holes.back().emplace_back(6000, 4000); + ff_holes.back().emplace_back(9000, 2500); + //Settings for a simple 2 walls, about as basic as possible. settings.add("alternate_extra_perimeter", "false"); settings.add("beading_strategy_type", "inward_distributed"); @@ -57,7 +86,7 @@ class WallsComputationTest : public testing::Test settings.add("wall_line_count", "2"); settings.add("wall_line_width_0", "0.4"); settings.add("wall_line_width_x", "0.4"); - settings.add("wall_transition_angle", "30"); + settings.add("wall_transition_angle", "10"); settings.add("wall_transition_filter_distance", "1"); settings.add("wall_transition_length", "1"); settings.add("wall_split_middle_threshold", "50"); @@ -110,4 +139,80 @@ TEST_F(WallsComputationTest, GenerateWallsZeroWalls) EXPECT_EQ(layer.parts.size(), 1) << "There is still just 1 part."; } +/*! + * Tests if the inner area is properly set. + */ +TEST_F(WallsComputationTest, WallToolPathsGetWeakOrder) +{ + settings.add("wall_line_count", "5"); + SliceLayer layer; + layer.parts.emplace_back(); + SliceLayerPart& part = layer.parts.back(); + part.outline.add(ff_holes); + + //Run the test. + walls_computation.generateWalls(&layer); + + const bool outer_to_inner = false; + const bool include_transitive = false; + std::unordered_set> order = WallToolPaths::getWeakOrder(part.wall_toolpaths, outer_to_inner, include_transitive); + + //Verify that something was generated. + EXPECT_FALSE(part.wall_toolpaths.empty()) << "There must be some walls."; + EXPECT_GT(part.print_outline.area(), 0) << "The print outline must encompass the outer wall, so it must be more than 0."; + EXPECT_LE(part.print_outline.area(), ff_holes.area()) << "The print outline must stay within the bounds of the original part."; + EXPECT_GE(part.inner_area.area(), 0) << "The inner area can never have negative area."; + EXPECT_EQ(layer.parts.size(), 1) << "There is still just 1 part."; + +#ifdef WALLS_COMPUTATION_TEST_SVG_OUTPUT + { + SVG svg("/tmp/wall_order.svg", AABB(part.outline)); + for (const VariableWidthLines& inset : part.wall_toolpaths) + { + for (const ExtrusionLine& line : inset) + { + if (line.is_odd) + { + svg.writePolyline(line.toPolygon(), SVG::Color::YELLOW); + svg.writePoints(line.toPolygon(), true); + } + else + svg.writePolygon(line.toPolygon(), SVG::Color::GREEN); + } + } + svg.writePolygons(part.outline, SVG::Color::RED); + svg.writePolygons(part.inner_area, SVG::Color::YELLOW); + svg.nextLayer(); + for (auto [first, second] : order) + { + if ( ! second->is_odd) + svg.writeArrow(first->front().p, (++second->begin())->p, SVG::Color::BLUE); + } + svg.nextLayer(); + for (auto [first, second] : order) + { + if (second->is_odd) + svg.writeArrow(first->front().p, (++second->begin())->p, SVG::Color::MAGENTA); + } + } +#endif // WALLS_COMPUTATION_TEST_SVG_OUTPUT + + size_t n_paths = 0; + for (auto& lines : part.wall_toolpaths) + for (auto& line : lines) + if ( ! line.empty()) + n_paths ++; + + EXPECT_GT(order.size(), 0) << "There should be ordered pairs!"; + std::unordered_set has_order_info(part.wall_toolpaths.size()); + for (auto [from, to] : order) + { + EXPECT_FALSE(from->is_odd) << "Odd gap filler lines are never required to go before anything."; + has_order_info.emplace(from); + has_order_info.emplace(to); + } + EXPECT_EQ(has_order_info.size(), n_paths) << "Every path should have order information."; + +} + } From 9ba6211324ab4472a702b3a7af31ea95fe3f6875 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 20 Jan 2022 19:05:59 +0100 Subject: [PATCH 11/71] make wall order better There used to be unnecessary constraints between wall polygons when the outlines and holes got close to each other. The number of order constraints has now been reduced to be closer to the real ones. --- src/WallToolPaths.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index ae5a656d04..5515c8f911 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -526,6 +526,38 @@ void WallToolPaths::getWeakOrder(size_t node_idx, const std::unordered_mapsecond; if (other_child == child) continue; + + + // See if there's an overlap in region_id. + // If not then they are not adjacent, so we don't include order requirement + bool overlap = false; + { + std::unordered_set other_child_region_ids; + for (const ExtrusionJunction& j : other_child->junctions) + { + other_child_region_ids.emplace(j.region_id); + } + for (const ExtrusionJunction& j : child->junctions) + { + if (other_child_region_ids.count(j.region_id)) + { + overlap = true; + break; + } + } + if (other_child->is_odd) + { // Odd gap fillers should have two region_ids, but they don't, so let's be more conservative on them + for (const ExtrusionJunction& j : parent->junctions) + { + if (other_child_region_ids.count(j.region_id)) + { // if an odd gap filler has the region_id set to the outline then it could also be adjacent to child, but not registered as such. + overlap = true; + break; + } + } + } + } + if ( ! overlap) continue; if (other_child->is_odd) { assert(other_child->inset_idx == child->inset_idx + 1); From c27ee8abf3a21baaac75d1c6012e1da2b9de30c5 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Fri, 21 Jan 2022 11:15:05 +0100 Subject: [PATCH 12/71] option to use insertion method to path optimization --- src/PathOrderOptimizer.h | 178 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 3 deletions(-) diff --git a/src/PathOrderOptimizer.h b/src/PathOrderOptimizer.h index e18e01ec6f..05cad3e298 100644 --- a/src/PathOrderOptimizer.h +++ b/src/PathOrderOptimizer.h @@ -94,6 +94,27 @@ class PathOrderOptimizer */ size_t start_vertex; + Point getStartLocation() const + { + return (*converted)[start_vertex]; + } + + Point getEndLocation() const + { + if (is_closed) + { + return getStartLocation(); + } + if (start_vertex == 0) + { + return converted->back(); + } + else + { + return converted->front(); + } + } + /*! * Whether the path should be closed at the ends or not. * @@ -146,12 +167,13 @@ class PathOrderOptimizer * it into a polygon. * \param combing_boundary Boundary to avoid when making travel moves. */ - PathOrderOptimizer(const Point start_point, const ZSeamConfig seam_config = ZSeamConfig(), const bool detect_loops = false, const Polygons* combing_boundary = nullptr, const bool reverse_direction = false) + PathOrderOptimizer(const Point start_point, const ZSeamConfig seam_config = ZSeamConfig(), const bool detect_loops = false, const Polygons* combing_boundary = nullptr, const bool reverse_direction = false, bool selection_optimization = true) : start_point(start_point) , seam_config(seam_config) , combing_boundary((combing_boundary != nullptr && !combing_boundary->empty()) ? combing_boundary : nullptr) , detect_loops(detect_loops) , reverse_direction(reverse_direction) + , selection_optimization(selection_optimization) { } @@ -207,7 +229,20 @@ class PathOrderOptimizer } } } - + + if (selection_optimization) + { + optimizeSelection(); + } + else + { + optimizeInsertion(); + } + + combing_grid.reset(); + } + void optimizeSelection() + { //Add all vertices to a bucket grid so that we can find nearby endpoints quickly. const coord_t snap_radius = 10_mu; // 0.01mm grid cells. Chaining only needs to consider polylines which are next to each other. SparsePointGridInclusive line_bucket_grid(snap_radius); @@ -357,7 +392,132 @@ class PathOrderOptimizer { std::swap(optimized_order, paths); } - combing_grid.reset(); + } + + void optimizeInsertion() + { + if (seam_config.type == EZSeamType::USER_SPECIFIED) + { + start_point = seam_config.pos; // WARNING: is this correct?! + } + + for (Path& path : paths) + { + if (!path.is_closed) + { + continue; //Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. + } + if (path.converted->empty()) + { + continue; + } + path.start_vertex = findStartLocation(path, seam_config.pos); + } + + std::list> optimized_order; // Distance to and next location + + std::function getDistance = + combing_boundary ? + std::function( [this](Point from, Point to) { return getCombingDistance(from, to); } ) + : std::function( [this](Point from, Point to) { return getDirectDistance(from, to); } ); + + Path& first_path = paths.front(); // arbitrarily select the first path to add + coord_t distance = std::sqrt(getDistance(start_point, first_path.getStartLocation())); + optimized_order.emplace_back(distance, first_path); + + for (size_t to_be_inserted_idx = 1; to_be_inserted_idx < paths.size(); to_be_inserted_idx++) + { + typename std::list>::iterator best_pos_it = optimized_order.end(); + coord_t best_detour_distance = std::numeric_limits::max(); + coord_t best_dist_before_to_here = 0; + coord_t best_dist_here_to_after = 0; + bool best_is_flipped = false; + + Path& to_be_inserted = paths[to_be_inserted_idx]; + Point start_here = to_be_inserted.getStartLocation(); + Point end_here = to_be_inserted.getEndLocation(); + + // TODO: use grid + + for (auto pos_after_it = optimized_order.end(); ; --pos_after_it) // update is performed at the end of the for loop + { + auto pos_before_it = pos_after_it; + pos_before_it--; + Path* path_before = (pos_after_it == optimized_order.begin()) ? nullptr : &pos_before_it->second; + Path* path_after = (pos_after_it == optimized_order.end()) ? nullptr : &pos_after_it->second; + Point loc_before = path_before? path_before->getEndLocation() : start_point; + coord_t current_distance = path_after? pos_after_it->first : 0; + + coord_t dist_before = std::sqrt(getDistance(loc_before, start_here)); + coord_t dist_after = path_after ? std::sqrt(getDistance(end_here, path_after->getStartLocation())) : 0; + + if ( ! to_be_inserted.is_closed) + { + coord_t flipped_dist_before = std::sqrt(getDistance(loc_before, end_here)); + coord_t flipped_dist_after = path_after ? std::sqrt(getDistance(start_here, path_after->getStartLocation())) : 0; + best_is_flipped = false; + if (flipped_dist_before + flipped_dist_after < dist_before + dist_after) + { + dist_before = flipped_dist_before; + dist_after = flipped_dist_after; + best_is_flipped = true; + } + } + + coord_t detour_distance = dist_before + dist_after - current_distance; + if (detour_distance < best_detour_distance) // Less of a detour than the best candidate so far. + { + best_pos_it = pos_after_it; + best_detour_distance = detour_distance; + best_dist_before_to_here = dist_before; + best_dist_here_to_after = dist_after; + } + + if (pos_after_it == optimized_order.begin()) + { + break; + } + } + + if (best_is_flipped) + { + assert( ! to_be_inserted.is_closed); + to_be_inserted.start_vertex = (to_be_inserted.start_vertex != 0) * (to_be_inserted.converted->size() - 1); + } + if (best_pos_it != optimized_order.end()) + { + best_pos_it->first = best_dist_here_to_after; + } + optimized_order.insert(best_pos_it, std::make_pair(best_dist_before_to_here, to_be_inserted)); + + } + + if (seam_config.type == EZSeamType::SHORTEST) + { // only recompute the start when needed + Point current_location = start_point; + for (auto& [dist, path] : optimized_order) + { + path.start_vertex = findStartLocation(path, current_location); + current_location = path.getStartLocation(); + } + } + + //Apply the optimized order to the output field. Reverse if ordered to reverse. + paths.clear(); + if (reverse_direction) + { + for (auto it = optimized_order.rbegin(); it != optimized_order.rend(); ++it) + { + paths.emplace_back(it->second); + } + } + else + { + for (auto& [fist, path] : optimized_order) + { + paths.emplace_back(path); + } + } } protected: @@ -411,6 +571,18 @@ class PathOrderOptimizer */ bool reverse_direction; + /*! + * The core algorithm to use: + * - Selection sort based + * - Insertion sort based + * + * Selection does a greedy approach of finding the next best candidate to append to the result. + * Insertion considers the best location within the result to insert each path. + * + * Insertion sort can be wildly inefficient when polylines haven't been stitched. + */ + bool selection_optimization; + /*! * Find the vertex which will be the starting point of printing a polygon or * polyline. From bd064704336c9bbfae9f3e6a953745204d6944b0 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sat, 22 Jan 2022 13:39:25 +0100 Subject: [PATCH 13/71] Fix ordering requirements in complex cases with outer thin walls --- src/WallToolPaths.cpp | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 5515c8f911..22f6a3030f 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -462,7 +462,13 @@ std::unordered_set> WallTo std::unordered_set> result; - getWeakOrder(0, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); + for (auto [idx, line] : poly_idx_to_extrusionline) + { // there might be multiple roots + if (line->inset_idx == 0) + { + getWeakOrder(idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); + } + } if (include_transitive) { @@ -513,7 +519,7 @@ void WallToolPaths::getWeakOrder(size_t node_idx, const std::unordered_mapis_odd && child->inset_idx == parent->inset_idx && child->inset_idx <= max_inset_idx) - { + { // unusual case // There are insets with one higher inset index which are adjacent to both this child and the parent. // And potentially also insets which are adjacent to this child and other children. // Moreover there are probably gap filler lines in between the child and the parent. @@ -560,11 +566,21 @@ void WallToolPaths::getWeakOrder(size_t node_idx, const std::unordered_mapis_odd) { - assert(other_child->inset_idx == child->inset_idx + 1); - result.emplace(child, other_child); + if (other_child->inset_idx == child->inset_idx + 1) + { // normal gap filler + result.emplace(child, other_child); + } + else + { // outer thin wall 'gap filler' enclosed in an internal hole. + // E.g. in the middle of a thin '8' shape when the middle looks like '>-<=>-<' + assert(parent->inset_idx == 0); // enclosing 8 shape + assert(child->inset_idx == 0); // thick section of the middle + assert(other_child->inset_idx == 0); // thin section of the middle + // no order requirement between thin wall, because it has no eclosing wall + } } else - { + { // other child is an even wall as well if (other_child->inset_idx == child->inset_idx) continue; assert(other_child->inset_idx == child->inset_idx + 1); @@ -579,7 +595,7 @@ void WallToolPaths::getWeakOrder(size_t node_idx, const std::unordered_mapis_odd && "There can be no polygons inside a polyline"); const ExtrusionLine* before = parent; From 203f3ff0e178a88f24a7cf489866ff6d2c8e47f2 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sat, 22 Jan 2022 13:43:02 +0100 Subject: [PATCH 14/71] fix insertion optimization and allow for weak order constraints --- src/PathOrderOptimizer.cpp | 9 ++++ src/PathOrderOptimizer.h | 91 +++++++++++++++++++++++++++++--------- 2 files changed, 78 insertions(+), 22 deletions(-) diff --git a/src/PathOrderOptimizer.cpp b/src/PathOrderOptimizer.cpp index 5e0cc1bfe2..8087babc60 100644 --- a/src/PathOrderOptimizer.cpp +++ b/src/PathOrderOptimizer.cpp @@ -52,4 +52,13 @@ namespace cura return ConstPolygonRef(poly); } + template<> + ConstPolygonRef PathOrderOptimizer::getVertexData(const ExtrusionLine* path) + { + cached_vertices.emplace_back(); + Polygon& poly = cached_vertices.back(); + poly = path->toPolygon(); + return ConstPolygonRef(poly); + } + } diff --git a/src/PathOrderOptimizer.h b/src/PathOrderOptimizer.h index 05cad3e298..bb1ad4c408 100644 --- a/src/PathOrderOptimizer.h +++ b/src/PathOrderOptimizer.h @@ -167,13 +167,14 @@ class PathOrderOptimizer * it into a polygon. * \param combing_boundary Boundary to avoid when making travel moves. */ - PathOrderOptimizer(const Point start_point, const ZSeamConfig seam_config = ZSeamConfig(), const bool detect_loops = false, const Polygons* combing_boundary = nullptr, const bool reverse_direction = false, bool selection_optimization = true) + PathOrderOptimizer(const Point start_point, const ZSeamConfig seam_config = ZSeamConfig(), const bool detect_loops = false, const Polygons* combing_boundary = nullptr, const bool reverse_direction = false, bool selection_optimization = true, const std::function& canPrecede = canAlwaysPrecede) : start_point(start_point) , seam_config(seam_config) , combing_boundary((combing_boundary != nullptr && !combing_boundary->empty()) ? combing_boundary : nullptr) , detect_loops(detect_loops) , reverse_direction(reverse_direction) , selection_optimization(selection_optimization) + , canPrecede(canPrecede) { } @@ -424,7 +425,7 @@ class PathOrderOptimizer Path& first_path = paths.front(); // arbitrarily select the first path to add coord_t distance = std::sqrt(getDistance(start_point, first_path.getStartLocation())); optimized_order.emplace_back(distance, first_path); - + for (size_t to_be_inserted_idx = 1; to_be_inserted_idx < paths.size(); to_be_inserted_idx++) { typename std::list>::iterator best_pos_it = optimized_order.end(); @@ -439,42 +440,71 @@ class PathOrderOptimizer // TODO: use grid + auto upper_bound = optimized_order.end(); for (auto pos_after_it = optimized_order.end(); ; --pos_after_it) // update is performed at the end of the for loop { auto pos_before_it = pos_after_it; pos_before_it--; Path* path_before = (pos_after_it == optimized_order.begin()) ? nullptr : &pos_before_it->second; Path* path_after = (pos_after_it == optimized_order.end()) ? nullptr : &pos_after_it->second; - Point loc_before = path_before? path_before->getEndLocation() : start_point; - coord_t current_distance = path_after? pos_after_it->first : 0; - coord_t dist_before = std::sqrt(getDistance(loc_before, start_here)); - coord_t dist_after = path_after ? std::sqrt(getDistance(end_here, path_after->getStartLocation())) : 0; - - if ( ! to_be_inserted.is_closed) + if (path_before && ! canPrecede(*path_before, to_be_inserted)) + { + upper_bound = pos_before_it; + } + + if (pos_after_it == optimized_order.begin()) + { + break; + } + } + + for (auto pos_after_it = upper_bound; ; --pos_after_it) // update is performed at the end of the for loop + { + auto pos_before_it = pos_after_it; + pos_before_it--; + Path* path_before = (pos_after_it == optimized_order.begin()) ? nullptr : &pos_before_it->second; + Path* path_after = (pos_after_it == optimized_order.end()) ? nullptr : &pos_after_it->second; + + if ( ! path_before || canPrecede(*path_before, to_be_inserted)) { - coord_t flipped_dist_before = std::sqrt(getDistance(loc_before, end_here)); - coord_t flipped_dist_after = path_after ? std::sqrt(getDistance(start_here, path_after->getStartLocation())) : 0; - best_is_flipped = false; - if (flipped_dist_before + flipped_dist_after < dist_before + dist_after) + + Point loc_before = path_before? path_before->getEndLocation() : start_point; + coord_t dist_before = std::sqrt(getDistance(loc_before, start_here)); + coord_t dist_after = path_after ? std::sqrt(getDistance(end_here, path_after->getStartLocation())) : 0; + + if ( ! to_be_inserted.is_closed) + { + coord_t flipped_dist_before = std::sqrt(getDistance(loc_before, end_here)); + coord_t flipped_dist_after = path_after ? std::sqrt(getDistance(start_here, path_after->getStartLocation())) : 0; + best_is_flipped = false; + if (flipped_dist_before + flipped_dist_after < dist_before + dist_after) + { + dist_before = flipped_dist_before; + dist_after = flipped_dist_after; + best_is_flipped = true; + } + } + + coord_t current_distance = path_after? pos_after_it->first : 0; + coord_t detour_distance = dist_before + dist_after - current_distance; + if (detour_distance < best_detour_distance) // Less of a detour than the best candidate so far. { - dist_before = flipped_dist_before; - dist_after = flipped_dist_after; - best_is_flipped = true; + best_pos_it = pos_after_it; + best_detour_distance = detour_distance; + best_dist_before_to_here = dist_before; + best_dist_here_to_after = dist_after; } } - coord_t detour_distance = dist_before + dist_after - current_distance; - if (detour_distance < best_detour_distance) // Less of a detour than the best candidate so far. + if (path_before && ! canPrecede(to_be_inserted, *path_before)) { - best_pos_it = pos_after_it; - best_detour_distance = detour_distance; - best_dist_before_to_here = dist_before; - best_dist_here_to_after = dist_after; + assert(best_detour_distance != std::numeric_limits::max()); + break; } - if (pos_after_it == optimized_order.begin()) { + assert(best_detour_distance != std::numeric_limits::max()); break; } } @@ -490,8 +520,17 @@ class PathOrderOptimizer } optimized_order.insert(best_pos_it, std::make_pair(best_dist_before_to_here, to_be_inserted)); +#ifdef DEBUG + for (auto it = optimized_order.begin(); it != optimized_order.end(); ++it) + { + auto second_it = it; + for (second_it++; second_it != optimized_order.end(); ++second_it) + assert(canPrecede(it->second, second_it->second)); + } +#endif // DEBUG } + if (seam_config.type == EZSeamType::SHORTEST) { // only recompute the start when needed Point current_location = start_point; @@ -582,6 +621,10 @@ class PathOrderOptimizer * Insertion sort can be wildly inefficient when polylines haven't been stitched. */ bool selection_optimization; + + static std::function canAlwaysPrecede; + + std::function canPrecede; /*! * Find the vertex which will be the starting point of printing a polygon or @@ -834,6 +877,10 @@ class PathOrderOptimizer ConstPolygonRef getVertexData(const PathType path); }; +template +std::function::Path&, const typename PathOrderOptimizer::Path&)> PathOrderOptimizer::canAlwaysPrecede = + std::function( [](const Path&, const Path&) { return true; } ); + } //namespace cura #endif //PATHORDEROPTIMIZER_H From 987f82672503ad170f52ff68dffa2d311d2398ae Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sat, 22 Jan 2022 14:42:03 +0100 Subject: [PATCH 15/71] let getWeakOrder work on a flat vector of lines --- src/WallToolPaths.cpp | 42 ++++++++++++++++------------------ src/WallToolPaths.h | 2 +- tests/WallsComputationTest.cpp | 6 ++++- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 22f6a3030f..e3ca625708 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -415,35 +415,33 @@ void WallToolPaths::stitchContours(const VariableWidthPaths& input, const coord_ } -std::unordered_set> WallToolPaths::getWeakOrder(const VariableWidthPaths& input, const bool outer_to_inner, const bool include_transitive) +std::unordered_set> WallToolPaths::getWeakOrder(const std::vector& input, const bool outer_to_inner, const bool include_transitive) { size_t max_inset_idx = 0; Polygons all_polygons; std::unordered_map poly_idx_to_extrusionline; - for (const VariableWidthLines& inset : input) + for (const ExtrusionLine* line_p : input) { - for (const ExtrusionLine& line : inset) + const ExtrusionLine& line = *line_p; + if (line.empty()) continue; + max_inset_idx = std::max(max_inset_idx, line.inset_idx); + if ( ! shorterThan(line.front().p - line.back().p, coincident_point_distance)) // TODO: check if it is a closed polygon or not { - if (line.empty()) continue; - max_inset_idx = std::max(max_inset_idx, line.inset_idx); - if ( ! shorterThan(line.front().p - line.back().p, coincident_point_distance)) // TODO: check if it is a closed polygon or not - { - // Make a small triangle representative of the polyline - // otherwise the polyline would get erased by the clipping operation - all_polygons.emplace_back(); - assert(line.junctions.size() >= 2); - Point middle = ( line.junctions[line.junctions.size() / 2 - 1].p + line.junctions[line.junctions.size() / 2].p ) / 2; - PolygonRef poly = all_polygons.back(); - poly.emplace_back(middle); - poly.emplace_back(middle + Point(5, 0)); - poly.emplace_back(middle + Point(0, 5)); - } - else - { - all_polygons.emplace_back(line.toPolygon()); - } - poly_idx_to_extrusionline.emplace(all_polygons.size() - 1, &line); + // Make a small triangle representative of the polyline + // otherwise the polyline would get erased by the clipping operation + all_polygons.emplace_back(); + assert(line.junctions.size() >= 2); + Point middle = ( line.junctions[line.junctions.size() / 2 - 1].p + line.junctions[line.junctions.size() / 2].p ) / 2; + PolygonRef poly = all_polygons.back(); + poly.emplace_back(middle); + poly.emplace_back(middle + Point(5, 0)); + poly.emplace_back(middle + Point(0, 5)); + } + else + { + all_polygons.emplace_back(line.toPolygon()); } + poly_idx_to_extrusionline.emplace(all_polygons.size() - 1, &line); } std::vector> nesting = all_polygons.getNesting(); diff --git a/src/WallToolPaths.h b/src/WallToolPaths.h index a7686bc0a8..c2031adcfd 100644 --- a/src/WallToolPaths.h +++ b/src/WallToolPaths.h @@ -92,7 +92,7 @@ class WallToolPaths * * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. */ - static std::unordered_set> getWeakOrder(const VariableWidthPaths& input, const bool outer_to_inner, const bool include_transitive = true); + static std::unordered_set> getWeakOrder(const std::vector& input, const bool outer_to_inner, const bool include_transitive = true); protected: /*! diff --git a/tests/WallsComputationTest.cpp b/tests/WallsComputationTest.cpp index ea84d8a7db..5be2736d70 100644 --- a/tests/WallsComputationTest.cpp +++ b/tests/WallsComputationTest.cpp @@ -155,7 +155,11 @@ TEST_F(WallsComputationTest, WallToolPathsGetWeakOrder) const bool outer_to_inner = false; const bool include_transitive = false; - std::unordered_set> order = WallToolPaths::getWeakOrder(part.wall_toolpaths, outer_to_inner, include_transitive); + std::vector all_paths; + for (auto& inset : part.wall_toolpaths) + for (auto& line : inset) + all_paths.emplace_back(&line); + std::unordered_set> order = WallToolPaths::getWeakOrder(all_paths, outer_to_inner, include_transitive); //Verify that something was generated. EXPECT_FALSE(part.wall_toolpaths.empty()) << "There must be some walls."; From 732bd94670259200004e95480b56c70b049b5538 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sat, 22 Jan 2022 14:43:49 +0100 Subject: [PATCH 16/71] Optimize wall order using insertion ordering with weak adjacency ordering constraint --- src/InsetOrderOptimizer.cpp | 133 +++++++++++++++++++++++++++++------- src/LayerPlan.cpp | 41 +---------- src/LayerPlan.h | 16 +---- 3 files changed, 114 insertions(+), 76 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 99cb3e74b1..c23486118c 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -45,10 +45,9 @@ bool InsetOrderOptimizer::optimize(const WallType& wall_type) const InsetDirection inset_direction = mesh.settings.get("inset_direction"); const bool center_last = inset_direction == InsetDirection::CENTER_LAST; - //Bin the insets in order to print the inset indices together, and to optimize the order of each bin to reduce travels. - std::set bins_with_index_zero_insets; - BinJunctions insets = variableWidthPathToBinJunctions(paths, pack_by_inset, center_last, &bins_with_index_zero_insets); - + + const bool outer_to_inner = inset_direction == InsetDirection::OUTSIDE_IN; + size_t start_inset; size_t end_inset; int direction; @@ -62,12 +61,12 @@ bool InsetOrderOptimizer::optimize(const WallType& wall_type) if(inset_direction == InsetDirection::OUTSIDE_IN) { start_inset = 0; - end_inset = insets.size(); + end_inset = paths.size(); direction = 1; } else //INSIDE_OUT or CENTER_LAST. { - start_inset = insets.size() - 1; + start_inset = paths.size() - 1; end_inset = -1; direction = -1; } @@ -85,7 +84,7 @@ bool InsetOrderOptimizer::optimize(const WallType& wall_type) } else if(extruder_nr == wall_x_extruder_nr) { - start_inset = insets.size() - 1; + start_inset = paths.size() - 1; end_inset = 0; // Ignore outer wall direction = -1; } @@ -99,31 +98,117 @@ bool InsetOrderOptimizer::optimize(const WallType& wall_type) return added_something; } - + + std::vector walls_to_be_added; //Add all of the insets one by one. - constexpr Ratio flow = 1.0_r; - const bool retract_before_outer_wall = mesh.settings.get("travel_retract_before_outer_wall"); - const coord_t wall_0_wipe_dist = mesh.settings.get("wall_0_wipe_dist"); - for(size_t inset = start_inset; inset != end_inset; inset += direction) + for(size_t inset_idx = start_inset; inset_idx != end_inset; inset_idx += direction) { - if(insets[inset].empty()) + if (paths[inset_idx].empty()) { continue; //Don't switch extruders either, etc. } - added_something = true; - gcode_writer.setExtruder_addPrime(storage, gcode_layer, extruder_nr); - gcode_layer.setIsInside(true); //Going to print walls, which are always inside. - ZSeamConfig z_seam_config(mesh.settings.get("z_seam_type"), mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2); - - const bool alternate_direction_modifier = inset % 2 == 0; - if(bins_with_index_zero_insets.count(inset) > 0) //Print using outer wall config. + const VariableWidthLines& inset = paths[inset_idx]; + for (const ExtrusionLine& wall : inset) { - gcode_layer.addWalls(insets[inset], mesh.settings, inset_0_non_bridge_config, inset_0_bridge_config, z_seam_config, wall_0_wipe_dist, flow, retract_before_outer_wall, alternate_direction_modifier); + walls_to_be_added.emplace_back(&wall); } - else + } + + + constexpr bool include_transitive = true; + std::unordered_set> order = WallToolPaths::getWeakOrder(walls_to_be_added, outer_to_inner, include_transitive); + + if (center_last) + { + for (const ExtrusionLine* line : walls_to_be_added) + if (line->is_odd) + for (const ExtrusionLine* other_line : walls_to_be_added) + if ( ! other_line->is_odd) + order.emplace(std::make_pair(other_line, line)); + } + + using Optimizer = PathOrderOptimizer; + std::function canPrecede = + [&order](const Optimizer::Path& before, const Optimizer::Path& after) { - gcode_layer.addWalls(insets[inset], mesh.settings, inset_X_non_bridge_config, inset_X_bridge_config, z_seam_config, 0, flow, false, alternate_direction_modifier); - } + // [before] cannot precede [after] if we have an order constraint that [after] must be before [before] + return ! order.count(std::make_pair(after.vertices, before.vertices)); + }; +#ifdef DEBUG + { + AABB aabb; + for (auto& inset : paths) + for (auto& line : inset) + for (auto p : line) + aabb.include(p.p); + SVG svg("/tmp/order.svg", aabb); + for (auto& inset : paths) + for (auto& line : inset) + svg.writePolyline(line.toPolygon(), line.is_odd? SVG::Color::RED : SVG::Color::GREEN); + svg.nextLayer(); + for (auto& inset : paths) + for (auto& line : inset) + svg.writePoints(line.toPolygon(), true, 1.0); + svg.nextLayer(); + for (auto [before, after] : order) + if ( ! after->is_odd) + svg.writeArrow(before->junctions[1 % before->junctions.size()].p, after->junctions[2 % after->junctions.size()].p, SVG::Color::BLUE); + svg.nextLayer(); + for (auto [before, after] : order) + if (after->is_odd) + svg.writeArrow(before->junctions[1 % before->junctions.size()].p, after->junctions[2 % after->junctions.size()].p, SVG::Color::MAGENTA); + } +#endif // DEBUG + + constexpr Ratio flow = 1.0_r; + + bool added_something = false; + + constexpr bool detect_loops = true; + constexpr Polygons* combing_boundary = nullptr; + //When we alternate walls, also alternate the direction at which the first wall starts in. + //On even layers we start with normal direction, on odd layers with inverted direction. + constexpr bool reverse_all_paths = false; + constexpr bool selection_optimization = false; + PathOrderOptimizer order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition(), z_seam_config, detect_loops, combing_boundary, reverse_all_paths, selection_optimization, canPrecede); + + for (const ExtrusionLine* line : walls_to_be_added) + { + order_optimizer.addPolyline(line); + } + + + order_optimizer.optimize(); + + const bool retract_before_outer_wall = mesh.settings.get("travel_retract_before_outer_wall"); + const coord_t wall_0_wipe_dist = mesh.settings.get("wall_0_wipe_dist"); + const bool alternate_walls = mesh.settings.get("material_alternate_walls"); + + cura::Point p_end {0, 0}; + for(const PathOrderOptimizer::Path& path : order_optimizer.paths) + { + if (path.vertices->empty()) continue; + + const bool is_outer_wall = path.vertices->inset_idx == 0; // or thin wall 'gap filler' + const bool is_gap_filler = path.vertices->is_odd; + const GCodePathConfig& non_bridge_config = is_outer_wall ? inset_0_non_bridge_config : inset_X_non_bridge_config; + const GCodePathConfig& bridge_config = is_outer_wall? inset_0_bridge_config : inset_X_bridge_config; + const coord_t wipe_dist = is_outer_wall && ! is_gap_filler ? wall_0_wipe_dist : 0; + const bool retract_before = is_outer_wall ? retract_before_outer_wall : false; + + const bool alternate_direction_modifier = alternate_walls && (path.vertices->inset_idx % 2 == layer_nr % 2); + const bool backwards = path.backwards != alternate_direction_modifier; + + p_end = path.backwards ? path.vertices->back().p : path.vertices->front().p; + const cura::Point p_start = path.backwards ? path.vertices->front().p : path.vertices->back().p; + const bool linked_path = p_start != p_end; + + + added_something = true; + gcode_writer.setExtruder_addPrime(storage, gcode_layer, extruder_nr); + gcode_layer.setIsInside(true); //Going to print walls, which are always inside. + gcode_layer.addWall(*path.vertices, path.start_vertex, mesh.settings, non_bridge_config, bridge_config, wipe_dist, flow, retract_before, path.is_closed, backwards, linked_path); + added_something = true; } return added_something; } diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 401f59be66..89280ea8a7 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -790,7 +790,7 @@ void LayerPlan::addWall(ConstPolygonRef wall, int start_idx, const Settings& set constexpr size_t dummy_perimeter_id = 0; // <-- Here, don't care about which perimeter any more. const coord_t nominal_line_width = non_bridge_config.getLineWidth(); // <-- The line width which it's 'supposed to' be will be used to adjust the flow ratio each time, this'll give a flow-ratio-multiplier of 1. - LineJunctions ewall; + ExtrusionLine ewall; std::for_each(wall.begin(), wall.end(), [&dummy_perimeter_id, &nominal_line_width, &ewall](const Point& p) { ewall.emplace_back(p, nominal_line_width, dummy_perimeter_id); @@ -802,7 +802,7 @@ void LayerPlan::addWall(ConstPolygonRef wall, int start_idx, const Settings& set addWall(ewall, start_idx, settings, non_bridge_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract, is_closed, is_reversed, is_linked_path); } -void LayerPlan::addWall(const LineJunctions& wall, int start_idx, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, float flow_ratio, bool always_retract, const bool is_closed, const bool is_reversed, const bool is_linked_path) +void LayerPlan::addWall(const ExtrusionLine& wall, int start_idx, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, float flow_ratio, bool always_retract, const bool is_closed, const bool is_reversed, const bool is_linked_path) { if (wall.empty()) { @@ -1022,7 +1022,7 @@ void LayerPlan::addWall(const LineJunctions& wall, int start_idx, const Settings } } -void LayerPlan::addInfillWall(const LineJunctions& wall, const GCodePathConfig& path_config, +void LayerPlan::addInfillWall(const ExtrusionLine& wall, const GCodePathConfig& path_config, bool force_retract) { assert(("All empty walls should have been filtered at this stage", !wall.empty())); @@ -1063,41 +1063,6 @@ void LayerPlan::addWalls } } -void LayerPlan::addWalls -( - const PathJunctions& walls, - const Settings& settings, - const GCodePathConfig& non_bridge_config, - const GCodePathConfig& bridge_config, - const ZSeamConfig& z_seam_config, - coord_t wall_0_wipe_dist, - float flow_ratio, - bool always_retract, - bool alternate_inset_direction_modifier -) -{ - constexpr bool detect_loops = true; - constexpr Polygons* combing_boundary = nullptr; - //When we alternate walls, also alternate the direction at which the first wall starts in. - //On even layers we start with normal direction, on odd layers with inverted direction. - const bool alternate_walls = settings.get("material_alternate_walls") && (layer_nr % 2 == (alternate_inset_direction_modifier ? 1 : 0)); - PathOrderOptimizer order_optimizer(getLastPlannedPositionOrStartingPosition(), z_seam_config, detect_loops, combing_boundary, alternate_walls); - for(const LineJunctions& wall : walls) - { - order_optimizer.addPolyline(&wall); - } - - cura::Point p_end {0, 0}; - order_optimizer.optimize(); - for(const PathOrderOptimizer::Path& path : order_optimizer.paths) - { - if (path.vertices->empty()) continue; - p_end = path.backwards ? path.vertices->back().p : path.vertices->front().p; - const cura::Point p_start = path.backwards ? path.vertices->front().p : path.vertices->back().p; - const bool linked_path = p_start != p_end; - addWall(*path.vertices, path.start_vertex, settings, non_bridge_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract, path.is_closed, path.backwards, linked_path); - } -} void LayerPlan::addLinesByOptimizer ( diff --git a/src/LayerPlan.h b/src/LayerPlan.h index dada1c7693..ca50060865 100644 --- a/src/LayerPlan.h +++ b/src/LayerPlan.h @@ -558,7 +558,7 @@ class LayerPlan : public NoCopy * \param is_reversed Whether to print this wall in reverse direction. * \param is_linked_path Whether the path is a continuation off the previous path */ - void addWall(const LineJunctions& wall, int start_idx, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, float flow_ratio, bool always_retract, const bool is_closed, const bool is_reversed, const bool is_linked_path); + void addWall(const ExtrusionLine& wall, int start_idx, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, float flow_ratio, bool always_retract, const bool is_closed, const bool is_reversed, const bool is_linked_path); /*! * Add an infill wall to the g-code @@ -566,7 +566,7 @@ class LayerPlan : public NoCopy * \param wall he wall as ExtrusionJunctions * \param path_config The config with which to print the wall lines */ - void addInfillWall(const LineJunctions& wall, const GCodePathConfig& path_config, bool force_retract); + void addInfillWall(const ExtrusionLine& wall, const GCodePathConfig& path_config, bool force_retract); /*! * Add walls (polygons) to the gcode with optimized order. @@ -591,18 +591,6 @@ class LayerPlan : public NoCopy float flow_ratio = 1.0, bool always_retract = false ); - void addWalls - ( - const PathJunctions& walls, - const Settings& settings, - const GCodePathConfig& non_bridge_config, - const GCodePathConfig& bridge_config, - const ZSeamConfig& z_seam_config = ZSeamConfig(), - coord_t wall_0_wipe_dist = 0, - float flow_ratio = 1.0, - bool always_retract = false, - bool alternate_inset_direction_modifier = false - ); /*! * Add lines to the gcode with optimized order. From 3168a9d7c066598446f66568f3162156f2a03ffe Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 7 Feb 2022 12:22:24 +0100 Subject: [PATCH 17/71] Fix ironing being applied on wrong extruder(s) --- src/FffGcodeWriter.cpp | 9 ++++++--- src/FffGcodeWriter.h | 3 ++- src/TopSurface.cpp | 4 +++- src/TopSurface.h | 5 ++++- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 1f36259bdc..5fcfb9d383 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1405,7 +1405,10 @@ void FffGcodeWriter::addMeshLayerToGCode(const SliceDataStorage& storage, const addMeshPartToGCode(storage, mesh, extruder_nr, mesh_config, *path.vertices, gcode_layer); } - processIroning(mesh, layer, mesh_config.ironing_config, gcode_layer); + if (extruder_nr == mesh.settings.get("top_bottom_extruder_nr").extruder_nr) + { + processIroning(storage, mesh, layer, mesh_config.ironing_config, gcode_layer); + } if (mesh.settings.get("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && extruder_nr == mesh.settings.get("wall_0_extruder_nr").extruder_nr) { addMeshOpenPolyLinesToGCode(mesh, mesh_config, gcode_layer); @@ -2486,7 +2489,7 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La } } -bool FffGcodeWriter::processIroning(const SliceMeshStorage& mesh, const SliceLayer& layer, const GCodePathConfig& line_config, LayerPlan& gcode_layer) const +bool FffGcodeWriter::processIroning(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const SliceLayer& layer, const GCodePathConfig& line_config, LayerPlan& gcode_layer) const { bool added_something = false; const bool ironing_enabled = mesh.settings.get("ironing_enabled"); @@ -2497,7 +2500,7 @@ bool FffGcodeWriter::processIroning(const SliceMeshStorage& mesh, const SliceLay // But the truth is that we are inside a part, so we need to change it before we do the ironing // See CURA-8615 gcode_layer.setIsInside(true); - added_something |= layer.top_surface.ironing(mesh, line_config, gcode_layer); + added_something |= layer.top_surface.ironing(storage, mesh, line_config, gcode_layer, *this); gcode_layer.setIsInside(false); } return added_something; diff --git a/src/FffGcodeWriter.h b/src/FffGcodeWriter.h index ce6543199d..63594f1726 100644 --- a/src/FffGcodeWriter.h +++ b/src/FffGcodeWriter.h @@ -560,6 +560,7 @@ class FffGcodeWriter : public NoCopy * This produces additional low-extrusion moves that cover the top surface, * in order to smooth the surface more. * + * \param storage The slice data storage in the highly unlikely case that printing the ironing requires printing a brim just before it * \param mesh The settings storage to get the ironing settings and skin * angles from. * \param layer The layer to process the ironing for. @@ -568,7 +569,7 @@ class FffGcodeWriter : public NoCopy * \param[out] gcode_layer The output layer to put the resulting paths in. * \return Whether this function added anything to the layer plan. */ - bool processIroning(const SliceMeshStorage& mesh, const SliceLayer& part, const GCodePathConfig& line_config, LayerPlan& gcode_layer) const; + bool processIroning(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const SliceLayer& part, const GCodePathConfig& line_config, LayerPlan& gcode_layer) const; /*! * Add the support to the layer plan \p gcodeLayer of the current layer for all support parts with the given \p extruder_nr. diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp index 434eb54772..65bb46835a 100644 --- a/src/TopSurface.cpp +++ b/src/TopSurface.cpp @@ -5,6 +5,7 @@ #include "LayerPlan.h" #include "sliceDataStorage.h" #include "TopSurface.h" +#include "ExtruderTrain.h" namespace cura { @@ -38,13 +39,14 @@ void TopSurface::setAreasFromMeshAndLayerNumber(SliceMeshStorage& mesh, size_t l } } -bool TopSurface::ironing(const SliceMeshStorage& mesh, const GCodePathConfig& line_config, LayerPlan& layer) const +bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const GCodePathConfig& line_config, LayerPlan& layer, const FffGcodeWriter& gcode_writer) const { if (areas.empty()) { return false; //Nothing to do. } //Generate the lines to cover the surface. + const bool extruder_nr = mesh.settings.get("top_bottom_extruder_nr").extruder_nr; const EFillMethod pattern = mesh.settings.get("ironing_pattern"); const bool zig_zaggify_infill = pattern == EFillMethod::ZIG_ZAG; constexpr bool connect_polygons = false; // midway connections can make the surface less smooth diff --git a/src/TopSurface.h b/src/TopSurface.h index 28687865be..dcc5e5b9c2 100644 --- a/src/TopSurface.h +++ b/src/TopSurface.h @@ -10,6 +10,7 @@ namespace cura { class GCodePathConfig; +class FffGcodeWriter; class LayerPlan; class SliceMeshStorage; @@ -41,13 +42,15 @@ class TopSurface * strike the surface smooth by melting it with the hot nozzle and filling * crevices with a minute amount of material. * + * \param storage The slice data storage in the highly unlikely case that printing the ironing requires printing a brim just before it * \param mesh The settings base to get our ironing settings and skin angles * from. * \param line_config The configuration of the ironing lines to use. Note * that the flow might still get adjusted by the ironing settings. * \param[out] layer The output g-code layer to put the resulting lines in. + * \param gcode_writer The gcode writer for processing extra steps to write into the layer */ - bool ironing(const SliceMeshStorage& mesh, const GCodePathConfig& line_config, LayerPlan& layer) const; + bool ironing(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const GCodePathConfig& line_config, LayerPlan& layer, const FffGcodeWriter& gcode_writer) const; public: /*! From 3dde7481137682d7cf5a587c1bed837e1bd1e95b Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 7 Feb 2022 12:23:20 +0100 Subject: [PATCH 18/71] Extract feature specific settings out of InsetOrderOptimizer --- src/FffGcodeWriter.cpp | 107 +++++++++++++++++++----------------- src/InsetOrderOptimizer.cpp | 67 +++++++++++----------- src/InsetOrderOptimizer.h | 39 +++++++++---- src/TopSurface.cpp | 12 ++-- 4 files changed, 128 insertions(+), 97 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 5fcfb9d383..ed35908f29 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -4,6 +4,7 @@ #include #include // numeric_limits #include +#include #include "Application.h" #include "bridge.h" @@ -739,6 +740,8 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) AngleDegrees fill_angle = (num_surface_layers + 1) % 2 ? 45 : 135; //90 degrees rotated from the interface layer. constexpr bool zig_zaggify_infill = false; constexpr bool connect_polygons = true; // causes less jerks, so better adhesion + constexpr bool retract_before_outer_wall = false; + constexpr coord_t wipe_dist = 0; const size_t wall_line_count = base_settings.get("raft_base_wall_count"); const coord_t line_spacing = base_settings.get("raft_base_line_spacing"); @@ -761,11 +764,12 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) infill_comp.generate(raft_paths, raft_polygons, raftLines, base_settings); if(!raft_paths.empty()) { - const BinJunctions binned_paths = InsetOrderOptimizer::variableWidthPathToBinJunctions(raft_paths); - for (const PathJunctions& wall_junctions : binned_paths) - { - gcode_layer.addWalls(wall_junctions, base_settings, gcode_layer.configs_storage.raft_base_config, gcode_layer.configs_storage.raft_base_config); - } + const GCodePathConfig& config = gcode_layer.configs_storage.raft_base_config; + const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); + InsetOrderOptimizer wall_orderer(*this, storage, gcode_layer, base_settings, base_extruder_nr, + config, config, config, config, + retract_before_outer_wall, wipe_dist, wipe_dist, base_extruder_nr, base_extruder_nr, z_seam_config, raft_paths); + wall_orderer.addToLayer(); } gcode_layer.addLinesByOptimizer(raftLines, gcode_layer.configs_storage.raft_base_config, SpaceFillType::Lines); @@ -1763,8 +1767,13 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, L { for(const VariableWidthPaths& tool_paths: wall_tool_paths) { - InsetOrderOptimizer inset_order_optimizer(*this, storage, gcode_layer, mesh, extruder_nr, mesh_config, tool_paths, gcode_layer.getLayerNr()); - added_something |= inset_order_optimizer.optimize(InsetOrderOptimizer::WallType::EXTRA_INFILL); + constexpr bool retract_before_outer_wall = false; + constexpr coord_t wipe_dist = 0; + const ZSeamConfig z_seam_config(mesh.settings.get("z_seam_type"), mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), mesh_config.infill_config[0].getLineWidth() * 2); + InsetOrderOptimizer wall_orderer(*this, storage, gcode_layer, mesh.settings, extruder_nr, + mesh_config.infill_config[0], mesh_config.infill_config[0], mesh_config.infill_config[0], mesh_config.infill_config[0], + retract_before_outer_wall, wipe_dist, wipe_dist, extruder_nr, extruder_nr, z_seam_config, tool_paths); + added_something |= wall_orderer.addToLayer(); } } if (!infill_polygons.empty()) @@ -2106,8 +2115,14 @@ bool FffGcodeWriter::processInsets(const SliceDataStorage& storage, LayerPlan& g else { //Main case: Optimize the insets with the InsetOrderOptimizer. - InsetOrderOptimizer inset_order_optimizer(*this, storage, gcode_layer, mesh, extruder_nr, mesh_config, part.wall_toolpaths, gcode_layer.getLayerNr()); - added_something |= inset_order_optimizer.optimize(); + const coord_t wall_x_wipe_dist = 0; + const ZSeamConfig z_seam_config(mesh.settings.get("z_seam_type"), mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2); + InsetOrderOptimizer wall_orderer(*this, storage, gcode_layer, mesh.settings, extruder_nr, + mesh_config.inset0_config, mesh_config.insetX_config, mesh_config.bridge_inset0_config, mesh_config.bridge_insetX_config, + mesh.settings.get("travel_retract_before_outer_wall"), mesh.settings.get("wall_0_wipe_dist"), wall_x_wipe_dist, + mesh.settings.get("wall_0_extruder_nr").extruder_nr, mesh.settings.get("wall_x_extruder_nr").extruder_nr, + z_seam_config, part.wall_toolpaths); + added_something |= wall_orderer.addToLayer(); } return added_something; } @@ -2419,8 +2434,13 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La const size_t skin_extruder_nr = mesh.settings.get("top_bottom_extruder_nr").extruder_nr; if (extruder_nr == skin_extruder_nr) { - InsetOrderOptimizer inset_order_optimizer(*this, storage, gcode_layer, mesh, skin_extruder_nr, mesh_config, skin_paths, gcode_layer.getLayerNr()); - added_something |= inset_order_optimizer.optimize(InsetOrderOptimizer::WallType::EXTRA_SKIN); + constexpr bool retract_before_outer_wall = false; + constexpr coord_t wipe_dist = 0; + const ZSeamConfig z_seam_config(mesh.settings.get("z_seam_type"), mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), config.getLineWidth() * 2); + InsetOrderOptimizer wall_orderer(*this, storage, gcode_layer, mesh.settings, extruder_nr, + mesh_config.skin_config, mesh_config.skin_config, mesh_config.skin_config, mesh_config.skin_config, + retract_before_outer_wall, wipe_dist, wipe_dist, skin_extruder_nr, skin_extruder_nr, z_seam_config, skin_paths); + added_something |= wall_orderer.addToLayer(); } } if(!skin_polygons.empty()) @@ -2691,35 +2711,14 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer if (! wall_toolpaths.empty()) { - const bool pack_by_inset = !infill_extruder.settings.get("optimize_wall_printing_order"); - const InsetDirection inset_direction = infill_extruder.settings.get("inset_direction"); - const bool center_last = inset_direction == InsetDirection::CENTER_LAST; - - std::set* p_bins_with_index_zero_insets = nullptr; - BinJunctions bins = InsetOrderOptimizer::variableWidthPathToBinJunctions(wall_toolpaths, pack_by_inset, center_last, p_bins_with_index_zero_insets); - - bool alternate_bin_direction = false; - for (PathJunctions& paths : bins) - { - const ZSeamConfig& z_seam_config = ZSeamConfig(); - constexpr coord_t wall_0_wipe_dist = 0; - constexpr float flow_ratio = 1.0; - constexpr bool always_retract = false; - gcode_layer.addWalls - ( - paths, - infill_extruder.settings, - gcode_layer.configs_storage.support_infill_config[0], - gcode_layer.configs_storage.support_infill_config[0], - z_seam_config, - wall_0_wipe_dist, - flow_ratio, - always_retract, - alternate_bin_direction - ); - alternate_bin_direction = !alternate_bin_direction; - } - added_something = true; + const GCodePathConfig& config = gcode_layer.configs_storage.support_infill_config[0]; + constexpr bool retract_before_outer_wall = false; + constexpr coord_t wipe_dist = 0; + const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); + InsetOrderOptimizer wall_orderer(*this, storage, gcode_layer, infill_extruder.settings, extruder_nr, + config, config, config, config, + retract_before_outer_wall, wipe_dist, wipe_dist, extruder_nr, extruder_nr, z_seam_config, wall_toolpaths); + added_something |= wall_orderer.addToLayer(); } if (!support_polygons.empty()) @@ -2861,11 +2860,15 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, Lay } if (! roof_paths.empty()) { - const auto converted_paths = InsetOrderOptimizer::variableWidthPathToBinJunctions(roof_paths); - for (const auto& wall_junctions : converted_paths) - { - gcode_layer.addWalls(wall_junctions, roof_extruder.settings, gcode_layer.configs_storage.support_roof_config, gcode_layer.configs_storage.support_roof_config); - } + const GCodePathConfig& config = gcode_layer.configs_storage.support_roof_config; + constexpr bool retract_before_outer_wall = false; + constexpr coord_t wipe_dist = 0; + const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); + + InsetOrderOptimizer wall_orderer(*this, storage, gcode_layer, roof_extruder.settings, roof_extruder_nr, + config, config, config, config, + retract_before_outer_wall, wipe_dist, wipe_dist, roof_extruder_nr, roof_extruder_nr, z_seam_config, roof_paths); + wall_orderer.addToLayer(); } gcode_layer.addLinesByOptimizer(roof_lines, gcode_layer.configs_storage.support_roof_config, (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines); return true; @@ -2934,11 +2937,15 @@ bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, L } if (! bottom_paths.empty()) { - const auto converted_paths = InsetOrderOptimizer::variableWidthPathToBinJunctions(bottom_paths); - for (const auto& wall_junctions : converted_paths) - { - gcode_layer.addWalls(wall_junctions, bottom_extruder.settings, gcode_layer.configs_storage.support_bottom_config, gcode_layer.configs_storage.support_bottom_config); - } + const GCodePathConfig& config = gcode_layer.configs_storage.support_bottom_config; + constexpr bool retract_before_outer_wall = false; + constexpr coord_t wipe_dist = 0; + const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); + + InsetOrderOptimizer wall_orderer(*this, storage, gcode_layer, bottom_extruder.settings, bottom_extruder_nr, + config, config, config, config, + retract_before_outer_wall, wipe_dist, wipe_dist, bottom_extruder_nr, bottom_extruder_nr, z_seam_config, bottom_paths); + wall_orderer.addToLayer(); } gcode_layer.addLinesByOptimizer(bottom_lines, gcode_layer.configs_storage.support_bottom_config, (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines); return true; diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index c23486118c..63790899ca 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -11,50 +11,59 @@ namespace cura { -InsetOrderOptimizer::InsetOrderOptimizer(const FffGcodeWriter& gcode_writer, const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const int extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const VariableWidthPaths& paths, unsigned int layer_nr) : +InsetOrderOptimizer::InsetOrderOptimizer(const FffGcodeWriter& gcode_writer, + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const Settings& settings, + const int extruder_nr, + const GCodePathConfig& inset_0_non_bridge_config, + const GCodePathConfig& inset_X_non_bridge_config, + const GCodePathConfig& inset_0_bridge_config, + const GCodePathConfig& inset_X_bridge_config, + const bool retract_before_outer_wall, + const coord_t wall_0_wipe_dist, + const coord_t wall_x_wipe_dist, + const size_t wall_0_extruder_nr, + const size_t wall_x_extruder_nr, + const ZSeamConfig& z_seam_config, + const VariableWidthPaths& paths) : gcode_writer(gcode_writer), storage(storage), gcode_layer(gcode_layer), - mesh(mesh), + settings(settings), extruder_nr(extruder_nr), - mesh_config(mesh_config), + inset_0_non_bridge_config(inset_0_non_bridge_config), + inset_X_non_bridge_config(inset_X_non_bridge_config), + inset_0_bridge_config(inset_0_bridge_config), + inset_X_bridge_config(inset_X_bridge_config), + retract_before_outer_wall(retract_before_outer_wall), + wall_0_wipe_dist(wall_0_wipe_dist), + wall_x_wipe_dist(wall_x_wipe_dist), + wall_0_extruder_nr(wall_0_extruder_nr), + wall_x_extruder_nr(wall_x_extruder_nr), + z_seam_config(z_seam_config), paths(paths), - layer_nr(layer_nr), - z_seam_config(mesh.settings.get("z_seam_type"), mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2), + layer_nr(gcode_layer.getLayerNr()), added_something(false), retraction_region_calculated(false) { } -bool InsetOrderOptimizer::optimize(const WallType& wall_type) +bool InsetOrderOptimizer::addToLayer() { // Settings & configs: - const GCodePathConfig& skin_or_infill_config = wall_type == WallType::EXTRA_SKIN ? mesh_config.skin_config : mesh_config.infill_config[0]; - const bool do_outer_wall = wall_type == WallType::OUTER_WALL; - const GCodePathConfig& inset_0_non_bridge_config = do_outer_wall ? mesh_config.inset0_config : skin_or_infill_config; - const GCodePathConfig& inset_X_non_bridge_config = do_outer_wall ? mesh_config.insetX_config : skin_or_infill_config; - const GCodePathConfig& inset_0_bridge_config = do_outer_wall ? mesh_config.bridge_inset0_config : skin_or_infill_config; - const GCodePathConfig& inset_X_bridge_config = do_outer_wall ? mesh_config.bridge_insetX_config : skin_or_infill_config; - - const size_t wall_0_extruder_nr = mesh.settings.get("wall_0_extruder_nr").extruder_nr; - const size_t wall_x_extruder_nr = mesh.settings.get("wall_x_extruder_nr").extruder_nr; - const size_t top_bottom_extruder_nr = mesh.settings.get("top_bottom_extruder_nr").extruder_nr; - const size_t infill_extruder_nr = mesh.settings.get("infill_extruder_nr").extruder_nr; - - const bool pack_by_inset = !mesh.settings.get("optimize_wall_printing_order"); - const InsetDirection inset_direction = mesh.settings.get("inset_direction"); + const bool pack_by_inset = ! settings.get("optimize_wall_printing_order"); // TODO + const InsetDirection inset_direction = settings.get("inset_direction"); const bool center_last = inset_direction == InsetDirection::CENTER_LAST; + const bool alternate_walls = settings.get("material_alternate_walls"); - const bool outer_to_inner = inset_direction == InsetDirection::OUTSIDE_IN; size_t start_inset; size_t end_inset; int direction; //If the entire wall is printed with the current extruder, print all of it. - if((wall_type == WallType::OUTER_WALL && wall_0_extruder_nr == wall_x_extruder_nr && wall_x_extruder_nr == extruder_nr) || - (wall_type == WallType::EXTRA_SKIN && extruder_nr == top_bottom_extruder_nr) || - (wall_type == WallType::EXTRA_INFILL && extruder_nr == infill_extruder_nr)) + if (wall_0_extruder_nr == wall_x_extruder_nr && wall_x_extruder_nr == extruder_nr) { //If printing the outer inset first, start with the lowest inset. //Otherwise start with the highest inset and iterate backwards. @@ -72,7 +81,7 @@ bool InsetOrderOptimizer::optimize(const WallType& wall_type) } } //If the wall is partially printed with the current extruder, print the correct part. - else if(wall_type == WallType::OUTER_WALL && wall_0_extruder_nr != wall_x_extruder_nr) + else if (wall_0_extruder_nr != wall_x_extruder_nr) { //If the wall_0 and wall_x extruders are different, then only include the insets that should be printed by the //current extruder_nr. @@ -179,10 +188,6 @@ bool InsetOrderOptimizer::optimize(const WallType& wall_type) order_optimizer.optimize(); - - const bool retract_before_outer_wall = mesh.settings.get("travel_retract_before_outer_wall"); - const coord_t wall_0_wipe_dist = mesh.settings.get("wall_0_wipe_dist"); - const bool alternate_walls = mesh.settings.get("material_alternate_walls"); cura::Point p_end {0, 0}; for(const PathOrderOptimizer::Path& path : order_optimizer.paths) @@ -193,7 +198,7 @@ bool InsetOrderOptimizer::optimize(const WallType& wall_type) const bool is_gap_filler = path.vertices->is_odd; const GCodePathConfig& non_bridge_config = is_outer_wall ? inset_0_non_bridge_config : inset_X_non_bridge_config; const GCodePathConfig& bridge_config = is_outer_wall? inset_0_bridge_config : inset_X_bridge_config; - const coord_t wipe_dist = is_outer_wall && ! is_gap_filler ? wall_0_wipe_dist : 0; + const coord_t wipe_dist = is_outer_wall && ! is_gap_filler ? wall_0_wipe_dist : wall_x_wipe_dist; const bool retract_before = is_outer_wall ? retract_before_outer_wall : false; const bool alternate_direction_modifier = alternate_walls && (path.vertices->inset_idx % 2 == layer_nr % 2); @@ -207,7 +212,7 @@ bool InsetOrderOptimizer::optimize(const WallType& wall_type) added_something = true; gcode_writer.setExtruder_addPrime(storage, gcode_layer, extruder_nr); gcode_layer.setIsInside(true); //Going to print walls, which are always inside. - gcode_layer.addWall(*path.vertices, path.start_vertex, mesh.settings, non_bridge_config, bridge_config, wipe_dist, flow, retract_before, path.is_closed, backwards, linked_path); + gcode_layer.addWall(*path.vertices, path.start_vertex, settings, non_bridge_config, bridge_config, wipe_dist, flow, retract_before, path.is_closed, backwards, linked_path); added_something = true; } return added_something; diff --git a/src/InsetOrderOptimizer.h b/src/InsetOrderOptimizer.h index 86b768e5a1..4b13e04749 100644 --- a/src/InsetOrderOptimizer.h +++ b/src/InsetOrderOptimizer.h @@ -16,12 +16,6 @@ class LayerPlan; class InsetOrderOptimizer { public: - enum class WallType - { - OUTER_WALL, - EXTRA_SKIN, - EXTRA_INFILL - }; /*! * Constructor for inset ordering optimizer. @@ -40,7 +34,22 @@ class InsetOrderOptimizer * \param part The part from which to read the previously generated insets. * \param layer_nr The current layer number. */ - InsetOrderOptimizer(const FffGcodeWriter& gcode_writer, const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const int extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const VariableWidthPaths& paths, unsigned int layer_nr); + InsetOrderOptimizer(const FffGcodeWriter& gcode_writer, + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const Settings& settings, + const int extruder_nr, + const GCodePathConfig& inset_0_non_bridge_config, + const GCodePathConfig& inset_X_non_bridge_config, + const GCodePathConfig& inset_0_bridge_config, + const GCodePathConfig& inset_X_bridge_config, + const bool retract_before_outer_wall, + const coord_t wall_0_wipe_dist, + const coord_t wall_x_wipe_dist, + const size_t wall_0_extruder_nr, + const size_t wall_x_extruder_nr, + const ZSeamConfig& z_seam_config, + const VariableWidthPaths& paths); /*! * Adds the insets to the given layer plan. @@ -49,19 +58,27 @@ class InsetOrderOptimizer * class, so this optimize function needs no additional information. * \return Whether anything was added to the layer plan. */ - bool optimize(const WallType& wall_type = WallType::OUTER_WALL); + bool addToLayer(); private: const FffGcodeWriter& gcode_writer; const SliceDataStorage& storage; LayerPlan& gcode_layer; - const SliceMeshStorage& mesh; + const Settings& settings; const size_t extruder_nr; - const PathConfigStorage::MeshPathConfigs& mesh_config; + const GCodePathConfig& inset_0_non_bridge_config; + const GCodePathConfig& inset_X_non_bridge_config; + const GCodePathConfig& inset_0_bridge_config; + const GCodePathConfig& inset_X_bridge_config; + const bool retract_before_outer_wall; + const coord_t wall_0_wipe_dist; + const coord_t wall_x_wipe_dist; + const size_t wall_0_extruder_nr; + const size_t wall_x_extruder_nr; + const ZSeamConfig& z_seam_config; const VariableWidthPaths& paths; const unsigned int layer_nr; - const ZSeamConfig z_seam_config; bool added_something; bool retraction_region_calculated; //Whether the retraction_region field has been calculated or not. std::vector> inset_polys; // vector of vectors holding the inset polygons diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp index 65bb46835a..7bf5be852c 100644 --- a/src/TopSurface.cpp +++ b/src/TopSurface.cpp @@ -137,11 +137,13 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage } if(!ironing_paths.empty()) { - const BinJunctions binned_paths = InsetOrderOptimizer::variableWidthPathToBinJunctions(ironing_paths); - for(const PathJunctions& wall_junctions : binned_paths) - { - layer.addWalls(wall_junctions, mesh.settings, line_config, line_config); - } + constexpr bool retract_before_outer_wall = false; + constexpr coord_t wipe_dist = 0u; + const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); + InsetOrderOptimizer wall_orderer(gcode_writer, storage, layer, mesh.settings, extruder_nr, + line_config, line_config, line_config, line_config, + retract_before_outer_wall, wipe_dist, wipe_dist, extruder_nr, extruder_nr, z_seam_config, ironing_paths); + wall_orderer.addToLayer(); added = true; } From 4d9af7e73b53af7459cff2ff6094623b02de009a Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sat, 22 Jan 2022 16:32:52 +0100 Subject: [PATCH 19/71] clean up unused functionality in InsetOrderOptimizer --- src/InsetOrderOptimizer.cpp | 94 ----------------------------------- src/InsetOrderOptimizer.h | 22 -------- src/PathOrderOptimizer.cpp | 13 ----- src/utils/ExtrusionJunction.h | 4 +- 4 files changed, 1 insertion(+), 132 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 63790899ca..58e5b80ca1 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -218,98 +218,4 @@ bool InsetOrderOptimizer::addToLayer() return added_something; } -size_t InsetOrderOptimizer::getOuterRegionId(const VariableWidthPaths& toolpaths, size_t& out_max_region_id) -{ - // Polygons show up here one by one, so there are always only a) the outer lines and b) the lines that are part of the holes. - // Therefore, the outer-regions' lines will always have the region-id that is larger then all of the other ones. - - // First, build the bounding boxes: - std::map region_ids_to_bboxes; //Use a sorted map, ordered by region_id, so that we can find the largest region_id quickly. - for (const VariableWidthLines& path : toolpaths) - { - for (const ExtrusionLine& line : path) - { - AABB& aabb = region_ids_to_bboxes[line.region_id]; // Empty AABBs are default initialized when region_ids are encountered for the first time. - for (const auto& junction : line.junctions) - { - aabb.include(junction.p); - } - } - } - - // Then, the largest of these will be the one that's needed for the outer region, the others' all belong to hole regions: - AABB outer_bbox; - size_t outer_region_id = 0; // Region-ID 0 is reserved for 'None'. - for (const auto& region_id_bbox_pair : region_ids_to_bboxes) - { - if (region_id_bbox_pair.second.contains(outer_bbox)) - { - outer_bbox = region_id_bbox_pair.second; - outer_region_id = region_id_bbox_pair.first; - } - } - - // Maximum Region-ID (using the ordering of the map) - out_max_region_id = region_ids_to_bboxes.empty() ? 0 : region_ids_to_bboxes.rbegin()->first; - return outer_region_id; -} - -BinJunctions InsetOrderOptimizer::variableWidthPathToBinJunctions(const VariableWidthPaths& toolpaths, const bool pack_regions_by_inset, - const bool center_last, std::set* p_bins_with_index_zero_insets) -{ - // Find the largest inset-index: - size_t max_inset_index = 0; - for (const VariableWidthLines& path : toolpaths) - { - max_inset_index = std::max(path.front().inset_idx, max_inset_index); - } - - // Find which regions are associated with the outer-outer walls (which region is the one the rest is holes inside of): - size_t max_region_id = 0; - const size_t outer_region_id = getOuterRegionId(toolpaths, max_region_id); - - //Since we're (optionally!) splitting off in the outer and inner regions, it may need twice as many bins as inset-indices. - //Add two extra bins for the center-paths, if they need to be stored separately. One bin for inner and one for outer walls. - const size_t max_bin = (pack_regions_by_inset ? (max_region_id * 2) + 2 : (max_inset_index + 1) * 2) + center_last * 2; - BinJunctions insets(max_bin + 1); - for (const VariableWidthLines& path : toolpaths) - { - if (path.empty()) // Don't bother printing these. - { - continue; - } - const size_t inset_index = path.front().inset_idx; - - // Convert list of extrusion lines to vectors of extrusion junctions, and add those to the binned insets. - for (const ExtrusionLine& line : path) - { - // Sort into the right bin, ... - size_t bin_index; - const bool in_hole_region = line.region_id != outer_region_id && line.region_id != 0; - if(center_last && line.is_odd) - { - bin_index = inset_index > 0; - } - else if(pack_regions_by_inset) - { - bin_index = std::min(inset_index, static_cast(1)) + 2 * (in_hole_region ? line.region_id : 0) + center_last * 2; - } - else - { - bin_index = inset_index + (in_hole_region ? (max_inset_index + 1) : 0) + center_last * 2; - } - - insets[bin_index].emplace_back(line.junctions.begin(), line.junctions.end()); - - // Collect all bins that have zero-inset indices in them, if needed: - if (inset_index == 0 && p_bins_with_index_zero_insets != nullptr) - { - p_bins_with_index_zero_insets->insert(bin_index); - } - } - } - - return insets; -} - }//namespace cura diff --git a/src/InsetOrderOptimizer.h b/src/InsetOrderOptimizer.h index 4b13e04749..9baf23c03f 100644 --- a/src/InsetOrderOptimizer.h +++ b/src/InsetOrderOptimizer.h @@ -83,28 +83,6 @@ class InsetOrderOptimizer bool retraction_region_calculated; //Whether the retraction_region field has been calculated or not. std::vector> inset_polys; // vector of vectors holding the inset polygons Polygons retraction_region; //After printing an outer wall, move into this region so that retractions do not leave visible blobs. Calculated lazily if needed (see retraction_region_calculated). - - /*! - * Retrieves the region-id of the outer region (belongs to the outer outline, not to a hole). - */ - static size_t getOuterRegionId(const VariableWidthPaths& toolpaths, size_t& out_max_region_id); - -public: - /*! - * Converts the VariableWidthPath to a bin of walls, consisting of a vector of paths, consisting of a vector of - * lines - * \param toolpaths The toolpaths to convert - * \param pack_by_inset Pack regions by inset, otherwise, pack insets by region. Useful for outer/inner first situations. - * \param p_bins_with_index_zero_insets When optimizing, not all inset zero indices are in the zeroth bin. (Can be set to nullptr, which won't negate optimize.) - * \return A bin of walls, consisting of a vector of paths consisting of vector of lines - */ - static BinJunctions variableWidthPathToBinJunctions - ( - const VariableWidthPaths& toolpaths, - const bool pack_regions_by_inset = true, - const bool center_last = false, - std::set* p_bins_with_index_zero_insets = nullptr - ); }; } //namespace cura diff --git a/src/PathOrderOptimizer.cpp b/src/PathOrderOptimizer.cpp index 8087babc60..951e967b5b 100644 --- a/src/PathOrderOptimizer.cpp +++ b/src/PathOrderOptimizer.cpp @@ -39,19 +39,6 @@ namespace cura { return path->outline.outerPolygon(); } - - template<> - ConstPolygonRef PathOrderOptimizer::getVertexData(const LineJunctions* path) - { - cached_vertices.emplace_back(); - Polygon& poly = cached_vertices.back(); - for (const ExtrusionJunction junction : *path) - { - poly.add(junction.p); - } - return ConstPolygonRef(poly); - } - template<> ConstPolygonRef PathOrderOptimizer::getVertexData(const ExtrusionLine* path) { diff --git a/src/utils/ExtrusionJunction.h b/src/utils/ExtrusionJunction.h index d53997b75a..6483f9477e 100644 --- a/src/utils/ExtrusionJunction.h +++ b/src/utils/ExtrusionJunction.h @@ -60,9 +60,7 @@ inline const Point& make_point(const ExtrusionJunction& ej) return ej.p; } -using LineJunctions = std::vector; //; //; //; // Date: Sat, 22 Jan 2022 16:37:37 +0100 Subject: [PATCH 20/71] move getWeakOrder to InsetOrderOptimizer --- src/InsetOrderOptimizer.cpp | 205 ++++++++++++++++++++++++++++++++- src/InsetOrderOptimizer.h | 25 ++++ src/WallToolPaths.cpp | 199 -------------------------------- src/WallToolPaths.h | 23 ---- tests/WallsComputationTest.cpp | 4 +- 5 files changed, 231 insertions(+), 225 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 58e5b80ca1..d088b9991b 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -125,7 +125,7 @@ bool InsetOrderOptimizer::addToLayer() constexpr bool include_transitive = true; - std::unordered_set> order = WallToolPaths::getWeakOrder(walls_to_be_added, outer_to_inner, include_transitive); + std::unordered_set> order = getWeakOrder(walls_to_be_added, outer_to_inner, include_transitive); if (center_last) { @@ -218,4 +218,207 @@ bool InsetOrderOptimizer::addToLayer() return added_something; } + + + +std::unordered_set> InsetOrderOptimizer::getWeakOrder(const std::vector& input, const bool outer_to_inner, const bool include_transitive) +{ + size_t max_inset_idx = 0; + Polygons all_polygons; + std::unordered_map poly_idx_to_extrusionline; + for (const ExtrusionLine* line_p : input) + { + const ExtrusionLine& line = *line_p; + if (line.empty()) continue; + max_inset_idx = std::max(max_inset_idx, line.inset_idx); + if ( ! shorterThan(line.front().p - line.back().p, coincident_point_distance)) // TODO: check if it is a closed polygon or not + { + // Make a small triangle representative of the polyline + // otherwise the polyline would get erased by the clipping operation + all_polygons.emplace_back(); + assert(line.junctions.size() >= 2); + Point middle = ( line.junctions[line.junctions.size() / 2 - 1].p + line.junctions[line.junctions.size() / 2].p ) / 2; + PolygonRef poly = all_polygons.back(); + poly.emplace_back(middle); + poly.emplace_back(middle + Point(5, 0)); + poly.emplace_back(middle + Point(0, 5)); + } + else + { + all_polygons.emplace_back(line.toPolygon()); + } + poly_idx_to_extrusionline.emplace(all_polygons.size() - 1, &line); + } + + std::vector> nesting = all_polygons.getNesting(); + + { + SVG svg("/tmp/nesting.svg", AABB(all_polygons)); + svg.writePolygons(all_polygons, SVG::Color::BLUE); + for (size_t i = 0; i < all_polygons.size(); i++) + { + for (size_t child_idx : nesting[i]) + { + svg.writeArrow(all_polygons[i][0], all_polygons[child_idx][1]); + } + } + } + + std::unordered_set> result; + + for (auto [idx, line] : poly_idx_to_extrusionline) + { // there might be multiple roots + if (line->inset_idx == 0) + { + getWeakOrder(idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); + } + } + + if (include_transitive) + { + std::unordered_multimap order_mapping; + for (auto [from, to] : result) + { + order_mapping.emplace(from, to); + } + std::unordered_set> transitive_order = result; + for (auto [from, to] : result) + { + std::queue starts_of_next_relation; + starts_of_next_relation.emplace(to); + while ( ! starts_of_next_relation.empty()) + { + const ExtrusionLine* start_of_next_relation = starts_of_next_relation.front(); + starts_of_next_relation.pop(); + auto range = order_mapping.equal_range(start_of_next_relation); + for (auto it = range.first; it != range.second; ++it) + { + auto [ next_from, next_to ] = *it; + starts_of_next_relation.emplace(next_to); + transitive_order.emplace(from, next_to); + } + } + } + result = transitive_order; + } + + return result; +} + +void InsetOrderOptimizer::getWeakOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result) +{ + auto parent_it = poly_idx_to_extrusionline.find(node_idx); + assert(parent_it != poly_idx_to_extrusionline.end()); + const ExtrusionLine* parent = parent_it->second; + + assert(node_idx < nesting.size()); + for (size_t child_idx : nesting[node_idx]) + { + auto child_it = poly_idx_to_extrusionline.find(child_idx); + assert(child_it != poly_idx_to_extrusionline.end()); + const ExtrusionLine* child = child_it->second; + + if ( ! child->is_odd && child->inset_idx == parent->inset_idx && child->inset_idx == max_inset_idx) + { + // There is no order requirement between the innermost wall of a hole and the innermost wall of the outline. + } + else if ( ! child->is_odd && child->inset_idx == parent->inset_idx && child->inset_idx <= max_inset_idx) + { // unusual case + // There are insets with one higher inset index which are adjacent to both this child and the parent. + // And potentially also insets which are adjacent to this child and other children. + // Moreover there are probably gap filler lines in between the child and the parent. + // The nesting information doesn't tell which ones are adjacent, + // so just to be safe we add order requirements between the child and all gap fillers and wall lines. + for (size_t other_child_idx : nesting[node_idx]) + { + auto other_child_it = poly_idx_to_extrusionline.find(other_child_idx); + assert(other_child_it != poly_idx_to_extrusionline.end()); + const ExtrusionLine* other_child = other_child_it->second; + + if (other_child == child) continue; + + + // See if there's an overlap in region_id. + // If not then they are not adjacent, so we don't include order requirement + bool overlap = false; + { + std::unordered_set other_child_region_ids; + for (const ExtrusionJunction& j : other_child->junctions) + { + other_child_region_ids.emplace(j.region_id); + } + for (const ExtrusionJunction& j : child->junctions) + { + if (other_child_region_ids.count(j.region_id)) + { + overlap = true; + break; + } + } + if (other_child->is_odd) + { // Odd gap fillers should have two region_ids, but they don't, so let's be more conservative on them + for (const ExtrusionJunction& j : parent->junctions) + { + if (other_child_region_ids.count(j.region_id)) + { // if an odd gap filler has the region_id set to the outline then it could also be adjacent to child, but not registered as such. + overlap = true; + break; + } + } + } + } + if ( ! overlap) continue; + if (other_child->is_odd) + { + if (other_child->inset_idx == child->inset_idx + 1) + { // normal gap filler + result.emplace(child, other_child); + } + else + { // outer thin wall 'gap filler' enclosed in an internal hole. + // E.g. in the middle of a thin '8' shape when the middle looks like '>-<=>-<' + assert(parent->inset_idx == 0); // enclosing 8 shape + assert(child->inset_idx == 0); // thick section of the middle + assert(other_child->inset_idx == 0); // thin section of the middle + // no order requirement between thin wall, because it has no eclosing wall + } + } + else + { // other child is an even wall as well + if (other_child->inset_idx == child->inset_idx) continue; + assert(other_child->inset_idx == child->inset_idx + 1); + + const ExtrusionLine* before = child; + const ExtrusionLine* after = other_child; + if ( ! outer_to_inner) + { + std::swap(before, after); + } + result.emplace(before, after); + } + } + } + else + { // normal case + assert( ! parent->is_odd && "There can be no polygons inside a polyline"); + + const ExtrusionLine* before = parent; + const ExtrusionLine* after = child; + if ( (child->inset_idx < parent->inset_idx) == outer_to_inner + // ^ Order should be reversed for hole polyons + // and it should be reversed again when the global order is the other way around + && ! child->is_odd) // Odd polylines should always go after their enclosing wall polygon + { + std::swap(before, after); + } + result.emplace(before, after); + } + + // Recurvise call + getWeakOrder(child_idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); + } +} + + + }//namespace cura diff --git a/src/InsetOrderOptimizer.h b/src/InsetOrderOptimizer.h index 9baf23c03f..83f693c34c 100644 --- a/src/InsetOrderOptimizer.h +++ b/src/InsetOrderOptimizer.h @@ -4,6 +4,8 @@ #ifndef INSET_ORDER_OPTIMIZER_H #define INSET_ORDER_OPTIMIZER_H +#include + #include "PathOrderOptimizer.h" #include "sliceDataStorage.h" //For SliceMeshStorage, which is used here at implementation in the header. @@ -60,8 +62,23 @@ class InsetOrderOptimizer */ bool addToLayer(); + /*! + * Get the order constraints of the insets assuming the Wall Ordering is outer to inner. + * 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 std::unordered_set> getWeakOrder(const std::vector& input, const bool outer_to_inner, const bool include_transitive = true); private: + /*! + * Recursive part of \ref WallToolpPaths::getWeakOrder. + * For each node at \p node_idx we recurse on all its children at nesting[node_idx] + */ + static void getWeakOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result); + const FffGcodeWriter& gcode_writer; const SliceDataStorage& storage; LayerPlan& gcode_layer; @@ -79,10 +96,18 @@ class InsetOrderOptimizer const ZSeamConfig& z_seam_config; const VariableWidthPaths& paths; const unsigned int layer_nr; + bool added_something; bool retraction_region_calculated; //Whether the retraction_region field has been calculated or not. std::vector> inset_polys; // vector of vectors holding the inset polygons Polygons retraction_region; //After printing an outer wall, move into this region so that retractions do not leave visible blobs. Calculated lazily if needed (see retraction_region_calculated). + + /*! + * Endpoints of polylines that are closer together than this distance + * will be considered to be coincident, + * closing that polyline into a polygon. + */ + constexpr static coord_t coincident_point_distance = 10; }; } //namespace cura diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index e3ca625708..400a2e48c4 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -414,203 +414,4 @@ void WallToolPaths::stitchContours(const VariableWidthPaths& input, const coord_ } } - -std::unordered_set> WallToolPaths::getWeakOrder(const std::vector& input, const bool outer_to_inner, const bool include_transitive) -{ - size_t max_inset_idx = 0; - Polygons all_polygons; - std::unordered_map poly_idx_to_extrusionline; - for (const ExtrusionLine* line_p : input) - { - const ExtrusionLine& line = *line_p; - if (line.empty()) continue; - max_inset_idx = std::max(max_inset_idx, line.inset_idx); - if ( ! shorterThan(line.front().p - line.back().p, coincident_point_distance)) // TODO: check if it is a closed polygon or not - { - // Make a small triangle representative of the polyline - // otherwise the polyline would get erased by the clipping operation - all_polygons.emplace_back(); - assert(line.junctions.size() >= 2); - Point middle = ( line.junctions[line.junctions.size() / 2 - 1].p + line.junctions[line.junctions.size() / 2].p ) / 2; - PolygonRef poly = all_polygons.back(); - poly.emplace_back(middle); - poly.emplace_back(middle + Point(5, 0)); - poly.emplace_back(middle + Point(0, 5)); - } - else - { - all_polygons.emplace_back(line.toPolygon()); - } - poly_idx_to_extrusionline.emplace(all_polygons.size() - 1, &line); - } - - std::vector> nesting = all_polygons.getNesting(); - - { - SVG svg("/tmp/nesting.svg", AABB(all_polygons)); - svg.writePolygons(all_polygons, SVG::Color::BLUE); - for (size_t i = 0; i < all_polygons.size(); i++) - { - for (size_t child_idx : nesting[i]) - { - svg.writeArrow(all_polygons[i][0], all_polygons[child_idx][1]); - } - } - } - - std::unordered_set> result; - - for (auto [idx, line] : poly_idx_to_extrusionline) - { // there might be multiple roots - if (line->inset_idx == 0) - { - getWeakOrder(idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); - } - } - - if (include_transitive) - { - std::unordered_multimap order_mapping; - for (auto [from, to] : result) - { - order_mapping.emplace(from, to); - } - std::unordered_set> transitive_order = result; - for (auto [from, to] : result) - { - std::queue starts_of_next_relation; - starts_of_next_relation.emplace(to); - while ( ! starts_of_next_relation.empty()) - { - const ExtrusionLine* start_of_next_relation = starts_of_next_relation.front(); - starts_of_next_relation.pop(); - auto range = order_mapping.equal_range(start_of_next_relation); - for (auto it = range.first; it != range.second; ++it) - { - auto [ next_from, next_to ] = *it; - starts_of_next_relation.emplace(next_to); - transitive_order.emplace(from, next_to); - } - } - } - result = transitive_order; - } - - return result; -} - -void WallToolPaths::getWeakOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result) -{ - auto parent_it = poly_idx_to_extrusionline.find(node_idx); - assert(parent_it != poly_idx_to_extrusionline.end()); - const ExtrusionLine* parent = parent_it->second; - - assert(node_idx < nesting.size()); - for (size_t child_idx : nesting[node_idx]) - { - auto child_it = poly_idx_to_extrusionline.find(child_idx); - assert(child_it != poly_idx_to_extrusionline.end()); - const ExtrusionLine* child = child_it->second; - - if ( ! child->is_odd && child->inset_idx == parent->inset_idx && child->inset_idx == max_inset_idx) - { - // There is no order requirement between the innermost wall of a hole and the innermost wall of the outline. - } - else if ( ! child->is_odd && child->inset_idx == parent->inset_idx && child->inset_idx <= max_inset_idx) - { // unusual case - // There are insets with one higher inset index which are adjacent to both this child and the parent. - // And potentially also insets which are adjacent to this child and other children. - // Moreover there are probably gap filler lines in between the child and the parent. - // The nesting information doesn't tell which ones are adjacent, - // so just to be safe we add order requirements between the child and all gap fillers and wall lines. - for (size_t other_child_idx : nesting[node_idx]) - { - auto other_child_it = poly_idx_to_extrusionline.find(other_child_idx); - assert(other_child_it != poly_idx_to_extrusionline.end()); - const ExtrusionLine* other_child = other_child_it->second; - - if (other_child == child) continue; - - - // See if there's an overlap in region_id. - // If not then they are not adjacent, so we don't include order requirement - bool overlap = false; - { - std::unordered_set other_child_region_ids; - for (const ExtrusionJunction& j : other_child->junctions) - { - other_child_region_ids.emplace(j.region_id); - } - for (const ExtrusionJunction& j : child->junctions) - { - if (other_child_region_ids.count(j.region_id)) - { - overlap = true; - break; - } - } - if (other_child->is_odd) - { // Odd gap fillers should have two region_ids, but they don't, so let's be more conservative on them - for (const ExtrusionJunction& j : parent->junctions) - { - if (other_child_region_ids.count(j.region_id)) - { // if an odd gap filler has the region_id set to the outline then it could also be adjacent to child, but not registered as such. - overlap = true; - break; - } - } - } - } - if ( ! overlap) continue; - if (other_child->is_odd) - { - if (other_child->inset_idx == child->inset_idx + 1) - { // normal gap filler - result.emplace(child, other_child); - } - else - { // outer thin wall 'gap filler' enclosed in an internal hole. - // E.g. in the middle of a thin '8' shape when the middle looks like '>-<=>-<' - assert(parent->inset_idx == 0); // enclosing 8 shape - assert(child->inset_idx == 0); // thick section of the middle - assert(other_child->inset_idx == 0); // thin section of the middle - // no order requirement between thin wall, because it has no eclosing wall - } - } - else - { // other child is an even wall as well - if (other_child->inset_idx == child->inset_idx) continue; - assert(other_child->inset_idx == child->inset_idx + 1); - - const ExtrusionLine* before = child; - const ExtrusionLine* after = other_child; - if ( ! outer_to_inner) - { - std::swap(before, after); - } - result.emplace(before, after); - } - } - } - else - { // normal case - assert( ! parent->is_odd && "There can be no polygons inside a polyline"); - - const ExtrusionLine* before = parent; - const ExtrusionLine* after = child; - if ( (child->inset_idx < parent->inset_idx) == outer_to_inner - // ^ Order should be reversed for hole polyons - // and it should be reversed again when the global order is the other way around - && ! child->is_odd) // Odd polylines should always go after their enclosing wall polygon - { - std::swap(before, after); - } - result.emplace(before, after); - } - - // Recurvise call - getWeakOrder(child_idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); - } -} - } // namespace cura diff --git a/src/WallToolPaths.h b/src/WallToolPaths.h index c2031adcfd..14d491bd6c 100644 --- a/src/WallToolPaths.h +++ b/src/WallToolPaths.h @@ -5,7 +5,6 @@ #define CURAENGINE_WALLTOOLPATHS_H #include -#include #include "BeadingStrategy/BeadingStrategyFactory.h" #include "settings/Settings.h" @@ -84,30 +83,8 @@ class WallToolPaths */ static bool removeEmptyToolPaths(VariableWidthPaths& toolpaths); - /*! - * Get the order constraints of the insets assuming the Wall Ordering is outer to inner. - * 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 std::unordered_set> getWeakOrder(const std::vector& input, const bool outer_to_inner, const bool include_transitive = true); protected: - /*! - * Recursive part of \ref WallToolpPaths::getWeakOrder. - * For each node at \p node_idx we recurse on all its children at nesting[node_idx] - */ - static void getWeakOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result); - - /*! - * Endpoints of polylines that are closer together than this distance - * will be considered to be coincident, - * closing that polyline into a polygon. - */ - constexpr static coord_t coincident_point_distance = 10; - /*! * Stitches toolpaths together to form contours. * diff --git a/tests/WallsComputationTest.cpp b/tests/WallsComputationTest.cpp index 5be2736d70..1c884d9e57 100644 --- a/tests/WallsComputationTest.cpp +++ b/tests/WallsComputationTest.cpp @@ -8,7 +8,7 @@ #include "../src/utils/polygon.h" //To create example polygons. #include "../src/sliceDataStorage.h" //Sl #include "../src/WallsComputation.h" //Unit under test. -#include "../src/WallToolPaths.h" //Unit also under test. +#include "../src/InsetOrderOptimizer.h" //Unit also under test. #define WALLS_COMPUTATION_TEST_SVG_OUTPUT #ifdef WALLS_COMPUTATION_TEST_SVG_OUTPUT @@ -159,7 +159,7 @@ TEST_F(WallsComputationTest, WallToolPathsGetWeakOrder) for (auto& inset : part.wall_toolpaths) for (auto& line : inset) all_paths.emplace_back(&line); - std::unordered_set> order = WallToolPaths::getWeakOrder(all_paths, outer_to_inner, include_transitive); + std::unordered_set> order = InsetOrderOptimizer::getWeakOrder(all_paths, outer_to_inner, include_transitive); //Verify that something was generated. EXPECT_FALSE(part.wall_toolpaths.empty()) << "There must be some walls."; From 1862f642e400c3811334fdbce62271e3bf5570d9 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sat, 22 Jan 2022 16:52:01 +0100 Subject: [PATCH 21/71] remove region_id from line An extrusion line can have multiple region_ids after it's stitched --- src/SkeletalTrapezoidation.cpp | 13 ------------- src/SkeletalTrapezoidation.h | 5 ----- src/utils/ExtrusionLine.cpp | 1 - src/utils/ExtrusionLine.h | 11 ----------- 4 files changed, 30 deletions(-) diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index ecea2c6a10..de12f409b4 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -508,8 +508,6 @@ void SkeletalTrapezoidation::generateToolpaths(VariableWidthPaths& generated_too markRegions(); generateSegments(); - - liftRegionInfoToLines(); } void SkeletalTrapezoidation::updateIsCentral() @@ -2025,17 +2023,6 @@ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads() } } -void SkeletalTrapezoidation::liftRegionInfoToLines() -{ - std::for_each(p_generated_toolpaths->begin(), p_generated_toolpaths->end(), [](VariableWidthLines& lines) - { - std::for_each(lines.begin(), lines.end(), [](ExtrusionLine& line) - { - line.region_id = line.junctions.front().region_id; - }); - }); -} - // // ^^^^^^^^^^^^^^^^^^^^^ // TOOLPATH GENERATION diff --git a/src/SkeletalTrapezoidation.h b/src/SkeletalTrapezoidation.h index aa2aea263c..3ddf780452 100644 --- a/src/SkeletalTrapezoidation.h +++ b/src/SkeletalTrapezoidation.h @@ -588,11 +588,6 @@ class SkeletalTrapezoidation * Genrate small segments for local maxima where the beading would only result in a single bead */ void generateLocalMaximaSingleBeads(); - - /*! - * Extract region information from the junctions, for easier access to that info directly from the lines. - */ - void liftRegionInfoToLines(); }; } // namespace cura diff --git a/src/utils/ExtrusionLine.cpp b/src/utils/ExtrusionLine.cpp index 9ca14ecdf5..706de2d0d7 100644 --- a/src/utils/ExtrusionLine.cpp +++ b/src/utils/ExtrusionLine.cpp @@ -12,7 +12,6 @@ namespace cura ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd, const size_t region_id) : inset_idx(inset_idx) , is_odd(is_odd) -, region_id(region_id) {} template <> diff --git a/src/utils/ExtrusionLine.h b/src/utils/ExtrusionLine.h index e41f184f24..70da5dcf15 100644 --- a/src/utils/ExtrusionLine.h +++ b/src/utils/ExtrusionLine.h @@ -36,13 +36,6 @@ struct ExtrusionLine */ bool is_odd; - /*! - * Which region this line is part of. A solid polygon without holes has only one region. - * A polygon with holes has 2. Disconnected parts of the polygon are also separate regions. - * Will be 0 if no region was given. - */ - size_t region_id; - /*! * Gets the number of vertices in this polygon. * \return The number of vertices in this polygon. @@ -71,13 +64,11 @@ struct ExtrusionLine ExtrusionLine() : inset_idx(-1) , is_odd(true) - , region_id(-1) {} ExtrusionLine(const ExtrusionLine& other) : inset_idx(other.inset_idx) , is_odd(other.is_odd) - , region_id(other.region_id) , junctions(other.junctions) {} @@ -86,7 +77,6 @@ struct ExtrusionLine junctions = std::move(other.junctions); inset_idx = other.inset_idx; is_odd = other.is_odd; - region_id = other.region_id; return *this; } @@ -95,7 +85,6 @@ struct ExtrusionLine junctions = other.junctions; inset_idx = other.inset_idx; is_odd = other.is_odd; - region_id = other.region_id; return *this; } From 41d382b2e79aebaa9e5b239de64cc2ec52e5ff70 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sun, 23 Jan 2022 12:35:45 +0100 Subject: [PATCH 22/71] lil cleanup --- src/PathOrderOptimizer.h | 3 +-- src/WallToolPaths.cpp | 2 +- src/utils/ExtrusionLine.cpp | 8 +------- src/utils/ExtrusionLine.h | 8 ++------ 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/PathOrderOptimizer.h b/src/PathOrderOptimizer.h index bb1ad4c408..b2573ab157 100644 --- a/src/PathOrderOptimizer.h +++ b/src/PathOrderOptimizer.h @@ -446,8 +446,7 @@ class PathOrderOptimizer auto pos_before_it = pos_after_it; pos_before_it--; Path* path_before = (pos_after_it == optimized_order.begin()) ? nullptr : &pos_before_it->second; - Path* path_after = (pos_after_it == optimized_order.end()) ? nullptr : &pos_after_it->second; - + if (path_before && ! canPrecede(*path_before, to_be_inserted)) { upper_bound = pos_before_it; diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 400a2e48c4..d239276561 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -233,7 +233,7 @@ void WallToolPaths::computeInnerContour() //To get a correct shape, we need to make the outside contour positive and any holes inside negative. //This can be done by applying the even-odd rule to the shape. This rule is not sensitive to the winding order of the polygon. //The even-odd rule would be incorrect if the polygon self-intersects, but that should never be generated by the skeletal trapezoidation. - inner_contour = inner_contour.unionPolygons(Polygons(), ClipperLib::pftEvenOdd); + inner_contour = inner_contour.processEvenOdd(); } const Polygons& WallToolPaths::getInnerContour() diff --git a/src/utils/ExtrusionLine.cpp b/src/utils/ExtrusionLine.cpp index 706de2d0d7..6a725f9503 100644 --- a/src/utils/ExtrusionLine.cpp +++ b/src/utils/ExtrusionLine.cpp @@ -9,7 +9,7 @@ namespace cura { -ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd, const size_t region_id) +ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd) : inset_idx(inset_idx) , is_odd(is_odd) {} @@ -51,12 +51,6 @@ coord_t ExtrusionLine::getMinimalWidth() const })->w; } -void ExtrusionLine::appendJunctionsTo(LineJunctions& result) const -{ - result.insert(result.end(), junctions.begin(), junctions.end()); -} - - void ExtrusionLine::simplify(const coord_t smallest_line_segment_squared, const coord_t allowed_error_distance_squared, const coord_t maximum_extrusion_area_deviation) { if (junctions.size() <= 3) diff --git a/src/utils/ExtrusionLine.h b/src/utils/ExtrusionLine.h index 70da5dcf15..d320b02175 100644 --- a/src/utils/ExtrusionLine.h +++ b/src/utils/ExtrusionLine.h @@ -60,7 +60,8 @@ struct ExtrusionLine */ std::vector junctions; - ExtrusionLine(const size_t inset_idx, const bool is_odd, const size_t region_id = 0); + ExtrusionLine(const size_t inset_idx, const bool is_odd); + ExtrusionLine() : inset_idx(-1) , is_odd(true) @@ -202,11 +203,6 @@ struct ExtrusionLine */ coord_t getMinimalWidth() const; - /*! - * Export the included junctions as vector. - */ - void appendJunctionsTo(LineJunctions& result) const; - /*! * Chop off a segment of \p length of either end of this extrusionline * From 1a0478b17662c046d484f7b0768cbb9a3f9af0ff Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sun, 23 Jan 2022 12:49:18 +0100 Subject: [PATCH 23/71] Mark polygonal ExtrusionLines as such This simplifies later processing and doesn't require splitting of Extrusionlines and restitching them. --- src/InsetOrderOptimizer.cpp | 11 +- src/WallToolPaths.cpp | 295 +++++++++++------------------------- src/WallToolPaths.h | 18 +-- src/utils/ExtrusionLine.cpp | 9 +- src/utils/ExtrusionLine.h | 9 ++ 5 files changed, 112 insertions(+), 230 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index d088b9991b..6f45006376 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -173,7 +173,7 @@ bool InsetOrderOptimizer::addToLayer() bool added_something = false; - constexpr bool detect_loops = true; + constexpr bool detect_loops = false; constexpr Polygons* combing_boundary = nullptr; //When we alternate walls, also alternate the direction at which the first wall starts in. //On even layers we start with normal direction, on odd layers with inverted direction. @@ -183,7 +183,14 @@ bool InsetOrderOptimizer::addToLayer() for (const ExtrusionLine* line : walls_to_be_added) { - order_optimizer.addPolyline(line); + if (line->is_closed) + { + order_optimizer.addPolygon(line); + } + else + { + order_optimizer.addPolyline(line); + } } diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index d239276561..1766af90f6 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -69,44 +69,48 @@ const VariableWidthPaths& WallToolPaths::generate() prepared_outline.removeDegenerateVerts(); prepared_outline.removeSmallAreas(small_area_length * small_area_length, false); - if (prepared_outline.area() > 0) + if (prepared_outline.area() <= 0) { - const coord_t wall_transition_length = settings.get("wall_transition_length"); - const Ratio wall_split_middle_threshold = settings.get("wall_split_middle_threshold"); // For an uneven nr. of lines: When to split the middle wall into two. - const Ratio wall_add_middle_threshold = settings.get("wall_add_middle_threshold"); // For an even nr. of lines: When to add a new middle in between the innermost two walls. - const int wall_distribution_count = settings.get("wall_distribution_count"); - const size_t max_bead_count = (inset_count < std::numeric_limits::max() / 2) ? 2 * inset_count : std::numeric_limits::max(); - const auto beading_strat = BeadingStrategyFactory::makeStrategy - ( - strategy_type, - bead_width_0, - bead_width_x, - wall_transition_length, - transitioning_angle, - print_thin_walls, - min_bead_width, - min_feature_size, - wall_split_middle_threshold, - wall_add_middle_threshold, - max_bead_count, - wall_0_inset, - wall_distribution_count - ); - const coord_t transition_filter_dist = settings.get("wall_transition_filter_distance"); - SkeletalTrapezoidation wall_maker + assert(toolpaths.empty()); + return toolpaths; + } + + const coord_t wall_transition_length = settings.get("wall_transition_length"); + const Ratio wall_split_middle_threshold = settings.get("wall_split_middle_threshold"); // For an uneven nr. of lines: When to split the middle wall into two. + const Ratio wall_add_middle_threshold = settings.get("wall_add_middle_threshold"); // For an even nr. of lines: When to add a new middle in between the innermost two walls. + const int wall_distribution_count = settings.get("wall_distribution_count"); + const size_t max_bead_count = (inset_count < std::numeric_limits::max() / 2) ? 2 * inset_count : std::numeric_limits::max(); + const auto beading_strat = BeadingStrategyFactory::makeStrategy ( - prepared_outline, - *beading_strat, - beading_strat->getTransitioningAngle(), - discretization_step_size, - transition_filter_dist, - wall_transition_length + strategy_type, + bead_width_0, + bead_width_x, + wall_transition_length, + transitioning_angle, + print_thin_walls, + min_bead_width, + min_feature_size, + wall_split_middle_threshold, + wall_add_middle_threshold, + max_bead_count, + wall_0_inset, + wall_distribution_count ); - wall_maker.generateToolpaths(toolpaths); - computeInnerContour(); - } + const coord_t transition_filter_dist = settings.get("wall_transition_filter_distance"); + SkeletalTrapezoidation wall_maker + ( + prepared_outline, + *beading_strat, + beading_strat->getTransitioningAngle(), + discretization_step_size, + transition_filter_dist, + wall_transition_length + ); + wall_maker.generateToolpaths(toolpaths); stitchToolPaths(toolpaths, settings); + + separateOutInnerContour(); simplifyToolPaths(toolpaths, settings); @@ -146,16 +150,13 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting { continue; } - if (wall_polygon.junctions.size() > 1) - { - wall_polygon.junctions.emplace_back(wall_polygon.junctions.front()); // Make polygon back into polyline. TODO: make it possibleto simplify polygonal toolpaths and don't convert from polygons backto polylines here - } + wall_polygon.is_closed = true; wall_lines.emplace_back(std::move(wall_polygon)); } for (ExtrusionLine& line : wall_lines) { - line.inset_idx = wall_idx; + assert(line.inset_idx == wall_idx); } } } @@ -192,25 +193,63 @@ void WallToolPaths::pushToolPaths(VariableWidthPaths& paths) paths.insert(paths.end(), toolpaths.begin(), toolpaths.end()); } -void WallToolPaths::computeInnerContour() +void WallToolPaths::separateOutInnerContour() { //We'll remove all 0-width paths from the original toolpaths and store them separately as polygons. VariableWidthPaths actual_toolpaths; actual_toolpaths.reserve(toolpaths.size()); //A bit too much, but the correct order of magnitude. VariableWidthPaths contour_paths; contour_paths.reserve(toolpaths.size() / inset_count); - std::partition_copy(toolpaths.begin(), toolpaths.end(), std::back_inserter(actual_toolpaths), std::back_inserter(contour_paths), - [](const VariableWidthLines& path) + inner_contour.clear(); + for (const VariableWidthLines& inset : toolpaths) + { + if (inset.empty()) continue; + bool is_contour = false; + for (const ExtrusionLine& line : inset) { - for(const ExtrusionLine& line : path) + for (const ExtrusionJunction& j : line) { - for(const ExtrusionJunction& junction : line.junctions) + if (j.w == 0) + { + is_contour = true; + } + else { - return junction.w != 0; //On the first actual junction, decide: If it's got 0 width, this is a contour. Otherwise it is an actual toolpath. + is_contour = false; } + break; } - return true; //No junctions with any vertices? Classify it as a toolpath then. - }); + } + + + if (is_contour) + { +#ifdef DEBUG + for (const ExtrusionLine& line : inset) + for (const ExtrusionJunction& j : line) + assert(j.w == 0); +#endif // DEBUG + for (const ExtrusionLine& line : inset) + { + if (line.is_odd) + { + continue; // odd lines don't contribute to the contour + } + else + { + assert(line.is_closed && "All contour polylines should have been closed into a polygon"); + if (line.is_closed) + { + inner_contour.emplace_back(line.toPolygon()); + } + } + } + } + else + { + actual_toolpaths.emplace_back(inset); + } + } if (! actual_toolpaths.empty()) { toolpaths = std::move(actual_toolpaths); //Filtered out the 0-width paths. @@ -220,14 +259,6 @@ void WallToolPaths::computeInnerContour() toolpaths.clear(); } - //Now convert the contour_paths to Polygons to denote the inner contour of the walled areas. - inner_contour.clear(); - - //We're going to have to stitch these paths since not all walls may be closed contours. - //Since these walls have 0 width they should theoretically be closed. But there may be rounding errors. - const coord_t minimum_line_width = bead_width_0 / 2; - stitchContours(contour_paths, minimum_line_width, inner_contour); - //The output walls from the skeletal trapezoidation have no known winding order, especially if they are joined together from polylines. //They can be in any direction, clockwise or counter-clockwise, regardless of whether the shapes are positive or negative. //To get a correct shape, we need to make the outside contour positive and any holes inside negative. @@ -258,160 +289,4 @@ bool WallToolPaths::removeEmptyToolPaths(VariableWidthPaths& toolpaths) return toolpaths.empty(); } -void WallToolPaths::stitchContours(const VariableWidthPaths& input, const coord_t stitch_distance, Polygons& output) -{ - //Create a bucket grid to find endpoints that are close together. - struct ExtrusionLineStartLocator - { - Point operator()(const ExtrusionLine* line) - { - return Point(line->junctions.front().p); - } - }; - struct ExtrusionLineEndLocator - { - Point operator()(const ExtrusionLine* line) - { - return Point(line->junctions.back().p); - } - }; - SparsePointGrid line_starts(stitch_distance); //Only find endpoints closer than minimum_line_width, so we can't ever accidentally make crossing contours. - SparsePointGrid line_ends(stitch_distance); - for(const VariableWidthLines& path : input) - { - for(const ExtrusionLine& line : path) - { - line_starts.insert(&line); - line_ends.insert(&line); - } - } - //Then go through all lines and construct chains of polylines if the endpoints are nearby. - std::unordered_set processed_lines; //Track which lines were already processed to not process them twice. - for(const VariableWidthLines& path : input) - { - for(const ExtrusionLine& line : path) - { - if(processed_lines.find(&line) != processed_lines.end()) //We already added this line before. It got added as a nearby line. - { - continue; - } - //We'll create a chain of polylines that get joined together. We can add polylines on both ends! - std::deque chain; - std::deque is_reversed; //Lines could need to be inserted in reverse. Must coincide with the `chain` deque. - const ExtrusionLine* nearest = &line; //At every iteration, add the polyline that joins together most closely. - bool nearest_reverse = false; //Whether the next line to insert must be inserted in reverse. - bool nearest_before = false; //Whether the next line to insert must be inserted in the front of the chain. - while(nearest) - { - if(processed_lines.find(nearest) != processed_lines.end()) - { - break; //Looping. This contour is already processed. - } - processed_lines.insert(nearest); - if(nearest_before) - { - chain.push_front(nearest); - is_reversed.push_front(nearest_reverse); - } - else - { - chain.push_back(nearest); - is_reversed.push_back(nearest_reverse); - } - - //Find any nearby lines to attach. Look on both ends of our current chain and find both ends of polylines. - const Point chain_start = is_reversed.front() ? chain.front()->junctions.back().p : chain.front()->junctions.front().p; - const Point chain_end = is_reversed.back() ? chain.back()->junctions.front().p : chain.back()->junctions.back().p; - std::vector starts_near_start = line_starts.getNearby(chain_start, stitch_distance); - std::vector ends_near_start = line_ends.getNearby(chain_start, stitch_distance); - std::vector starts_near_end = line_starts.getNearby(chain_end, stitch_distance); - std::vector ends_near_end = line_ends.getNearby(chain_end, stitch_distance); - - nearest = nullptr; - coord_t nearest_dist2 = std::numeric_limits::max(); - for(const ExtrusionLine* candidate : starts_near_start) - { - if(processed_lines.find(candidate) != processed_lines.end()) - { - continue; //Already processed this line before. It's linked to something else. - } - const coord_t dist2 = vSize2(candidate->junctions.front().p - chain_start); - if(dist2 < nearest_dist2) - { - nearest = candidate; - nearest_dist2 = dist2; - nearest_reverse = true; - nearest_before = true; - } - } - for(const ExtrusionLine* candidate : ends_near_start) - { - if(processed_lines.find(candidate) != processed_lines.end()) - { - continue; - } - const coord_t dist2 = vSize2(candidate->junctions.back().p - chain_start); - if(dist2 < nearest_dist2) - { - nearest = candidate; - nearest_dist2 = dist2; - nearest_reverse = false; - nearest_before = true; - } - } - for(const ExtrusionLine* candidate : starts_near_end) - { - if(processed_lines.find(candidate) != processed_lines.end()) - { - continue; //Already processed this line before. It's linked to something else. - } - const coord_t dist2 = vSize2(candidate->junctions.front().p - chain_start); - if(dist2 < nearest_dist2) - { - nearest = candidate; - nearest_dist2 = dist2; - nearest_reverse = false; - nearest_before = false; - } - } - for(const ExtrusionLine* candidate : ends_near_end) - { - if(processed_lines.find(candidate) != processed_lines.end()) - { - continue; - } - const coord_t dist2 = vSize2(candidate->junctions.back().p - chain_start); - if(dist2 < nearest_dist2) - { - nearest = candidate; - nearest_dist2 = dist2; - nearest_reverse = true; - nearest_before = false; - } - } - } - - //Now serialize the entire chain into one polygon. - output.emplace_back(); - for(size_t i = 0; i < chain.size(); ++i) - { - if(!is_reversed[i]) - { - for(const ExtrusionJunction& junction : chain[i]->junctions) - { - output.back().add(junction.p); - } - } - else - { - for(auto junction = chain[i]->junctions.rbegin(); junction != chain[i]->junctions.rend(); ++junction) - { - output.back().add(junction->p); - } - } - } - } - } -} - } // namespace cura diff --git a/src/WallToolPaths.h b/src/WallToolPaths.h index 14d491bd6c..54568c3c57 100644 --- a/src/WallToolPaths.h +++ b/src/WallToolPaths.h @@ -60,7 +60,7 @@ class WallToolPaths * The inside can then be filled, e.g. with skin/infill for the walls of a part, or with a pattern in the case of * infill with extra infill walls. */ - void computeInnerContour(); + void separateOutInnerContour(); /*! * Gets the inner contour of the area which is inside of the generated tool @@ -84,22 +84,6 @@ class WallToolPaths static bool removeEmptyToolPaths(VariableWidthPaths& toolpaths); protected: - - /*! - * Stitches toolpaths together to form contours. - * - * All toolpaths are used. Paths that are not closed will get closed in the - * output by virtue of becoming polygons. As such, the input is expected to - * consist of almost completely closed contours, which may be split up into - * different polylines. - * This function combines those polylines into the polygons they are - * probably intended to depict. - * \param input The paths to stitch together. - * \param stitch_distance Any endpoints closer than this distance can be - * stitched together. An additional line segment will bridge the gap. - * \param output Where to store the output polygons. - */ - static void stitchContours(const VariableWidthPaths& input, const coord_t stitch_distance, Polygons& output) ; /*! * Stitch the polylines together and form closed polygons. * \param settings The settings as provided by the user diff --git a/src/utils/ExtrusionLine.cpp b/src/utils/ExtrusionLine.cpp index 6a725f9503..e5a1ad2c8e 100644 --- a/src/utils/ExtrusionLine.cpp +++ b/src/utils/ExtrusionLine.cpp @@ -12,6 +12,7 @@ namespace cura ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd) : inset_idx(inset_idx) , is_odd(is_odd) +, is_closed(false) {} template <> @@ -39,6 +40,10 @@ coord_t ExtrusionLine::getLength() const len += vSize(next.p - prev.p); prev = next; } + if (is_closed) + { + len += vSize(front().p - back().p); + } return len; } @@ -53,11 +58,13 @@ coord_t ExtrusionLine::getMinimalWidth() const void ExtrusionLine::simplify(const coord_t smallest_line_segment_squared, const coord_t allowed_error_distance_squared, const coord_t maximum_extrusion_area_deviation) { - if (junctions.size() <= 3) + if (junctions.size() <= 2 + is_closed) { return; } + // TODO: allow for the first point to be removed in case of simplifying closed Extrusionlines. + /* ExtrusionLines are treated as (open) polylines, so in case an ExtrusionLine is actually a closed polygon, its * starting and ending points will be equal (or almost equal). Therefore, the simplification of the ExtrusionLine * should not touch the first and last points. As a result, start simplifying from point at index 1. diff --git a/src/utils/ExtrusionLine.h b/src/utils/ExtrusionLine.h index d320b02175..28419d2014 100644 --- a/src/utils/ExtrusionLine.h +++ b/src/utils/ExtrusionLine.h @@ -36,6 +36,11 @@ struct ExtrusionLine */ bool is_odd; + /*! + * Whether this is a closed polygonal path + */ + bool is_closed; + /*! * Gets the number of vertices in this polygon. * \return The number of vertices in this polygon. @@ -65,11 +70,13 @@ struct ExtrusionLine ExtrusionLine() : inset_idx(-1) , is_odd(true) + , is_closed(false) {} ExtrusionLine(const ExtrusionLine& other) : inset_idx(other.inset_idx) , is_odd(other.is_odd) + , is_closed(other.is_closed) , junctions(other.junctions) {} @@ -78,6 +85,7 @@ struct ExtrusionLine junctions = std::move(other.junctions); inset_idx = other.inset_idx; is_odd = other.is_odd; + is_closed = other.is_closed; return *this; } @@ -86,6 +94,7 @@ struct ExtrusionLine junctions = other.junctions; inset_idx = other.inset_idx; is_odd = other.is_odd; + is_closed = other.is_closed; return *this; } From f23d587d0ddbd253dde3827be03e4b48b45ce58d Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Sun, 23 Jan 2022 12:51:51 +0100 Subject: [PATCH 24/71] remove debug svg output --- src/InsetOrderOptimizer.cpp | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 6f45006376..de4a3b7a91 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -143,31 +143,6 @@ bool InsetOrderOptimizer::addToLayer() // [before] cannot precede [after] if we have an order constraint that [after] must be before [before] return ! order.count(std::make_pair(after.vertices, before.vertices)); }; -#ifdef DEBUG - { - AABB aabb; - for (auto& inset : paths) - for (auto& line : inset) - for (auto p : line) - aabb.include(p.p); - SVG svg("/tmp/order.svg", aabb); - for (auto& inset : paths) - for (auto& line : inset) - svg.writePolyline(line.toPolygon(), line.is_odd? SVG::Color::RED : SVG::Color::GREEN); - svg.nextLayer(); - for (auto& inset : paths) - for (auto& line : inset) - svg.writePoints(line.toPolygon(), true, 1.0); - svg.nextLayer(); - for (auto [before, after] : order) - if ( ! after->is_odd) - svg.writeArrow(before->junctions[1 % before->junctions.size()].p, after->junctions[2 % after->junctions.size()].p, SVG::Color::BLUE); - svg.nextLayer(); - for (auto [before, after] : order) - if (after->is_odd) - svg.writeArrow(before->junctions[1 % before->junctions.size()].p, after->junctions[2 % after->junctions.size()].p, SVG::Color::MAGENTA); - } -#endif // DEBUG constexpr Ratio flow = 1.0_r; @@ -259,18 +234,6 @@ std::unordered_set> InsetO std::vector> nesting = all_polygons.getNesting(); - { - SVG svg("/tmp/nesting.svg", AABB(all_polygons)); - svg.writePolygons(all_polygons, SVG::Color::BLUE); - for (size_t i = 0; i < all_polygons.size(); i++) - { - for (size_t child_idx : nesting[i]) - { - svg.writeArrow(all_polygons[i][0], all_polygons[child_idx][1]); - } - } - } - std::unordered_set> result; for (auto [idx, line] : poly_idx_to_extrusionline) From 1f54e3c6e9b5db43276d54f28ee64576aab04057 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 10:17:35 +0100 Subject: [PATCH 25/71] const correctness and reference correctness for Polygon --- src/utils/polygon.h | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/utils/polygon.h b/src/utils/polygon.h index 83e82357e9..3176df0875 100644 --- a/src/utils/polygon.h +++ b/src/utils/polygon.h @@ -82,6 +82,8 @@ class ConstPolygonRef : path(const_cast(&polygon)) {} + ConstPolygonRef() =delete; // you cannot have a reference without an object! + virtual ~ConstPolygonRef() { } @@ -411,6 +413,8 @@ class PolygonRef : public ConstPolygonRef : ConstPolygonRef(*other.path) {} + PolygonRef() =delete; // you cannot have a reference without an object! + virtual ~PolygonRef() { } @@ -611,28 +615,39 @@ class ConstPolygonPointer } }; -class PolygonPointer +class PolygonPointer : public ConstPolygonPointer { -protected: - ClipperLib::Path* path; public: PolygonPointer() - : path(nullptr) + : ConstPolygonPointer(nullptr) {} PolygonPointer(PolygonRef* ref) - : path(ref->path) + : ConstPolygonPointer(ref) {} PolygonPointer(PolygonRef& ref) - : path(ref.path) + : ConstPolygonPointer(ref) {} PolygonRef operator*() { assert(path); - return PolygonRef(*path); + return PolygonRef(*const_cast(path)); } + + ConstPolygonRef operator*() const + { + assert(path); + return ConstPolygonRef(*path); + } + ClipperLib::Path* operator->() + { + assert(path); + return const_cast(path); + } + + const ClipperLib::Path* operator->() const { assert(path); return path; From 8c79bd53a6920df99c1fc388982daa9db9688566 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 10:17:54 +0100 Subject: [PATCH 26/71] std::hash for PolygonPointer --- src/utils/polygon.h | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/utils/polygon.h b/src/utils/polygon.h index 3176df0875..df768e9388 100644 --- a/src/utils/polygon.h +++ b/src/utils/polygon.h @@ -659,6 +659,40 @@ class PolygonPointer : public ConstPolygonPointer } }; +} // namespace cura + + +namespace std +{ +template<> +struct hash +{ + size_t operator()(const cura::ConstPolygonRef& poly) const + { + return std::hash()(&*poly); + } +}; +template<> +struct hash +{ + size_t operator()(const cura::ConstPolygonPointer& poly) const + { + return std::hash()(&**poly); + } +}; +template<> +struct hash +{ + size_t operator()(const cura::PolygonPointer& poly) const + { + const cura::ConstPolygonRef ref = *static_cast(poly); + return std::hash()(&*ref); + } +}; +}//namespace std + +namespace cura { + class Polygon : public PolygonRef { ClipperLib::Path poly; From e120eac6f4a9ce5d55a2148c0755ca208a9152e1 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 10:27:44 +0100 Subject: [PATCH 27/71] Disallow containers of references Components of containers must be assignable. Since std::vector is not allowed, we also shouldn't use container --- src/LayerPlan.cpp | 26 +++++++++++++------------- src/PathOrderOptimizer.cpp | 8 ++++---- src/Wireframe2gcode.cpp | 10 +++++----- tests/PathOrderOptimizerTest.cpp | 12 ++++++------ 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 89280ea8a7..a0f2554c78 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -571,7 +571,7 @@ void LayerPlan::addPolygonsByOptimizer(const Polygons& polygons, const GCodePath { return; } - PathOrderOptimizer orderOptimizer(start_near_location ? start_near_location.value() : getLastPlannedPositionOrStartingPosition(), z_seam_config); + PathOrderOptimizer orderOptimizer(start_near_location ? start_near_location.value() : getLastPlannedPositionOrStartingPosition(), z_seam_config); for(size_t poly_idx = 0; poly_idx < polygons.size(); poly_idx++) { orderOptimizer.addPolygon(polygons[poly_idx]); @@ -580,7 +580,7 @@ void LayerPlan::addPolygonsByOptimizer(const Polygons& polygons, const GCodePath if(!reverse_order) { - for(const PathOrderOptimizer::Path& path : orderOptimizer.paths) + for(const PathOrderOptimizer::Path& path : orderOptimizer.paths) { addPolygon(*path.vertices, path.start_vertex, path.backwards, config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract); } @@ -589,8 +589,8 @@ void LayerPlan::addPolygonsByOptimizer(const Polygons& polygons, const GCodePath { for(int index = orderOptimizer.paths.size() - 1; index >= 0; --index) { - const PathOrderOptimizer::Path& path = orderOptimizer.paths[index]; - addPolygon(*path.vertices, path.start_vertex, path.backwards, config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract); + const PathOrderOptimizer::Path& path = orderOptimizer.paths[index]; + addPolygon(**path.vertices, path.start_vertex, path.backwards, config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract); } } } @@ -1051,15 +1051,15 @@ void LayerPlan::addWalls ) { //TODO: Deprecated in favor of ExtrusionJunction version below. - PathOrderOptimizer orderOptimizer(getLastPlannedPositionOrStartingPosition(), z_seam_config); + PathOrderOptimizer orderOptimizer(getLastPlannedPositionOrStartingPosition(), z_seam_config); for(size_t poly_idx = 0; poly_idx < walls.size(); poly_idx++) { orderOptimizer.addPolygon(walls[poly_idx]); } orderOptimizer.optimize(); - for(const PathOrderOptimizer::Path& path : orderOptimizer.paths) + for(const PathOrderOptimizer::Path& path : orderOptimizer.paths) { - addWall(*path.vertices, path.start_vertex, settings, non_bridge_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract); + addWall(**path.vertices, path.start_vertex, settings, non_bridge_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract); } } @@ -1100,7 +1100,7 @@ void LayerPlan::addLinesByOptimizer boundary.simplify(MM2INT(0.1), MM2INT(0.1)); } constexpr bool detect_loops = true; - PathOrderOptimizer order_optimizer(near_start_location.value_or(getLastPlannedPositionOrStartingPosition()), ZSeamConfig(), detect_loops, &boundary, reverse_print_direction); + PathOrderOptimizer order_optimizer(near_start_location.value_or(getLastPlannedPositionOrStartingPosition()), ZSeamConfig(), detect_loops, &boundary, reverse_print_direction); for(size_t line_idx = 0; line_idx < polygons.size(); line_idx++) { order_optimizer.addPolyline(polygons[line_idx]); @@ -1111,7 +1111,7 @@ void LayerPlan::addLinesByOptimizer coord_t line_width_2 = half_line_width * half_line_width; for(size_t order_idx = 0; order_idx < order_optimizer.paths.size(); order_idx++) { - const PathOrderOptimizer::Path& path = order_optimizer.paths[order_idx]; + const PathOrderOptimizer::Path& path = order_optimizer.paths[order_idx]; ConstPolygonRef polyline = *path.vertices; const size_t start_idx = path.start_vertex; const Point start = polyline[start_idx]; @@ -1160,7 +1160,7 @@ void LayerPlan::addLinesByOptimizer // Don't wipe if next starting point is very near if(wipe && (order_idx < order_optimizer.paths.size() - 1)) { - const PathOrderOptimizer::Path& next_path = order_optimizer.paths[order_idx + 1]; + const PathOrderOptimizer::Path& next_path = order_optimizer.paths[order_idx + 1]; ConstPolygonRef next_polygon = *next_path.vertices; const size_t next_start = next_path.start_vertex; const Point& next_p0 = next_polygon[next_start]; @@ -1197,7 +1197,7 @@ void LayerPlan::addLinesMonotonic const Point last_position = getLastPlannedPositionOrStartingPosition(); // First lay all adjacent lines next to each other, to have a sensible input to the monotonic part of the algorithm. - PathOrderOptimizer line_order(last_position); + PathOrderOptimizer line_order(last_position); for(const ConstPolygonRef polyline : polygons) { line_order.addPolyline(polyline); @@ -1216,9 +1216,9 @@ void LayerPlan::addLinesMonotonic bool last_would_have_been_excluded = false; for(size_t line_idx = 0; line_idx < line_order.paths.size(); ++line_idx) { - const ConstPolygonRef polyline = line_order.paths[line_idx].vertices; + const ConstPolygonRef polyline = *line_order.paths[line_idx].vertices; const bool inside_exclusion = is_inside_exclusion(polyline); - const bool next_would_have_been_included = inside_exclusion && (line_idx < line_order.paths.size() - 1 && is_inside_exclusion(line_order.paths[line_idx + 1].vertices)); + const bool next_would_have_been_included = inside_exclusion && (line_idx < line_order.paths.size() - 1 && is_inside_exclusion(*line_order.paths[line_idx + 1].vertices)); if (inside_exclusion && last_would_have_been_excluded && next_would_have_been_included) { left_over.add(polyline); diff --git a/src/PathOrderOptimizer.cpp b/src/PathOrderOptimizer.cpp index 951e967b5b..c5fe5a6ead 100644 --- a/src/PathOrderOptimizer.cpp +++ b/src/PathOrderOptimizer.cpp @@ -11,15 +11,15 @@ namespace cura { template<> - ConstPolygonRef PathOrderOptimizer::getVertexData(ConstPolygonRef path) + ConstPolygonRef PathOrderOptimizer::getVertexData(ConstPolygonPointer path) { - return path; + return *path; } template<> - ConstPolygonRef PathOrderOptimizer::getVertexData(PolygonRef path) + ConstPolygonRef PathOrderOptimizer::getVertexData(PolygonPointer path) { - return path; + return *path; } template<> diff --git a/src/Wireframe2gcode.cpp b/src/Wireframe2gcode.cpp index 2d358685db..1702a7cfc9 100644 --- a/src/Wireframe2gcode.cpp +++ b/src/Wireframe2gcode.cpp @@ -637,7 +637,7 @@ void Wireframe2gcode::processSkirt() return; } Polygons skirt = wireFrame.bottom_outline.offset(MM2INT(100 + 5), ClipperLib::jtRound).offset(MM2INT(-100), ClipperLib::jtRound); - PathOrderOptimizer order(Point(INT32_MIN, INT32_MIN)); + PathOrderOptimizer order(Point(INT32_MIN, INT32_MIN)); for(PolygonRef skirt_path : skirt) { order.addPolygon(skirt_path); @@ -645,12 +645,12 @@ void Wireframe2gcode::processSkirt() order.optimize(); const Settings& scene_settings = Application::getInstance().current_slice->scene.settings; - for(const PathOrderOptimizer::Path& path : order.paths) + for(const PathOrderOptimizer::Path& path : order.paths) { - gcode.writeTravel(path.vertices[path.start_vertex], scene_settings.get("speed_travel")); - for(size_t vertex_index = 0; vertex_index < path.vertices.size(); ++vertex_index) + gcode.writeTravel((*path.vertices)[path.start_vertex], scene_settings.get("speed_travel")); + for(size_t vertex_index = 0; vertex_index < path.vertices->size(); ++vertex_index) { - Point vertex = path.vertices[(vertex_index + path.start_vertex + 1) % path.vertices.size()]; + Point vertex = (*path.vertices)[(vertex_index + path.start_vertex + 1) % path.vertices->size()]; gcode.writeExtrusion(vertex, scene_settings.get("skirt_brim_speed"), scene_settings.get("skirt_brim_line_width") * scene_settings.get("initial_layer_line_width_factor") * INT2MM(initial_layer_thickness), PrintFeatureType::SkirtBrim); } } diff --git a/tests/PathOrderOptimizerTest.cpp b/tests/PathOrderOptimizerTest.cpp index 54540a8684..8b794d6562 100644 --- a/tests/PathOrderOptimizerTest.cpp +++ b/tests/PathOrderOptimizerTest.cpp @@ -13,7 +13,7 @@ class PathOrderOptimizerTest : public testing::Test /*! * A blank optimizer with no polygons added yet. Fresh and virgin. */ - PathOrderOptimizer optimizer; + PathOrderOptimizer optimizer; /*! * A simple isosceles triangle. Base length and height 50. @@ -24,7 +24,7 @@ class PathOrderOptimizerTest : public testing::Test void SetUp() { - optimizer = PathOrderOptimizer(Point(0, 0)); + optimizer = PathOrderOptimizer(Point(0, 0)); triangle.clear(); triangle.add(Point(0, 0)); @@ -62,9 +62,9 @@ TEST_F(PathOrderOptimizerTest, ThreeTrianglesShortestOrder) optimizer.optimize(); - EXPECT_EQ(optimizer.paths[0].vertices[0], Point(100, 100)) << "Nearest triangle first."; - EXPECT_EQ(optimizer.paths[1].vertices[0], Point(500, 500)) << "Middle triangle second."; - EXPECT_EQ(optimizer.paths[2].vertices[0], Point(1000, 1000)) << "Far triangle last."; + EXPECT_EQ(optimizer.paths[0].vertices->front(), Point(100, 100)) << "Nearest triangle first."; + EXPECT_EQ(optimizer.paths[1].vertices->front(), Point(500, 500)) << "Middle triangle second."; + EXPECT_EQ(optimizer.paths[2].vertices->front(), Point(1000, 1000)) << "Far triangle last."; } -} \ No newline at end of file +} From d0428133050be8d69448c8dcbda89160b3c1ff7b Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 10:28:58 +0100 Subject: [PATCH 28/71] make order requirements native to pathOrderOptimizer The canPrecede predicate was less informative. Using the order_requirements natively allows for more flexible code --- src/InsetOrderOptimizer.cpp | 43 +++------------------------------- src/InsetOrderOptimizer.h | 43 ++++++++++++++++++++++++++++++++-- src/PathOrderOptimizer.h | 33 +++++++++++++++++++------- tests/WallsComputationTest.cpp | 3 +-- 4 files changed, 70 insertions(+), 52 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index de4a3b7a91..0fee54ee9f 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -124,8 +124,7 @@ bool InsetOrderOptimizer::addToLayer() } - constexpr bool include_transitive = true; - std::unordered_set> order = getWeakOrder(walls_to_be_added, outer_to_inner, include_transitive); + std::unordered_set> order = getWeakOrder(walls_to_be_added, outer_to_inner); if (center_last) { @@ -136,14 +135,6 @@ bool InsetOrderOptimizer::addToLayer() order.emplace(std::make_pair(other_line, line)); } - using Optimizer = PathOrderOptimizer; - std::function canPrecede = - [&order](const Optimizer::Path& before, const Optimizer::Path& after) - { - // [before] cannot precede [after] if we have an order constraint that [after] must be before [before] - return ! order.count(std::make_pair(after.vertices, before.vertices)); - }; - constexpr Ratio flow = 1.0_r; bool added_something = false; @@ -154,7 +145,7 @@ bool InsetOrderOptimizer::addToLayer() //On even layers we start with normal direction, on odd layers with inverted direction. constexpr bool reverse_all_paths = false; constexpr bool selection_optimization = false; - PathOrderOptimizer order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition(), z_seam_config, detect_loops, combing_boundary, reverse_all_paths, selection_optimization, canPrecede); + PathOrderOptimizer order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition(), z_seam_config, detect_loops, combing_boundary, reverse_all_paths, selection_optimization, order); for (const ExtrusionLine* line : walls_to_be_added) { @@ -203,7 +194,7 @@ bool InsetOrderOptimizer::addToLayer() -std::unordered_set> InsetOrderOptimizer::getWeakOrder(const std::vector& input, const bool outer_to_inner, const bool include_transitive) +std::unordered_set> InsetOrderOptimizer::getWeakOrder(const std::vector& input, const bool outer_to_inner) { size_t max_inset_idx = 0; Polygons all_polygons; @@ -244,34 +235,6 @@ std::unordered_set> InsetO } } - if (include_transitive) - { - std::unordered_multimap order_mapping; - for (auto [from, to] : result) - { - order_mapping.emplace(from, to); - } - std::unordered_set> transitive_order = result; - for (auto [from, to] : result) - { - std::queue starts_of_next_relation; - starts_of_next_relation.emplace(to); - while ( ! starts_of_next_relation.empty()) - { - const ExtrusionLine* start_of_next_relation = starts_of_next_relation.front(); - starts_of_next_relation.pop(); - auto range = order_mapping.equal_range(start_of_next_relation); - for (auto it = range.first; it != range.second; ++it) - { - auto [ next_from, next_to ] = *it; - starts_of_next_relation.emplace(next_to); - transitive_order.emplace(from, next_to); - } - } - } - result = transitive_order; - } - return result; } diff --git a/src/InsetOrderOptimizer.h b/src/InsetOrderOptimizer.h index 83f693c34c..826a01c80d 100644 --- a/src/InsetOrderOptimizer.h +++ b/src/InsetOrderOptimizer.h @@ -6,8 +6,8 @@ #include -#include "PathOrderOptimizer.h" #include "sliceDataStorage.h" //For SliceMeshStorage, which is used here at implementation in the header. +#include "settings/ZSeamConfig.h" namespace cura { @@ -70,7 +70,14 @@ class InsetOrderOptimizer * * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. */ - static std::unordered_set> getWeakOrder(const std::vector& input, const bool outer_to_inner, const bool include_transitive = true); + static std::unordered_set> getWeakOrder(const std::vector& input, const bool outer_to_inner); + + /*! + * Make order requirements transitive. + * If the input contains A,B and B,C then after this call it will also include A,C. + */ + template + static std::unordered_set> makeOrderIncludeTransitive(const std::unordered_set>& order_requirements); private: /*! @@ -110,6 +117,38 @@ class InsetOrderOptimizer constexpr static coord_t coincident_point_distance = 10; }; + +template +std::unordered_set> InsetOrderOptimizer::makeOrderIncludeTransitive(const std::unordered_set>& order_requirements) +{ + if (order_requirements.empty()) return order_requirements; + + std::unordered_multimap order_mapping; + for (auto [from, to] : order_requirements) + { + order_mapping.emplace(from, to); + } + std::unordered_set> transitive_order = order_requirements; + for (auto [from, to] : order_requirements) + { + std::queue starts_of_next_relation; + starts_of_next_relation.emplace(to); + while ( ! starts_of_next_relation.empty()) + { + PathType start_of_next_relation = starts_of_next_relation.front(); + starts_of_next_relation.pop(); + auto range = order_mapping.equal_range(start_of_next_relation); + for (auto it = range.first; it != range.second; ++it) + { + auto [ next_from, next_to ] = *it; + starts_of_next_relation.emplace(next_to); + transitive_order.emplace(from, next_to); + } + } + } + return transitive_order; +} + } //namespace cura #endif // INSET_ORDER_OPTIMIZER_H diff --git a/src/PathOrderOptimizer.h b/src/PathOrderOptimizer.h index b2573ab157..4c8d18c28b 100644 --- a/src/PathOrderOptimizer.h +++ b/src/PathOrderOptimizer.h @@ -4,6 +4,10 @@ #ifndef PATHORDEROPTIMIZER_H #define PATHORDEROPTIMIZER_H + +#include + +#include "InsetOrderOptimizer.h" // for makeOrderIncludeTransitive #include "pathPlanning/CombPath.h" //To calculate the combing distance if we want to use combing. #include "pathPlanning/LinePolygonsCrossings.h" //To prevent calculating combing distances if we don't cross the combing borders. #include "settings/EnumSettings.h" //To get the seam settings. @@ -167,14 +171,14 @@ class PathOrderOptimizer * it into a polygon. * \param combing_boundary Boundary to avoid when making travel moves. */ - PathOrderOptimizer(const Point start_point, const ZSeamConfig seam_config = ZSeamConfig(), const bool detect_loops = false, const Polygons* combing_boundary = nullptr, const bool reverse_direction = false, bool selection_optimization = true, const std::function& canPrecede = canAlwaysPrecede) + PathOrderOptimizer(const Point start_point, const ZSeamConfig seam_config = ZSeamConfig(), const bool detect_loops = false, const Polygons* combing_boundary = nullptr, const bool reverse_direction = false, bool selection_optimization = true, const std::unordered_set>& order_requirements = no_order_requirements) : start_point(start_point) , seam_config(seam_config) , combing_boundary((combing_boundary != nullptr && !combing_boundary->empty()) ? combing_boundary : nullptr) , detect_loops(detect_loops) , reverse_direction(reverse_direction) , selection_optimization(selection_optimization) - , canPrecede(canPrecede) + , order_requirements(&order_requirements) { } @@ -397,6 +401,16 @@ class PathOrderOptimizer void optimizeInsertion() { + const std::unordered_set> transitive_requirements = InsetOrderOptimizer::makeOrderIncludeTransitive(*order_requirements); + + using Optimizer = PathOrderOptimizer; + std::function canPrecede = + [&transitive_requirements](const Path& before, const Path& after) + { + // [before] cannot precede [after] if we have an order constraint that [after] must be before [before] + return ! transitive_requirements.count(std::make_pair(after.vertices, before.vertices)); + }; + if (seam_config.type == EZSeamType::USER_SPECIFIED) { start_point = seam_config.pos; // WARNING: is this correct?! @@ -620,10 +634,14 @@ class PathOrderOptimizer * Insertion sort can be wildly inefficient when polylines haven't been stitched. */ bool selection_optimization; - - static std::function canAlwaysPrecede; - std::function canPrecede; + static const std::unordered_set> no_order_requirements; + + /*! + * Order requirements on the paths. + * For each pair the first needs to be printe before the second. + */ + const std::unordered_set>* order_requirements; /*! * Find the vertex which will be the starting point of printing a polygon or @@ -876,9 +894,8 @@ class PathOrderOptimizer ConstPolygonRef getVertexData(const PathType path); }; -template -std::function::Path&, const typename PathOrderOptimizer::Path&)> PathOrderOptimizer::canAlwaysPrecede = - std::function( [](const Path&, const Path&) { return true; } ); +template +const std::unordered_set> PathOrderOptimizer::no_order_requirements; } //namespace cura diff --git a/tests/WallsComputationTest.cpp b/tests/WallsComputationTest.cpp index 1c884d9e57..a3cd5d6315 100644 --- a/tests/WallsComputationTest.cpp +++ b/tests/WallsComputationTest.cpp @@ -154,12 +154,11 @@ TEST_F(WallsComputationTest, WallToolPathsGetWeakOrder) walls_computation.generateWalls(&layer); const bool outer_to_inner = false; - const bool include_transitive = false; std::vector all_paths; for (auto& inset : part.wall_toolpaths) for (auto& line : inset) all_paths.emplace_back(&line); - std::unordered_set> order = InsetOrderOptimizer::getWeakOrder(all_paths, outer_to_inner, include_transitive); + std::unordered_set> order = InsetOrderOptimizer::getWeakOrder(all_paths, outer_to_inner); //Verify that something was generated. EXPECT_FALSE(part.wall_toolpaths.empty()) << "There must be some walls."; From c2e5cc0e7c5ad2570228dce8bff3d0a8ac39c52b Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 11:01:23 +0100 Subject: [PATCH 29/71] implement order requirements on selection sort path optimizer --- src/PathOrderOptimizer.h | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/PathOrderOptimizer.h b/src/PathOrderOptimizer.h index 4c8d18c28b..d0535e5ab7 100644 --- a/src/PathOrderOptimizer.h +++ b/src/PathOrderOptimizer.h @@ -290,6 +290,25 @@ class PathOrderOptimizer path.start_vertex = findStartLocation(path, seam_config.pos); } } + + std::vector blocked(paths.size(), 0); // Flag for seeing whether a path is blocked by a preceding toolpath to be printed first (and how many such blocking toolpaths there are) + std::vector> is_blocking(paths.size()); // For each path all paths that it is blocking, i.e. each path that it should precede + std::unordered_map path_to_index; + for (size_t idx = 0; idx < paths.size(); idx++) + { + path_to_index.emplace(paths[idx].vertices, idx); + } + for (auto [before, after] : *order_requirements) + { + auto after_it = path_to_index.find(after); + assert(after_it != path_to_index.end()); + blocked[after_it->second]++; + + auto before_it = path_to_index.find(before); + assert(before_it != path_to_index.end()); + is_blocking[before_it->second].emplace_back(after_it->second); + } + std::vector picked(paths.size(), false); //Fixed size boolean flag for whether each path is already in the optimized vector. Point current_position = start_point; @@ -306,7 +325,7 @@ class PathOrderOptimizer available_candidates.reserve(nearby_candidates.size()); for(const size_t candidate : nearby_candidates) { - if(picked[candidate]) + if(picked[candidate] || blocked[candidate]) { continue; //Not a valid candidate. } @@ -316,7 +335,7 @@ class PathOrderOptimizer { for(size_t candidate = 0; candidate < paths.size(); ++candidate) { - if(picked[candidate]) + if(picked[candidate] || blocked[candidate]) { continue; //Not a valid candidate. } @@ -360,6 +379,10 @@ class PathOrderOptimizer Path& best_path = paths[best_candidate]; optimized_order.push_back(best_path); picked[best_candidate] = true; + for (size_t unlocked_idx : is_blocking[best_candidate]) + { + blocked[unlocked_idx]--; + } if(!best_path.converted->empty()) //If all paths were empty, the best path is still empty. We don't upate the current position then. { From d3819c6d759d0c8654f9256db25c2e66417937b1 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 11:06:33 +0100 Subject: [PATCH 30/71] remove insertion sort path order optimizer --- src/InsetOrderOptimizer.cpp | 3 +- src/PathOrderOptimizer.h | 202 +----------------------------------- 2 files changed, 3 insertions(+), 202 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 0fee54ee9f..dbd75d1132 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -144,8 +144,7 @@ bool InsetOrderOptimizer::addToLayer() //When we alternate walls, also alternate the direction at which the first wall starts in. //On even layers we start with normal direction, on odd layers with inverted direction. constexpr bool reverse_all_paths = false; - constexpr bool selection_optimization = false; - PathOrderOptimizer order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition(), z_seam_config, detect_loops, combing_boundary, reverse_all_paths, selection_optimization, order); + PathOrderOptimizer order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition(), z_seam_config, detect_loops, combing_boundary, reverse_all_paths, order); for (const ExtrusionLine* line : walls_to_be_added) { diff --git a/src/PathOrderOptimizer.h b/src/PathOrderOptimizer.h index d0535e5ab7..9fb136a1d7 100644 --- a/src/PathOrderOptimizer.h +++ b/src/PathOrderOptimizer.h @@ -171,13 +171,12 @@ class PathOrderOptimizer * it into a polygon. * \param combing_boundary Boundary to avoid when making travel moves. */ - PathOrderOptimizer(const Point start_point, const ZSeamConfig seam_config = ZSeamConfig(), const bool detect_loops = false, const Polygons* combing_boundary = nullptr, const bool reverse_direction = false, bool selection_optimization = true, const std::unordered_set>& order_requirements = no_order_requirements) + PathOrderOptimizer(const Point start_point, const ZSeamConfig seam_config = ZSeamConfig(), const bool detect_loops = false, const Polygons* combing_boundary = nullptr, const bool reverse_direction = false, const std::unordered_set>& order_requirements = no_order_requirements) : start_point(start_point) , seam_config(seam_config) , combing_boundary((combing_boundary != nullptr && !combing_boundary->empty()) ? combing_boundary : nullptr) , detect_loops(detect_loops) , reverse_direction(reverse_direction) - , selection_optimization(selection_optimization) , order_requirements(&order_requirements) { } @@ -235,19 +234,6 @@ class PathOrderOptimizer } } - if (selection_optimization) - { - optimizeSelection(); - } - else - { - optimizeInsertion(); - } - - combing_grid.reset(); - } - void optimizeSelection() - { //Add all vertices to a bucket grid so that we can find nearby endpoints quickly. const coord_t snap_radius = 10_mu; // 0.01mm grid cells. Chaining only needs to consider polylines which are next to each other. SparsePointGridInclusive line_bucket_grid(snap_radius); @@ -420,181 +406,9 @@ class PathOrderOptimizer { std::swap(optimized_order, paths); } - } - - void optimizeInsertion() - { - const std::unordered_set> transitive_requirements = InsetOrderOptimizer::makeOrderIncludeTransitive(*order_requirements); - - using Optimizer = PathOrderOptimizer; - std::function canPrecede = - [&transitive_requirements](const Path& before, const Path& after) - { - // [before] cannot precede [after] if we have an order constraint that [after] must be before [before] - return ! transitive_requirements.count(std::make_pair(after.vertices, before.vertices)); - }; - - if (seam_config.type == EZSeamType::USER_SPECIFIED) - { - start_point = seam_config.pos; // WARNING: is this correct?! - } - for (Path& path : paths) - { - if (!path.is_closed) - { - continue; //Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. - } - if (path.converted->empty()) - { - continue; - } - path.start_vertex = findStartLocation(path, seam_config.pos); - } - - std::list> optimized_order; // Distance to and next location - - std::function getDistance = - combing_boundary ? - std::function( [this](Point from, Point to) { return getCombingDistance(from, to); } ) - : std::function( [this](Point from, Point to) { return getDirectDistance(from, to); } ); - - Path& first_path = paths.front(); // arbitrarily select the first path to add - coord_t distance = std::sqrt(getDistance(start_point, first_path.getStartLocation())); - optimized_order.emplace_back(distance, first_path); - - for (size_t to_be_inserted_idx = 1; to_be_inserted_idx < paths.size(); to_be_inserted_idx++) - { - typename std::list>::iterator best_pos_it = optimized_order.end(); - coord_t best_detour_distance = std::numeric_limits::max(); - coord_t best_dist_before_to_here = 0; - coord_t best_dist_here_to_after = 0; - bool best_is_flipped = false; - - Path& to_be_inserted = paths[to_be_inserted_idx]; - Point start_here = to_be_inserted.getStartLocation(); - Point end_here = to_be_inserted.getEndLocation(); - - // TODO: use grid - - auto upper_bound = optimized_order.end(); - for (auto pos_after_it = optimized_order.end(); ; --pos_after_it) // update is performed at the end of the for loop - { - auto pos_before_it = pos_after_it; - pos_before_it--; - Path* path_before = (pos_after_it == optimized_order.begin()) ? nullptr : &pos_before_it->second; - - if (path_before && ! canPrecede(*path_before, to_be_inserted)) - { - upper_bound = pos_before_it; - } - - if (pos_after_it == optimized_order.begin()) - { - break; - } - } - - for (auto pos_after_it = upper_bound; ; --pos_after_it) // update is performed at the end of the for loop - { - auto pos_before_it = pos_after_it; - pos_before_it--; - Path* path_before = (pos_after_it == optimized_order.begin()) ? nullptr : &pos_before_it->second; - Path* path_after = (pos_after_it == optimized_order.end()) ? nullptr : &pos_after_it->second; - - if ( ! path_before || canPrecede(*path_before, to_be_inserted)) - { - - Point loc_before = path_before? path_before->getEndLocation() : start_point; - coord_t dist_before = std::sqrt(getDistance(loc_before, start_here)); - coord_t dist_after = path_after ? std::sqrt(getDistance(end_here, path_after->getStartLocation())) : 0; - - if ( ! to_be_inserted.is_closed) - { - coord_t flipped_dist_before = std::sqrt(getDistance(loc_before, end_here)); - coord_t flipped_dist_after = path_after ? std::sqrt(getDistance(start_here, path_after->getStartLocation())) : 0; - best_is_flipped = false; - if (flipped_dist_before + flipped_dist_after < dist_before + dist_after) - { - dist_before = flipped_dist_before; - dist_after = flipped_dist_after; - best_is_flipped = true; - } - } - - coord_t current_distance = path_after? pos_after_it->first : 0; - coord_t detour_distance = dist_before + dist_after - current_distance; - if (detour_distance < best_detour_distance) // Less of a detour than the best candidate so far. - { - best_pos_it = pos_after_it; - best_detour_distance = detour_distance; - best_dist_before_to_here = dist_before; - best_dist_here_to_after = dist_after; - } - } - - if (path_before && ! canPrecede(to_be_inserted, *path_before)) - { - assert(best_detour_distance != std::numeric_limits::max()); - break; - } - if (pos_after_it == optimized_order.begin()) - { - assert(best_detour_distance != std::numeric_limits::max()); - break; - } - } - - if (best_is_flipped) - { - assert( ! to_be_inserted.is_closed); - to_be_inserted.start_vertex = (to_be_inserted.start_vertex != 0) * (to_be_inserted.converted->size() - 1); - } - if (best_pos_it != optimized_order.end()) - { - best_pos_it->first = best_dist_here_to_after; - } - optimized_order.insert(best_pos_it, std::make_pair(best_dist_before_to_here, to_be_inserted)); - -#ifdef DEBUG - for (auto it = optimized_order.begin(); it != optimized_order.end(); ++it) - { - auto second_it = it; - for (second_it++; second_it != optimized_order.end(); ++second_it) - assert(canPrecede(it->second, second_it->second)); - } -#endif // DEBUG - } - - - if (seam_config.type == EZSeamType::SHORTEST) - { // only recompute the start when needed - Point current_location = start_point; - for (auto& [dist, path] : optimized_order) - { - path.start_vertex = findStartLocation(path, current_location); - current_location = path.getStartLocation(); - } - } - - //Apply the optimized order to the output field. Reverse if ordered to reverse. - paths.clear(); - if (reverse_direction) - { - for (auto it = optimized_order.rbegin(); it != optimized_order.rend(); ++it) - { - paths.emplace_back(it->second); - } - } - else - { - for (auto& [fist, path] : optimized_order) - { - paths.emplace_back(path); - } - } + combing_grid.reset(); } - protected: /*! * If \ref detect_loops is enabled, endpoints of polylines that are closer @@ -646,18 +460,6 @@ class PathOrderOptimizer */ bool reverse_direction; - /*! - * The core algorithm to use: - * - Selection sort based - * - Insertion sort based - * - * Selection does a greedy approach of finding the next best candidate to append to the result. - * Insertion considers the best location within the result to insert each path. - * - * Insertion sort can be wildly inefficient when polylines haven't been stitched. - */ - bool selection_optimization; - static const std::unordered_set> no_order_requirements; /*! From 6af2530afbd320736768cd28be6047efcce61998 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 11:34:46 +0100 Subject: [PATCH 31/71] fix order by inset --- src/InsetOrderOptimizer.cpp | 73 +++++++++++++++++++++++++++++++--- src/InsetOrderOptimizer.h | 16 ++++++-- tests/WallsComputationTest.cpp | 2 +- 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index dbd75d1132..4b6231312d 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -52,7 +52,7 @@ InsetOrderOptimizer::InsetOrderOptimizer(const FffGcodeWriter& gcode_writer, bool InsetOrderOptimizer::addToLayer() { // Settings & configs: - const bool pack_by_inset = ! settings.get("optimize_wall_printing_order"); // TODO + const bool pack_by_inset = settings.get("optimize_wall_printing_order"); const InsetDirection inset_direction = settings.get("inset_direction"); const bool center_last = inset_direction == InsetDirection::CENTER_LAST; const bool alternate_walls = settings.get("material_alternate_walls"); @@ -124,7 +124,10 @@ bool InsetOrderOptimizer::addToLayer() } - std::unordered_set> order = getWeakOrder(walls_to_be_added, outer_to_inner); + std::unordered_set> order = + pack_by_inset? + getInsetOrder(walls_to_be_added, outer_to_inner) + : getRegionOrder(walls_to_be_added, outer_to_inner); if (center_last) { @@ -193,7 +196,7 @@ bool InsetOrderOptimizer::addToLayer() -std::unordered_set> InsetOrderOptimizer::getWeakOrder(const std::vector& input, const bool outer_to_inner) +std::unordered_set> InsetOrderOptimizer::getRegionOrder(const std::vector& input, const bool outer_to_inner) { size_t max_inset_idx = 0; Polygons all_polygons; @@ -230,14 +233,14 @@ std::unordered_set> InsetO { // there might be multiple roots if (line->inset_idx == 0) { - getWeakOrder(idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); + getRegionOrder(idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); } } return result; } -void InsetOrderOptimizer::getWeakOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result) +void InsetOrderOptimizer::getRegionOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result) { auto parent_it = poly_idx_to_extrusionline.find(node_idx); assert(parent_it != poly_idx_to_extrusionline.end()); @@ -347,10 +350,68 @@ void InsetOrderOptimizer::getWeakOrder(size_t node_idx, const std::unordered_map } // Recurvise call - getWeakOrder(child_idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); + getRegionOrder(child_idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); } } +std::unordered_set> InsetOrderOptimizer::getInsetOrder(const std::vector& input, const bool outer_to_inner) +{ + std::unordered_set> order; + + std::vector> walls_by_inset; + std::vector> fillers_by_inset; + + for (const ExtrusionLine* line : input) + { + if (line->is_odd) + { + if (line->inset_idx >= fillers_by_inset.size()) + { + fillers_by_inset.resize(line->inset_idx + 1); + } + fillers_by_inset[line->inset_idx].emplace_back(line); + } + else + { + if (line->inset_idx >= walls_by_inset.size()) + { + walls_by_inset.resize(line->inset_idx + 1); + } + walls_by_inset[line->inset_idx].emplace_back(line); + } + } + for (size_t inset_idx = 0; inset_idx + 1 < walls_by_inset.size(); inset_idx++) + { + for (const ExtrusionLine* line : walls_by_inset[inset_idx]) + { + for (const ExtrusionLine* inner_line : walls_by_inset[inset_idx + 1]) + { + const ExtrusionLine* before = inner_line; + const ExtrusionLine* after = line; + if (outer_to_inner) + { + std::swap(before, after); + } + order.emplace(before, after); + } + } + } + for (size_t inset_idx = 1; inset_idx < fillers_by_inset.size(); inset_idx++) + { + for (const ExtrusionLine* line : fillers_by_inset[inset_idx]) + { + assert(inset_idx - 1 < walls_by_inset.size()); + if (inset_idx - 1 >= walls_by_inset.size()) continue; + for (const ExtrusionLine* enclosing_wall : walls_by_inset[inset_idx - 1]) + { + order.emplace(enclosing_wall, line); + } + } + + } + + return order; +} }//namespace cura diff --git a/src/InsetOrderOptimizer.h b/src/InsetOrderOptimizer.h index 826a01c80d..aedd9f0b6d 100644 --- a/src/InsetOrderOptimizer.h +++ b/src/InsetOrderOptimizer.h @@ -63,14 +63,24 @@ class InsetOrderOptimizer bool addToLayer(); /*! - * Get the order constraints of the insets assuming the Wall Ordering is outer to inner. + * 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 std::unordered_set> getWeakOrder(const std::vector& input, const bool outer_to_inner); + static std::unordered_set> getRegionOrder(const std::vector& input, const bool outer_to_inner); + + /*! + * Get the order constraints of the insets when printing walls per inset. + * 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 std::unordered_set> getInsetOrder(const std::vector& input, const bool outer_to_inner); /*! * Make order requirements transitive. @@ -84,7 +94,7 @@ class InsetOrderOptimizer * Recursive part of \ref WallToolpPaths::getWeakOrder. * For each node at \p node_idx we recurse on all its children at nesting[node_idx] */ - static void getWeakOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result); + static void getRegionOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result); const FffGcodeWriter& gcode_writer; const SliceDataStorage& storage; diff --git a/tests/WallsComputationTest.cpp b/tests/WallsComputationTest.cpp index a3cd5d6315..925bf5a80d 100644 --- a/tests/WallsComputationTest.cpp +++ b/tests/WallsComputationTest.cpp @@ -158,7 +158,7 @@ TEST_F(WallsComputationTest, WallToolPathsGetWeakOrder) for (auto& inset : part.wall_toolpaths) for (auto& line : inset) all_paths.emplace_back(&line); - std::unordered_set> order = InsetOrderOptimizer::getWeakOrder(all_paths, outer_to_inner); + std::unordered_set> order = InsetOrderOptimizer::getRegionOrder(all_paths, outer_to_inner); //Verify that something was generated. EXPECT_FALSE(part.wall_toolpaths.empty()) << "There must be some walls."; From 0351841b873ac698d5063417a2b3aeac3eadc2e9 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 11:43:08 +0100 Subject: [PATCH 32/71] lil comment --- src/WallToolPaths.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 1766af90f6..a9dc263e04 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -129,20 +129,14 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting { const coord_t stitch_distance = settings.get("wall_line_width_x") / 4; - // separate polygonal wall lines and gap filling odd lines and stitch the polygonal lines into closed polygons - const size_t inset_count = toolpaths.size(); - for (unsigned int wall_idx = 0; wall_idx < inset_count; wall_idx++) + for (unsigned int wall_idx = 0; wall_idx < toolpaths.size(); wall_idx++) { VariableWidthLines& wall_lines = toolpaths[wall_idx]; - for (ExtrusionLine& line : wall_lines) - { - assert(line.inset_idx == wall_idx); - } VariableWidthLines stitched_polylines; VariableWidthLines closed_polygons; PolylineStitcher::stitch(wall_lines, stitched_polylines, closed_polygons, stitch_distance); - wall_lines = stitched_polylines; + wall_lines = stitched_polylines; // replace input toolpaths with stitched polylines for (ExtrusionLine& wall_polygon : closed_polygons) { @@ -151,13 +145,14 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting continue; } wall_polygon.is_closed = true; - wall_lines.emplace_back(std::move(wall_polygon)); + wall_lines.emplace_back(std::move(wall_polygon)); // add stitched polygons to result } - +#ifdef DEBUG for (ExtrusionLine& line : wall_lines) { assert(line.inset_idx == wall_idx); } +#endif // DEBUG } } From 8405d5705aa3b3f7076d2b2fb918723064760338 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 11:49:51 +0100 Subject: [PATCH 33/71] fix check for open polyline --- src/InsetOrderOptimizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 4b6231312d..69030414a3 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -206,7 +206,7 @@ std::unordered_set> InsetO const ExtrusionLine& line = *line_p; if (line.empty()) continue; max_inset_idx = std::max(max_inset_idx, line.inset_idx); - if ( ! shorterThan(line.front().p - line.back().p, coincident_point_distance)) // TODO: check if it is a closed polygon or not + if ( ! line.is_closed) { // Make a small triangle representative of the polyline // otherwise the polyline would get erased by the clipping operation From 69161728838d3d20e347e7f19047ebe43f218c10 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 12:15:15 +0100 Subject: [PATCH 34/71] lil doc --- src/PathOrderOptimizer.h | 7 +++++++ src/WallToolPaths.h | 3 +++ src/utils/ExtrusionLine.h | 9 +++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/PathOrderOptimizer.h b/src/PathOrderOptimizer.h index 9fb136a1d7..0dcee984db 100644 --- a/src/PathOrderOptimizer.h +++ b/src/PathOrderOptimizer.h @@ -98,11 +98,18 @@ class PathOrderOptimizer */ size_t start_vertex; + /*! + * Get the location of the start vertex + */ Point getStartLocation() const { return (*converted)[start_vertex]; } + /*! + * Get the location of the end vertex. + * In case of a polygonal line which isn't stitched yet this might be the same / near the start location. + */ Point getEndLocation() const { if (is_closed) diff --git a/src/WallToolPaths.h b/src/WallToolPaths.h index 54568c3c57..7ad46f5415 100644 --- a/src/WallToolPaths.h +++ b/src/WallToolPaths.h @@ -86,6 +86,9 @@ class WallToolPaths protected: /*! * Stitch the polylines together and form closed polygons. + * + * Works on both toolpaths and inner contours simultaneously. + * * \param settings The settings as provided by the user */ static void stitchToolPaths(VariableWidthPaths& toolpaths, const Settings& settings); diff --git a/src/utils/ExtrusionLine.h b/src/utils/ExtrusionLine.h index 28419d2014..c3269f71a2 100644 --- a/src/utils/ExtrusionLine.h +++ b/src/utils/ExtrusionLine.h @@ -197,6 +197,11 @@ struct ExtrusionLine coord_t getLength() const; coord_t polylineLength() const { return getLength(); } + /*! + * Put all junction locations into a polygon object. + * + * When this path is not closed the returned Polygon should be handled as a polyline, rather than a polygon. + */ Polygon toPolygon() const { Polygon ret; @@ -334,7 +339,7 @@ struct ExtrusionLine static coord_t calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width); }; -using VariableWidthLines = std::vector; //; //; //; // Date: Mon, 24 Jan 2022 12:22:39 +0100 Subject: [PATCH 35/71] remove unused ExtrusionLine::chopEnd --- src/utils/ExtrusionLine.cpp | 12 ------- src/utils/ExtrusionLine.h | 71 ------------------------------------- 2 files changed, 83 deletions(-) diff --git a/src/utils/ExtrusionLine.cpp b/src/utils/ExtrusionLine.cpp index e5a1ad2c8e..26573a7673 100644 --- a/src/utils/ExtrusionLine.cpp +++ b/src/utils/ExtrusionLine.cpp @@ -15,18 +15,6 @@ ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd) , is_closed(false) {} -template <> -void ExtrusionLine::erase(std::vector::iterator begin, std::vector::iterator end) -{ - junctions.erase(begin, end); -} - -template <> -void ExtrusionLine::erase(std::vector::reverse_iterator begin, std::vector::reverse_iterator end) -{ - junctions.erase(--end.base(), --begin.base()); -} - coord_t ExtrusionLine::getLength() const { if (junctions.empty()) diff --git a/src/utils/ExtrusionLine.h b/src/utils/ExtrusionLine.h index c3269f71a2..32f72eba15 100644 --- a/src/utils/ExtrusionLine.h +++ b/src/utils/ExtrusionLine.h @@ -217,77 +217,6 @@ struct ExtrusionLine */ coord_t getMinimalWidth() const; - /*! - * Chop off a segment of \p length of either end of this extrusionline - * - * \warning Should only be called on non closed extrusionlines. - * - * \param start_at_front Whether we chop from the beginning or from th eend of this line. - * \return whether the line has collapsed to a single point - */ - bool chopEnd(bool start_at_front, coord_t length) - { - assert(length > 10 && "Too small lengths will never be chopped due to rounding."); - if (start_at_front) - return chopEnd(junctions.begin(), junctions.end(), length); - else - return chopEnd(junctions.rbegin(), junctions.rend(), length); - } -protected: - /*! - * Chop off a segment of \p length of either end of this extrusionline - * - * \warning Should only be called on non closed extrusionlines. - * - * \warning the \p start_pos and \p other_end should refer to iterators in this ExtrusionLine - * - * \param start_pos Iterator to either begin() or rbegin() - * \param other_end Iterator to either end() or rend() - * \return whether the line has collapsed to a single point - */ - template - bool chopEnd(iterator start_pos, iterator other_end, coord_t length) - { - iterator current_it = start_pos; - - coord_t length_removed = 0; - ExtrusionJunction last = *current_it; - for (++current_it; current_it != other_end; ++current_it) - { - ExtrusionJunction here = *current_it; - Point p1 = last.p; - Point p2 = here.p; - Point v12 = p2 - p1; - coord_t dist = vSize(v12); - if (length_removed + dist >= length - 10) - { - if (length_removed + dist <= length) - { - erase(start_pos, current_it); - return junctions.size() <= 1; - } - else - { // Cut My Line Into Pieces - --current_it; - current_it->p = p1 + (length - length_removed) * v12 / dist; - current_it->w = last.w + (length - length_removed) * (here.w - last.w) / dist; - erase(start_pos, current_it); - return false; - } - } - length_removed += dist; - last = here; - } - erase(start_pos, --other_end); - junctions.emplace_back(junctions.front()); - junctions.back().p.X += 10; - return true; - } - - - template - void erase(iterator begin, iterator end); - public: /*! * Removes vertices of the ExtrusionLines to make sure that they are not too high From 7fa80c64534f7cfdea79af0aa52ff7193654a1dc Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 12:22:58 +0100 Subject: [PATCH 36/71] remove unused Polygon::set(paths) --- src/utils/polygon.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/utils/polygon.h b/src/utils/polygon.h index df768e9388..b03d820abb 100644 --- a/src/utils/polygon.h +++ b/src/utils/polygon.h @@ -754,11 +754,6 @@ class Polygons return paths.size(); } - void set(const ClipperLib::Paths& new_paths) - { - paths = new_paths; - } - /*! * Convenience function to check if the polygon has no points. * From e68b581d397996930cdcb293dc3b6c108b327718 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 24 Jan 2022 12:23:27 +0100 Subject: [PATCH 37/71] remove unused PathOrderOptimizer::Path helper functions --- src/PathOrderOptimizer.h | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/PathOrderOptimizer.h b/src/PathOrderOptimizer.h index 0dcee984db..aa6aec8833 100644 --- a/src/PathOrderOptimizer.h +++ b/src/PathOrderOptimizer.h @@ -98,34 +98,6 @@ class PathOrderOptimizer */ size_t start_vertex; - /*! - * Get the location of the start vertex - */ - Point getStartLocation() const - { - return (*converted)[start_vertex]; - } - - /*! - * Get the location of the end vertex. - * In case of a polygonal line which isn't stitched yet this might be the same / near the start location. - */ - Point getEndLocation() const - { - if (is_closed) - { - return getStartLocation(); - } - if (start_vertex == 0) - { - return converted->back(); - } - else - { - return converted->front(); - } - } - /*! * Whether the path should be closed at the ends or not. * From c61bced027d78f730cc3751eee88bd2555586506 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 25 Jan 2022 09:30:23 +0100 Subject: [PATCH 38/71] Flip back optimize_wall_printing_order to it's original meaning That setting got reinterpreted invertedly when re-enabling the inset optimizer, but the settings remained at their default, so this flip changed the behavior of default profiles. Now we change it back so that the original pre-arachne profiles behave the same. --- src/InsetOrderOptimizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 69030414a3..1b19b9d448 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -52,7 +52,7 @@ InsetOrderOptimizer::InsetOrderOptimizer(const FffGcodeWriter& gcode_writer, bool InsetOrderOptimizer::addToLayer() { // Settings & configs: - const bool pack_by_inset = settings.get("optimize_wall_printing_order"); + const bool pack_by_inset = ! settings.get("optimize_wall_printing_order"); const InsetDirection inset_direction = settings.get("inset_direction"); const bool center_last = inset_direction == InsetDirection::CENTER_LAST; const bool alternate_walls = settings.get("material_alternate_walls"); From 5fe9532aec7c500d39203dcd5938aaf73555f7a2 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 25 Jan 2022 10:58:09 +0100 Subject: [PATCH 39/71] documentation and debugging for even contour walls which we haven't been able to stitch --- src/WallToolPaths.cpp | 42 ++++++++++++++++++++++++++++++------ src/utils/PolylineStitcher.h | 2 ++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index a9dc263e04..6e16c2766a 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -137,6 +137,40 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting VariableWidthLines closed_polygons; PolylineStitcher::stitch(wall_lines, stitched_polylines, closed_polygons, stitch_distance); wall_lines = stitched_polylines; // replace input toolpaths with stitched polylines +#ifdef DEBUG + for (const ExtrusionLine& line : stitched_polylines) + { + if ( ! line.is_odd && ! line.is_closed && + line.polylineLength() > 3 * stitch_distance && line.size() > 3) + { + logError("Some even contour lines could not be closed into a polygon!\n"); + AABB aabb; + for (const VariableWidthLines& inset : toolpaths) + for (auto line : inset) + for (auto j : line) + aabb.include(j.p); + SVG svg("/tmp/contours.svg", aabb); + for (const VariableWidthLines& inset : toolpaths) + { + for (auto line : inset) + { + SVG::Color col = SVG::Color::BLACK; + if ( ! line.is_closed && line.is_odd) col = SVG::Color::GRAY; + if (line.is_closed && ! line.is_odd) col = SVG::Color::BLUE; + if ( ! line.is_closed && ! line.is_odd) col = SVG::Color::RED; + if ( ! line.is_closed && ! line.is_odd) std::cerr << "Non-closed even wall of size: " << line.size() << "\n"; + if ( ! line.is_closed && ! line.is_odd) svg.writePoint(line.front().p, true, 1.0); + if (line.is_closed && line.is_odd) col = SVG::Color::MAGENTA; + if (line.is_closed) + svg.writePolygon(line.toPolygon(), col); + else + svg.writePolyline(line.toPolygon(), col); + } + } + assert( false && "Long even polylines should be closed into polygons."); + } + } +#endif // DEBUG for (ExtrusionLine& wall_polygon : closed_polygons) { @@ -230,13 +264,9 @@ void WallToolPaths::separateOutInnerContour() { continue; // odd lines don't contribute to the contour } - else + else if (line.is_closed) // sometimes an very small even polygonal wall is not stitched into a polygon { - assert(line.is_closed && "All contour polylines should have been closed into a polygon"); - if (line.is_closed) - { - inner_contour.emplace_back(line.toPolygon()); - } + inner_contour.emplace_back(line.toPolygon()); } } } diff --git a/src/utils/PolylineStitcher.h b/src/utils/PolylineStitcher.h index 5ab55e3a65..d8bff20a7d 100644 --- a/src/utils/PolylineStitcher.h +++ b/src/utils/PolylineStitcher.h @@ -30,6 +30,8 @@ class PolylineStitcher * Only stitch polylines into closed polygons if they are larger than 3 * \p max_stitch_distance, * in order to prevent small segments to accidentally get closed into a polygon. * + * \warning Tiny polylines (smaller than 3 * max_stitch_distance) will not be closed into polygons. + * * \note Resulting polylines and polygons are added onto the existing containers, so you can directly output onto a polygons container with existing polygons in it. * However, you shouldn't call this function with the same parameter in \p lines as \p result_lines, because that would duplicate (some of) the polylines. */ From 12b9a058d5d006c5dc16563ae22db72f79a73ec1 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 25 Jan 2022 11:02:05 +0100 Subject: [PATCH 40/71] lil: allow for odd wall lines to be closed --- src/InsetOrderOptimizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 1b19b9d448..0f7e48ea5d 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -335,7 +335,7 @@ void InsetOrderOptimizer::getRegionOrder(size_t node_idx, const std::unordered_m } else { // normal case - assert( ! parent->is_odd && "There can be no polygons inside a polyline"); + assert(parent->is_closed && "There can be no polygons inside a polyline"); const ExtrusionLine* before = parent; const ExtrusionLine* after = child; From 3f30291366039a2b8ff7da0422cc350773936dfd Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 25 Jan 2022 11:48:05 +0100 Subject: [PATCH 41/71] fix inner contour not being generated on all sides Sometimes the inner contour is very close to the middle. If only one side of the contour gets generated then a hollow cylinder might get filled, because the inner contour of only one side of the circle is generated. With this commit the inner contours always overlap with the walls by 5 micron, which should be negligible. --- src/BeadingStrategy/LimitedBeadingStrategy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BeadingStrategy/LimitedBeadingStrategy.cpp b/src/BeadingStrategy/LimitedBeadingStrategy.cpp index 76e20a6878..90ae49f8b5 100644 --- a/src/BeadingStrategy/LimitedBeadingStrategy.cpp +++ b/src/BeadingStrategy/LimitedBeadingStrategy.cpp @@ -77,14 +77,14 @@ LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thicknes //This wall can then be used by other structures to e.g. fill the infill area adjacent to the variable-width walls. coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1]; coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1]; - ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2); + ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2 - 5); ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0); //Symmetry on both sides. Symmetry is guaranteed since this code is stopped early if the bead_count <= max_bead_count, and never reaches this point then. const size_t opposite_bead = bead_count - (max_bead_count / 2 - 1); innermost_toolpath_location = ret.toolpath_locations[opposite_bead]; innermost_toolpath_width = ret.bead_widths[opposite_bead]; - ret.toolpath_locations.insert(ret.toolpath_locations.begin() + opposite_bead, innermost_toolpath_location - innermost_toolpath_width / 2); + ret.toolpath_locations.insert(ret.toolpath_locations.begin() + opposite_bead, innermost_toolpath_location - innermost_toolpath_width / 2 - 5); ret.bead_widths.insert(ret.bead_widths.begin() + opposite_bead, 0); return ret; From 0b6575213d6944fa7b2a1233bb35552e00396012 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 25 Jan 2022 13:52:19 +0100 Subject: [PATCH 42/71] Fix maximal single beads length. This length used to be hardcoded, but it can actually be computed from the width. --- src/SkeletalTrapezoidation.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index de12f409b4..3d1a48b739 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -2016,9 +2016,18 @@ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads() } generated_toolpaths[inset_index].emplace_back(inset_index, is_odd); ExtrusionLine& line = generated_toolpaths[inset_index].back(); - line.junctions.emplace_back(node.p, beading.bead_widths[inset_index], inset_index, region_id); - line.junctions.emplace_back(node.p + Point(50, 0), beading.bead_widths[inset_index], inset_index, region_id); - // TODO: ^^^ magic value ... + Point(50, 0) ^^^ + const coord_t width = beading.bead_widths[inset_index]; + // total area to be extruded is pi*(w/2)^2 = pi*w*w/4 + // Width a constant extrusion width w, that would be a length of pi*w/4 + // If we make a small circle to fill up the hole, then that circle would have a circumference of 2*pi*r + // So our circle needs to be such that r=w/8 + const coord_t r = width / 8; + constexpr coord_t n_segments = 6; + for (coord_t segment = 0; segment < n_segments; segment++) + { + float a = 2.0 * M_PI / n_segments * segment; + line.junctions.emplace_back(node.p + Point(r * cos(a), r * sin(a)), width, inset_index, region_id); + } } } } From 970d3ab76e7d10f1a42e15d92ca0a7157979b369 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 25 Jan 2022 13:55:50 +0100 Subject: [PATCH 43/71] remove small odd gap fillers These gap fillers are often only overlappin with an even wall at a split, so they only cause overextrusion and extra travels. Furthermore, if there is a tiny odd gap filler in between two even wall polygons the nesting algorithm can have problems. In the nesting algorithm we generate a tiny triangle to be representative of a polyline, but if the polyline is shorter than the size of that triangle than it may come to overlap with the polygons, causing Clipper to get confused. --- src/WallToolPaths.cpp | 25 ++++++++++++++++++++++++- src/WallToolPaths.h | 5 +++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 6e16c2766a..4b9f8ddd12 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -107,8 +107,10 @@ const VariableWidthPaths& WallToolPaths::generate() wall_transition_length ); wall_maker.generateToolpaths(toolpaths); - + stitchToolPaths(toolpaths, settings); + + removeSmallLines(toolpaths); separateOutInnerContour(); @@ -190,6 +192,27 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting } } +void WallToolPaths::removeSmallLines(VariableWidthPaths& toolpaths) +{ + for (VariableWidthLines& inset : toolpaths) + { + for (size_t line_idx = 0; line_idx < inset.size(); line_idx++) + { + ExtrusionLine& line = inset[line_idx]; + coord_t min_width = std::numeric_limits::max(); + for (const ExtrusionJunction& j : line) + { + min_width = std::min(min_width, j.w); + } + if (line.is_odd && ! line.is_closed && shorterThan(line, min_width / 2)) + { // remove line + line = std::move(inset.back()); + inset.erase(--inset.end()); + } + } + } +} + void WallToolPaths::simplifyToolPaths(VariableWidthPaths& toolpaths, const Settings& settings) { for (size_t toolpaths_idx = 0; toolpaths_idx < toolpaths.size(); ++toolpaths_idx) diff --git a/src/WallToolPaths.h b/src/WallToolPaths.h index 7ad46f5415..6f491ba5d8 100644 --- a/src/WallToolPaths.h +++ b/src/WallToolPaths.h @@ -93,6 +93,11 @@ class WallToolPaths */ static void stitchToolPaths(VariableWidthPaths& toolpaths, const Settings& settings); + /*! + * Remove polylines shorter than half the smallest line width along that polyline. + */ + static void removeSmallLines(VariableWidthPaths& toolpaths); + /*! * Simplifies the variable-width toolpaths by calling the simplify on every line in the toolpath using the provided * settings. From ed68d52d51404c426627eca9374bdadaba85cbca Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 26 Jan 2022 12:18:09 +0100 Subject: [PATCH 44/71] Force disconnecting generated paths when jumping to a new region. Very sometimes the new region would start exactly on the outer side where the last region left off, connecting the innermost segment of the new region to the innermost segment of the old region. This could cause walls from different regions to be connected to each other. --- src/SkeletalTrapezoidation.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index 3d1a48b739..bce4f6a870 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -1902,6 +1902,7 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ { edge_t* poly_domain_start = *unprocessed_quad_starts.begin(); edge_t* quad_start = poly_domain_start; + bool new_domain_start = true; do { edge_t* quad_end = quad_start; @@ -1986,9 +1987,10 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ } passed_odd_edges.emplace(quad_start->next); - const bool force_new_path = is_odd_segment && quad_start->to->isMultiIntersection(); + const bool force_new_path = new_domain_start || (is_odd_segment && quad_start->to->isMultiIntersection()); addToolpathSegment(from, to, is_odd_segment, force_new_path); } + new_domain_start = false; } while(quad_start = quad_start->getNextUnconnected(), quad_start != poly_domain_start); } From c34457dc199fccf95a235d932adee483ee1a5432 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 26 Jan 2022 15:13:57 +0100 Subject: [PATCH 45/71] Ensure CW direction of arachne walls and ensure cutting at 3-way intersections Prefer adding a from-to segment in that order (cases in SkeletalTrapezoidation::addToolpathSegment have been reordered) Never add to a path if the last location was a 3-way intersection. --- src/SkeletalTrapezoidation.cpp | 57 +++++++++++++++++++++-------- src/SkeletalTrapezoidation.h | 4 +- src/SkeletalTrapezoidationGraph.cpp | 4 ++ 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index bce4f6a870..ca9019bd76 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -1847,7 +1847,7 @@ std::shared_ptr SkeletalTrapezo return nullptr; } -void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path) +void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way) { if (from == to) return; @@ -1859,24 +1859,37 @@ void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, c generated_toolpaths.resize(inset_idx + 1); } assert((generated_toolpaths[inset_idx].empty() || !generated_toolpaths[inset_idx].back().junctions.empty()) && "empty extrusion lines should never have been generated"); - if (!force_new_path - && !generated_toolpaths[inset_idx].empty() - && generated_toolpaths[inset_idx].back().is_odd == is_odd - && shorterThen(generated_toolpaths[inset_idx].back().junctions.back().p - to.p, 10) - && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - to.w) < 10 + if (generated_toolpaths[inset_idx].empty() + || generated_toolpaths[inset_idx].back().is_odd != is_odd + || generated_toolpaths[inset_idx].back().junctions.back().perimeter_index != inset_idx // inset_idx should always be consistent + || ( ! is_odd && ! from_is_3way && ! to_is_3way && ( // if this is a normal even line ... + from.region_id != to.region_id // It should always have a consistent region_id + || generated_toolpaths[inset_idx].back().junctions.back().region_id != to.region_id + )) ) { - generated_toolpaths[inset_idx].back().junctions.push_back(from); + force_new_path = true; } - else if (!force_new_path - && !generated_toolpaths[inset_idx].empty() - && generated_toolpaths[inset_idx].back().is_odd == is_odd + if (!force_new_path && shorterThen(generated_toolpaths[inset_idx].back().junctions.back().p - from.p, 10) && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - from.w) < 10 + && ! from_is_3way // force new path at 3way intersection ) { generated_toolpaths[inset_idx].back().junctions.push_back(to); } + else if (!force_new_path + && shorterThen(generated_toolpaths[inset_idx].back().junctions.back().p - to.p, 10) + && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - to.w) < 10 + && ! to_is_3way // force new path at 3way intersection + ) + { + if ( ! is_odd) + { + logError("Reversing even wall line causes it to be printed CCW instead of CW!\n"); + } + generated_toolpaths[inset_idx].back().junctions.push_back(from); + } else { generated_toolpaths[inset_idx].emplace_back(inset_idx, is_odd); @@ -1922,12 +1935,14 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ edge_junctions.emplace_back(std::make_shared()); edge_to_peak->data.setExtrusionJunctions(edge_junctions.back()); } + // The junctions on the edge(s) from the start of the quad to the node with highest R LineJunctions from_junctions = *edge_to_peak->data.getExtrusionJunctions(); if (! edge_from_peak->twin->data.hasExtrusionJunctions()) { edge_junctions.emplace_back(std::make_shared()); edge_from_peak->twin->data.setExtrusionJunctions(edge_junctions.back()); } + // The junctions on the edge(s) from the end of the quad to the node with highest R LineJunctions to_junctions = *edge_from_peak->twin->data.getExtrusionJunctions(); if (edge_to_peak->prev) { @@ -1975,20 +1990,30 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ { logWarning("Connecting two perimeters with different indices! Perimeter %i and %i", from.perimeter_index, to.perimeter_index); } - const bool is_odd_segment = edge_to_peak->to->data.bead_count > 0 && edge_to_peak->to->data.bead_count % 2 == 1 // quad contains single bead segment - && edge_to_peak->to->data.transition_ratio == 0 && edge_to_peak->from->data.transition_ratio == 0 && edge_from_peak->to->data.transition_ratio == 0 // We're not in a transition + const bool from_is_odd = + quad_start->to->data.bead_count > 0 && quad_start->to->data.bead_count % 2 == 1 // quad contains single bead segment + && quad_start->to->data.transition_ratio == 0 // We're not in a transition && junction_rev_idx == segment_count - 1 // Is single bead segment - && shorterThen(from.p - quad_start->to->p, 5) && shorterThen(to.p - quad_end->from->p, 5); + && shorterThen(from.p - quad_start->to->p, 5); + const bool to_is_odd = + quad_end->from->data.bead_count > 0 && quad_end->from->data.bead_count % 2 == 1 // quad contains single bead segment + && quad_end->from->data.transition_ratio == 0 // We're not in a transition + && junction_rev_idx == segment_count - 1 // Is single bead segment + && shorterThen(to.p - quad_end->from->p, 5); + const bool is_odd_segment = from_is_odd && to_is_odd; if (is_odd_segment && passed_odd_edges.count(quad_start->next->twin) > 0) // Only generate toolpath for odd segments once { continue; // Prevent duplication of single bead segments } - + + bool from_is_3way = from_is_odd && quad_start->to->isMultiIntersection(); + bool to_is_3way = to_is_odd && quad_end->from->isMultiIntersection(); + passed_odd_edges.emplace(quad_start->next); - const bool force_new_path = new_domain_start || (is_odd_segment && quad_start->to->isMultiIntersection()); - addToolpathSegment(from, to, is_odd_segment, force_new_path); + + addToolpathSegment(from, to, is_odd_segment, new_domain_start, from_is_3way, to_is_3way); } new_domain_start = false; } diff --git a/src/SkeletalTrapezoidation.h b/src/SkeletalTrapezoidation.h index 3ddf780452..c80b008d65 100644 --- a/src/SkeletalTrapezoidation.h +++ b/src/SkeletalTrapezoidation.h @@ -475,7 +475,7 @@ class SkeletalTrapezoidation /*! * From a quad (a group of linked edges in one cell of the Voronoi), find - * the edge that is furthest away from the border of the polygon. + * the edge pointing to the node that is furthest away from the border of the polygon. * \param quad_start_edge The first edge of the quad. * \return The edge of the quad that is furthest away from the border. */ @@ -577,7 +577,7 @@ class SkeletalTrapezoidation /*! * add a new toolpath segment, defined between two extrusion-juntions */ - void addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path); + void addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way); /*! * connect junctions in each quad diff --git a/src/SkeletalTrapezoidationGraph.cpp b/src/SkeletalTrapezoidationGraph.cpp index 2b029c0a01..b43b0e09c3 100644 --- a/src/SkeletalTrapezoidationGraph.cpp +++ b/src/SkeletalTrapezoidationGraph.cpp @@ -126,6 +126,10 @@ bool STHalfEdgeNode::isMultiIntersection() edge_t* outgoing = this->incident_edge; do { + if ( ! outgoing) + { // This is a node on the outside + return false; + } if (outgoing->data.isCentral()) { odd_path_count++; From dd824f33170fad51f43e78e81d6841bddcdce8ac Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 26 Jan 2022 15:17:55 +0100 Subject: [PATCH 46/71] lil: logError instead of assert --- src/InsetOrderOptimizer.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 0f7e48ea5d..3f9e024c28 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -320,8 +320,11 @@ void InsetOrderOptimizer::getRegionOrder(size_t node_idx, const std::unordered_m } else { // other child is an even wall as well - if (other_child->inset_idx == child->inset_idx) continue; - assert(other_child->inset_idx == child->inset_idx + 1); + if (other_child->inset_idx == child->inset_idx) + { + logError("Nesting provided child with unexpected inset relation.\n"); + continue; + } const ExtrusionLine* before = child; const ExtrusionLine* after = other_child; @@ -335,7 +338,10 @@ void InsetOrderOptimizer::getRegionOrder(size_t node_idx, const std::unordered_m } else { // normal case - assert(parent->is_closed && "There can be no polygons inside a polyline"); + if ( ! parent->is_closed) + { + logWarning("Stitching polyline into a polygon failed.\n"); + } const ExtrusionLine* before = parent; const ExtrusionLine* after = child; From d841021936fe2ea124dbe676d4c4e18958c0d67b Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 26 Jan 2022 15:19:25 +0100 Subject: [PATCH 47/71] debug output --- src/WallToolPaths.cpp | 70 +++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 4b9f8ddd12..db67087b7e 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -138,41 +138,67 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting VariableWidthLines stitched_polylines; VariableWidthLines closed_polygons; PolylineStitcher::stitch(wall_lines, stitched_polylines, closed_polygons, stitch_distance); - wall_lines = stitched_polylines; // replace input toolpaths with stitched polylines #ifdef DEBUG for (const ExtrusionLine& line : stitched_polylines) { - if ( ! line.is_odd && ! line.is_closed && + if ( ! line.is_odd && line.polylineLength() > 3 * stitch_distance && line.size() > 3) { - logError("Some even contour lines could not be closed into a polygon!\n"); + logError("Some even contour lines could not be closed into polygons!\n"); AABB aabb; - for (const VariableWidthLines& inset : toolpaths) - for (auto line : inset) - for (auto j : line) - aabb.include(j.p); - SVG svg("/tmp/contours.svg", aabb); - for (const VariableWidthLines& inset : toolpaths) + for (auto line2 : wall_lines) + for (auto j : line2) + aabb.include(j.p); + { + SVG svg("/tmp/contours_before.svg", aabb); + SVG::Color col = SVG::Color::GRAY; + for (auto& inset : toolpaths) + for (auto& line2 : inset) + { +// svg.writePolyline(line2.toPolygon(), col); + + Polygon poly = line2.toPolygon(); + Point last = poly.front(); + for (size_t idx = 1 ; idx < poly.size(); idx++) + { + Point here = poly[idx]; + svg.writeLine(last, here, col); + svg.writeText((last + here) / 2, std::to_string(line2.junctions[idx].region_id), SVG::Color::BLACK, 2.0); + last = here; + } + svg.writePoint(poly[0], false, 2.0, col); + svg.nextLayer(); +// svg.writePoints(poly, true, 0.1); +// svg.nextLayer(); + col = SVG::Color((int(col) + 1) % int(SVG::Color::NONE)); + } + } { - for (auto line : inset) + SVG svg("/tmp/contours.svg", aabb); + for (auto& inset : toolpaths) + for (auto& line2 : inset) + svg.writePolyline(line2.toPolygon(), SVG::Color::GRAY); + for (auto& line2 : stitched_polylines) { - SVG::Color col = SVG::Color::BLACK; - if ( ! line.is_closed && line.is_odd) col = SVG::Color::GRAY; - if (line.is_closed && ! line.is_odd) col = SVG::Color::BLUE; - if ( ! line.is_closed && ! line.is_odd) col = SVG::Color::RED; - if ( ! line.is_closed && ! line.is_odd) std::cerr << "Non-closed even wall of size: " << line.size() << "\n"; - if ( ! line.is_closed && ! line.is_odd) svg.writePoint(line.front().p, true, 1.0); - if (line.is_closed && line.is_odd) col = SVG::Color::MAGENTA; - if (line.is_closed) - svg.writePolygon(line.toPolygon(), col); - else - svg.writePolyline(line.toPolygon(), col); + SVG::Color col = line2.is_odd ? SVG::Color::GRAY : SVG::Color::RED; + if ( ! line2.is_odd) std::cerr << "Non-closed even wall of size: " << line2.size() << " at " << line2.front().p << "\n"; + if ( ! line2.is_odd) svg.writePoint(line2.front().p, true, 1.0); + Polygon poly = line2.toPolygon(); + Point last = poly.front(); + for (size_t idx = 1 ; idx < poly.size(); idx++) + { + Point here = poly[idx]; + svg.writeLine(last, here, col); + last = here; + } } + for (auto line2 : closed_polygons) + svg.writePolygon(line2.toPolygon()); } - assert( false && "Long even polylines should be closed into polygons."); } } #endif // DEBUG + wall_lines = stitched_polylines; // replace input toolpaths with stitched polylines for (ExtrusionLine& wall_polygon : closed_polygons) { From e55763c7ccaeb10e9800ad69a427154196fe7c00 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 26 Jan 2022 15:19:56 +0100 Subject: [PATCH 48/71] fix removing small line after another small line has just been removed --- src/WallToolPaths.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index db67087b7e..178489f5d1 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -234,6 +234,7 @@ void WallToolPaths::removeSmallLines(VariableWidthPaths& toolpaths) { // remove line line = std::move(inset.back()); inset.erase(--inset.end()); + line_idx--; // reconsider the current position } } } From c4a0c18dc08e5c108b95c7f2a8521f1e195d915b Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 26 Jan 2022 15:27:21 +0100 Subject: [PATCH 49/71] prevent printing order infinite loop Sometimes when paths have overlap somehow the wrong polygons get detected in the clipper output, and a polygon would be noted as nesting inside itself. This would cause an infinite loop in the InsetOrderOptimizer::getRegionOrder. --- src/utils/polygon.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/utils/polygon.cpp b/src/utils/polygon.cpp index fa4b35f6e0..3090ea98b4 100644 --- a/src/utils/polygon.cpp +++ b/src/utils/polygon.cpp @@ -12,6 +12,7 @@ #include "PolylineStitcher.h" #include "logoutput.h" +#include "SVG.h" namespace cura { @@ -1588,15 +1589,39 @@ std::vector> Polygons::getNesting() const std::vector> ret(size()); + bool loops_detected = false; for (auto [node, index] : node_to_index) { for (auto child : node->Childs) { auto it = node_to_index.find(child); assert(it != node_to_index.end() && "Each PolyNode should be mapped to the corresponding Polygon index!"); - ret[index].emplace_back(it->second); + size_t child_idx = it->second; + if (child_idx == index) + { + loops_detected = true; + } + if (child_idx != index) + ret[index].emplace_back(child_idx); + } + } +#ifdef DEBUG + if (loops_detected) + { + logError("Nesting loops detected. Wall printing order might suffer.\n"); + SVG svg("/tmp/nesting.svg", AABB(*this)); + svg.writePolygons(*this); + for (auto& path : paths) + svg.writePoint(path[0], true, 1.0); + for (size_t idx = 0; idx < ret.size(); idx++) + { + for (size_t child_idx : ret[idx]) + { + svg.writeArrow(paths[idx][0], paths[child_idx][1], SVG::Color::BLUE); + } } } +#endif // DEBUG return ret; } From 3cb2fdeb934927e330c8fafe2a880e1392884ff9 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 26 Jan 2022 15:29:39 +0100 Subject: [PATCH 50/71] Fix handling of shared vertices in polygon::getNesting If vertices are shared among two polygonal wall toolpaths those vertices cannot be used to uniquely identify which clipper output path corresponds to which Polygon object. --- src/utils/polygon.cpp | 91 ++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 48 deletions(-) diff --git a/src/utils/polygon.cpp b/src/utils/polygon.cpp index 3090ea98b4..978837de3c 100644 --- a/src/utils/polygon.cpp +++ b/src/utils/polygon.cpp @@ -1630,67 +1630,63 @@ std::unordered_map Polygons::getPolyTreeToP { std::unordered_map result; - std::unordered_map start_loc_to_index; + std::unordered_map loc_to_index; + std::vector duplicates; for (size_t idx = 0; idx < paths.size(); idx++) { const ClipperLib::Path& path = paths[idx]; - if (path.empty()) continue; - start_loc_to_index.emplace(path.front(), idx); + for (Point p : path) + { + auto it = loc_to_index.find(p); + if (it != loc_to_index.end()) + { // Multiple polygons share a point + duplicates.emplace_back(p); + } + else + { + loc_to_index.emplace(p, idx); + } + } + } + for (Point dup : duplicates) + { + loc_to_index.erase(dup); } std::queue queue; std::vector unprocessed; - for (int i = 0; i < 2; i++) + + queue.emplace(&root); + while ( ! queue.empty()) { - + const ClipperLib::PolyNode* node = queue.front(); + queue.pop(); + for (auto child : node->Childs) + queue.emplace(child); + if (node->Contour.empty()) continue; - queue.emplace(&root); - while ( ! queue.empty()) + std::unordered_map::iterator it; + for (Point p : node->Contour) { - const ClipperLib::PolyNode* node = queue.front(); - queue.pop(); - for (auto child : node->Childs) - queue.emplace(child); - if (node->Contour.empty()) continue; - - std::unordered_map::iterator it; - for (Point p : node->Contour) - { - it = start_loc_to_index.find(p); - if (it != start_loc_to_index.end()) - { - // If Clipper preserves the order of points then the first iteration should already get here - result.emplace(node, it->second); - break; - } - } - if (it == start_loc_to_index.end()) - { - unprocessed.emplace_back(node); + it = loc_to_index.find(p); + if (it != loc_to_index.end()) + { // should be true for the first point that's not shared with other polygons + result.emplace(node, it->second); + break; } } - - if (unprocessed.empty()) - { - return result; - } - - // some points in the original polygons were removed by clipper, probably because of colinear segments - // retry with mapping all points - start_loc_to_index.clear(); - for (size_t idx = 0; idx < paths.size(); idx++) + if (it == loc_to_index.end()) { - const ClipperLib::Path& path = paths[idx]; - for (Point p : path) - { - start_loc_to_index.emplace(p, idx); - } + unprocessed.emplace_back(node); } - for (const ClipperLib::PolyNode* node : unprocessed) - queue.emplace(node); - unprocessed.clear(); } - + + if (unprocessed.empty()) + { + return result; + } + + logWarning("Couldn't find result of nesting in original polygons"); for (auto node : unprocessed) { std::cerr << "Couldn't find match for node with locations:\n"; @@ -1699,12 +1695,11 @@ std::unordered_map Polygons::getPolyTreeToP std::cerr << '\n'; } std::cerr << "Among registered locations: \n"; - for (auto [p, i] : start_loc_to_index) + for (auto [p, i] : loc_to_index) std::cerr << p << " to index " << i << '\n'; std::cerr <<'\n'; assert(false && "The first Point in each polygon should be contained in the clipper output!"); - logError("Polygons::getPolyTreeToPolygonsMapping(.): Extensive polygon matching not implemented.\n"); return result; } From 69146b80438e04d77a80ed1a7b2a3f64a2722439 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 26 Jan 2022 15:30:41 +0100 Subject: [PATCH 51/71] Revert "fix inner contour not being generated on all sides" This reverts commit a526a46b74d41ee5e06174cf13c92c86ef2b49f9. The fix didn't really fix what it was supposed to fix. --- src/BeadingStrategy/LimitedBeadingStrategy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BeadingStrategy/LimitedBeadingStrategy.cpp b/src/BeadingStrategy/LimitedBeadingStrategy.cpp index 90ae49f8b5..76e20a6878 100644 --- a/src/BeadingStrategy/LimitedBeadingStrategy.cpp +++ b/src/BeadingStrategy/LimitedBeadingStrategy.cpp @@ -77,14 +77,14 @@ LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thicknes //This wall can then be used by other structures to e.g. fill the infill area adjacent to the variable-width walls. coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1]; coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1]; - ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2 - 5); + ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2); ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0); //Symmetry on both sides. Symmetry is guaranteed since this code is stopped early if the bead_count <= max_bead_count, and never reaches this point then. const size_t opposite_bead = bead_count - (max_bead_count / 2 - 1); innermost_toolpath_location = ret.toolpath_locations[opposite_bead]; innermost_toolpath_width = ret.bead_widths[opposite_bead]; - ret.toolpath_locations.insert(ret.toolpath_locations.begin() + opposite_bead, innermost_toolpath_location - innermost_toolpath_width / 2 - 5); + ret.toolpath_locations.insert(ret.toolpath_locations.begin() + opposite_bead, innermost_toolpath_location - innermost_toolpath_width / 2); ret.bead_widths.insert(ret.bead_widths.begin() + opposite_bead, 0); return ret; From d4b0c037e5bc2b65167f21fa34b377cb1bbc1b49 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 26 Jan 2022 16:12:05 +0100 Subject: [PATCH 52/71] fix asserts for more unusual cases --- src/InsetOrderOptimizer.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 3f9e024c28..db207a9abe 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -310,19 +310,15 @@ void InsetOrderOptimizer::getRegionOrder(size_t node_idx, const std::unordered_m result.emplace(child, other_child); } else - { // outer thin wall 'gap filler' enclosed in an internal hole. - // E.g. in the middle of a thin '8' shape when the middle looks like '>-<=>-<' - assert(parent->inset_idx == 0); // enclosing 8 shape - assert(child->inset_idx == 0); // thick section of the middle - assert(other_child->inset_idx == 0); // thin section of the middle - // no order requirement between thin wall, because it has no eclosing wall + { // all 3 lines considered are on the same level + assert(other_child->inset_idx == child->inset_idx); + // no order requirement needed } } else { // other child is an even wall as well if (other_child->inset_idx == child->inset_idx) - { - logError("Nesting provided child with unexpected inset relation.\n"); + { // There is no order relation between two children of the same inset index continue; } From 827ec0206c9e835a3c7607266a37d1a1825e9ea4 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 7 Feb 2022 12:55:59 +0100 Subject: [PATCH 53/71] lil fix for top surface skin ironing --- src/FffGcodeWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index ed35908f29..b389f55baf 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1409,7 +1409,7 @@ void FffGcodeWriter::addMeshLayerToGCode(const SliceDataStorage& storage, const addMeshPartToGCode(storage, mesh, extruder_nr, mesh_config, *path.vertices, gcode_layer); } - if (extruder_nr == mesh.settings.get("top_bottom_extruder_nr").extruder_nr) + if (extruder_nr == mesh.settings.get((mesh.settings.get("roofing_layer_count") > 0)? "roofing_extruder_nr" : "top_bottom_extruder_nr").extruder_nr) { processIroning(storage, mesh, layer, mesh_config.ironing_config, gcode_layer); } From 0f6e26daa187a2feb534c732115f38209d8b9cfa Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 17 Feb 2022 10:52:35 +0100 Subject: [PATCH 54/71] clarify test condition Old code was less intelligible. --- src/utils/PolylineStitcher.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/PolylineStitcher.h b/src/utils/PolylineStitcher.h index d8bff20a7d..a0e446871b 100644 --- a/src/utils/PolylineStitcher.h +++ b/src/utils/PolylineStitcher.h @@ -105,7 +105,9 @@ class PolylineStitcher { // it was already moved to output return true; // keep looking for a connection } - if (!canReverse(nearby) && ((nearby.point_idx == 0) == go_in_reverse_direction)) + bool nearby_would_be_reversed = nearby.point_idx != 0; + nearby_would_be_reversed = nearby_would_be_reversed != go_in_reverse_direction; // flip nearby_would_be_reversed when searching in the reverse direction + if (!canReverse(nearby) && nearby_would_be_reversed) { // connecting the segment would reverse the polygon direction return true; // keep looking for a connection } From eabfeae578a6747293cf933e97f9ff4fdadc8b05 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 17 Feb 2022 11:03:47 +0100 Subject: [PATCH 55/71] remove debugging code --- src/WallToolPaths.cpp | 52 ++----------------------------------------- 1 file changed, 2 insertions(+), 50 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 178489f5d1..4478ad808a 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -145,56 +145,8 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting line.polylineLength() > 3 * stitch_distance && line.size() > 3) { logError("Some even contour lines could not be closed into polygons!\n"); - AABB aabb; - for (auto line2 : wall_lines) - for (auto j : line2) - aabb.include(j.p); - { - SVG svg("/tmp/contours_before.svg", aabb); - SVG::Color col = SVG::Color::GRAY; - for (auto& inset : toolpaths) - for (auto& line2 : inset) - { -// svg.writePolyline(line2.toPolygon(), col); - - Polygon poly = line2.toPolygon(); - Point last = poly.front(); - for (size_t idx = 1 ; idx < poly.size(); idx++) - { - Point here = poly[idx]; - svg.writeLine(last, here, col); - svg.writeText((last + here) / 2, std::to_string(line2.junctions[idx].region_id), SVG::Color::BLACK, 2.0); - last = here; - } - svg.writePoint(poly[0], false, 2.0, col); - svg.nextLayer(); -// svg.writePoints(poly, true, 0.1); -// svg.nextLayer(); - col = SVG::Color((int(col) + 1) % int(SVG::Color::NONE)); - } - } - { - SVG svg("/tmp/contours.svg", aabb); - for (auto& inset : toolpaths) - for (auto& line2 : inset) - svg.writePolyline(line2.toPolygon(), SVG::Color::GRAY); - for (auto& line2 : stitched_polylines) - { - SVG::Color col = line2.is_odd ? SVG::Color::GRAY : SVG::Color::RED; - if ( ! line2.is_odd) std::cerr << "Non-closed even wall of size: " << line2.size() << " at " << line2.front().p << "\n"; - if ( ! line2.is_odd) svg.writePoint(line2.front().p, true, 1.0); - Polygon poly = line2.toPolygon(); - Point last = poly.front(); - for (size_t idx = 1 ; idx < poly.size(); idx++) - { - Point here = poly[idx]; - svg.writeLine(last, here, col); - last = here; - } - } - for (auto line2 : closed_polygons) - svg.writePolygon(line2.toPolygon()); - } + assert(false && "Some even contour lines could not be closed into polygons!"); + // NOTE: if this assertion fails then revert the debugging code removed in this commit (git blame on this line) } } #endif // DEBUG From 4b0fa036516aec6dfcd99ced30f40ebce8e37e86 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 17 Feb 2022 11:26:08 +0100 Subject: [PATCH 56/71] lil code style and debug check --- src/utils/polygon.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/utils/polygon.cpp b/src/utils/polygon.cpp index 978837de3c..9bfa61f9fc 100644 --- a/src/utils/polygon.cpp +++ b/src/utils/polygon.cpp @@ -1612,7 +1612,9 @@ std::vector> Polygons::getNesting() const SVG svg("/tmp/nesting.svg", AABB(*this)); svg.writePolygons(*this); for (auto& path : paths) + { svg.writePoint(path[0], true, 1.0); + } for (size_t idx = 0; idx < ret.size(); idx++) { for (size_t child_idx : ret[idx]) @@ -1662,7 +1664,9 @@ std::unordered_map Polygons::getPolyTreeToP const ClipperLib::PolyNode* node = queue.front(); queue.pop(); for (auto child : node->Childs) + { queue.emplace(child); + } if (node->Contour.empty()) continue; std::unordered_map::iterator it; @@ -1687,19 +1691,24 @@ std::unordered_map Polygons::getPolyTreeToP } logWarning("Couldn't find result of nesting in original polygons"); +#ifdef DEBUG for (auto node : unprocessed) { std::cerr << "Couldn't find match for node with locations:\n"; for (auto p : node->Contour) + { std::cerr << Point(p) << ", "; + } std::cerr << '\n'; } std::cerr << "Among registered locations: \n"; for (auto [p, i] : loc_to_index) + { std::cerr << p << " to index " << i << '\n'; + } std::cerr <<'\n'; - assert(false && "The first Point in each polygon should be contained in the clipper output!"); +#endif return result; } From 8bde507201006db87a8b9451fa2b733c81fe26a8 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 21 Feb 2022 14:05:20 +0100 Subject: [PATCH 57/71] Simpler getRegionOrder implementation Simply use a SparseGrid to see which lines are adjacent to each other, rather than using nesting informtoin and info from region_idx. This makes the code simpler to read, the algorithm more reobust to when stitching hasn't been accomplished where it should have, while have negligle impact in slicing time. --- src/InsetOrderOptimizer.cpp | 174 ++++++++---------------------------- src/InsetOrderOptimizer.h | 7 -- 2 files changed, 39 insertions(+), 142 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index db207a9abe..8820cb1668 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -198,162 +198,66 @@ bool InsetOrderOptimizer::addToLayer() std::unordered_set> InsetOrderOptimizer::getRegionOrder(const std::vector& input, const bool outer_to_inner) { - size_t max_inset_idx = 0; - Polygons all_polygons; - std::unordered_map poly_idx_to_extrusionline; - for (const ExtrusionLine* line_p : input) - { - const ExtrusionLine& line = *line_p; - if (line.empty()) continue; - max_inset_idx = std::max(max_inset_idx, line.inset_idx); - if ( ! line.is_closed) - { - // Make a small triangle representative of the polyline - // otherwise the polyline would get erased by the clipping operation - all_polygons.emplace_back(); - assert(line.junctions.size() >= 2); - Point middle = ( line.junctions[line.junctions.size() / 2 - 1].p + line.junctions[line.junctions.size() / 2].p ) / 2; - PolygonRef poly = all_polygons.back(); - poly.emplace_back(middle); - poly.emplace_back(middle + Point(5, 0)); - poly.emplace_back(middle + Point(0, 5)); - } - else - { - all_polygons.emplace_back(line.toPolygon()); - } - poly_idx_to_extrusionline.emplace(all_polygons.size() - 1, &line); - } + std::unordered_set> order_requirements; - std::vector> nesting = all_polygons.getNesting(); - - std::unordered_set> result; - - for (auto [idx, line] : poly_idx_to_extrusionline) - { // there might be multiple roots - if (line->inset_idx == 0) + coord_t max_line_w = 0u; + { // compute max_line_w + for (const ExtrusionLine* line : input) { - getRegionOrder(idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); + for (const ExtrusionJunction& junction : *line) + { + max_line_w = std::max(max_line_w, junction.w); + } } } + if (max_line_w == 0u) return order_requirements; + + const coord_t searching_radius = max_line_w * 2; + using GridT = SparsePointGridInclusive; + GridT grid(searching_radius); - return result; -} - -void InsetOrderOptimizer::getRegionOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result) -{ - auto parent_it = poly_idx_to_extrusionline.find(node_idx); - assert(parent_it != poly_idx_to_extrusionline.end()); - const ExtrusionLine* parent = parent_it->second; - assert(node_idx < nesting.size()); - for (size_t child_idx : nesting[node_idx]) + for (const ExtrusionLine* line : input) { - auto child_it = poly_idx_to_extrusionline.find(child_idx); - assert(child_it != poly_idx_to_extrusionline.end()); - const ExtrusionLine* child = child_it->second; - - if ( ! child->is_odd && child->inset_idx == parent->inset_idx && child->inset_idx == max_inset_idx) + for (const ExtrusionJunction& junction : *line) { - // There is no order requirement between the innermost wall of a hole and the innermost wall of the outline. + grid.insert(junction.p, line); } - else if ( ! child->is_odd && child->inset_idx == parent->inset_idx && child->inset_idx <= max_inset_idx) - { // unusual case - // There are insets with one higher inset index which are adjacent to both this child and the parent. - // And potentially also insets which are adjacent to this child and other children. - // Moreover there are probably gap filler lines in between the child and the parent. - // The nesting information doesn't tell which ones are adjacent, - // so just to be safe we add order requirements between the child and all gap fillers and wall lines. - for (size_t other_child_idx : nesting[node_idx]) + } + for (const std::pair>& p : grid) + { + const ExtrusionLine* here = p.second.val; + Point loc_here = p.second.point; + std::vector nearby_verts = grid.getNearbyVals(loc_here, searching_radius); + for (const ExtrusionLine* nearby : nearby_verts) + { + 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 (here->is_odd || nearby->is_odd) { - auto other_child_it = poly_idx_to_extrusionline.find(other_child_idx); - assert(other_child_it != poly_idx_to_extrusionline.end()); - const ExtrusionLine* other_child = other_child_it->second; - - if (other_child == child) continue; - - - // See if there's an overlap in region_id. - // If not then they are not adjacent, so we don't include order requirement - bool overlap = false; + if (here->is_odd && ! nearby->is_odd && nearby->inset_idx < here->inset_idx) { - std::unordered_set other_child_region_ids; - for (const ExtrusionJunction& j : other_child->junctions) - { - other_child_region_ids.emplace(j.region_id); - } - for (const ExtrusionJunction& j : child->junctions) - { - if (other_child_region_ids.count(j.region_id)) - { - overlap = true; - break; - } - } - if (other_child->is_odd) - { // Odd gap fillers should have two region_ids, but they don't, so let's be more conservative on them - for (const ExtrusionJunction& j : parent->junctions) - { - if (other_child_region_ids.count(j.region_id)) - { // if an odd gap filler has the region_id set to the outline then it could also be adjacent to child, but not registered as such. - overlap = true; - break; - } - } - } + order_requirements.emplace(std::make_pair(nearby, here)); } - if ( ! overlap) continue; - if (other_child->is_odd) + if (nearby->is_odd && ! here->is_odd && here->inset_idx < nearby->inset_idx) { - if (other_child->inset_idx == child->inset_idx + 1) - { // normal gap filler - result.emplace(child, other_child); - } - else - { // all 3 lines considered are on the same level - assert(other_child->inset_idx == child->inset_idx); - // no order requirement needed - } - } - else - { // other child is an even wall as well - if (other_child->inset_idx == child->inset_idx) - { // There is no order relation between two children of the same inset index - continue; - } - - const ExtrusionLine* before = child; - const ExtrusionLine* after = other_child; - if ( ! outer_to_inner) - { - std::swap(before, after); - } - result.emplace(before, after); + order_requirements.emplace(std::make_pair(here, nearby)); } } - } - else - { // normal case - if ( ! parent->is_closed) + else if (nearby->inset_idx < here->inset_idx == outer_to_inner) { - logWarning("Stitching polyline into a polygon failed.\n"); + order_requirements.emplace(std::make_pair(nearby, here)); } - - const ExtrusionLine* before = parent; - const ExtrusionLine* after = child; - if ( (child->inset_idx < parent->inset_idx) == outer_to_inner - // ^ Order should be reversed for hole polyons - // and it should be reversed again when the global order is the other way around - && ! child->is_odd) // Odd polylines should always go after their enclosing wall polygon + else { - std::swap(before, after); + assert(nearby->inset_idx > here->inset_idx == outer_to_inner); + order_requirements.emplace(std::make_pair(here, nearby)); } - result.emplace(before, after); } - - // Recurvise call - getRegionOrder(child_idx, poly_idx_to_extrusionline, nesting, max_inset_idx, outer_to_inner, result); } + return order_requirements; } std::unordered_set> InsetOrderOptimizer::getInsetOrder(const std::vector& input, const bool outer_to_inner) diff --git a/src/InsetOrderOptimizer.h b/src/InsetOrderOptimizer.h index aedd9f0b6d..23ed9a8ea3 100644 --- a/src/InsetOrderOptimizer.h +++ b/src/InsetOrderOptimizer.h @@ -89,13 +89,6 @@ class InsetOrderOptimizer template static std::unordered_set> makeOrderIncludeTransitive(const std::unordered_set>& order_requirements); private: - - /*! - * Recursive part of \ref WallToolpPaths::getWeakOrder. - * For each node at \p node_idx we recurse on all its children at nesting[node_idx] - */ - static void getRegionOrder(size_t node_idx, const std::unordered_map& poly_idx_to_extrusionline, const std::vector>& nesting, size_t max_inset_idx, const bool outer_to_inner, std::unordered_set>& result); - const FffGcodeWriter& gcode_writer; const SliceDataStorage& storage; LayerPlan& gcode_layer; From 8c33703537ed11b4abd9f83aa9f9aa2ae06b9647 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 21 Feb 2022 14:05:42 +0100 Subject: [PATCH 58/71] remove unused polygon::getNesting(.) --- src/utils/polygon.cpp | 135 ------------------------------------------ src/utils/polygon.h | 16 ----- 2 files changed, 151 deletions(-) diff --git a/src/utils/polygon.cpp b/src/utils/polygon.cpp index 9bfa61f9fc..97b86c1949 100644 --- a/src/utils/polygon.cpp +++ b/src/utils/polygon.cpp @@ -1577,141 +1577,6 @@ void Polygons::splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Poly } } - -std::vector> Polygons::getNesting() const -{ - ClipperLib::Clipper clipper(clipper_init); - clipper.AddPaths(paths, ClipperLib::ptSubject, true); - ClipperLib::PolyTree resultPolyTree; - clipper.Execute(ClipperLib::ctUnion, resultPolyTree, ClipperLib::pftEvenOdd); - - std::unordered_map node_to_index = getPolyTreeToPolygonsMapping(resultPolyTree); - - std::vector> ret(size()); - - bool loops_detected = false; - for (auto [node, index] : node_to_index) - { - for (auto child : node->Childs) - { - auto it = node_to_index.find(child); - assert(it != node_to_index.end() && "Each PolyNode should be mapped to the corresponding Polygon index!"); - size_t child_idx = it->second; - if (child_idx == index) - { - loops_detected = true; - } - if (child_idx != index) - ret[index].emplace_back(child_idx); - } - } -#ifdef DEBUG - if (loops_detected) - { - logError("Nesting loops detected. Wall printing order might suffer.\n"); - SVG svg("/tmp/nesting.svg", AABB(*this)); - svg.writePolygons(*this); - for (auto& path : paths) - { - svg.writePoint(path[0], true, 1.0); - } - for (size_t idx = 0; idx < ret.size(); idx++) - { - for (size_t child_idx : ret[idx]) - { - svg.writeArrow(paths[idx][0], paths[child_idx][1], SVG::Color::BLUE); - } - } - } -#endif // DEBUG - return ret; -} - - -std::unordered_map Polygons::getPolyTreeToPolygonsMapping(const ClipperLib::PolyNode& root) const -{ - std::unordered_map result; - - std::unordered_map loc_to_index; - std::vector duplicates; - for (size_t idx = 0; idx < paths.size(); idx++) - { - const ClipperLib::Path& path = paths[idx]; - for (Point p : path) - { - auto it = loc_to_index.find(p); - if (it != loc_to_index.end()) - { // Multiple polygons share a point - duplicates.emplace_back(p); - } - else - { - loc_to_index.emplace(p, idx); - } - } - } - for (Point dup : duplicates) - { - loc_to_index.erase(dup); - } - - std::queue queue; - std::vector unprocessed; - - queue.emplace(&root); - while ( ! queue.empty()) - { - const ClipperLib::PolyNode* node = queue.front(); - queue.pop(); - for (auto child : node->Childs) - { - queue.emplace(child); - } - if (node->Contour.empty()) continue; - - std::unordered_map::iterator it; - for (Point p : node->Contour) - { - it = loc_to_index.find(p); - if (it != loc_to_index.end()) - { // should be true for the first point that's not shared with other polygons - result.emplace(node, it->second); - break; - } - } - if (it == loc_to_index.end()) - { - unprocessed.emplace_back(node); - } - } - - if (unprocessed.empty()) - { - return result; - } - - logWarning("Couldn't find result of nesting in original polygons"); -#ifdef DEBUG - for (auto node : unprocessed) - { - std::cerr << "Couldn't find match for node with locations:\n"; - for (auto p : node->Contour) - { - std::cerr << Point(p) << ", "; - } - std::cerr << '\n'; - } - std::cerr << "Among registered locations: \n"; - for (auto [p, i] : loc_to_index) - { - std::cerr << p << " to index " << i << '\n'; - } - std::cerr <<'\n'; - assert(false && "The first Point in each polygon should be contained in the clipper output!"); -#endif - return result; -} - void Polygons::ensureManifold() { const Polygons& polys = *this; diff --git a/src/utils/polygon.h b/src/utils/polygon.h index b03d820abb..96e3f92884 100644 --- a/src/utils/polygon.h +++ b/src/utils/polygon.h @@ -1242,22 +1242,6 @@ class Polygons private: void splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Polygons& reordered, ClipperLib::PolyNode* node) const; public: - - /*! - * Compute nesting information. - * For each polygon in the input we return a vector of indices to polygons which lie directly inside of it. - * - * \warning The polygons should not be overlapping each other - */ - std::vector> getNesting() const; -private: - /*! - * Get a mapping from the nodes in a PolyTree to their corresponding Polygon indices - * - * \warning The polygons should not be overlapping each other - */ - std::unordered_map getPolyTreeToPolygonsMapping(const ClipperLib::PolyNode& root) const; -public: /*! From e745b8fdad3da2db4233caf9e7b6db3414cb6977 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 21 Feb 2022 14:28:44 +0100 Subject: [PATCH 59/71] fix order between too far away lines We didn't check the distance between two lines properly, so wall lines which weren;t adjacent could end up with an order constraint. This effectively meant that in some cases Optimize Wall Printing Order acted as if it wasn't enabled. --- src/InsetOrderOptimizer.cpp | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 8820cb1668..7471e9332c 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -212,8 +212,22 @@ std::unordered_set> InsetO } if (max_line_w == 0u) return order_requirements; - const coord_t searching_radius = max_line_w * 2; - using GridT = SparsePointGridInclusive; + struct LineLoc + { + ExtrusionJunction j; + const ExtrusionLine* line; + }; + struct Locator + { + Point operator()(const LineLoc& elem) + { + return elem.j.p; + } + }; + + constexpr float diagonal_extension = 1.7; // how much farther two verts may be apart due to corners + const coord_t searching_radius = max_line_w * diagonal_extension; + using GridT = SparsePointGrid; GridT grid(searching_radius); @@ -221,20 +235,23 @@ std::unordered_set> InsetO { for (const ExtrusionJunction& junction : *line) { - grid.insert(junction.p, line); + grid.insert(LineLoc{junction, line}); } } - for (const std::pair>& p : grid) + for (const std::pair& pair : grid) { - const ExtrusionLine* here = p.second.val; - Point loc_here = p.second.point; - std::vector nearby_verts = grid.getNearbyVals(loc_here, searching_radius); - for (const ExtrusionLine* nearby : nearby_verts) + 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 ( ! shorterThan(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) From ae378b240bbad9edd6f213529b9036e04b39bb1b Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 21 Feb 2022 16:04:12 +0100 Subject: [PATCH 60/71] remove printing of infill walls which are never generated Fixes an issue with code duplication introduced in ca83659b9ee574e449533ced9822b53df666c833 Contributes to CURA-8067 --- src/FffGcodeWriter.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index c39f75b3b6..55181ae9a0 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2720,6 +2720,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer wall_count, infill_origin, support_connect_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size); infill_comp.generate(wall_toolpaths_here, support_polygons, support_lines, infill_extruder.settings, storage.support.cross_fill_provider); + assert(wall_toolpaths_here.empty()); } setExtruder_addPrime(storage, gcode_layer, extruder_nr); // only switch extruder if we're sure we're going to switch @@ -2728,17 +2729,6 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer const bool alternate_inset_direction = infill_extruder.settings.get("material_alternate_walls"); const bool alternate_layer_print_direction = alternate_inset_direction && gcode_layer.getLayerNr() % 2 == 1; - if(!wall_toolpaths_here.empty()) - { - const GCodePathConfig& config = gcode_layer.configs_storage.support_infill_config[0]; - constexpr bool retract_before_outer_wall = false; - constexpr coord_t wipe_dist = 0; - const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); - InsetOrderOptimizer wall_orderer(*this, storage, gcode_layer, infill_extruder.settings, extruder_nr, - config, config, config, config, - retract_before_outer_wall, wipe_dist, wipe_dist, extruder_nr, extruder_nr, z_seam_config, wall_toolpaths_here); - added_something |= wall_orderer.addToLayer(); - } if(!support_polygons.empty()) { constexpr bool force_comb_retract = false; From 39e4d71538a2e3506836caf8a8c8f6d19c344c55 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 21 Feb 2022 16:45:40 +0100 Subject: [PATCH 61/71] fix unit test for ExtrusionLine The test assumed that the pat hwas closed. Only for closed polygons is a size of 2 or 1 not allowed. --- tests/utils/ExtrusionLineTest.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/utils/ExtrusionLineTest.cpp b/tests/utils/ExtrusionLineTest.cpp index 867b4ad2e4..d26820a982 100644 --- a/tests/utils/ExtrusionLineTest.cpp +++ b/tests/utils/ExtrusionLineTest.cpp @@ -394,6 +394,7 @@ namespace cura d.emplace_back(Point(0, 0), 500, 0); d.emplace_back(Point(10, 55), 500, 0); d.emplace_back(Point(0, 110), 500, 0); + d_polylines.is_closed = true; d_polylines.simplify(100 * 100, 15 * 15, 2000); From f99f91159f61eaae7a87759c98d539fd79419b14 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 22 Feb 2022 11:52:21 +0100 Subject: [PATCH 62/71] lil code style and doc --- src/FffGcodeWriter.cpp | 7 ++++--- src/InsetOrderOptimizer.cpp | 38 ++++++++++++++++++++++++++++++------ src/SkeletalTrapezoidation.h | 9 ++++++++- src/WallToolPaths.cpp | 9 ++++++++- src/utils/ExtrusionLine.cpp | 3 ++- src/utils/PolylineStitcher.h | 8 ++++---- src/utils/polygon.cpp | 2 -- src/utils/polygon.h | 14 ++++++------- 8 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 55181ae9a0..dce2db2702 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1413,7 +1413,8 @@ void FffGcodeWriter::addMeshLayerToGCode(const SliceDataStorage& storage, const addMeshPartToGCode(storage, mesh, extruder_nr, mesh_config, *path.vertices, gcode_layer); } - if (extruder_nr == mesh.settings.get((mesh.settings.get("roofing_layer_count") > 0)? "roofing_extruder_nr" : "top_bottom_extruder_nr").extruder_nr) + const std::string extruder_identifier = (mesh.settings.get("roofing_layer_count") > 0)? "roofing_extruder_nr" : "top_bottom_extruder_nr"; + if (extruder_nr == mesh.settings.get(extruder_identifier).extruder_nr) { processIroning(storage, mesh, layer, mesh_config.ironing_config, gcode_layer); } @@ -2860,13 +2861,13 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, Lay { gcode_layer.addPolygonsByOptimizer(wall, gcode_layer.configs_storage.support_roof_config); } - if (!roof_polygons.empty()) + if ( ! roof_polygons.empty()) { constexpr bool force_comb_retract = false; gcode_layer.addTravel(roof_polygons[0][0], force_comb_retract); gcode_layer.addPolygonsByOptimizer(roof_polygons, gcode_layer.configs_storage.support_roof_config); } - if (! roof_paths.empty()) + if ( ! roof_paths.empty()) { const GCodePathConfig& config = gcode_layer.configs_storage.support_roof_config; constexpr bool retract_before_outer_wall = false; diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 7471e9332c..44b67a3237 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -110,7 +110,7 @@ bool InsetOrderOptimizer::addToLayer() std::vector walls_to_be_added; //Add all of the insets one by one. - for(size_t inset_idx = start_inset; inset_idx != end_inset; inset_idx += direction) + for (size_t inset_idx = start_inset; inset_idx != end_inset; inset_idx += direction) { if (paths[inset_idx].empty()) { @@ -132,10 +132,18 @@ bool InsetOrderOptimizer::addToLayer() if (center_last) { for (const ExtrusionLine* line : walls_to_be_added) + { if (line->is_odd) + { for (const ExtrusionLine* other_line : walls_to_be_added) + { if ( ! other_line->is_odd) + { order.emplace(std::make_pair(other_line, line)); + } + } + } + } } constexpr Ratio flow = 1.0_r; @@ -200,14 +208,32 @@ std::unordered_set> InsetO { std::unordered_set> 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 ExtrusionLine* line : input) + for (const ExtrusionJunction& junction : *line) { - for (const ExtrusionJunction& junction : *line) - { - max_line_w = std::max(max_line_w, junction.w); - } + max_line_w = std::max(max_line_w, junction.w); } } if (max_line_w == 0u) return order_requirements; diff --git a/src/SkeletalTrapezoidation.h b/src/SkeletalTrapezoidation.h index c80b008d65..c576409926 100644 --- a/src/SkeletalTrapezoidation.h +++ b/src/SkeletalTrapezoidation.h @@ -575,7 +575,14 @@ class SkeletalTrapezoidation void generateJunctions(ptr_vector_t& node_beadings, ptr_vector_t& edge_junctions); /*! - * add a new toolpath segment, defined between two extrusion-juntions + * Add a new toolpath segment, defined between two extrusion-juntions. + * + * \param from The junction from which to add a segment. + * \param to The junction to which to add a segment. + * \param is_odd Whether this segment is an odd gap filler along the middle of the skeleton. + * \param force_new_path Whether to prevent adding this path to an existing path which ends in \p from + * \param from_is_3way Whether the \p from junction is a splitting junction where two normal wall lines and a gap filler line come together. + * \param to_is_3way Whether the \p to junction is a splitting junction where two normal wall lines and a gap filler line come together. */ void addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way); diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 4478ad808a..3c292fc8b1 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -234,7 +234,10 @@ void WallToolPaths::separateOutInnerContour() inner_contour.clear(); for (const VariableWidthLines& inset : toolpaths) { - if (inset.empty()) continue; + if (inset.empty()) + { + continue; + } bool is_contour = false; for (const ExtrusionLine& line : inset) { @@ -257,8 +260,12 @@ void WallToolPaths::separateOutInnerContour() { #ifdef DEBUG for (const ExtrusionLine& line : inset) + { for (const ExtrusionJunction& j : line) + { assert(j.w == 0); + } + } #endif // DEBUG for (const ExtrusionLine& line : inset) { diff --git a/src/utils/ExtrusionLine.cpp b/src/utils/ExtrusionLine.cpp index 26573a7673..1a50d8f2c7 100644 --- a/src/utils/ExtrusionLine.cpp +++ b/src/utils/ExtrusionLine.cpp @@ -46,7 +46,8 @@ coord_t ExtrusionLine::getMinimalWidth() const void ExtrusionLine::simplify(const coord_t smallest_line_segment_squared, const coord_t allowed_error_distance_squared, const coord_t maximum_extrusion_area_deviation) { - if (junctions.size() <= 2 + is_closed) + const size_t min_path_size = is_closed ? 3 : 2; + if (junctions.size() <= min_path_size) { return; } diff --git a/src/utils/PolylineStitcher.h b/src/utils/PolylineStitcher.h index a0e446871b..5629e3df91 100644 --- a/src/utils/PolylineStitcher.h +++ b/src/utils/PolylineStitcher.h @@ -65,8 +65,8 @@ class PolylineStitcher Path chain = line; bool closest_is_closing_polygon = false; - for (bool go_in_reverse_direction : { false, true }) - { + for (bool go_in_reverse_direction : { false, true }) // first go in the unreversed direction, to try to prevent the chain.reverse() operation. + { // NOTE: Implementation only works for this order; we currently only re-reverse the chain when it's closed. if (go_in_reverse_direction) { // try extending chain in the other direction chain.reverse(); @@ -107,11 +107,11 @@ class PolylineStitcher } bool nearby_would_be_reversed = nearby.point_idx != 0; nearby_would_be_reversed = nearby_would_be_reversed != go_in_reverse_direction; // flip nearby_would_be_reversed when searching in the reverse direction - if (!canReverse(nearby) && nearby_would_be_reversed) + if ( ! canReverse(nearby) && nearby_would_be_reversed) { // connecting the segment would reverse the polygon direction return true; // keep looking for a connection } - if (!canConnect(chain, (*nearby.polygons)[nearby.poly_idx])) + if ( ! canConnect(chain, (*nearby.polygons)[nearby.poly_idx])) { return true; // keep looking for a connection } diff --git a/src/utils/polygon.cpp b/src/utils/polygon.cpp index 97b86c1949..9b2fbc83f4 100644 --- a/src/utils/polygon.cpp +++ b/src/utils/polygon.cpp @@ -11,8 +11,6 @@ #include "ListPolyIt.h" #include "PolylineStitcher.h" -#include "logoutput.h" -#include "SVG.h" namespace cura { diff --git a/src/utils/polygon.h b/src/utils/polygon.h index 96e3f92884..ab4dce6d2b 100644 --- a/src/utils/polygon.h +++ b/src/utils/polygon.h @@ -82,15 +82,15 @@ class ConstPolygonRef : path(const_cast(&polygon)) {} - ConstPolygonRef() =delete; // you cannot have a reference without an object! + ConstPolygonRef() = delete; // you cannot have a reference without an object! virtual ~ConstPolygonRef() { } - bool operator==(ConstPolygonRef& other) const =delete; // polygon comparison is expensive and probably not what you want when you use the equality operator + bool operator==(ConstPolygonRef& other) const = delete; // polygon comparison is expensive and probably not what you want when you use the equality operator - ConstPolygonRef& operator=(const ConstPolygonRef& other) =delete; // Cannot assign to a const object + ConstPolygonRef& operator=(const ConstPolygonRef& other) = delete; // Cannot assign to a const object /*! * Gets the number of vertices in this polygon. @@ -413,7 +413,7 @@ class PolygonRef : public ConstPolygonRef : ConstPolygonRef(*other.path) {} - PolygonRef() =delete; // you cannot have a reference without an object! + PolygonRef() = delete; // you cannot have a reference without an object! virtual ~PolygonRef() { @@ -430,9 +430,9 @@ class PolygonRef : public ConstPolygonRef return path->insert(pos, first, last); } - PolygonRef& operator=(const ConstPolygonRef& other) =delete; // polygon assignment is expensive and probably not what you want when you use the assignment operator + PolygonRef& operator=(const ConstPolygonRef& other) = delete; // polygon assignment is expensive and probably not what you want when you use the assignment operator - PolygonRef& operator=(ConstPolygonRef& other) =delete; // polygon assignment is expensive and probably not what you want when you use the assignment operator + PolygonRef& operator=(ConstPolygonRef& other) = delete; // polygon assignment is expensive and probably not what you want when you use the assignment operator // { path = other.path; return *this; } PolygonRef& operator=(PolygonRef&& other) @@ -894,7 +894,7 @@ class Polygons Polygons& operator=(const Polygons& other) { paths = other.paths; return *this; } Polygons& operator=(Polygons&& other) { paths = std::move(other.paths); return *this; } - bool operator==(const Polygons& other) const =delete; + bool operator==(const Polygons& other) const = delete; /*! * Convert ClipperLib::PolyTree to a Polygons object, From 3962373607eab160dbd4c8749300b56968191338 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 22 Feb 2022 11:52:50 +0100 Subject: [PATCH 63/71] make alternate_walls code more explicit --- src/InsetOrderOptimizer.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 44b67a3237..8a43812dba 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -183,9 +183,10 @@ bool InsetOrderOptimizer::addToLayer() const GCodePathConfig& bridge_config = is_outer_wall? inset_0_bridge_config : inset_X_bridge_config; const coord_t wipe_dist = is_outer_wall && ! is_gap_filler ? wall_0_wipe_dist : wall_x_wipe_dist; const bool retract_before = is_outer_wall ? retract_before_outer_wall : false; - - const bool alternate_direction_modifier = alternate_walls && (path.vertices->inset_idx % 2 == layer_nr % 2); - const bool backwards = path.backwards != alternate_direction_modifier; + + const bool revert_inset = alternate_walls && (path.vertices->inset_idx % 2); + const bool revert_layer = alternate_walls && (layer_nr % 2); + const bool backwards = path.backwards != (revert_inset != revert_layer); p_end = path.backwards ? path.vertices->back().p : path.vertices->front().p; const cura::Point p_start = path.backwards ? path.vertices->front().p : path.vertices->back().p; From 5a2f5dd3de79fe55a4b7253bd035c82f70dc55ee Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 22 Feb 2022 11:53:05 +0100 Subject: [PATCH 64/71] remove superfluous double assignment --- src/InsetOrderOptimizer.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 8a43812dba..9102f7debf 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -191,9 +191,7 @@ bool InsetOrderOptimizer::addToLayer() p_end = path.backwards ? path.vertices->back().p : path.vertices->front().p; const cura::Point p_start = path.backwards ? path.vertices->front().p : path.vertices->back().p; const bool linked_path = p_start != p_end; - - - added_something = true; + gcode_writer.setExtruder_addPrime(storage, gcode_layer, extruder_nr); gcode_layer.setIsInside(true); //Going to print walls, which are always inside. gcode_layer.addWall(*path.vertices, path.start_vertex, settings, non_bridge_config, bridge_config, wipe_dist, flow, retract_before, path.is_closed, backwards, linked_path); From e6e114f23c2986f7b17c512913e5efc92d7375ff Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 22 Feb 2022 11:53:47 +0100 Subject: [PATCH 65/71] lil refactor for readability --- src/SkeletalTrapezoidation.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index ca9019bd76..fdf21bc84a 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -1858,14 +1858,16 @@ void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, c { generated_toolpaths.resize(inset_idx + 1); } + const bool is_normal_segment = ! is_odd && ! from_is_3way && ! to_is_3way; + bool has_inconsistent_region_id = + generated_toolpaths[inset_idx].back().junctions.back().region_id != to.region_id + || from.region_id != to.region_id; // This should never be the case for normal segments assert((generated_toolpaths[inset_idx].empty() || !generated_toolpaths[inset_idx].back().junctions.empty()) && "empty extrusion lines should never have been generated"); + assert( ! is_normal_segment || from.region_id == to.region_id && "Non gap fillers should always have consistent region_id!"); if (generated_toolpaths[inset_idx].empty() || generated_toolpaths[inset_idx].back().is_odd != is_odd || generated_toolpaths[inset_idx].back().junctions.back().perimeter_index != inset_idx // inset_idx should always be consistent - || ( ! is_odd && ! from_is_3way && ! to_is_3way && ( // if this is a normal even line ... - from.region_id != to.region_id // It should always have a consistent region_id - || generated_toolpaths[inset_idx].back().junctions.back().region_id != to.region_id - )) + || (is_normal_segment && has_inconsistent_region_id) ) { force_new_path = true; From 63ec9583518be917abbfbf020aee840d0d8a1e74 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 22 Feb 2022 11:55:10 +0100 Subject: [PATCH 66/71] prevent walls which should have been closed from being printed in reverse direction Polygonal walls with even index should always be closed into polygons, but if they somehow weren't closed, we should at least retain their printing direction. --- src/utils/PolylineStitcher.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/utils/PolylineStitcher.h b/src/utils/PolylineStitcher.h index 5629e3df91..a24e32b12c 100644 --- a/src/utils/PolylineStitcher.h +++ b/src/utils/PolylineStitcher.h @@ -178,6 +178,12 @@ class PolylineStitcher } else { + PathsPointIndex ppi_here(&lines, line_idx, 0); + if ( ! canReverse(ppi_here)) + { // Since closest_is_closing_polygon is false we went through the second iterations of the for-loop, where go_in_reverse_direction is true + // the polyline isn't allowed to be reversed, so we re-reverse it. + chain.reverse(); + } result_lines.emplace_back(chain); } } From 5cc439e5a4c1187a50571270f7a5940f5f723bf5 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 22 Feb 2022 12:02:15 +0100 Subject: [PATCH 67/71] increase searching radius of getRegionOrder a little Is better for finding neighboring walls in uncommon cases where the vertices of consectuive walls are not next to each other. --- src/InsetOrderOptimizer.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 9102f7debf..9c200bae81 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -250,7 +250,13 @@ std::unordered_set> InsetO } }; - constexpr float diagonal_extension = 1.7; // how much farther two verts may be apart due to corners + // 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.9; const coord_t searching_radius = max_line_w * diagonal_extension; using GridT = SparsePointGrid; GridT grid(searching_radius); From 766c239b69f9d73b0b16396708906d90a8c27698 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 22 Feb 2022 13:52:07 +0100 Subject: [PATCH 68/71] lil brackets to prevent compile warning --- src/InsetOrderOptimizer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 9c200bae81..e51f349f06 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -294,13 +294,13 @@ std::unordered_set> InsetO order_requirements.emplace(std::make_pair(here, nearby)); } } - else if (nearby->inset_idx < here->inset_idx == outer_to_inner) + 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); + assert((nearby->inset_idx > here->inset_idx) == outer_to_inner); order_requirements.emplace(std::make_pair(here, nearby)); } } From 5f06212dcb0cbf2206078c29b572b1a9c3f303d3 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 22 Feb 2022 13:52:45 +0100 Subject: [PATCH 69/71] make arachne stitch prevention choice more clear --- src/SkeletalTrapezoidation.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index fdf21bc84a..a28d5c92d1 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -1860,10 +1860,13 @@ void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, c } const bool is_normal_segment = ! is_odd && ! from_is_3way && ! to_is_3way; bool has_inconsistent_region_id = - generated_toolpaths[inset_idx].back().junctions.back().region_id != to.region_id - || from.region_id != to.region_id; // This should never be the case for normal segments + ( ! generated_toolpaths[inset_idx].empty() && generated_toolpaths[inset_idx].back().junctions.back().region_id != to.region_id) + || from.region_id != to.region_id; // Either junction is probably the middle between different regions + // Different regions should ideally get annotated with 2 region ids, but they only have one region_id field, + // so we have no way of knowing whether the lines should be cut before or after this segment. + // We cut it here to be more safe. And stitch afterwards with the PolylineStitcher. + // Still this doesn't guarantee we cut in the right place. assert((generated_toolpaths[inset_idx].empty() || !generated_toolpaths[inset_idx].back().junctions.empty()) && "empty extrusion lines should never have been generated"); - assert( ! is_normal_segment || from.region_id == to.region_id && "Non gap fillers should always have consistent region_id!"); if (generated_toolpaths[inset_idx].empty() || generated_toolpaths[inset_idx].back().is_odd != is_odd || generated_toolpaths[inset_idx].back().junctions.back().perimeter_index != inset_idx // inset_idx should always be consistent From fdbf8c15e5ede6e99478eb2c84558bbce4e68787 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 22 Feb 2022 13:57:36 +0100 Subject: [PATCH 70/71] remove ExtrusionJunction::region_id An extrusion junction lies on an edge of the MAT, but since each edge has two half edges which may belong to a different region, the extrusion junction should have recorded two region_ids. The region_id recorded in the junction was therefore not reliable, and code depending on it was very bug-prone. --- src/SkeletalTrapezoidation.cpp | 14 ++------------ src/utils/ExtrusionJunction.cpp | 5 ++--- src/utils/ExtrusionJunction.h | 9 +-------- src/utils/ExtrusionLine.cpp | 2 +- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index a28d5c92d1..ef7e6daefe 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -1759,7 +1759,7 @@ void SkeletalTrapezoidation::generateJunctions(ptr_vector_t& { // Snap to start node if it is really close, in order to be able to see 3-way intersection later on more robustly junction = a; } - ret.emplace_back(junction, beading->bead_widths[junction_idx], junction_idx, edge_.data.getRegion()); + ret.emplace_back(junction, beading->bead_widths[junction_idx], junction_idx); } } } @@ -1858,19 +1858,10 @@ void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, c { generated_toolpaths.resize(inset_idx + 1); } - const bool is_normal_segment = ! is_odd && ! from_is_3way && ! to_is_3way; - bool has_inconsistent_region_id = - ( ! generated_toolpaths[inset_idx].empty() && generated_toolpaths[inset_idx].back().junctions.back().region_id != to.region_id) - || from.region_id != to.region_id; // Either junction is probably the middle between different regions - // Different regions should ideally get annotated with 2 region ids, but they only have one region_id field, - // so we have no way of knowing whether the lines should be cut before or after this segment. - // We cut it here to be more safe. And stitch afterwards with the PolylineStitcher. - // Still this doesn't guarantee we cut in the right place. assert((generated_toolpaths[inset_idx].empty() || !generated_toolpaths[inset_idx].back().junctions.empty()) && "empty extrusion lines should never have been generated"); if (generated_toolpaths[inset_idx].empty() || generated_toolpaths[inset_idx].back().is_odd != is_odd || generated_toolpaths[inset_idx].back().junctions.back().perimeter_index != inset_idx // inset_idx should always be consistent - || (is_normal_segment && has_inconsistent_region_id) ) { force_new_path = true; @@ -2040,7 +2031,6 @@ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads() if (beading.bead_widths.size() % 2 == 1 && node.isLocalMaximum(true) && !node.isCentral()) { const size_t inset_index = beading.bead_widths.size() / 2; - const size_t& region_id = node.incident_edge->data.getRegion(); constexpr bool is_odd = true; if (inset_index >= generated_toolpaths.size()) { @@ -2058,7 +2048,7 @@ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads() for (coord_t segment = 0; segment < n_segments; segment++) { float a = 2.0 * M_PI / n_segments * segment; - line.junctions.emplace_back(node.p + Point(r * cos(a), r * sin(a)), width, inset_index, region_id); + line.junctions.emplace_back(node.p + Point(r * cos(a), r * sin(a)), width, inset_index); } } } diff --git a/src/utils/ExtrusionJunction.cpp b/src/utils/ExtrusionJunction.cpp index a8aa7f4842..17e072d1e5 100644 --- a/src/utils/ExtrusionJunction.cpp +++ b/src/utils/ExtrusionJunction.cpp @@ -13,11 +13,10 @@ bool ExtrusionJunction::operator ==(const ExtrusionJunction& other) const && perimeter_index == other.perimeter_index; } -ExtrusionJunction::ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index, const size_t region_id) +ExtrusionJunction::ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), - perimeter_index(perimeter_index), - region_id(region_id) + perimeter_index(perimeter_index) {} } diff --git a/src/utils/ExtrusionJunction.h b/src/utils/ExtrusionJunction.h index 6483f9477e..2fc40e64c0 100644 --- a/src/utils/ExtrusionJunction.h +++ b/src/utils/ExtrusionJunction.h @@ -37,14 +37,7 @@ struct ExtrusionJunction */ size_t perimeter_index; - /*! - * Which region this junction is part of. A solid polygon without holes has only one region. - * A polygon with holes has 2. Disconnected parts of the polygon are also separate regions. - * Will be 0 if no region was given. - */ - size_t region_id; - - ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index, const size_t region_id = 0); + ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index); bool operator==(const ExtrusionJunction& other) const; }; diff --git a/src/utils/ExtrusionLine.cpp b/src/utils/ExtrusionLine.cpp index 1a50d8f2c7..7dd940c93b 100644 --- a/src/utils/ExtrusionLine.cpp +++ b/src/utils/ExtrusionLine.cpp @@ -157,7 +157,7 @@ void ExtrusionLine::simplify(const coord_t smallest_line_segment_squared, const else { // New point seems like a valid one. - const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index, current.region_id); + const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index); // If there was a previous point added, remove it. if(!new_junctions.empty()) { From d83717973f0b8f21e8698c086a246fd66f7100c8 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 22 Feb 2022 14:00:08 +0100 Subject: [PATCH 71/71] remove edge_t::region It's not used anymore. --- src/SkeletalTrapezoidation.cpp | 48 -------------------------------- src/SkeletalTrapezoidation.h | 6 ---- src/SkeletalTrapezoidationEdge.h | 17 ----------- 3 files changed, 71 deletions(-) diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index ef7e6daefe..7ecd3a933a 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -505,8 +505,6 @@ void SkeletalTrapezoidation::generateToolpaths(VariableWidthPaths& generated_too generateExtraRibs(); - markRegions(); - generateSegments(); } @@ -1365,52 +1363,6 @@ void SkeletalTrapezoidation::generateExtraRibs() // // ^^^^^^^^^^^^^^^^^^^^^ // TRANSTISIONING -// ===================== - -void SkeletalTrapezoidation::markRegions() -{ - // Painters algorithm, loop over all edges and skip those that have already been 'painted' with a region. - size_t region = 0; // <- Region zero is 'None', it will be incremented before the first edge. - for (edge_t& edge : graph.edges) - { - if (edge.data.regionIsSet()) - { - continue; - } - - // An edge that didn't have a region painted is encountered, so make a new region and start a worklist: - ++region; - std::queue worklist; - worklist.push(&edge); - - // Loop over all edges that are connected to this one, except don't cross any medial axis edges: - while (!worklist.empty()) - { - edge_t* p_side = worklist.front(); - worklist.pop(); - - edge_t* p_next = p_side; - do - { - if (!p_next->data.regionIsSet()) - { - p_next->data.setRegion(region); - if(p_next->twin != nullptr && (p_next->next == nullptr || p_next->prev == nullptr)) - { - worklist.push(p_next->twin); - } - } - else - { - assert(region == p_next->data.getRegion()); - } - - p_next = p_next->next; - } while (p_next != nullptr && p_next != p_side); - } - } -} - // ===================== // TOOLPATH GENERATION // vvvvvvvvvvvvvvvvvvvvv diff --git a/src/SkeletalTrapezoidation.h b/src/SkeletalTrapezoidation.h index c576409926..49a4043046 100644 --- a/src/SkeletalTrapezoidation.h +++ b/src/SkeletalTrapezoidation.h @@ -460,12 +460,6 @@ class SkeletalTrapezoidation // ^ transitioning ^ - /*! - * It's useful to know when the paths get back to the consumer, to (what part of) a polygon the paths 'belong'. - * A single polygon without a hole is one region, a polygon with (a) hole(s) has 2 regions. - */ - void markRegions(); - // v toolpath generation v /*! diff --git a/src/SkeletalTrapezoidationEdge.h b/src/SkeletalTrapezoidationEdge.h index 6b5a3ceed1..cf3b087f92 100644 --- a/src/SkeletalTrapezoidationEdge.h +++ b/src/SkeletalTrapezoidationEdge.h @@ -58,7 +58,6 @@ class SkeletalTrapezoidationEdge SkeletalTrapezoidationEdge(const EdgeType& type) : type(type) , is_central(Central::UNKNOWN) - , region(0) {} bool isCentral() const @@ -75,21 +74,6 @@ class SkeletalTrapezoidationEdge return is_central != Central::UNKNOWN; } - size_t getRegion() const - { - assert(region != 0); - return region; - } - void setRegion(const size_t& r) - { - assert(region == 0); - region = r; - } - bool regionIsSet() const - { - return region > 0; - } - bool hasTransitions(bool ignore_empty = false) const { return transitions.use_count() > 0 && (ignore_empty || ! transitions.lock()->empty()); @@ -131,7 +115,6 @@ class SkeletalTrapezoidationEdge private: Central is_central; //! whether the edge is significant; whether the source segments have a sharp angle; -1 is unknown - size_t region; //! what 'region' this edge is in ... if the originating polygon has no holes, there's one region -- useful for later algorithms that need to know where the paths came from std::weak_ptr> transitions; std::weak_ptr> transition_ends;